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 ID | zanii-testnet-1 |
|---|---|
| RPC endpoint | https://blockchain.zanii.agency |
| Explorer | blockchain.zanii.agency/explorer |
| Web wallet | blockchain.zanii.agency/wallet |
| Faucet | POST /faucet (10 ZAN per request, rate limited) or use the wallet |
| Block time | ~2 seconds, deterministic finality after the 2-chain commit rule |
| Signing | Ed25519 over blake3, domain tag zanii/tx/v1 |
| Addresses | bech32, prefix zan1... (20-byte blake3 of the public key) |
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"])
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):
| Field | Type | Meaning |
|---|---|---|
chain_id | string | zanii-testnet-1, replay protection across networks |
nonce | int | sequential per sender account |
sender | bytes32 | Ed25519 public key of the signer |
recipient | bytes20 | destination address (raw 20 bytes) |
amount | int | zi to transfer |
gas_limit | int | 21,000 for a plain transfer |
max_fee_per_gas | int | fee cap, EIP-1559 style |
max_priority_fee_per_gas | int | tip to the proposer |
payload | bytes | system commands, contract calls, or session envelopes |
signature | bytes64 | Ed25519 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
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.
/statusChain 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}
/block/<height>Full block: header fields, proposer address, quorum certificate voter count, and decoded transactions.
/tx/<txid>Look up a transaction by hex txid. Returns its block height, index, timestamp, and decoded fields.
/account/<zan1...>{"balance":9999979000,"nonce":3,"rent_paid_ms":1765912000000}
/address/<zan1...>/txsThe most recent transactions touching an address (up to 50, newest first), each with height and timestamp.
/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.
/agents?limit=100&offset=0Registered agents, newest first: address, name, metadata, registration
time. limit defaults to 100 (max 500); page with offset.
/streams?limit=100&offset=0Open payment streams, most recently active first: id, payer, payee,
rate_zi_per_s, remaining escrow, last claim time. Same paging params.
/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.
/vproof/<height>Validator-set-change proof at an epoch boundary, for light clients following the validator set (spec section 06).
/txSubmit a signed transaction. Body: {"tx":"<hex of canonical
encoded signed tx>"}. Returns {"txid":"..."}. The SDKs call this
for you.
/faucetBody: {"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.