Skip to main content

Documentation Index

Fetch the complete documentation index at: https://0arena.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Solidity 0.8.24, OpenZeppelin v5.1, Foundry. Two layers — qualifier (cert + iNFT + oracle) and arena (live cert + season). All deployed and verified on 0G mainnet (chainId 16661).

7.1. AgentCertificate.sol

Append-only registry of backtest certificates. Anchors runHash + metrics + storage roots. The entrance ticket.
FunctionPurpose
submit(runHash, storageRootHash, datasetHash, attestationHash, totalReturnBps, sharpeX1000, maxDrawdownBps, winRateBps, trustTier, market)Anchor a backtest cert. Returns certId. Reverts on empty hash, invalid tier (1/2/3), invalid market (0=spot or 1=perp), or attestation mismatch (T3 must carry attestation; T1/T2 must not).
get(certId)Read the full Certificate struct. Reverts UnknownCertificate if not set.
exists(certId)Bool.
EventIndexedNon-indexed
CertificateSubmittedcertId, owner, runHashstorageRootHash, trustTier, market
Certificate struct fieldTypeNote
runHashbytes32Canonical hash
storageRootHashbytes320G Storage root
datasetHashbytes32Dataset identity
attestationHashbytes320x0 for T1/T2
totalReturnBpsint128Signed
sharpeX1000uint128
owneraddressmsg.sender
createdAtuint48block.timestamp
maxDrawdownBps, winRateBpsuint16
trustTier, marketuint8
Ownable2Step admin is reserved for v0.6+ — no admin functions exposed in v0.5. Submissions are immutable.

7.2. ZeroArenaINFT.sol (ERC-7857)

Mints require a certificate that clears the threshold. Vanilla transferFrom/safeTransferFrom are disabled — ownership moves only through transfer / clone, which require an oracle proof that the sealed key was re-encrypted for the recipient.
FunctionPurpose
mint(certificateId, metadataHash, storageRoot)Cert owner mints. Reverts if cert.owner != msg.sender, or if metrics don’t clear minTotalReturnBps / minSharpeX1000.
transfer(from, to, tokenId, sealedKey, proof)ERC-7857 transfer. proof = abi.encode(bytes32 newMetadataHash, uint256 deadline, bytes signature). Verified against ReencryptionOracle.
clone(from, to, tokenId, sealedKey, proof)Mints a new iNFT for the recipient with the same metadata/storage references — copy, not move.
setOracle(address)Admin only.
setThresholds(int128 minReturn, uint128 minSharpe)Admin only.
MappingUse
metadataHashes[tokenId]Current encrypted-metadata hash
storageRoots[tokenId]0G Storage root for the bundle
certificateOf[tokenId]Backing static cert ID
EventIndexedNon-indexed
AgentMintedtokenId, owner, certificateIdmetadataHash, storageRoot
SealedKeyDeliveredtokenId, tosealedKey
MetadataUpdatedtokenIdmetadataHash
OracleUpdatedoldOracle, newOracle
ThresholdsUpdatedminTotalReturnBps, minSharpeX1000
Default thresholds: minTotalReturnBps = 0 and minSharpeX1000 = 1000 (Sharpe ≥ 1.0).

7.3. ReencryptionOracle.sol

v0.1 / v0.5 trusted-ECDSA stub. The off-chain transfer-oracle service signs an EIP-191 digest over (chainId, inftAddress, tokenId, from, to, sealedKeyHash, newMetadataHash, deadline); this contract recovers the signer and verifies it matches the registered oracle signer address.
FunctionPurpose
verifyProof(...)Recovers signer from signature, asserts equals signer().
setSigner(address)Admin only.
signer()Current authorized signer (compare against transfer-oracle /health).
In v1.0 the verifier is swapped for 0G Compute TEE-quote verification; same call shape, only the trust root changes. Until v1.0 ships, the mainnet oracle key is the trust root for every ERC-7857 transfer — treat the signer wallet as a custody root (hardware-wallet recommended, rotate via setSigner() on any suspicion).

7.4. LiveCertificate.sol

Append-only hash chain extending each iNFT’s static runHash with one operator-signed epoch at a time. The arena’s source of truth for live performance.
FunctionPurpose
start(tokenId, initialCumulativeHash)Owner-only. Opens the live cert. initialCumulativeHash must equal the static cert’s runHash (GenesisMismatch revert otherwise).
stop(tokenId)Owner-only. Halts updates. Status → STOPPED.
update(tokenId, epochIndex, epochHash, liveTotalReturnBps, liveSharpeX1000, liveMaxDrawdownBps, liveWinRateBps)Authorized operator only. Enforces epochIndex == r.epochCount (EpochOutOfOrder otherwise). Sets cumulativeHash := keccak256(prev ‖ epochHash).
markLiquidated(tokenId)Authorized operator only. Status → LIQUIDATED.
setUpdater(address, bool)Admin only. Global authorized-operators mapping.
get(tokenId)Reads the full LiveRun struct.
isActive(tokenId)Bool.
EventIndexedNon-indexed
PaperRunStartedtokenId, ownerstartedAt, initialCumulativeHash
EpochCommittedtokenId, epochIndexcumulativeHash, epochHash, liveTotalReturnBps, liveSharpeX1000
PaperRunStoppedtokenIdstatus, stoppedAt
UpdaterSetupdaterallowed
LiveRun struct fieldTypeNote
cumulativeHashbytes32Latest hash-chain head
startedAt, lastUpdatedAtuint64
epochCountuint64Strictly monotonic
statusuint80=ACTIVE / 1=STOPPED / 2=LIQUIDATED
liveMaxDrawdownBps, liveWinRateBpsuint16
liveTotalReturnBpsint128Signed
liveSharpeX1000uint128
Verification: any third party replays from the static runHash and arrives at the on-chain cumulativeHash. Cherry-picking is detectable — every commit fingerprints the previous state.

7.5. Season.sol

A scheduled competition window. Fixed (dataset, balance, fees, market, maxLeverage) per season. Top-3 paid 50% / 30% / 20% of the prize pool by liveTotalReturnBps.
FunctionPurpose
createSeason(spec) (payable, admin only)Open a season. msg.value >= spec.prizePool. Reverts on invalid window.
enroll(seasonId, tokenId)iNFT owner enrolls. Reverts after startTime (EnrollmentClosed), on market mismatch (MarketMismatch), or duplicate enroll.
settle(seasonId, sortedTokens)Permissionless after endTime. Caller passes a leaderboard hint sorted-descending by liveTotalReturnBps; contract verifies monotonicity in O(N) and pays the top 3. Idempotent (AlreadySettled).
getParticipants(seasonId), participantCount(seasonId)Read helpers.
EventIndexedNon-indexed
SeasonCreatedid, datasetSpecstartTime, endTime, prizePool
EnrolledseasonId, tokenId, owner
SettledseasonIdsortedWinners, paidOut
PrizeAwardedseasonId, tokenId, winneramount
SeasonSpec struct fieldTypeNote
datasetSpecbytes32e.g. keccak("BTCUSDT-15m-spot")
initialBalanceuint64Quote currency
feeBps, slippageBpsuint16
marketuint80=spot / 1=perp
maxLeverageuint81..10
startTime, endTimeuint64Unix seconds
prizePooluint256Wei
creatoraddress
settledboolIdempotent flag
Prize splitShare
1st50% (FIRST_PRIZE_BPS = 5000)
2nd30% (SECOND_PRIZE_BPS = 3000)
3rd20% (THIRD_PRIZE_BPS = 2000)

7.6. Build, test, deploy

git submodule update --init --recursive
forge build
forge test
FOUNDRY_PROFILE=ci forge test                # heavy fuzz + invariant

forge script script/DeployAll.s.sol:DeployAll \
  --rpc-url $MAINNET_RPC_URL \
  --private-key $DEPLOYER_PRIVATE_KEY \
  --broadcast
EnvFor
MAINNET_RPC_URLhttps://evmrpc.0g.ai
DEPLOYER_PRIVATE_KEYDeployer wallet (must hold mainnet 0G to pay gas)
DEPLOYER_ADDRESSSame wallet’s address (for verification constructor args)
ORACLE_SIGNER_ADDRESSOff-chain transfer-oracle signer (hardware-wallet recommended)
OPERATOR_ADDRESSPaper-engine operator authorized in LiveCertificate.authorizedUpdaters
Full mainnet deploy runbook with sanity checks + rollback notes: zero-arena-contracts/MAINNET-DEPLOY.md.

7.7. Source verification (block explorer)

forge verify-contract \
  --chain-id 16661 --num-of-optimizations 200 \
  --compiler-version "v0.8.24+commit.e11b9ed9" \
  --verifier custom --verifier-url https://chainscan.0g.ai/open/api \
  --verifier-api-key PLACEHOLDER \
  <addr> src/<Path>.sol:<Contract>
ContractConstructor signature
AgentCertificateconstructor(address admin)
ReencryptionOracleconstructor(address admin, address signer)
ZeroArenaINFTconstructor(address admin, address oracle, address cert)
LiveCertificateconstructor(address admin, address inft)
Seasonconstructor(address admin, address live, address inft)
Append --constructor-args $(cast abi-encode "<sig>" <args>). Status polling (forge verify-check is flaky on 0G explorers — poll the API directly):
curl -s "https://chainscan.0g.ai/open/api?module=contract&action=checkverifystatus&guid=<GUID>"

7.8. Release flow

After every redeploy:
StepAction
1Tag zero-arena-contractsvX.Y.Z
2CI publishes @zero-arena/contracts@X.Y.Z (ABIs + addresses)
3SDK bumps the dep and cuts a matching zeroarena release
4FE consumes new ABIs (lib/chain/contracts.ts mirrors the published package)