Start the 2 minute tour

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

Illustration

Friday 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_agentHoldsNothing
  • The 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_onlyOnce
  • Underwriters 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_noPayoutExceedsReservation
  • Underwriters can't take reserved money

    release reverts InsufficientFreeBond beyond the free bond.

    test_release_onlyUnderwriter_onlyFreeinvariant_coverHoldsItsBond
  • No 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_reservationCoversWorstCase
  • The 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 source
  • The 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

147 unit, 13 fuzz, 8 invariants. Last run, with the 4 fork tests: 172 passed, 0 failed.

Line coverage

100%

554/554 lines. Statements 99.72%, branches 98.43%, functions 100%.

Slither

0 High·0 Medium

Plus 21 Low and 5 informational findings (Slither 0.11.6 on contracts/src, 4 Oct 2026).

Fork tests

4

Against the real USDG on Robinhood Chain testnet, and Chainlink's TSLA and AMZN feeds on a mainnet fork. Run with FORK_TESTS=true.

Gas, from the test suite

contracts/reports/gas.txt · median and max per call

CallMedianMax
createOfferWithAuthorizationBondlineMarket463,632463,656
createOfferBondlineMarket333,960351,060
openBondlineCover620,581625,129
depositBondlineCover13,34078,750
settleBondlineCover55,918133,323
withdrawBondlineCover93,58193,581
closeBondlineCover34,04034,040
tradeAgentAccount212,377212,773
pushMirrorFeed75,45992,703

Every deployed contract

Verified source, checked live on the explorer

  • BondlineMarket

    Live market: factory and registry, no owner

  • BondlineMarket

    Replay market: factory and registry, no owner

  • BondlineCover

    Implementation every offer clones (EIP-1167)

  • AgentAccount

    Implementation every covered account clones

  • OracleVenue

    Live market's demo exchange

  • OracleVenue

    Replay market's demo exchange

  • MirrorFeed

    TSLA price, Live market (pushed by our keeper)

  • MirrorFeed

    AMZN price, Live market (pushed by our keeper)

  • MirrorFeed

    TSLA price, Replay market (pushed by our keeper)

  • MirrorFeed

    AMZN price, Replay market (pushed by our keeper)

  • ProofOfCover

    Read only lookup: whether an account has active cover, its underwriter, limit and cap, and the cover's free capacity