Skip to main content

WASM runtime reference

Gora runs two kinds of app artifacts: This page is the WASM reference. For the sandbox contract, see the runtime contract in Create a Gora app.

Pick the right runtime (read this first)

Capabilities differ — this table is the honest state of the runtime today: 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

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:
with "entrypoint": "run" and "output_length": 27.
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.

Rust example

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

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: The capability is declared under capabilities.http in gora.app.json (gora init scaffolds it for you):
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

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:

gora::set_output — return a variable-length result

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

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. (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

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: 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, 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:
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:
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).

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):
Deploy it, then schedule it so it runs on its own (the node fires due schedules automatically — see Serve smart contracts / scheduling):
gora::llm requires the node to have a model endpoint configured; a -61 return means it’s unavailable there.

Limits and metering

Every execution returns metering you can see in the request record:
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).
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.

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:
Next: Create a Gora app · Web UI and verified signing · Connect to the testnet