Protocol
Settlement is a pure function of what was posted
Anyone can call settle. Nobody decides a breach. These are the formulas, the states, the measurement and the parameters, read from the contracts on this page.
§01
Model
per epoch e of service s (onchain aggregates only, voided windows removed)
allowed = floor((1 - availTarget) * probes)
fails = failed slots (availability, freshness, deviation, tool stability)
excess = max(0, fails - allowed)
fAvail = min(capAvail, excess * bpsPerExcessProbe)
fLat = latBpsPerBucket * #{hours with p95 bin > latTarget}
f = min(maxSlashPerEpoch, fAvail + fLat)
per leg L in {USDG, $PBI}: S_L = f * assets_L(end of e)
the operator's junior pays min(S_L, junior_L) first; the rest comes from
senior backers pro-rata (the share price falls)
S_USDG -> compensation(s, e) S_$PBI -> burned (0x...dEaD)
compensation: customers C = payers of USDG to s.payTo during e (Transfer logs)
comp_c = min(compMultiple * paid_c, S_USDG * paid_c / sum(paid))
leftover = S_USDG - sum(comp_c) -> reserve
posted as one Merkle root of (customer, amount, paid); claimed with a proof
premium per second: p = premiumBps * faceUSDG / year
reserve <- protocolPremiumBps * p
leg USDG <- legSplit * rest leg $PBI <- (1 - legSplit) * rest
within a leg: pro-rata to assets, junior and senior alike§02
State machines
Service
Epoch
Open becomes Active when the first backer arrives; premium streams from then on. Lapsed means the premium balance ran out: backers may leave at the end of the current epoch. Retired returns capital after the final settlement.
§03
Measurement
The prober set
One prober today, ours, on one machine in Europe. A second is added through the timelock with addProber; with several, a slot fails only when a majority of those that reported on it say so.
The latest post
Anchor rule: each record carries the hash of the latest block seen before its probe, and its window lands onchain within 30 min of closing. Outage rule: when 50% or more of the probed services fail most of a window, the window is voided for everyone. 115 windows posted, 28,650 probes counted, 0 windows voided.
§04
Settlement and compensation
Canary, epoch 14, worked through with its own numbers
probes = 60, failed = 0, target = 99% allowed = floor(1% * 60) = 0 excess = 0 f = 0% (0 x 10 bps, capped) slashed = 0.000000 USDG to compensation, 0.0000 $PBI burned
§05
Invariants
| I1 | cash | USDG held by BondBook is at least every leg, premium balance, earned premium, crystallised exit and the reserve. |
| I2 | cap | No epoch slashes more than maxSlashPerEpoch of a leg's assets at that epoch's close. |
| I3 | first loss | If a senior backer loses anything in an epoch, the junior tranche of that leg is zero after it. |
| I4 | order | Epochs settle once and in order, from aggregates posted before epoch end plus postDeadline. |
| I5 | no dodge | An exit never executes before every epoch it overlapped is settled. |
| I6 | bounded compensation | comp_c is at most compMultiple times paid_c, and the sum of comp_c is at most S_USDG. |
| I7 | owner | The owner can never move bond capital, premium balances or compensation. Its withdrawals reach the reserve and free ETH only. |
A stateful fuzzer checks them after every call: 128 runs of depth 64, so 8,192 random calls per invariant, on every build. Lost by customers without compensation to date: 0.000000 USDG committed and not yet claimed, 0.000000 USDG awaiting its customer list.
§06
Parameters
| Parameter | Now | Bounds in code | Meaning |
|---|---|---|---|
| probeInterval | 1 min | 60 s to 10 min | probe cadence per service |
| postEvery | 10 min | 5 to 60 min | one onchain post per window |
| postDeadline | 30 min | up to 6 h | latest a window can be posted |
| outageVoid | 50% | 30% to 90% | share of failing services that voids a window |
| bpsPerExcessProbe | 10 bps | 1 to 100 bps | slash per failed probe beyond the allowance |
| capAvail | 25% | up to 50% | availability part of an epoch slash |
| latBpsPerBucket | 50 bps | 0 to 500 bps | per hour over the latency target |
| maxSlashPerEpoch | 30% | 1% to 50% | ceiling per epoch per leg |
| compMultiple | 3x | 1x to 10x | compensation ceiling against what a customer paid |
| minOperatorBps | 20% | 5% to 100% | junior tranche floor per leg |
| minPremiumRunway | 7 d | 1 to 90 d | premium pre-funded at issue |
| protocolPremiumBps | 10% | 0% to 25% | share of premium to the reserve |
| settleDelay | 2 h | 0 to 24 h | wait after an epoch ends before it settles |
Read live from getParams() on BondBook and ProbeLog. Owner: a TimelockController with a 48 h minimum delay.
Timelock queue
Nothing queued.
§07
$PBI
Token
not bound yet
the $PBI legs open when it is bound
Burned by slashes
0.00
$PBI sent to 0x...dEaD by settlement
ETH accumulator
0.000000
ETH credited to $PBI backing; 0.000000 ETH unallocated
A $PBI leg earns its share of the service's premium in USDG, and its pro-rata share of the Pons creator-fee ETH if the token's creator fees are pointed at BondBook (harvest() pulls them). A slash on the leg burns the operator's junior $PBI first, then backers'.
§08
Interface
| Function | Who | What |
|---|---|---|
| ProbeLog.post(window, anchorBlock, anchorHash, root, aggs[]) | prober | one window of aggregates, a Merkle root and a block anchor |
| ProbeLog.register(kind, endpointHash, uri) | registrar | adds a watched service |
| ProbeLog.setOperator(id, operator, payTo) | registrar | records a verified claim |
| ProbeLog.epochStats(id, from, to, latBin) | anyone (view) | probes, failed slots, hours over the latency target |
| BondBook.issue(params) | claimed operator | opens a bond with junior capital and premium |
| BondBook.back(bond, leg, amount) | anyone | senior shares in a leg, up to its cap |
| BondBook.requestExit / withdrawExit | backer | leaves after the next settlement |
| BondBook.settle(bond) | anyone | settles the next epoch by formula |
| BondBook.claimPremium(bond, leg) | backer | USDG premium, and ETH on the $PBI leg |
| BondBook.isBonded(service, minCapital, mask) | anyone (view) | for agents, spending policies and hooks |
| Compensation.postRoot(bond, epoch, root, total) | publisher | the customers of a slashed epoch |
| Compensation.claim(bond, epoch, account, amount, paid, proof) | anyone | always pays the account |
| BondBook.setParams / ProbeLog.addProber | timelock (48 h) | parameters inside their bounds, the prober set |
| BondBook.bindToken(token) | binder, once | opens the $PBI legs |
cast call 0xe43B9179c9FB05072860bcf64778041f30Eb5292 "getParams()((uint16,uint16,uint16,uint16,uint16,uint16,uint32,uint16,uint32))" \ --rpc-url https://rpc.mainnet.chain.robinhood.com cast call 0xe43B9179c9FB05072860bcf64778041f30Eb5292 "isBonded(uint256,uint256,uint256)(bool)" 1 100000 1 --rpc-url ... cast call 0x091A79A686fa96005243628adA4cA40C69Ebe387 "epochStats(uint256,uint256,uint256,uint8)(uint256,uint256,uint256)" 1 <from> <to> 255 --rpc-url ... cast send 0xe43B9179c9FB05072860bcf64778041f30Eb5292 "settle(uint256)" <bondId> --rpc-url ... --private-key ...
BondBook
0xe43B9179...5292ProbeLog
0x091A79A6...e387Compensation
0xE6774598...C272Timelock
0x30a9DA9b...74ddPasskey accounts
0x88FfDB2E...4571Sources are verified on Blockscout. Security lists who holds each role.