Build on Zanii

Zanii is a self-healing, agent-native Layer 1. This page is everything you need to send transactions, give AI agents budgeted wallets, meter payments per second, and verify chain state, from JavaScript or Python.

Network

Chain IDzanii-testnet-1
RPC endpointhttps://blockchain.zanii.agency
Explorerblockchain.zanii.agency/explorer
Web walletblockchain.zanii.agency/wallet
FaucetPOST /faucet (10 ZAN per request, rate limited) or use the wallet
Block time~2 seconds, deterministic finality after the 2-chain commit rule
SigningEd25519 over blake3, domain tag zanii/tx/v1
Addressesbech32, prefix zan1... (20-byte blake3 of the public key)
Testnet. Coins have no value and state may reset. Do not build value-bearing products against this network yet.

Quickstart

JavaScript / TypeScript

npm i @zanii/chain
import { Wallet, ZaniiClient } from "@zanii/chain";

const client = new ZaniiClient("https://blockchain.zanii.agency");
const wallet = Wallet.generate();          // keep wallet.seed (32 bytes) safe

await client.faucet(wallet.address);        // 10 testnet ZAN
await client.transfer(wallet, "zan1...", 1_000_000_000n);  // 1 ZAN

const acct = await client.account(wallet.address);
console.log(acct.balance);                  // in zi

Works in Node 18+ and the browser. A prebuilt browser bundle is served at /sdk.js (global zaniiChain).

Python

pip install zanii-chain
from zanii_chain import Wallet, ZaniiClient

client = ZaniiClient("https://blockchain.zanii.agency")
wallet = Wallet.generate()                  # keep wallet.seed.hex() safe

client.faucet(wallet.address)
client.transfer(wallet, "zan1...", 1_000_000_000)   # 1 ZAN, in zi

print(client.account(wallet.address)["balance"])
Byte-identical SDKs. Both SDKs implement the same canonical encoding and signing. A transaction built in JS and one built in Python from the same fields produce the same bytes and the same txid, verified by cross-language conformance tests in CI.

Accounts & units

  • Unit: the base unit is the zi. 1 ZAN = 1,000,000,000 zi (10^9). All API amounts are integers in zi. Never use floats for money.
  • Keys: Ed25519. A wallet is a 32-byte seed. The address is bech32("zan", blake3(pubkey)[:20]).
  • Nonces: strictly sequential per account, starting at 0. The SDKs manage nonces for you; if you build raw transactions, read the account nonce first.
  • State rent: accounts pay a small time-based rent; an account that cannot cover rent for a long period expires and is pruned. Faucet-funded accounts last years at current rates.

Transactions & fees

A transaction is a 10-field msgpack tuple, signed with Ed25519 over blake3("zanii/tx/v1" || encoded_fields):

FieldTypeMeaning
chain_idstringzanii-testnet-1, replay protection across networks
nonceintsequential per sender account
senderbytes32Ed25519 public key of the signer
recipientbytes20destination address (raw 20 bytes)
amountintzi to transfer
gas_limitint21,000 for a plain transfer
max_fee_per_gasintfee cap, EIP-1559 style
max_priority_fee_per_gasinttip to the proposer
payloadbytessystem commands, contract calls, or session envelopes
signaturebytes64Ed25519 signature

Fees: a per-block base_fee adjusts with demand and is burned; the priority fee goes to the proposer. Read the current base fee from GET /status. The SDKs set sane fee fields automatically.

Session keys

The core agent primitive. An owner delegates a capped, expiring, revocable key to an agent. The agent then signs its own transactions; the chain debits the owner, enforces the spend cap and expiry, and rejects anything outside the allowed action set (transfers and streams only: an agent key can never stake, vote, or manage keys).

// owner side (JS): authorize an agent key for 7 days with a 5 ZAN cap
const expiry = Date.now() + 7 * 24 * 3600 * 1000;
await client.sessionAuthorize(ownerWallet, agentPubkeyBytes, expiry, 5_000_000_000n);

// revoke instantly at any time
await client.sessionRevoke(ownerWallet, agentPubkeyBytes);
# agent side (Python): spend within the budget, acting for the owner
from zanii_chain import AgentWallet

agent = AgentWallet(RPC, AGENT_SEED, owner_address)
agent.pay("zan1payee...", amount_zan=0.25)

In JS, a delegated agent spends with client.sessionTransfer(agentWallet, ownerAddress, recipient, amount).

Payment streams

Escrowed, per-second metered payments. A payer opens a stream with a rate and a deposit; value accrues to the payee every second; the payee claims whenever it likes; either side can close and the unspent deposit returns. Ideal for agents paying per API call, per token, or per second of a service.

# open: 0.001 ZAN per second, 2 ZAN escrowed
stream_id = client.stream_open(payer_wallet, "zan1payee...",
                               rate_zi_per_s=1_000_000, deposit_zi=2_000_000_000)

client.stream_claim(payee_wallet, stream_id)   # pull accrued value
client.stream_close(payer_wallet, stream_id)   # stop, refund the rest

Streams also work through session keys, so a delegated agent can open and fund streams from its owner budget. Live streams are listed at GET /streams.

AgentWallet: give an LLM a wallet

AgentWallet (Python SDK) wraps a session key in an interface designed for LLM tool-calling loops, including ready-made JSON tool schemas:

from zanii_chain import AgentWallet

agent = AgentWallet("https://blockchain.zanii.agency", AGENT_SEED, OWNER_ADDRESS)

agent.balance()                              # owner balance + remaining session cap
agent.pay("zan1provider...", amount_zan=0.25)
agent.open_stream("zan1api...", rate_zan_per_second=0.001, deposit_zan=2.0)

tools = agent.openai_tools()                 # plug into any tool-calling loop
result = agent.call_tool(name, arguments)    # execute a tool call the model made
Safety model: the agent holds only its own session seed. It can never exceed the owner cap, never act after expiry or revocation, and never touch staking, governance, or key management. The worst a fully compromised agent can do is spend its remaining budget.

Agent registry

Agents can register a verifiable on-chain identity (name + metadata). The metadata field is free-form and designed to carry did:key documents from the Zanii identity ledger, linking an agent's proof-of-action history to its on-chain wallet.

client.register_agent(wallet, name="summarizer-01",
                      metadata='{"did":"did:key:z6Mk..."}')

Browse registered agents at GET /agents or in the explorer.

REST API reference

Base URL https://blockchain.zanii.agency. All responses are JSON. Errors return {"error": "..."} with a 4xx/5xx status.

GET/status

Chain head and health.

{"chain_id":"zanii-testnet-1","height":184223,"tip_hash":"9f3c...","view":184224,
 "base_fee":25,"gas_limit":30000000,"mempool":0,"in_recovery":false,
 "alerts":[],"faucet":true}
GET/block/<height>

Full block: header fields, proposer address, quorum certificate voter count, and decoded transactions.

GET/tx/<txid>

Look up a transaction by hex txid. Returns its block height, index, timestamp, and decoded fields.

GET/account/<zan1...>
{"balance":9999979000,"nonce":3,"rent_paid_ms":1765912000000}
GET/address/<zan1...>/txs

The most recent transactions touching an address (up to 50, newest first), each with height and timestamp.

GET/validators
[{"pubkey":"ab12...","stake":100000000000000,"jailed_at_epoch":-1}]

jailed_at_epoch: -1 active, -2 tombstoned (slashed, permanent), any other value is the epoch the validator was jailed at.

GET/agents?limit=100&offset=0

Registered agents, newest first: address, name, metadata, registration time. limit defaults to 100 (max 500); page with offset.

GET/streams?limit=100&offset=0

Open payment streams, most recently active first: id, payer, payee, rate_zi_per_s, remaining escrow, last claim time. Same paging params.

GET/proof/<zan1...>

Sparse Merkle Tree proof for an account against the current state root. present:true with a value is a membership proof; present:false is a verifiable non-membership proof.

GET/vproof/<height>

Validator-set-change proof at an epoch boundary, for light clients following the validator set (spec section 06).

POST/tx

Submit a signed transaction. Body: {"tx":"<hex of canonical encoded signed tx>"}. Returns {"txid":"..."}. The SDKs call this for you.

POST/faucet

Body: {"address":"zan1..."}. Sends 10 testnet ZAN. Rate limited per address; returns 429 during cooldown.

WASM contracts

Zanii executes WebAssembly contracts in a fuel-metered VM. A contract exports entry(ptr, len) -> i32 and linear memory; host functions provide state read/write, caller identity, transfer, and logging. Deploy and call through the transaction payload's system-command envelope:

// deploy (JS): the payload is the command tuple ("deploy", wasmBytes)
import { decodeAddress } from "@zanii/chain";
await client.system(wallet, ["deploy", wasmBytes], { gasLimit: 2_000_000 });

// call: recipient is the contract address, payload is ("call", inputBytes)
await client.system(wallet, ["call", inputBytes],
                    { recipient: decodeAddress("zan1contract...") });

Contract storage requires a refundable deposit per byte, so state stays bounded. A reverting call is still a valid, gas-charged transaction.

Light-client proofs

Every block header commits to the state root of a 256-bit Sparse Merkle Tree. A light client can verify any account (or its absence) with GET /proof, and can follow validator-set changes across epochs with GET /vproof, without ever downloading state. Blocks embed their parent quorum certificate, so a header chain is self-proving.

Protocol notes

  • Consensus: pipelined 2-chain BFT (HotStuff/Jolteon family), stake-weighted leader rotation, epoch-based validator sets, deterministic finality.
  • Self-healing: automatic jailing of dead validators, provable slashing of equivocators, supervised node restarts, autonomous recovery from network partitions, and forkless coordinated upgrades.
  • Canonical encoding: a strict msgpack subset (definite lengths, minimal integer widths, no floats or maps in consensus objects). One byte sequence per value, one value per byte sequence.

Questions, audits, partnerships: info@zanii.agency · Engineered in the United Arab Emirates.