ZooBC wiki
Apps and building on ZooBC

Building an app as one HTML page

A ZooBC application can be a single HTML file with no server behind it. The page reads the chain from a node's HTTP API, builds and signs transactions in the browser with the user's key, and posts the signed bytes. Users do not run a node, and there is no API key to request. The official wallet is the proof: it is itself one HTML file, which every node serves at / and /wallet (web wallet).

One HTML file reads with GET, signs in the browser and posts signed bytes to a node or gateway.
One HTML file reads with GET, signs in the browser and posts signed bytes to a node or gateway.

The node API

Every node serves a plaintext HTTP/1.1 API, on port 8080 by default, with more than a hundred routes under /api/v1/: blocks, accounts, tokens, apps, the exchange and more (node API). Every response carries these headers, and an OPTIONS request is answered 200 at once, so a page hosted anywhere can call any node:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Node-Public-Key, X-Timestamp, X-Signature

The node itself has no TLS. Public domains terminate TLS at a gateway, which adds its own CORS headers. Browsers block plain-HTTP calls from a page loaded over HTTPS, so a public page talks to a gateway.

All but eight routes are unauthenticated reads, and posting a transaction needs no key of any kind, because the transaction carries its own signature. Limits apply per client address: 300 write requests per minute, then 429 with a Retry-After header; reads are unlimited unless the operator sets a limit; and 20 failed signed authentications within 5 minutes lock the client out for 300 seconds.

Signing in the browser

The page signs a digest that binds the transaction to one chain:

digest = SHA3-256("ZBC-TX" ‖ genesis hash ‖ unsigned transaction bytes)

A native account signs it with Ed25519. The page reads genesis_hash and signing_version (2) from GET /api/v1/node/info, and because the genesis hash is inside the digest, a signature made for one chain is worthless on another (chain-bound signatures). No SDK is required: Ed25519, SHA3-256 and the genesis hash are enough, and the byte layout is described in transaction envelope. The public tools also offer ready-made libraries, including a dependency-free JavaScript file for a script tag (command-line tools). The key never leaves the page.

Posting and tracking

POST /api/v1/transactions               → 202, with "transaction_hash"
GET  /api/v1/transactions/<hash>/status → staging, mempool, confirmed (with block_height),
                                          rejected (with the reason and stage), or 404 not_found

A 202 means accepted, not done. The node has parsed the transaction, run its first checks and taken it into staging; a block producer still has to include it. Until the status says confirmed, no balance has changed. At one block every 15 seconds, polling every couple of seconds is plenty.

If a reply is lost, resend the identical signed bytes. The transaction's hash is SHA3-256 of the unsigned bytes and the signature, so a node that already holds the transaction answers 202 with "duplicate": true, adding "state": "confirmed" once it is in a block, and the transaction executes only once. Signing again with a new timestamp makes a different transaction, which is how double payments happen.

By default a block may include a transaction up to 3,600 seconds after its timestamp; a version-3 transaction states its own window, up to 86,400 seconds; and a timestamp more than 3,600 seconds in the future is refused (expiry and timestamps). Before releasing goods against a payment, wait for confirmation, then for the finalized flag, which a transaction gets once a co-signed spine block covers its block (finality and rollback). The API also reports finalized_height, each block's block_seed_proof, a transaction's expiry, and spine blocks at /spine/blocks/:height.

After 202: staging, mempool, confirmed, finalized; a lost reply is resent with the same bytes.
After 202: staging, mempool, confirmed, finalized; a lost reply is resent with the same bytes.

Routes for apps

The node itself, not the archival service, serves the app routes: /apps (filters status, limit and category, which is solo, pvp or party), /apps/open, /apps/pool, /apps/stats, /apps/:id (with any settlement) and /accounts/:a/apps.

Checklist

  • Read through a gateway over HTTPS; write by posting signed bytes, with the key kept in the page.
  • Track by hash, and retry by resending the same bytes, never by signing again.
  • Respect 429 and Retry-After.
  • Show chain text with textContent (chain data is untrusted input).
  • Treat "confirmed" as received and "finalized" as settled.

See also