Building an app as one HTML page
You are looking at an old version of this page: revision #53 by zoobc.how, 2026-10-26 20:39 UTC. See the current version
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).
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.
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.