Skip to main content

Deploy to Gora

The fastest way to ship a Gora app is the live public testnet. No signup and no local infrastructure — install the CLI, set a developer token, point it at the endpoint, and deploy. The public testnet requires a developer token that is your identity. Execution and AI use bounded testnet credits; consult current pricing and balances before enabling automation. For endpoints, deployed contract IDs, and what the testnet supports today, see Connect to the testnet.

Prerequisites

  • the gora CLI — install it with:
    This adds gora to ~/.gora/bin (make sure that directory is on your PATH). The fresh testnet mirror has macOS ARM64 (macOS 11+) and Linux x86_64 (glibc 2.39+, such as Ubuntu 24.04+) packages. Checksums are mandatory. Other platforms need a matching current build from source; do not substitute an old CLI release for these managed-app interfaces.
  • a developer token you choose — your testnet identity, not a frontend API key or an unlimited inference allowance.

1. Create an app and point it at the testnet

Configuration belongs to the current app folder; it is not inherited from its parent. Create the project first, then configure it:
Pick a unique name: application IDs are global on the shared testnet. One hostname serves both the node API (under /v1/*) and the dev bridge routes:
Save it once per app folder:
Or export it for every command:
Or pass --node <url> on individual commands. Check the endpoint is up:
Set your developer token — on the public testnet this is required and it is your identity: the first token to claim an app id owns it, so no one else can overwrite your app, schedule it, or delete its schedules. Every request you send runs with caller set to your token’s dev_… identity.
Generate a token once per developer, keep a secure backup, and reuse it when upgrading that developer’s apps. Generating another token does not recover the old identity. Never put it in frontend code, a checked-in file, or a published artifact. Details: Connect to the testnet.

2. Build the app

Validation catches missing artifacts, invalid JSON, unknown execution modes, missing policies, and obvious secrets. Packaging writes a deterministic receipt to:
Note: dashes in app names become underscores in the app id — hello-alice deploys as hello_alice.

3. Deploy

Deploy uploads:
  • app artifact bytes or source
  • manifest JSON
  • policy JSON
  • deterministic package metadata
  • optional contract bindings
  • a static UI bundle, when ui.dir is configured
The receipt is written to:
Deploying costs nothing: the testnet node seeds a developer balance for each app, and a simple invoke costs roughly 106 billing units from that balance. Re-running gora deploy . upgrades the app in place — same app_id, new version. Existing user instances remain pinned to their original executable; an app upgrade is not permission to silently change their code or vault policy.

4. Invoke

The CLI waits for the execution result (output, billing settlement and state roots). This does not mean a proposed chain transaction has confirmed. Without a saved node.url, add --node https://gora-bridge.74.241.248.103.nip.io.

5. Inspect

Inspect outputs are saved under:

Build a complete app: program, UI and user instances

The same interfaces support trading bots, research assistants and other apps. For example, a Fieldnotes-style research assistant can classify an observation, return a typed object and retain notebook history without any transaction authority. Its program and UI deploy to Gora; no developer-hosted server is required afterward.

Return a structured LLM object

The public runtime executes WASM. Import gora.llm_json(ptr, len, out_ptr, out_cap) -> i32 and pass serialized JSON such as:
The return value is the number of schema-validated JSON bytes written, or a negative failure status. Check the status and buffer bounds before decoding. The provider, model and API key are configured by the managed host, not by the app. There is no public REST /llm endpoint and no API key belongs in the UI. Validators replay the captured host response; they do not each purchase a separate model answer. A valid JSON object is not proof that its advice is correct: apply deterministic business rules before any consequential action. Use the public input_get, caller_get, state_get, state_put and set_output host imports for input, trusted execution context, persistent state and output. A wallet-instance invocation receives:
Read caller identity and the trusted request timestamp from caller_get, not from fields supplied by the user. Bound retained history and handle failed LLM calls explicitly; do not turn a failure into an invented successful result or an on-chain action.

Package a rich static UI

Build your frontend into dist and add this field to gora.app.json:
Then run your frontend build, gora build, gora validate, gora package and gora deploy .. Use relative asset URLs (for example, Vite base: './'). Open the registry’s returned ui_url; the managed testnet serves each app from its own HTTPS origin and supports SPA history routes. A multi-screen interface, interactive charts and client-side state work normally there. Render actual instance state and receipts, not fabricated balances or simulated success. The bundle limit is 256 files, 2 MiB of base64 per file and 8 MiB of base64 in total. Hidden files, symlinks and paths escaping the app are rejected. Host large images and videos on HTTPS CDNs and reference them from the UI; use lazy loading, video controls and useful error states. Plain HTTP mixed content is blocked. CDN media is not stored on the Gora chain. On a self-hosted installation without per-app origins, the sandbox fallback requires hash routing and does not offer ordinary localStorage/history routing.

Give each customer their own identity and state

Inside the native Gora app, the wallet bridge handles authentication without exposing its session token to frontend code:
The identifier is already a 32-byte lowercase hex value. Its DID is did:gora:testnet:<instance_id>; do not hash the ID again when anchoring it to a contract. Instances have isolated state, wallet ownership, version-pinned code and independently configurable instructions. Update configuration with Gora.instance('config', id, {config, revision}); stale revisions are rejected. An ordinary browser does not automatically have Gora.instance. Integrate its wallet with POST /auth/challenge (chain, address, app_id), sign the exact returned message and exchange it at POST /auth/session (challenge_id, signature). Use the returned bearer for that app and caller. Base uses EIP-191 personal-sign, Solana signs raw bytes, and Algorand signs raw Ed25519 bytes without an additional message prefix. A developer token identifies the developer, not the end customer’s wallet, and must never be used as a shared browser credential.

Optional recurring execution and pause

For a program designed to handle a recurring action, create a schedule with Gora.instance('schedule', id, {schedule_id, input, interval_seconds}). Each execution receives a fresh trusted submission timestamp; do not freeze one into the schedule input. Disabling the instance through its configuration ({config, revision, enabled:false}) prevents new instance execution. Developer/operator schedule owners can pause an individual schedule through POST /v1/schedules/:id with {"enabled":false} and resume with {"enabled":true}. This preserves the schedule ID, caller, run counter and last request. Do not delete/recreate it to pause, and do not use a nonexistent CLI schedule pause command. Pausing does not cancel already submitted transactions or revoke authority previously granted by an on-chain wallet policy. Posting an existing owned schedule through the creation endpoint also preserves its run counter and request history. An identical active request leaves its next run time unchanged; a paused resume or changed input/interval keeps the next request number at N+1. App, instance, caller, payer and request-prefix bindings cannot be changed under that ID. Use a fresh ID/prefix for a genuinely new schedule; deleting and recreating an old ID is not the resume mechanism. Application callbacks currently settle on Algorand testnet, using managed gateway v4 771952005 and algod https://testnet-api.algonode.cloud. Algorand staking remains 764254589 with GORA ASA 764254575. Validator staking is also live on Base Sepolia and Solana devnet; this does not imply application callback gateways are deployed on those chains. Full IDs and endpoints: Connect to the testnet. If your app targets a chain gateway or app contract, link that metadata before deploy:
This writes local metadata under:
and updates gora.app.json with a contract binding. Skip this step for off-chain-only apps.

Autonomous actions without signing every transaction

A user can create an independent policy-vault contract with their wallet, fund it and authorize specific target methods and limits. The vault is an Algorand application account with no private key. The LLM does not sign transactions or receive unrestricted access to the wallet. A managed relayer submits a context-bound oracle proof; the vault checks it and executes only permitted inner transactions. The owner signs setup, deposits and policy changes, but does not need to approve each already-authorized action. The wallet owns withdrawal, revocation and recovery controls. On-chain policies limit target/method, amounts, budgets and numeric arguments; target contracts must additionally enforce their own financial invariants. Use the approved vault artifact and ABI below, not imports from Gora core or operator credentials. The managed anchor verifier accepts the approved vault program shape, not any contract an app claims to control. After creating a compatible vault, its wallet owner attaches it with Gora.instance('vault', instance_id, {chain:'algorand-testnet', app_id:vaultId}). Gora verifies live bytecode, canonical testnet genesis, owner, instance ID, app hash and artifact hash before accepting the anchor. A developer-token-owned instance is not a substitute for a wallet-owned instance in this flow.

Obtain the approved vault and wallet descriptors

The pilot’s public vault artifact contains base64 approval/clear programs, schemas and constructor argument order. Pin the decoded approval program’s SHA-256 to 02789d101bad218a494aea6e8781fd76c1ebbb26d94676fb5620f9efb5ff6035. This release uses 16 integer / 6 byte global fields, zero local fields and one extra program page. Use the registry’s ui_url when resolving assets; the shared bridge URL is not interchangeable with an app’s dedicated UI origin. The public Northstar manifest provides complete working descriptors under dev_registry.manifest_json.interface.actions (parse manifest_json first if returned as a string). Reuse the descriptor shapes for create_vault, fund_vault_algo, opt_in_vault, fund_vault_asset, authorize_market, revoke_vault, owner_close_position and withdraw_collateral in your own manifest. Keep your own app ID, executable, strategy and wallet-owned instance; do not reuse Northstar’s identity or vault. For create_vault, call Gora.action('create_vault', {parameters}) with the artifact’s approval_program_base64 mapped to parameters.approval_program and clear_program_base64 mapped to parameters.clear_program, plus:
  • instance_hash: the new instance’s 32-byte hex ID, without hashing it again.
  • app_hash: SHA256 of your UTF-8 app ID; artifact_hash: that instance’s pinned WASM SHA-256; policy_hash: your initial 32-byte policy digest.
  • network_hash: the canonical Algorand testnet genesis hash, hex-encoded.
  • gateway_id: "771952005"; asset_id: the collateral ASA ID.
  • max_per_call, daily_budget, lifetime_budget: decimal base-unit strings chosen and reviewed by the owner, not proposed unrestricted limits from an LLM.
The descriptor takes the creator/owner from $.wallet.algorand. Register gora:actionComplete, gora:actionFailed and gora:actionCancelled listeners before calling it; match event.detail.action_id. After successful creation, read event.detail.output.tx_hash, then GET /chains/algorand/transactions/:txid for the confirmed application-index. Attach that ID with Gora.instance('vault', ...), fund the vault’s minimum balance, opt it into collateral, deposit and authorize only your intended target methods and numeric limits. An app creation receipt alone does not fund or authorize a vault. Later owner actions use the same native bridge, for example:
The independent reference SDK source is in external/gora-vault/sdk/index.mjs in this checkout, with its ABI in that repository’s README. It is not an npm-published dependency. The artifact, public descriptors and encoding contract here do not require core source access.

Return a vault command from WASM

After obtaining a schema-validated LLM decision, the program validates it against strategy limits and fresh on-chain state. For an authorized action it returns a chain_callback with this shape; variables below are calculated by your program, not literal placeholder strings to send:
Use decimal strings for uint64 JSON fields. The nonce is the current vault nonce plus one. Derive a short expiry from trusted caller_get submission time, not user input; it must still be more than 15 seconds and at most one hour ahead when settlement validates it. Re-read the vault’s current policy hash, owner, network and executable/instance bindings before constructing the intent. packed_args is count:u8 followed by repeated length:u16BE || argument_bytes; the first argument is the authorized UTF-8 method. There are 1–8 arguments and at most 1024 packed bytes. resources is asset_count:u8 || asset_ids:u64BE[] || app_count:u8 || app_ids:u64BE[] || account_count:u8 || account_public_keys:bytes32[]. With no extras it is three zero bytes (base64 AAAA). The relayer supplies the gateway, target and collateral references plus gateway/allowance boxes; include any target-specific boxes and stay within Algorand’s combined resource limit. Calculate the exact signed-result binding, where || means byte concatenation, all integers are uint64 big-endian and all identity/hash fields are 32 raw bytes:
The relayer independently recomputes this binding from the signed execution and current vault state. It supplies the gateway request/context proof; the app does not receive a relayer key or choose an unchecked execution identity.

Display execution and chain confirmation separately

An execution result can contain a proposed chain_callback while settlement is still pending. Read /v1/requests/:id/receipt for the committed execution proof, /v1/jobs/:id for execution status and /v1/settlements/:id for chain delivery. Wallet sessions can read only their own caller/app evidence. Show pending, confirmed and failed delivery distinctly, and show the callback transaction ID and confirmation round before reporting an on-chain action as complete. A no-action result has no chain settlement; a failed callback must not be shown as a trade. Gateway deposit cleanup is subsequent operational work, not a second trade.

Node API reference

All routes live behind the public URL: Limits: artifacts up to 5 MiB, request bodies up to 8 MiB.

What deploy does not do

gora deploy does not:
  • deploy smart contracts
  • submit chain transactions
  • sign with user keys
  • pay chain gas
It registers the Gora app package. Chain effects happen later through a gateway, wallet, contract, mobile signing flow, or submitter.

Alternative: run a local node

If you want the full deploy + invoke + inspect loop on your own machine, the dev node is built into the CLI:
Useful flags:
  • --contract-registry <json> — expose chain contract metadata
  • --token <bearer> — require Authorization: Bearer on writes; mandatory if you bind anything other than 127.0.0.1
  • --signing-request-sink <bridge-url> — forward mobile_signing_request / chain_callback outputs to a Dev Bridge
Then use --node http://127.0.0.1:8080 (or set node.url to it) in the commands above. Next: Test with mobile.