zeroarena
v0.5.1
Published
Verifiable AI trading agents on 0G — backtest, certify, mint as ERC-7857 iNFT.
Maintainers
Readme
zeroarena
The on-chain arena for AI trading agents. Backtest qualifies your agent; live seasons prove it. Deterministic engine, encrypted strategy, every epoch chain-committed.
Production endpoints — 0G mainnet (chainId 16661)
| | URL / Address |
| - | - |
| Dashboard | zero-arena-fe.vercel.app |
| Transfer oracle | https://transfer-oracle-production-f390.up.railway.app |
| 0G Chain RPC | https://evmrpc.0g.ai |
| 0G Storage indexer | https://indexer-storage-turbo.0g.ai |
| 0G Explorer | chainscan.0g.ai |
| AgentCertificate | 0x21a5DEA59cfA07B261d389A9554477e137805c2f |
| ZeroArenaINFT | 0x4Bd4d45f206861aa7cD4421785a316A1dD06036f |
| ReencryptionOracle | 0x63909dA30b0d65ad72b32b3C8C82515f7BFA6Fd6 |
| LiveCertificate | 0x168c244c872f5FC2D737D3126D08e9EEE45fFbc7 |
| Season | 0x4e900860565F9D399B7295c0D28CC7954202524e |
These addresses ship pre-pinned via npx zeroarena init — you usually don't need to copy them by hand.
Mainnet preview caveat.
ReencryptionOracleis the v0.1 trusted-ECDSA stub. The wallet holding the oracle private key can forge any ERC-7857 transfer. v0.4 swapsverifyTransfer()for 0G Compute TEE-quote verification (no client-side change). Until then, treat the mainnet oracle key as a custody root and avoid high-value transfers.
Scaffold a project in one command
npx zeroarena init my-agent
cd my-agent
npm start # backtest → certify → mint, end-to-endThe interactive wizard walks you through:
- Strategy template — RSI mean reversion, MACD trend, EMA crossover, LLM-driven, or empty scaffold
- Market — spot (long-only) or perpetual futures (leverage, funding, liquidation)
- LLM provider (if picked) — Anthropic Claude API, OpenAI, Google Gemini, or local Claude Code CLI (no API key needed)
- Strategy parameters — oversold/overbought thresholds, position size, leverage, etc.
- Wallet setup — paste your key, generate one with
cast wallet new, or fill.envlater
Mainnet addresses are pre-pinned. The wizard writes agent.ts, run.ts, .env, package.json, and a per-template README.
Or install manually
npm install zeroarenaQuick start
import { ZeroArena, Agent, type Action, type Observation } from 'zeroarena';
class RsiAgent extends Agent {
decide(obs: Observation): Action {
if (obs.rsi14 < 30) return { direction: 1, size: 0.5 };
if (obs.rsi14 > 70) return { direction: 0, size: 0 };
return { direction: obs.position > 0 ? 1 : 0, size: obs.position > 0 ? 0.5 : 0 };
}
override toJSON() { return { className: 'RsiAgent', oversold: 30, overbought: 70 }; }
}
const za = new ZeroArena({
rpc: 'https://evmrpc.0g.ai', // 0G mainnet (16661)
indexer: 'https://indexer-storage-turbo.0g.ai',
privateKey: process.env.PRIVATE_KEY!,
addresses: {
AgentCertificate: process.env.ZA_ADDR_CERT!,
ZeroArenaINFT: process.env.ZA_ADDR_INFT!,
ReencryptionOracle: process.env.ZA_ADDR_ORACLE!,
},
});
const dataset = await za.loadDataset({ rootHash: '0xabc…' });
const result = await za.backtest(new RsiAgent(), dataset, { initialBalance: 10_000, market: 'spot' });
const cert = await za.certify(result);
const inft = await za.mintAgent({ agent: new RsiAgent(), certificate: cert, name: 'RSI v1' });PRIVATE_KEY is your own wallet — pays gas, signs the tx, owns the iNFT.
Public API
class ZeroArena {
constructor(config: ZeroArenaConfig);
uploadDataset(csvPath: string): Promise<Dataset>;
loadDataset(opts: { rootHash: string }): Promise<Dataset>;
backtest(agent: Agent, dataset: Dataset, opts: BacktestOptions): Promise<BacktestResult>;
certify(result: BacktestResult, opts?: { trustTier?: TrustTier; attestationHash?: string }): Promise<Certificate>;
mintAgent(opts: { agent: Agent; certificate: Certificate; name: string; description?: string }): Promise<INFT>;
transferAgent(opts: { tokenId: bigint; to: string; recipientPubKey: string }): Promise<TransferResult>;
}
abstract class Agent {
abstract decide(obs: Observation): Promise<Action> | Action;
toJSON(): Record<string, unknown>;
}Delegate live operation (v0.3, opt-in)
After mint, the owner can either self-operate the paper daemon OR delegate to Zero Arena's backend so live-cert metrics keep ticking without the owner running their own server. The on-chain operator role is admin-curated globally; the owner's signed /onboard payload is per-token consent.
import { HttpOnboardClient } from 'zeroarena';
import { Wallet } from 'ethers';
import { readFileSync } from 'node:fs';
const owner = new Wallet(process.env.PRIVATE_KEY!);
const onboard = new HttpOnboardClient({
url: 'https://onboard-production-ed6c.up.railway.app',
authToken: process.env.ONBOARD_AUTH_TOKEN, // required by the production deployment
// encrypt: true (default, since SDK 0.4) — auto-fetches the operator's
// secp256k1 pubkey from /health and ECIES-encrypts the agent source
// before posting. TLS-terminating intermediaries see only ciphertext.
// Set { encrypt: false } only for debugging.
});
const result = await onboard.onboard(
{
tokenId: 5n,
agentSource: readFileSync('./agent.ts', 'utf8'),
genesisHash: '0x…', // your static cert's runHash
barsPerEpoch: 4, // optional — defaults to 96 (24h at 15m)
},
owner, // any ethers.js signer
);
// → { status: "onboarded", tokenId: "5", operator: "0xB1a5402E…", pid: 113, startedAt: "…" }
// Later — stop the delegated daemon:
await onboard.offboard({ tokenId: 5n }, owner);Trust shift: from owner reputation (self-operate, cheatable) to Zero Arena's public operator reputation (one entity, accountable). v0.4 moves the orchestrator into a 0G Compute TEE; the HTTP surface and the SDK client interface stay the same.
Manual encryption
If your stack can't use HttpOnboardClient directly (e.g. multi-sig signing in a separate process), encrypt the bundle yourself:
import { encryptAgentSource } from 'zeroarena';
const health = await fetch('https://onboard-production-ed6c.up.railway.app/health').then((r) => r.json());
const bundle = encryptAgentSource(readFileSync('./agent.ts', 'utf8'), health.operatorPubKey);
// bundle = { scheme: "ecies-secp256k1-aes256gcm-v1", blob: "<base64>" }
// POST it as `agentSource` in the onboard body — the server treats it identically.Oracle (transfer flow)
import { ZeroArena, HttpOracleClient } from 'zeroarena';
const za = new ZeroArena({
...,
oracle: new HttpOracleClient({
// Production oracle deployed by Zero Arena. Or run your own (see zero-arena-bacend).
url: 'https://transfer-oracle-production-f390.up.railway.app',
}),
});CLI
npx zeroarena init my-agent
npx zeroarena dataset upload ./btcusdt-15m.csv
npx zeroarena backtest --agent ./agent.ts --csv ./btcusdt-15m.csv --balance 10000
npx zeroarena certify --agent ./agent.ts --csv ./btcusdt-15m.csv
npx zeroarena mint --agent ./agent.ts --cert 1 --name 'RSI v1' \
--run-hash 0x… --storage-root 0x… --dataset-hash 0x…CLI reads process.env: PRIVATE_KEY, ZA_RPC, ZA_INDEXER, ZA_ADDR_CERT, ZA_ADDR_INFT, ZA_ADDR_ORACLE.
What you get
- Deterministic
BacktestEngine(noMath.random, noDate.now). Same agent + same dataset → samerunHash, byte-identical. - Spot is the v0.2 canonical market. The perp engine is feature-complete (configurable leverage ≤10×, 8h funding accrual, isolated-margin liquidation) but is officially v0.3 scope — usable today via
market: 'perp', not yet the canonical demo path. - AES-256-GCM encryption on run logs and agent metadata — your code never leaves the machine in plaintext.
- 0G Storage upload via
@0gfoundation/0g-storage-ts-sdk. - 0G Chain anchoring of
runHash, storage root, dataset root, metrics, andtrustTier. - ERC-7857 mint + oracle re-encryption transfer.
Model-agnostic: whatever runs inside decide() — a rule, an LLM call, an RL policy — is your choice.
Trust model
Each certificate is tagged with its tier. v0.1 ships T1 + T2:
- T1 — Commitment.
runHashanchored on-chain. Trades cannot be edited after submission. - T2 — Reproducibility. Owner can authorize a verifier with the encrypted run log + AES key; verifier reruns and asserts the same
runHash. - T3 — TEE attestation (future). Engine + agent run inside a 0G Compute enclave; trustless verification by anyone, agent code never revealed.
Determinism contract
The verifiability story collapses if backtests aren't reproducible. The engine enforces:
- No
Math.random— seed a PRNG if you need randomness. - No
Date.now— useobs.timestamp. - Fixed iteration order; no
for…inover objects in the hot path. runHash = keccak256(agentHash || datasetHash || optionsHash || tradesHash)with stable JSON for each.
Non-deterministic sources (LLM APIs) are still committed via runHash, but the certificate stays at T2 only.
Security
npm audit is clean as of 0.5.0. The package pins axios>=1.12.0 via npm overrides to fix two upstream advisories (GHSA-xx6v-rp6x-q39c, GHSA-43fc-jf86-j433) that arrive transitively through @0gfoundation/[email protected] → open-jsonrpc-provider. Devtools (vitest, vite, esbuild) are pinned to the latest stable major. Verified: 109/109 tests pass, end-to-end download + backtest reproduces the same runHash.
License
MIT.
