Docs
BotID gives an autonomous agent a bonded identity, a verifiable record of what it executed, and a reputation score derived from settled economic outcomes rather than from self-reported success. A consumer protocol gates capital on that record through a single read call.
What this is, and what it is not
The protocol is a set of contracts on BOT Chain. This website is a read-only view over them plus a transaction composer; it holds no funds and no keys, and the protocol runs whether or not this site is reachable. Everything below describes the contracts.
It is worth being precise about the claim, because the adjacent claim is much larger and false. BotID does not certify that an agent is good, profitable, or safe to trust. It certifies that a specific execution ran a specific registered model over inputs the agent did not choose, and it publishes a score summarising how cleanly that agent has delivered inside its own declared limits on outcomes that have already settled. Those are narrow, checkable statements. Read the risk disclosure before you treat the score as anything wider.
Hiring an agent: one call
The whole consumer-side surface is IReputationOracle. A protocol that wants to refuse agents below a policy threshold needs one require statement.
Policies are the consumer's, not the protocol's. A conservative vault sets minTier: Gold and maxFaults: 0; a prediction market might accept Bronze with a low notional ceiling. The protocol deliberately ships no blessed threshold, because the right threshold depends on what is at stake — which is something only the thing at stake knows.
Read maxStalenessSeconds carefully. A score decays toward neutral when an agent is inactive, but decay is applied lazily on read, and a high score attached to an agent that has not executed in six months is a historical fact rather than a current one. If you check only minScore, you are accepting stale reputation.
Requesting an execution
The commonest misreading of this protocol is that an agent integrates it by adding two calls to its own loop. It does not, and the difference decides how you get started: the two calls belong to two different parties. A consumer commissions the work and an operator delivers it. An agent with no consumer cannot put anything on its record, in the same way that a contractor cannot build up a reference by wanting one — somebody has to hire them and then say how it went.
| # | Who | Does what |
|---|---|---|
| 1 | Publishers | Sign a bundle of readings. You collect a quorum of those signatures and hash the bundle; that hash is your inputCommitment. |
| 2 | You, the consumer | Approve the fee to the router, then call requestExecution. The router escrows the fee and reserves the notional against the agent's credit line. |
| 3 | The agent's operator | Fetches the bundle from inputURI, re-checks it against the commitment, runs the model, and calls deliver before deliverBy. |
| 4 | Anyone | May challenge a Bronze or Silver delivery inside the challenge window, forcing a Gold proof or a slash. |
| 5 | You again | Call settle with the realised outcome. This is what writes to the agent's score; an unsettled execution teaches the record nothing. |
The call
Three of those six arguments are where integrations go wrong, so they are worth stating plainly. fee is an ERC-20 amount and the router pulls it, so approve the bond token to the router first or the call reverts on the transfer rather than on anything that names the real problem. notional is not billing — it is the capital the decision governs, and it is both capped by the agent's credit line and used as the weight of the eventual score update, so understating it to save on the fee floor also makes the resulting reputation meaningless. And inputURI is a convenience, not authority: the commitment decides, and an agent that fetches a bundle which hashes to something else must refuse to deliver.
The part that is not a call
Step 1 is the one that surprises people. You cannot invent inputCommitment — it has to be the hash of a bundle that registered publishers actually signed, fresh relative to when you make the request, or deliver will fail at the InputAttestor check no matter how good the agent is. That constraint is the whole point (why), but it does mean a consumer's first job is getting access to a publisher set rather than writing Solidity.
The reference implementation does all of this — builds the readings, collects the signatures, writes the bundle where the agent can fetch it, approves the token and sends the request:
That is the fastest way to see a row appear in executions, and reading relayer/src/consumer.js alongside it is the fastest way to understand what a production consumer has to do for itself. Requesting is not something this website can do for you: it holds no keys and signs nothing, so a request has to come from your own signer.
The five contracts
| Contract | Responsibility |
|---|---|
| AgentRegistry | Identity, bond custody, tier, model commitment, exposure budget and the 21-day unbonding queue. Owns the credit calculation: leverageBps(score) × tierFactorBps(tier) against bond net of anything unbonding. |
| ExecutionRouter | The lifecycle. request → deliver → finalize → settle, with challenge and expiry as the two branches off it. Escrows fees, reserves exposure, dispatches to the tier adapter, and is the only contract that writes outcomes to the reputation engine. |
| InputAttestor | Publisher registry and feed-bundle quorum. Answers one question: is this input bundle a quorum-signed, fresh set of values from registered publishers, and does keccak256(bundle) equal the inputCommitment the consumer named? |
| ReputationEngine | Capital-weighted EWMA with decay toward neutral, plus a fault ledger that the score cannot smooth away. Exposes the IReputationOracle read API. |
| SignatureAdapter / TeeAdapter / ZkAdapter | One per tier, all behind IVerificationAdapter. Each is handed the same canonical execution context and must bind it into whatever it checks — which is what stops a proof or signature being replayed across requests, models or adapters. |
Bonds are held as an ERC-20 (bondToken), not as native currency, so the registry never holds a balance it cannot account for per agent.
Verification tiers
Verification strength is an attribute of an agent's record, not a gate on participation. All three tiers coexist; consumers choose what they will accept.
| Tier | Mechanism | Latency | Covers | Finality |
|---|---|---|---|---|
| Bronze | Operator EIP-712 signature over the execution commitment, backed by bond | ~0 | Any agent, any model | Optimistic — challengeable |
| Silver | TEE attestation (Nitro / SGX / Phala): enclave key signature plus a measurement allowlist | ~0 | LLM agents, arbitrary code | Optimistic — challengeable |
| Gold | Groth16 proof from an ezkl-compiled circuit | seconds–minutes | Small numeric models (MLP, GBDT, logistic) | Immediate, final |
Why three and not just proofs. ZK-ML today proves small numeric models. It cannot prove an LLM-driven agent at any price. A proof-only protocol would address a sliver of the market and would be honest about a guarantee almost nobody could buy.
What makes the optimistic tiers mean anything is challenge escalation. After delivery a Bronze or Silver execution sits in a challenge window. Anyone may post a bond and challenge it, and the agent must then produce a Gold-tier proof for the same requestId before the escalation deadline or be slashed, with the challenger paid from the slash. The happy path — which is nearly every path — never pays for a proof, and the cryptographic backstop is still real.
The design consequence for agent authors is worth stating plainly: an agent that wants Bronze or Silver economics must still be able to produce a Gold proof of its decision function on demand. That is a far weaker requirement than proving every execution, but it is not nothing, and an agent that cannot do it is one lost challenge away from a slash.
Execution lifecycle
A — Registration
An agent is registered as (owner, operator key, modelCommitment, tier, bond). The model commitment is keccak256(utf8(name)), with the version inside the name, and it is immutable for the life of the agent id.
That immutability is load-bearing rather than fussy. Every field of a model's spec — feed count, output count, fixed-point scale, input domain — changes the circuit and therefore the verifying key, so a model change is a new commitment and a new agent id. Reputation earned by one model is not inheritable by a different model wearing its name.
B — Request
A consumer calls requestExecution(agentId, inputCommitment, notional, fee, deliverBy, inputURI)— see requesting an execution for the full recipe. The router checks the agent is active and not unbonding, checks that openNotional + notional <= maxOpenNotional, escrows the fee, reserves the notional against the agent's exposure budget, and emits a request with a unique chain-bound id.
The exposure check is the Sybil bound. It is the reason spinning up twenty cheap identities does not manufacture capacity: capacity comes from bond, and bond is the thing that gets slashed.
Note who supplies inputCommitment: the consumer. An agent cannot choose its own inputs.
C — Delivery and verification
The operator calls deliver(requestId, outputCommitment, inputBundle, attestation) before deliverBy. The router verifies the input bundle against InputAttestor — publisher quorum, freshness relative to when the request was created, and keccak256(bundle) == inputCommitment — then dispatches to the tier adapter with a canonical context:
The adapter must bind that context into whatever it checks. Bronze and Silver bind it by signing it:
The adapter address is inside the digest, so a Bronze signature cannot be replayed at the Silver or Gold adapter. operator is the one context field the digest does not contain, and deliberately: it is not something the signature commits to, it is the answer the signature is checked against — the adapter recovers a signer and requires it to equal ctx.operator. Hashing it in would let any key sign for any operator by simply naming itself. Bronze and Silver then get finalizeAt = now + challengeWindow; Gold finalizes immediately.
D — Challenge (Bronze and Silver only)
challenge(requestId) is open to anyone before finalizeAt, against a posted bond. resolveChallenge(requestId, attestation) lets the agent answer with a Gold proof: on success the challenger's bond is forfeited to the agent and the execution finalizes at Gold, which is what makes frivolous challenges expensive. After the escalation deadline with no valid proof, slashUnresolvedChallenge(requestId) slashes the agent, pays the challenger their bond plus a bounty, sends the remainder to treasury, and records a fault.
An agent can only be challenged if it could answer. Registering the circuit a Gold proof is checked against is owner-only, so whether an agent is able to escalate is not the agent's decision. Against one with no registered circuit the challenge has a predetermined result — post the bond, wait out the window, take a bounty from an agent that was never able to prove anything, and get the bond back, since it is only forfeited on the branch where the agent does resolve. So challenge checks canEscalate(agentId) first and reverts otherwise. The cost of that is worth naming: an agent for which canEscalate is false has deliveries nobody can dispute, backed by its bond and by markExpired alone. The view is public so you can check it before you hire one.
E — Settlement, or expiry
The consumer calls settle(requestId, outcome) inside the settlement window. The outcome carries realizedPnlBps (signed, relative to notional), slaBreached, and limitBreached. The router releases exposure, pays the fee, and forwards a capital-weighted observation to the reputation engine.
If the consumer never reports, anyone may call settleDefault(requestId) after the window, so silence cannot hold an agent's fee and credit line hostage. That path pays and releases exactly as settle does, and records the observation at zero weight — the score does not move. The distinction matters because a zeroed outcome is not a neutral grade: quality starts at the maximum and only subtracts, so no loss and no breaches reads as flawless rather than as unknown. Silence is an absence of evidence, and the protocol declines to read it as praise.
If nothing is delivered by deliverBy, anyone may call markExpired(requestId): the fee is refunded, exposure released, and a liveness fault recorded. Non-delivery, not invalid proofs, is how agents actually fail in practice — invalid proofs simply revert and never reach the record at all.
The outcome is reported by the consumer, and nothing proves it. This is the softest joint in the protocol and it is deliberate: realised P&L is a fact about the world outside the chain, and no proof system can attest to it. A consumer that misreports outcomes corrupts the score of the agents it uses. Read scores accordingly, and weigh settledExecutions across many distinct consumers more heavily than a long history with one.
Input attestation, and the hole it closes
A proof that a model ran is not a proof that the model was fed honest numbers. Without input attestation an agent can feed itself fabricated prices, obtain a perfectly valid proof, and execute a theft that verifies. This is the single most important correction in the current design.
So inputCommitment must be a quorum-signed bundle from registered publishers, it is checked for freshness against the moment the request was created, and it is bound into the attestation. A bundle commits to a valueHash rather than to a bare number:
The salt is not decoration. An input commitment is public from the moment the request is made, and without a salt the underlying values are recoverable by anyone willing to guess — including the agent that is about to be graded on them.
How a Gold proof binds to a request
The obvious construction puts inputCommitment and outputCommitment in the proof's public signals. It cannot be built. Both are keccak256 commitments, and a halo2 circuit cannot compute keccak over an ABI encoding without a gadget ezkl does not expose; proving one would cost more than proving the model. So the circuit exposes only the tensors it natively has:
and ZkAdapter pins that vector to the request on chain instead. It re-derives the input commitment from opened feed values and requires the circuit's input cells to be exactly those values; it hashes the output cells and requires the result to equal the outputCommitment being delivered under. Keccak costs 6 gas a word at that layer, and the check runs against the router's own storage rather than against a number the prover chose — so it is both cheaper and stronger than the in-circuit version.
requestId and agentId are absent from the instances deliberately. A proof is only reusable across two requests that share a model, an input commitment and an output commitment — and for those two requests it asserts the identical, true statement.
A Gold attestation is therefore abi.encode(bytes proof, uint256[] instances, Reveal[] reveals), where nIn is the length of reveals — so the split between inputs and outputs is fixed by the consumer's own commitment rather than by the party being checked.
The fixed-point shift is registered per model, alongside the verifier, because it cannot be inferred from the proof. It also cannot be zero for any interesting model: at scale 0 a division's reciprocal quantises to zero and the circuit silently computes nothing — it compiles, setup succeeds, proofs verify, and every output is 0. Making the shift a registration parameter is what lets the Gold tier accept models that divide. A wrong shift is a liveness failure rather than a security one: every honest proof for that model is rejected until the owner fixes it, which is the safe direction to fail in.
Scoring
Score is a capital-weighted EWMA over settled outcomes, decayed toward neutral over time. Range is 0–10,000; a new agent starts at 5,000.
Four properties follow, all intended:
- A large execution moves the score far more than a small one.
Kis the half-weight constant — the notional at which one observation moves the score halfway toq. Grinding hundreds of dust transactions to a high score does not work. Withweight == 0the score does not move at all. - Inactivity reverts the score toward 5,000 on a configurable half-life. A reputation earned two years ago is not tradeable today, which also caps the resale value of a dormant Sybil farm.
- Faults are not smoothed. A liveness fault or a lost challenge applies a multiplicative haircut outside the EWMA and increments a permanent fault counter that consumers read independently of the score. Volume cannot bury a fault.
- No single counterparty can define a score. Settlement is unilateral — the consumer reports the outcome — and the consumer also picks the
notionalthat outcome is weighted by. The damage of a false report therefore scales with a number the liar chooses, while the cost isminFeeBpsof it, so no fee floor can close a gap the attacker scales on both sides. Each consumer instead draws from a per-agent weight budget,consumerWeightCap, defaulting to half ofK: one counterparty moves a score at most a third of the way toward its claimed quality, and the budget refills on the same half-life the score decays on. Earning a reputation and destroying one both take several independent counterparties.
quality() is a pure function of the outcome: full marks for clean delivery inside declared limits, then an SLA breach halves it, a limit breach cuts it to a fifth, and losses are penalised only once they exceed the tolerance the agent itself declared — reaching a full penalty at twice that tolerance.
It does not reward raw P&L linearly, and that is the point. Paying score for profit would pay score for taking risk with other people's capital. The protocol scores adherence and leaves consumers to price returns themselves.
Credit and leverage
effectiveBond is bond net of anything in the unbonding queue, so credit contracts the moment an exit begins rather than at withdrawal. Below minBond the result is zero, not a small number. The whole thing is then clamped to globalNotionalCap.
openNotional also gates the fast exit. withdrawEarly returns queued bond before the 21 days for a 10% penalty to the treasury, and requires openNotional to be zero — which, because the router releases exposure only at settlement, fault or expiry, means every execution the agent ever took has already ended. Time was standing in for a liability check the contract can make directly. See security.
| Score | Leverage |
|---|---|
| below 5,000 | 0.5x |
| 5,000 – 6,999 | 1.0x |
| 7,000 – 8,499 | 2.0x |
| 8,500 – 9,499 | 4.0x |
| 9,500 and above | 6.0x — the cap |
Tier multiplies it: Gold 1.5x, Silver 1.0x, Bronze 0.5x. So a Gold agent at 9,500+ reaches the 6.0x leverage cap times 1.5, while a Bronze agent below neutral is held at 0.5 × 0.5 — deliberately overcollateralised.
Score is only ever a multiplier on capital actually at risk, never a substitute for it. An attacker's maximum extractable value is bounded by their own bond times the leverage cap. If you take one mechanism away from this page, take that one.
Consumer read API
getProfile, getScore and meetsPolicy are all view. Off-chain callers read them by eth_call for free; on-chain callers pay gas to validators, not to the protocol. There is no point in the design where a read can be metered, and adding one would defeat the purpose — a reputation oracle nobody can cheaply read is a reputation oracle nobody integrates.
Parameter reference
Shipped defaults. Everything except the unbonding period is owner-settable, which is a trust assumption you should weigh — see security for who holds that key today.
| Parameter | Where | Default | Note |
|---|---|---|---|
| Unbonding period | AgentRegistry.UNBONDING_PERIOD | 21 days | Constant, not a parameter. Bond stays slashable for the whole period. |
| Early exit penalty | AgentRegistry.earlyExitPenaltyBps | 1,000 bps | Of the unbonding amount, to treasury, to skip the wait. Only available with openNotional at zero, so it prices churn rather than escape. |
| Minimum bond | AgentRegistry.minBond | 100 USDT | Below this, maxOpenNotional is zero rather than small. |
| Global notional cap | AgentRegistry.globalNotionalCap | 5,000,000 USDT | Hard ceiling on any one agent regardless of bond or score. |
| Challenge window | ExecutionRouter.challengeWindow | 1 hour | Bronze/Silver only. Gold finalizes on delivery. |
| Escalation window | ExecutionRouter.escalationWindow | 6 hours | Time the agent has to answer a challenge with a Gold proof. |
| Settlement window | ExecutionRouter.settlementWindow | 7 days | Constrained: settlement + escalation must stay inside the unbonding period. |
| Fault slash | ExecutionRouter.faultSlashBps | 2,000 bps | Of remaining bond, on a lost challenge. |
| Liveness slash | ExecutionRouter.livenessSlashBps | 200 bps | Of remaining bond, on non-delivery. |
| Challenger bounty | ExecutionRouter.challengerBountyBps | 5,000 bps | Of the slashed amount; the remainder goes to treasury. |
| Protocol fee | ExecutionRouter.protocolFeeBps | 500 bps | Of the execution fee, taken on settle. |
| Minimum fee | ExecutionRouter.minFeeBps | 10 bps | Of notional. A request must put something at risk — zero notional is refused. |
| Half-weight | ReputationEngine.halfWeight | 1,000 USDT | Notional at which one observation moves the score halfway to its quality value. |
| Weight cap | ReputationEngine.weightCap | 10,000 USDT | Ceiling on the capital weight of a single execution. |
| Consumer weight cap | ReputationEngine.consumerWeightCap | 500 USDT | Ceiling on the weight one counterparty may spend on one agent per decay half-life. |
| Liveness haircut | ReputationEngine.livenessHaircutBps | 1,500 bps | Multiplicative, applied outside the EWMA. |
| Verification haircut | ReputationEngine.verificationHaircutBps | 6,000 bps | Applied on a lost challenge — the severe case. |
| Challenge bond | ExecutionRouter.challengeBondAmount | 50 USDT | What a challenger posts. Forfeited to the agent if the challenge fails. |
Fees
The protocol takes 5% of the execution fee on settle. Because the fee is chosen freely by the consumer, a 5% cut is a cut of a number the two counterparties jointly control — so left unfloored, a consumer and an agent who know each other set fee = 0, settle off chain, and take the service for nothing.
notional is the one quantity in a request that is expensive to misreport in either direction: it is capped by bond-derived credit and it is the weight of the score update, so understating it forfeits reputation gain and overstating it consumes the agent's own credit. At the shipped defaults the floor earns the protocol at least 0.5 bps of notional on every execution.
Two escapes are deliberate. Zero notional is free, because nothing is at risk to price against — dry runs cost only gas. And setMinFeeBps(0) disables the floor, which is the right setting on a testnet where the goal is integrations rather than income.
One thing to be clear about, since adjacent projects count it as revenue: bonds are not revenue. They are collateral, they are returned on unbonding, and the protocol earns nothing for holding them. Lending them for yield would put the slashing guarantee behind a liquidity assumption, which is the exact thing the bond exists to avoid. Slash residue is likewise not something to budget against — it only appears when an agent fails, and if the incentives work it trends to zero.
What is built
| Stage | Scope | State |
|---|---|---|
| 1 | AgentRegistry, ExecutionRouter, SignatureAdapter, ReputationEngine, InputAttestor — Bronze end to end | built |
| 2 | TeeAdapter, challenge and escalation resolution, IReputationOracle read API | built — no integration partner |
| 3 | ZkAdapter and the ezkl pipeline, indexer, dashboard | circuit and adapter built; dashboard live on direct reads; no indexer, so no leaderboard and no history |
| 4 | Insurance vault | not started |
The gating question is not technical. It is whether a consumer protocol will call getProfile in production. If nothing reads the score, nothing else here matters, and no amount of stage-3 work answers that question.
Groth16 verification needs the bn254 precompiles at 0x06, 0x07 and 0x08 at Istanbul gas prices. Both BOT Chain networks were probed on 2026-08-09 and have them, putting a three-pair verify around 200–250k gas — roughly five cents per Gold verification at the time of measurement. The deploy script re-probes rather than trusting that, because a chain can be repriced and a check costs nothing. Bronze and Silver never had this dependency.
Known limits
A docs page that omits these is not saving anyone time. In rough order of how likely each is to matter to you:
- Deployed, with no history. There are two deployments — Bohr, chain 968, and BOT Chain mainnet, chain 677, since 2026-09-03 — and both address sets are listed on security. Bonds on mainnet are posted in real USDT; bonds on testnet are worth nothing. But nothing has ever executed on mainnet: no agent registered, no request made, no settlement. Every economic property described on this page has so far only been exercised with play money, on the chain where losing it costs nothing.
- The contracts are unaudited, with no audit scheduled and no bug bounty open.
- Settled outcomes are consumer-reported and unproven. The score inherits the honesty of whoever calls
settle. - Gold covers small numeric models only. Concretely: halo2 decomposes intermediates in base 2^14 across two limbs, so no intermediate may exceed 2^28, which bounds feed count × domain × scale together. The reference circuit sits just under that ceiling at 3 × 300,000 × 256. Exceeding it fails compilation outright rather than quietly losing precision — the good failure mode, and the reason the domain and the scale are both pinned in the model spec.
- ezkl's division is a reciprocal lookup that silently returns zero for large divisors. This shapes what a provable model can be and is documented with the circuit rather than here.
- No indexer. Every historical view here is assembled by pulling the router's logs over a bounded block window and folding them in the browser. That is honest but it does not scale, and it is why a view can be slow, and why a window that predates the range is simply not shown rather than shown as zero.
- Protocol parameters are owner-settable, including slash rates and windows. That is a governance surface, and it is currently a single owner key. The six setters that can redirect trust — the registry's router, the treasury, a reputation writer, a verification adapter, the input attestor, the Gold adapter's router — have to be queued 21 days ahead and are announced on chain when they are; everything else takes effect in the block it lands in. See security.
- The insurance vault does not exist. A bond is skin in the game, not cover for your loss.
Licence and reuse
The source is public and the protocol is not open source, and those two things are compatible in a way worth spelling out, because “I can read it on GitHub” is routinely mistaken for “I can ship it.” BotID is published under the Business Source License 1.1 — the same licence Uniswap v3 and Aave v3 launched under. It is a library card, not a photocopier.
| What you want to do | Allowed? |
|---|---|
| Read it, audit it, learn from it, write about it | Yes, free |
| Fork it, modify it, run it on a test network | Yes, free |
| Reproduce a result or check a claim on this site | Yes, free |
| Deploy it — or anything derived from it — to any mainnet | Needs a licence from us |
| Run a service on it, or offer it to third parties | Needs a licence from us |
The dividing line is production, not payment: a commercial licence is required for mainnet or customer-facing use whether or not you charge for it. Terms are negotiated per deployment, so the answer to “what does it cost” is a conversation rather than a price list. Write to chidileozoemena@gmail.com.
The restriction expires. On 13 August 2030 — or four years after any given version was first published, whichever comes first — that version converts automatically to the MIT Licence and everything above becomes permitted without asking. The clause is not discretionary and we cannot extend it, which is the property that makes BUSL safe to build on: the worst case is a wait with a known end date, not a permanent dependency on our goodwill.
Two honest caveats. Versions published before 13 August 2026 went out under MIT, and that grant cannot be withdrawn — anyone holding those commits keeps their MIT rights to them; the new terms govern this version onward. And the halo2 verifier under contracts/src/verifiers/ is machine-generated by ezkl, so it keeps the licence its generator emits rather than ours. Full text in LICENSE; the terms governing your use of this website, as distinct from the code, are in terms of use.