Judge kit
Every claim, with its proof.
The agent bonds we've seen pay when an agent breaks a rule. Ours can't place a trade that breaks its rules; Bondline pays when the market breaks through your limit.
Testnet. A capped, fully backed protection bond, not regulated insurance. Independent project, not affiliated with Robinhood.
The criteria
Each criterion, and where to check it
- Smart contract quality
- Bondline's own contracts are not upgradeable and its markets have no owner. The fixed keeper controls testnet prices, which affect trades and payouts; USDG and Stock Tokens retain issuer controls and upgrade paths. 168 Foundry tests, including 13 fuzz tests and 8 invariants, plus 4 fork tests against the real USDG and Chainlink's live feeds. 100% line coverage (554/554). Slither: 0 High, 0 Medium. Every contract's source is verified on the explorer (11/11, checked live). The numbers
- Product market fit
- Robinhood: “over 150,000 customers have opened agentic trading accounts” and “You assume all risk for trades executed by AI agents” (HOOD Summit 2026). AIUC raised $55M to insure agents offchain ($15M seed, $40M Series A). Onchain right now: 0 outside underwriters and 0 outside buyers yet; team operated test wallets are labelled and not counted (market).
- Innovation
- A third party underwriting market for AI traders. Underwriters set premiums. The site compares those premiums with reference prices from each agent's rules and stored record snapshot. A lower modeled risk does not automatically change an offer's premium. See Careful's record and Bold's: both now have a record price from executed trades, next to the rule based reference price.
- Real problem
- The risk Robinhood's disclosure assigns to users: a price that gaps through your limit, where no stop loss can sell. Bondline aims to cover a capped part of losses when prices move beyond a selected limit. An illustration of what the cover pays, and the real claim, onchain: a scripted gap took a team test account from 92 to 75 USDG, a 25% loss against a 10% limit, and the bond paid 15.000001 USDG seconds after the Monday open price (settle transaction).
- USDG
- Every flow is USDG: bonds, premiums, cover and claims. Underwriting is one USDG signature and one transaction (EIP-3009 receiveWithAuthorization): see it onchain. The issuer's pause and freeze controls are checked before anyone signs, and a frozen user can still stop the agent. USDG is “issued by Paxos Digital Singapore Pte. Ltd. (PDS)”, which “is a Major Payments Institution supervised by the Monetary Authority of Singapore” (Paxos); that describes USDG, not Bondline.
The worked example Illustration
What the cover pays, in round numbers
An illustration of the contract's formula, not a transaction. The real claim onchain is smaller, because testnet USDG comes from Paxos's faucet at 100 per wallet per day: it's the last of the onchain moments below.
The gap
IllustrationFriday close: your $1,000 account is down 8%. Monday it opens down 25%. A stop loss can't sell inside a gap.
With Bondline the AI is stopped, you lose $100 (your 10% limit), and the underwriter's bond pays the other $150. You paid the 1% premium on your deposit, about $10.
You would lose
$100
The bond would pay
$150
The contract formula, on the illustration · BondlineCover.settle
- P
- = net USDG deposited
- $1,000
- l
- = the limit, in bps
- 1000 (10%)
- value
- = cash + Σ stocks × price
- $750 at the open
- loss
- = max(0, P − value)
- $250
- limit
- = ⌈P × l / 10000⌉
- $100
- payout
- = min(loss − limit, ⌊P × (3000 − l) / 10000⌋)
- min($150, $200) = $150
- Anyone can call settle once the loss passes the limit. It stops the agent and pays once only if the required prices are fresh and the USDG transfer succeeds. The keeper attempts settlement while it is running; transaction latency, stale prices and failed transfers can delay it. You end at $900, made whole down to your limit, and the stocks stay in the account.
- Fully backed before it's sold: the deposit reserved ⌈net × (3000 − l) / 10000⌉ = $200 of the bond, and would have been refused if the free bond couldn't cover it.
- The premium: fee = ⌊amount × feeBps / 10000⌋. A $1,010.10 deposit at 1% pays $10.10 and leaves $1,000 covered.
Onchain
The moments, each with its transaction
Found in the chain's events when this page was rendered, not typed in, with the real numbers. Verify rehashes an AI decision in your browser.
By construction
What nobody can do
Enforced by the contracts, and checked by named tests in contracts/test.
The agent can't withdraw
Its only function is trade. Money leaves an account only to its owner: through the cover's withdraw, or by sweep after a settle or close.
testFuzz_agentCanNeverMoveMoneyOutinvariant_agentHoldsNothingThe agent can't change its rules
The rules are set once, in initialize, when the cover opens the account. There is no setter.
test_initialize_onlyOnceUnderwriters can't veto payouts
settle(account) is open to anyone. An underwriter cannot release reserved bond or veto a payout by delisting. Settlement still depends on fresh prices and a successful USDG transfer, and unsolicited listed stock dust can currently block it.
test_settle_paysTheGapinvariant_noPayoutExceedsReservationUnderwriters can't take reserved money
release reverts InsufficientFreeBond beyond the free bond.
test_release_onlyUnderwriter_onlyFreeinvariant_coverHoldsItsBondNo deposit beyond capacity
Every deposit reserves its worst case, rounded up, and reverts InsufficientCapacity if the free bond can't cover it.
test_deposit_refusedBeyondCapacityinvariant_reservedNeverExceedsBondinvariant_reservationCoversWorstCaseThe market has no owner
BondlineMarket has no owner; its configuration is fixed at deploy. Bondline's own contracts are not upgradeable: covers and accounts are EIP-1167 clones, initialized once. The fixed keeper controls testnet prices, which affect trades and payouts; USDG and Stock Tokens retain issuer controls and upgrade paths.
The contract sourceThe market keeps no money
createOfferWithAuthorization pulls the bond and funds the new cover in the same transaction.
invariant_marketHoldsNoUsdg
Backtest Hypothetical covers
Careful and Bold on real Chainlink prices
Backtest: hypothetical covers on real Chainlink prices. No cover here was sold. Prices: Chainlink Data Feeds on Robinhood Chain mainnet. 72 trading days, 2026-06-23 to 2026-10-02.
Careful
Careful: 30% in stocks (half TSLA, half AMZN), 10% limit, held 30 days
- Covers
- 51
- $1,000 each
- Claims
- 0
- 0.0% of covers
- Largest payout
- $0.00
- Worst loss, median
- 1.1%
- max 5.7%
- Model price
- 13.1 bps
- 30 days, 10% limit
- Offer premium
- 101 bps
- loss ratio 0.0%
Bold
Bold: 80% in stocks (half TSLA, half AMZN), 10% limit, held 30 days
- Covers
- 51
- $1,000 each
- Claims
- 17
- 33.3% of covers
- Largest payout
- $1.87
- Worst loss, median
- 3.0%
- max 10.2%
- Model price
- 174.7 bps
- 30 days, 10% limit
- Offer premium
- 417 bps
- loss ratio 0.8%
Caveats (7)
- Short history: 72 trading days (23 Jun - 2 Oct 2026). Overlapping covers share the same price moves, so claims cluster: they are not independent samples.
- In-sample: σ in the model price was estimated from closes in the same window (shared/src/volatility.json), so the model premium had the window's volatility in hand.
- The agents trade in reality; the backtest holds a fixed basket for the hold, initialized at the maximum stock share allowed after a buy, so it tests that basket, not the agents' behaviour.
- All 17 claims settled between 2026-07-23 and 2026-07-28: the window held one sharp drawdown, so the claims are one event seen from 17 different opening days, not 17 independent events.
- Annualised returns scale a 3-month window to a year; they are arithmetic on this window, not a forecast, and are large because 1% to 4% premiums are charged on 30-day covers that mostly expired without a claim.
- Chainlink posts a round when the price moves about 0.5% (only 12 of the 1408 moves between TSLA rounds used here and 12 of the 845 between AMZN rounds were smaller). These feeds include updates outside NYSE core trading hours. In this historical window the longest observed round gaps were 77.8 hours for TSLA and 77.1 hours for AMZN, and 14 and 14 gaps are longer than the 25-hour max price age. Settlement waits whenever an included price exceeds the market's maximum age. So a settle at 'the first round past the limit' can overshoot by up to one deviation step even without a gap, and by the whole move across a long gap between rounds.
- Hypothetical covers on real prices: none of these covers was sold. Testnet. A capped, fully backed protection bond, not regulated insurance.
Read from docs/data/backtest.json, generated 2026-10-04. Rerun it with npx tsx scripts/backtest.ts.
Proofs
Symbolic proofs, invariants, mutation testing
Read from contracts/reports/proofs.json. Each block appears only after an independent verifier reran it.
- Halmos proof instances
- 43 of 48
- 5 properties, proven within stated bounds: each P1 to P4 instance fixes the deposit or the limit and leaves the rest symbolic; P5 holds within its stated assumptions. 5 timed out and are not proven.
Properties (5)
- P1: settle never pays more than the cover's reservation for that account. proven within stated bounds; 5 of 18 instances not proven (solver timeout)
- P2: loss <= limit: settle reverts WithinLimit(loss, limit) and moves no USDG. proven within stated bounds
- P3: loss <= cap: value + payout == principal - limit (limit rounded up). proven within stated bounds
- P4: after every deposit and withdraw: reserve >= principal x (cap - limit) / 10000. proven within stated bounds
- P5: the market keeps no USDG after createOffer and createOfferWithAuthorization. proven for all paths within its assumptions (a valid signature; the underwriter is not the zero address, the market or USDG); a BondlineMarket property
- Runs
- 1,000
- Depth
- 200
- Calls per invariant
- 200,000
- handler calls; those whose precondition fails return early
- Invariants
- 8
Invariants (8)
- invariant_agentHoldsNothing
- invariant_coverHoldsItsBond
- invariant_marketHoldsNoUsdg
- invariant_noPayoutExceedsReservation
- invariant_onlyMarketCoversAreOffers
- invariant_reservationCoversWorstCase
- invariant_reservedIsSumOfReservations
- invariant_reservedNeverExceedsBond
- Mutants
- 747
- deliberate bugs in BondlineCover.sol that compile; 230 more didn't
- Caught by tests
- 720
- 96.4% of them
- Survived
- 27
- judged equivalent by reading the code; listed below
Surviving mutants (27)
- line 29: uint256 private constant BPS = 10_000; ==> uint128 private constant BPS = 10_000; (Equivalent: the narrower type still holds the value (10_000 and 1e6 fit in uint128); no behaviour change.)
- line 29: uint256 private constant BPS = 10_000; ==> uint256 private immutable BPS = 10_000; (Equivalent: constant -> immutable keeps the value and the getter; only deployment gas differs.)
- line 32: uint256 public constant MIN_DEPOSIT = 1e6; ==> uint128 public constant MIN_DEPOSIT = 1e6; (Equivalent: the narrower type still holds the value (10_000 and 1e6 fit in uint128); no behaviour change.)
- line 32: uint256 public constant MIN_DEPOSIT = 1e6; ==> uint256 public immutable MIN_DEPOSIT = 1e6; (Equivalent: constant -> immutable keeps the value and the getter; only deployment gas differs.)
- line 34: uint16 public constant MAX_SLIPPAGE_BPS = 500; ==> uint16 public immutable MAX_SLIPPAGE_BPS = 500; (Equivalent: constant -> immutable keeps the value and the getter; only deployment gas differs.)
- line 36: uint32 public constant MAX_DAILY_BPS = 100_000; ==> uint32 public immutable MAX_DAILY_BPS = 100_000; (Equivalent: constant -> immutable keeps the value and the getter; only deployment gas differs.)
- line 65: Position storage pos = _positions[account]; ==> Position memory pos = _positions[account]; (Equivalent: the pointer is only read, never written through, so storage vs memory returns the same values.)
- line 66: if (pos.status == CoverStatus.None) revert UnknownAccount(account); ==> if (pos.status <= CoverStatus.None) revert UnknownAccount(account); (Equivalent: None is 0, the smallest enum value, so <= None is == None.)
- line 95: if (amount == 0) revert ZeroAmount(); ==> if (amount <= 0) revert ZeroAmount(); (Equivalent: the operand is unsigned, so <= 0 is == 0.)
- line 105: if (amount == 0) revert ZeroAmount(); ==> if (amount <= 0) revert ZeroAmount(); (Equivalent: the operand is unsigned, so <= 0 is == 0.)
- line 130: Terms storage t = _terms; ==> Terms memory t = _terms; (Equivalent: the pointer is only read, never written through, so storage vs memory returns the same values.)
- line 160: if (pos.status != CoverStatus.Active) revert NotActive(account); ==> if (pos.status > CoverStatus.Active) revert NotActive(account); (Equivalent: differs only for status None, which the preceding UnknownAccount check (or onlyUser) has already rejected on this path.)
- line 161: if (amount == 0) revert ZeroAmount(); ==> if (amount <= 0) revert ZeroAmount(); (Equivalent: the operand is unsigned, so <= 0 is == 0.)
- line 179: if (pos.status == CoverStatus.None) revert UnknownAccount(account); ==> if (pos.status <= CoverStatus.None) revert UnknownAccount(account); (Equivalent: None is 0, the smallest enum value, so <= None is == None.)
- line 180: if (pos.status != CoverStatus.Active) revert NotActive(account); ==> if (pos.status > CoverStatus.Active) revert NotActive(account); (Equivalent: differs only for status None, which the preceding UnknownAccount check (or onlyUser) has already rejected on this path.)
- line 204: if (pos.status != CoverStatus.Active) revert NotActive(account); ==> if (pos.status > CoverStatus.Active) revert NotActive(account); (Equivalent: differs only for status None, which the preceding UnknownAccount check (or onlyUser) has already rejected on this path.)
- line 252: Position memory pos = _positions[account]; ==> Position storage pos = _positions[account]; (Equivalent: the pointer is only read, never written through, so storage vs memory returns the same values.)
- line 257: if (pos.status == CoverStatus.None) return h; ==> if (pos.status <= CoverStatus.None) return h; (Equivalent: None is 0, the smallest enum value, so <= None is == None.)
- line 265: if (pos.status == CoverStatus.Active && h.fresh && h.loss > h.limit) { ==> if (pos.status <= CoverStatus.Active && h.fresh && h.loss > h.limit && h.fresh && h.loss > h.limit) { (Equivalent: differs only for status None, for which health() has already returned (line 257).)
- line 286: if (pos.status == CoverStatus.None) revert UnknownAccount(account); ==> if (pos.status <= CoverStatus.None) revert UnknownAccount(account); (Equivalent: None is 0, the smallest enum value, so <= None is == None.)
- line 287: if (pos.status != CoverStatus.Active) revert NotActive(account); ==> if (pos.status > CoverStatus.Active) revert NotActive(account); (Equivalent: differs only for status None, which the preceding UnknownAccount check (or onlyUser) has already rejected on this path.)
- line 318: if (v.oldestUpdate == 0) return v.stockValue == 0; ==> if (v.oldestUpdate <= 0) return v.stockValue == 0; (Equivalent: the operand is unsigned, so <= 0 is == 0.)
- line 333: if (r.assetMask == 0 || uint256(r.assetMask) >= (1 << assetCount)) return false; ==> if (r.assetMask <= 0 || uint256(r.assetMask) >= (1 << assetCount) || uint256(r.assetMask) >= (1 << assetCount)) return false; (Equivalent: the operand is unsigned, so <= 0 is == 0.)
- line 333: if (r.assetMask == 0 || uint256(r.assetMask) >= (1 << assetCount)) return false; ==> if (r.assetMask == 0 || uint128(r.assetMask) >= (1 << assetCount)) return false; (Equivalent: widening a uint8 mask before the comparison changes nothing.)
- line 335: if (r.maxTradeBps == 0 || r.maxTradeBps > BPS) return false; ==> if (r.maxTradeBps <= 0 || r.maxTradeBps > BPS || r.maxTradeBps > BPS) return false; (Equivalent: the operand is unsigned, so <= 0 is == 0.)
- line 336: if (r.maxDailyBps == 0 || r.maxDailyBps > MAX_DAILY_BPS) return false; ==> if (r.maxDailyBps <= 0 || r.maxDailyBps > MAX_DAILY_BPS || r.maxDailyBps > MAX_DAILY_BPS) return false; (Equivalent: the operand is unsigned, so <= 0 is == 0.)
- line 338: if (r.maxPriceAge == 0 || r.maxPriceAge > maxPriceAge) return false; ==> if (r.maxPriceAge <= 0 || r.maxPriceAge > maxPriceAge || r.maxPriceAge > maxPriceAge) return false; (Equivalent: the operand is unsigned, so <= 0 is == 0.)
Not an audit Proofs cover the properties listed, not the whole system. See What nobody can do above.
Honesty
What's real, and what's demo
Real
- Paxos USDG on Robinhood Chain testnet: every bond, premium, cover and claim.
- Robinhood's official testnet Stock Tokens, TSLA and AMZN.
- Chainlink's TSLA and AMZN prices on the live market, mirrored from Robinhood Chain mainnet with their real timestamps.
- The AI's submitted decisions: each completed trade or rule check refusal carries its decision JSON, with its reasoning and the model it names, in the transaction, and a hash of those bytes in its receipt. Offchain holds are absent, and the hash verifies the bytes, not that a model produced them.
- Every transaction on this site is on the public explorer, and every market number (bonds, cover, premiums, claims, trades, prices) is read from the chain. Test counts, coverage, gas, model prices and the backtest come from the reports and formulas named on this page.
Demo, and labelled everywhere
- Replay marketReplay market: real prices from 28 Sep to 2 Oct, sped up. Live stock prices are frozen for the weekend.
- Scripted gapA scripted “Monday open” on the replay feeds that takes a covered account through its limit, so the keeper settles and the bond pays the gap.
- Demo exchangeDemo exchange: fills at the oracle price, because testnet Stock Tokens have no market.
- Team operatedThe test underwriter and test buyer, our keeper, and the two agents, Careful and Bold. All are labelled wherever they appear, and never counted as outside users.
Testnet. A capped, fully backed protection bond, not regulated insurance. Independent project, not affiliated with Robinhood.
Contract quality
Tests, coverage, Slither, gas
Read from contracts/reports and contracts/test when this page was built.
Foundry tests
168
Line coverage
100%
Slither
0 High·0 Medium
Fork tests
4
Gas, from the test suite
contracts/reports/gas.txt · median and max per call
| Call | Median | Max |
|---|---|---|
| createOfferWithAuthorizationBondlineMarket | 463,632 | 463,656 |
| createOfferBondlineMarket | 333,960 | 351,060 |
| openBondlineCover | 620,581 | 625,129 |
| depositBondlineCover | 13,340 | 78,750 |
| settleBondlineCover | 55,918 | 133,323 |
| withdrawBondlineCover | 93,581 | 93,581 |
| closeBondlineCover | 34,040 | 34,040 |
| tradeAgentAccount | 212,377 | 212,773 |
| pushMirrorFeed | 75,459 | 92,703 |
Every deployed contract
Verified source, checked live on the explorer
BondlineMarket
Live market: factory and registry, no owner
Verified0x1263…Da8DBondlineMarket
Replay market: factory and registry, no owner
Verified0x734E…DE7dBondlineCover
Implementation every offer clones (EIP-1167)
Verified0x341E…2F1fAgentAccount
Implementation every covered account clones
Verified0xF442…36FfOracleVenue
Live market's demo exchange
Verified0x3384…f7d5OracleVenue
Replay market's demo exchange
Verified0xEEc9…97A2MirrorFeed
TSLA price, Live market (pushed by our keeper)
Verified0xF187…972DMirrorFeed
AMZN price, Live market (pushed by our keeper)
Verified0x404e…C524MirrorFeed
TSLA price, Replay market (pushed by our keeper)
Verified0x9e5D…2279MirrorFeed
AMZN price, Replay market (pushed by our keeper)
Verified0x8b59…B7eCProofOfCover
Read only lookup: whether an account has active cover, its underwriter, limit and cap, and the cover's free capacity
Verified0x168e…4888