> ## Documentation Index
> Fetch the complete documentation index at: https://gora.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# WASM runtime reference

> The exact ABI, host functions, limits, and metering for WASM Gora apps — plus when to use WASM vs the JS/TS/Python sandbox.

# WASM runtime reference

Gora runs two kinds of app artifacts:

| Runtime     | `artifact.kind`                      | How it runs                                              | Best for                                                         |
| ----------- | ------------------------------------ | -------------------------------------------------------- | ---------------------------------------------------------------- |
| **WASM**    | `wasm`                               | wasmtime with fuel metering, deterministic, host imports | Metered, reproducible compute; allowlisted HTTP; state writes    |
| **Sandbox** | `javascript`, `typescript`, `python` | `node` / `python3` subprocess, JSON over stdin/stdout    | Input-driven app logic, fast iteration, rich language ecosystems |

This page is the WASM reference. For the sandbox contract, see
[the runtime contract in Create a Gora app](./create-offchain-wasm-app#runtime-contract).

## Pick the right runtime (read this first)

Capabilities differ — this table is the honest state of the runtime today:

| Capability                                             | WASM                                          | JS / TS / Python sandbox              |
| ------------------------------------------------------ | --------------------------------------------- | ------------------------------------- |
| Reads `input` from the request (`gora::input_get`)     | ✅                                             | ✅ stdin envelope                      |
| Read/write own state (`gora::state_get` / `state_put`) | ✅ durable memory                              | ❌                                     |
| Call a language model (`gora::llm`)                    | ✅ attested                                    | ❌                                     |
| Outbound HTTP (`gora::http_get`, allowlisted)          | ✅                                             | ✅ (unsandboxed)                       |
| Deterministic + fuel-metered                           | ✅                                             | ❌ (wall-clock limit only)             |
| Runs on the public testnet                             | ✅                                             | ❌ (self-hosted node only — see below) |
| Write any language                                     | Rust, AssemblyScript, WAT, anything → `.wasm` | JS, TS (node ≥ 23.6), Python 3        |

**Rule of thumb:** WASM is now the capable, safe default — it can read input,
keep durable state, call an LLM, and fetch over HTTP, all under fuel metering and
an attestation transcript. Build agents and oracles in WASM. The JS/TS/Python
sandbox runs as an unsandboxed subprocess, so it is **disabled on the public
testnet** and only available on a node you run yourself
(`--allow-unsafe-runtimes`).

## The manifest artifact block

```json theme={null}
{
  "artifact": {
    "kind": "wasm",
    "path": "artifacts/program.wasm",
    "entrypoint": "run",
    "output_length": 256
  }
}
```

| Field           | Meaning                                         | Validation                                                         |
| --------------- | ----------------------------------------------- | ------------------------------------------------------------------ |
| `kind`          | `wasm`                                          | required                                                           |
| `path`          | artifact file, relative to the app root         | must exist; sha256 recorded at package time and re-checked on load |
| `entrypoint`    | exported function name                          | non-empty; must exist in the module                                |
| `output_length` | bytes the runtime reads from your output buffer | `> 0`; must not exceed `max_memory_bytes`                          |

## The ABI contract

Your module must satisfy exactly three things:

1. **Export linear memory named `memory`.**
2. **Export the entrypoint** declared in the manifest: a function with **zero
   parameters returning `i32`** — a pointer into linear memory where your
   output starts.
3. **Place your output at that pointer.** The runtime reads exactly
   `output_length` bytes from it and UTF-8 decodes them. Invalid UTF-8 fails
   the execution (`runtime.output_utf8`). By convention the output is JSON.

The smallest valid module, in WAT:

```wasm theme={null}
(module
  (memory (export "memory") 1)
  (data (i32.const 0) "{\"status\":\"ok\",\"answer\":42}")
  (func (export "run") (result i32)
    (i32.const 0)))
```

with `"entrypoint": "run"` and `"output_length": 27`.

<Note>
  `output_length` is fixed at deploy time, and the runtime reads exactly that
  many bytes. Pad your buffer (trailing spaces inside a JSON string field work
  well) or size the buffer to your maximum and make the JSON self-terminating.
</Note>

### Rust example

```rust theme={null}
// Cargo.toml: crate-type = ["cdylib"], target wasm32-unknown-unknown
static mut OUTPUT: [u8; 256] = [b' '; 256];

#[unsafe(no_mangle)]
pub extern "C" fn run() -> i32 {
    let result = br#"{"status":"ok","filtered_square_sum":1234}"#;
    unsafe {
        OUTPUT[..result.len()].copy_from_slice(result);
        OUTPUT.as_ptr() as i32
    }
}
```

```bash theme={null}
cargo build --release --target wasm32-unknown-unknown
cp target/wasm32-unknown-unknown/release/my_app.wasm artifacts/program.wasm
```

Set `"output_length": 256` to match the buffer. Since the runtime reads a
fixed number of bytes, make the unused tail parseable: end your JSON with a
padding string field sized to fill the buffer, for example
`{"status":"ok","value":42,"pad":"        "}`. Consumers read the fields they
care about and ignore `pad`.

## Host imports

All host functions live in the `gora` import module.

### `gora::http_get` — allowlisted HTTP fetch

```wat theme={null}
(import "gora" "http_get"
  (func $http_get (param i32 i32 i32 i32) (result i32)))
```

```text theme={null}
http_get(url_ptr, url_len, out_ptr, out_cap) -> i32
```

Reads a UTF-8 URL from `[url_ptr, url_ptr+url_len)`, performs a GET, writes the
response body to `[out_ptr, ...)`, and returns the number of bytes written.
**Negative return values are errors:**

| Code  | Meaning                                                       |
| ----- | ------------------------------------------------------------- |
| `-10` | bad arguments (negative pointer, zero length)                 |
| `-11` | app declares no `capabilities.http`                           |
| `-12` | module exports no `memory`                                    |
| `-13` | URL read out of bounds                                        |
| `-14` | URL is not valid UTF-8                                        |
| `-15` | URL has no parseable host (must be `http://` or `https://`)   |
| `-16` | host not in `capabilities.http.allowed_hosts`                 |
| `-17` | request failed (network / non-2xx treated as transport error) |
| `-18` | reading the response body failed                              |
| `-19` | response exceeds `max_response_bytes`                         |
| `-20` | response larger than your `out_cap` buffer                    |
| `-21` | writing the response into module memory failed                |

The capability is declared under `capabilities.http` in `gora.app.json`
(`gora init` scaffolds it for you):

```json theme={null}
{
  "capabilities": {
    "http": {
      "allowed_hosts": ["api.coingecko.com"],
      "timeout_ms": 3000,
      "max_response_bytes": 65536
    }
  }
}
```

Hosts are matched case-insensitively against the URL's host (ports, paths,
query strings, and `user@` prefixes are stripped before matching). Every call
is recorded in the execution result's `http_requests` transcript — URL, host,
status code, response sha256, byte count, and elapsed ms — which is what lets
validators agree on what was fetched.

### `gora::input_get` — read the request input

```wat theme={null}
(import "gora" "input_get"
  (func $input_get (param i32 i32) (result i32)))
```

```
input_get(out_ptr, out_cap) -> i32
```

Copies the raw request input into module memory at `out_ptr` (up to `out_cap`
bytes) and returns the byte length, so a WASM app can **branch on its input**.
Always available. Negative codes:

| Code  | Meaning                              |
| ----- | ------------------------------------ |
| `-30` | bad arguments (negative pointer/cap) |
| `-31` | input larger than `out_cap`          |
| `-32` | module exports no `memory`           |
| `-33` | writing into module memory failed    |

### `gora::set_output` — return a variable-length result

```wat theme={null}
(import "gora" "set_output"
  (func $set_output (param i32 i32)))
```

```
set_output(ptr, len)   ; no return
```

By default the runtime reads a fixed `output_length` (from your manifest) at the
pointer your entrypoint returns. Call `set_output(ptr, len)` to return a result
whose size isn't known at deploy time — an LLM reply, a dynamic JSON blob, an
echoed input. After calling it, the runtime returns exactly `len` bytes at
`ptr`. Always available; if you never call it, the fixed `output_length` applies.

### `gora::state_get` / `gora::state_put` — read and write your own state

```wat theme={null}
(import "gora" "state_get"
  (func $state_get (param i32 i32 i32 i32) (result i32)))
(import "gora" "state_put"
  (func $state_put (param i32 i32 i32 i32) (result i32)))
```

```
state_get(key_ptr, key_len, out_ptr, out_cap) -> i32   ; bytes read, -1 if absent
state_put(key_ptr, key_len, val_ptr, val_len) -> i32   ; 0 on success
```

`state_get` reads a value your app persisted on an earlier invocation into
`out_ptr` (returns `-1` if the key doesn't exist). `state_put` buffers a write
that is committed only if the run succeeds. Together they give an app **durable
memory across invocations** — the foundation for a stateful agent that reads its
prior state, acts, and writes new state each run. Both require state to be
enabled (`state.kv_enabled` in the deploy metadata — the CLI sets this on by
default); importing them otherwise fails instantiation.

| `state_get` code | Meaning                     |   | `state_put` code | Meaning                      |
| ---------------- | --------------------------- | - | ---------------- | ---------------------------- |
| `-1`             | key not present             |   | `0`              | success                      |
| `-40`            | bad arguments               |   | `-50`            | bad arguments                |
| `-41`            | no `memory` export          |   | `-51`            | no `memory` export           |
| `-42`            | key read out of bounds      |   | `-52`            | key/value read out of bounds |
| `-43`            | key not valid UTF-8         |   | `-53`            | key not valid UTF-8          |
| `-44`            | value larger than `out_cap` |   |                  |                              |

(`gora::state_put_input`, which writes the input to the fixed key `last_input`,
still exists for backward compatibility.)

### `gora::llm` — call a language model

```wat theme={null}
(import "gora" "llm"
  (func $llm (param i32 i32 i32 i32) (result i32)))
```

```
llm(prompt_ptr, prompt_len, out_ptr, out_cap) -> i32   ; reply bytes, or negative
```

Sends the prompt to the node-configured OpenAI-compatible chat endpoint and
copies the model's reply text into memory at `out_ptr`. **The API key lives on
the node, never in your app** — you get model access without holding secrets.
Every call is recorded in the attestation transcript (prompt and response
hashes), so the committee can see what was asked and answered. Pair it with
`set_output` to return the (variable-length) reply. Negative codes:

| Code  | Meaning                                                            |
| ----- | ------------------------------------------------------------------ |
| `-60` | bad arguments                                                      |
| `-61` | node has no LLM configured (operator must set `GORA_LLM_ENDPOINT`) |
| `-62` | no `memory` export                                                 |
| `-63` | prompt read out of bounds                                          |
| `-64` | prompt not valid UTF-8                                             |
| `-65` | request to the model endpoint failed                               |
| `-66` | model response was not valid JSON                                  |
| `-67` | no reply text in the response                                      |
| `-68` | reply larger than `out_cap` / the output cap                       |
| `-69` | writing the reply into module memory failed                        |

`gora::llm` is **operator-dependent**: it works only if the node you deploy to
has an endpoint configured (check with the Gora team for the public testnet). A
`-61` means no model is available on that node. This is what lets a WASM app be
an AI agent: read input/state → prompt the model → act.

With `input_get` + `state_get`/`state_put` + `llm` + a
[schedule](../launch/cli-price-feed-oracle), a WASM app can run as an autonomous
loop: wake on a timer, read its memory, think with an LLM, act (via `http_get`
or a chain callback), and persist new state — with every step attested.

## Building a WASM app

You have two toolchains; both produce the `.wasm` that `gora deploy` uploads.

**WAT (no extra toolchain).** `gora build` compiles a `src/program.wat` file
directly to `artifacts/program.wasm`. `gora init --language wasm` scaffolds a
working WAT app (reads input, keeps a counter in state, echoes both) — edit it
and rebuild:

```bash theme={null}
gora init myagent --language wasm --chain algorand --app-kind offchain-only --yes
cd myagent
# edit src/program.wat (uncomment the llm / http_get imports to go further)
gora build          # WAT -> artifacts/program.wasm
gora deploy .
```

**Rust → wasm32 (for larger apps).** Write a `cdylib` crate that declares the
host imports and compile to `wasm32-unknown-unknown`, then point the manifest's
`artifact.path` at the output `.wasm`:

```rust theme={null}
// lib.rs (crate-type = ["cdylib"])
#[link(wasm_import_module = "gora")]
extern "C" {
    fn input_get(out_ptr: *mut u8, out_cap: i32) -> i32;
    fn state_get(k: *const u8, klen: i32, out: *mut u8, cap: i32) -> i32;
    fn state_put(k: *const u8, klen: i32, v: *const u8, vlen: i32) -> i32;
    fn llm(p: *const u8, plen: i32, out: *mut u8, cap: i32) -> i32;
    fn set_output(ptr: *const u8, len: i32);
}

static mut BUF: [u8; 8192] = [0; 8192];

#[no_mangle]
pub extern "C" fn run() -> i32 {
    unsafe {
        let n = input_get(BUF.as_mut_ptr(), BUF.len() as i32);   // read input
        // ... call state_get / llm / http_get, build a reply in BUF ...
        set_output(BUF.as_ptr(), n.max(0));                      // return it
        BUF.as_ptr() as i32
    }
}
```

```bash theme={null}
cargo build --release --target wasm32-unknown-unknown
cp target/wasm32-unknown-unknown/release/myagent.wasm artifacts/program.wasm
gora deploy .
```

Memory notes: you own linear memory — pass pointers into your own buffers,
respect `out_cap`, and remember every host function returns a byte length (or a
negative error you should check). Export `memory` and a zero-arg entrypoint
returning an `i32` pointer (see [ABI contract](#the-abi-contract)).

## A worked example: an autonomous agent

An app that, on each (scheduled) invocation, reads its input, asks a model what
to do, remembers the last decision, and returns the model's reply — attested end
to end. In WAT (JSON building elided for brevity):

```wat theme={null}
(module
  (import "gora" "input_get"  (func $input_get  (param i32 i32) (result i32)))
  (import "gora" "state_get"  (func $state_get  (param i32 i32 i32 i32) (result i32)))
  (import "gora" "state_put"  (func $state_put  (param i32 i32 i32 i32) (result i32)))
  (import "gora" "llm"        (func $llm        (param i32 i32 i32 i32) (result i32)))
  (import "gora" "set_output" (func $set_output (param i32 i32)))
  (memory (export "memory") 1)
  (data (i32.const 0) "last")                    ;; state key
  (func (export "run") (result i32)
    (local $inlen i32) (local $reply i32)
    (local.set $inlen (call $input_get (i32.const 64) (i32.const 1024)))   ;; read input -> 64
    (local.set $reply (call $llm (i32.const 64) (local.get $inlen)         ;; prompt = input
                                 (i32.const 2048) (i32.const 4096)))       ;; reply -> 2048
    (drop (call $state_put (i32.const 0) (i32.const 4)                     ;; remember reply
                           (i32.const 2048) (local.get $reply)))
    (call $set_output (i32.const 2048) (local.get $reply))                 ;; return reply
    (i32.const 2048)))
```

Deploy it, then schedule it so it runs on its own (the node fires due schedules
automatically — see [Serve smart contracts / scheduling](../launch/cli-price-feed-oracle)):

```bash theme={null}
gora deploy .
gora schedule create --app myagent --input fixtures/request.json --every-seconds 60 --schedule-id agent-loop
```

`gora::llm` requires the node to have a model endpoint configured; a `-61` return
means it's unavailable there.

## Limits and metering

| Limit              | Default                        | On violation                                                                 |
| ------------------ | ------------------------------ | ---------------------------------------------------------------------------- |
| `max_memory_bytes` | 16 MiB                         | instantiation/output fails (`PolicyViolation` if `output_length` exceeds it) |
| `max_fuel`         | 10,000,000 wasmtime fuel units | trap → `Failed`                                                              |
| `max_execution_ms` | 1,000 ms wall clock            | `TimedOut`                                                                   |

Every execution returns metering you can see in the request record:

```json theme={null}
{
  "exit_status": "succeeded",
  "output": "{\"status\":\"ok\"}",
  "metering": {
    "elapsed_ms": 3,
    "fuel_consumed": 18411,
    "output_bytes": 16
  },
  "http_requests": [
    { "url": "https://api.coingecko.com/...", "status_code": 200,
      "response_sha256": "…", "response_bytes": 412, "elapsed_ms": 120 }
  ],
  "llm_calls": [
    { "model": "gpt-4o-mini", "prompt_sha256": "…", "response_sha256": "…",
      "response_bytes": 240, "elapsed_ms": 810 }
  ]
}
```

`exit_status` values: `succeeded`, `failed` (trap, fuel exhausted, bad
entrypoint signature), `timed_out`, `policy_violation`.

## Sandbox runtimes, for contrast

JS/TS/Python artifacts are executed as `node <path>` / `python3 <path>` with:

* **stdin** — `{"request_id": "...", "input": <your JSON>}` (one envelope)
* **stdout** — your JSON result (nothing else; logs go to stderr)
* **exit 0** — success; non-zero fails the request with stderr as the error
* limits: same 1,000 ms wall clock, stdout capped at `output_length`
  (≤ 1 MiB), stderr at 64 KiB

No host functions, no HTTP, no state — put everything the app needs in the
input, and return everything you produced in the output. Determinism is your
responsibility (don't read clocks or use `Math.random()` if the result feeds
consensus — derive randomness from the input, like the
[raffle drawer's HMAC draw](../examples/gora-raffle#the-drawer)).

<Warning>
  The node that executes sandbox artifacts runs them with plain `node`. Ship
  **plain JavaScript** (`.mjs`) unless you know every node in your network runs
  Node ≥ 22 (built-in type stripping). TypeScript sources that need stripping
  fail on Node 20 with a syntax error.
</Warning>

## Validation checklist

Before `gora deploy`, the package step re-checks:

* artifact exists, is non-empty, sha256 matches the recorded digest
* `entrypoint` non-empty, `output_length > 0`
* `capabilities.http.allowed_hosts` entries are bare lowercase hostnames
  (no scheme, port, path, or leading/trailing dots)

Quick local loop:

```bash theme={null}
gora build       # builds/verifies artifacts/program.wasm
gora validate    # manifest + policy + artifact + secret scan
gora invoke --app my_app --input fixtures/request.json
```

Next: [Create a Gora app](./create-offchain-wasm-app) ·
[Web UI and verified signing](./web-ui-and-signing) ·
[Connect to the testnet](../launch/connect-to-testnet)
