Corpus delicti for AI agents. Before anyone is judged, prove the deed happened.
DELICTI is a corroboration-and-consequence layer for the actions of autonomous AI agents, anchored on Flare. It does not define yet another receipt format. It takes the signed receipts that effectors already produce (KYA-OS / Checkpoint _meta proofs, ACTA / ASQAV receipts, flario x402_receipts) and adds the four things none of them have:
- Mandate before act — a principal commits, on-chain, what the agent may do (budget, window, delegation chain) before the episode. Children can only narrow parents.
- Two witnesses to the same overt act — the effector's receipt is witness one; Flare's Data Connector (FDC) attesting the effect in the world is witness two. Agreement is evidence. Disagreement is a contradicted deed.
- Delta over the sequence — violations are computed against the cumulative budget of the mandate, not per action, so structuring ("salami") is caught.
- A brake that can see the sequence — the effector reads the mandate's cumulative tally before it acts, so the fifth slice of a salami is refused in milliseconds rather than slashed minutes later. An effector that skips the tally is choosing to be judged by the FDC instead.
- Consequence without a court — a bond is slashed on proof, pattern lifted from FAssets' challenger role. Challenger gets 10%, the harmed party gets the rest.
When a mind becomes alien, its words stop being evidence. Its deeds, confirmed independently, remain. — the thesis, after J. Pachocki's An Alien Mind.
flowchart LR
P[Principal] -- "commit(mandate)" --> MR[(MandateRegistry)]
A[Agent] -- acts through --> E[Effector<br/>MCP server / x402]
E -- "signed receipt<br/>(witness 1)" --> AL[(AnchorLog<br/>Merkle root)]
W[World<br/>XRPL / EVM / BTC] -- "FDC attestation<br/>(witness 2)" --> B{Bond}
AL --> B
MR --> B
B -- "witnesses agree" --> OK[evidence class A]
B -- "witnesses disagree<br/>or sum > budget" --> S[slash → victim + challenger<br/>mandate revoked]
Nothing below is a claim about what the contracts would do. Each line is a transaction anyone can open.
| What was proven | Transaction |
|---|---|
A receipt that lied: the agent's anchored receipt claims an XRPL payment, FDC ReferencedPaymentNonexistence proves it never happened → slash |
0x91bb1909…5fdc91 |
| Structuring, native: five 1-FLR deeds under a 4-FLR budget, each legal alone, each corroborated by FDC — the sum convicts | 0xa547ebad…96557f |
Structuring over x402: five genuine EIP-3009 settlements, genuine flario-receipt/2, FDC proofs carrying the Transfer event |
0x19c73850…014ed1 |
| The same loop driven through a running flario MCP server — witness 1 emitted by the effector process, not hand-built | 0x118bc486…d922f0 |
| The effector-side brake: a live mandate pays, while a missing, revoked or borrowed one is refused before any funds move | 0x2b59d401…277664 |
The hardened v0.5 contracts, same loop again, proceeds now credited and pulled with claim() |
0xc3ec3064…8c5fc4 |
| Structuring refused while it was still happening — four slices fit, the fifth was rejected by the meter with no FDC round and no transaction at all | meter state, mandate #1 |
| The effector recorded two settlements out of five; the FDC proved five, and the tally was convicted of the difference | 0x53664b9f…0b20ff |
| A deed with no receipt behind it: nobody answered, the window closed, the bond went | 0x432ca347…72490b |
| The same accusation, answered in time with the anchored receipt — dismissed, and the accuser's stake forfeited | 0x1bbcaf3f…6ae48b |
| A copier that lifted a finished challenge out of the mempool — refused, on-chain, because it had not committed in time | 0xab529327…4da115 (reverted, CommittedTooLate) |
| …and the same calldata, revealed two blocks later by the address that committed first | 0xdbf70a53…9a7628 |
Every challenge type DELICTI defines has now been executed on Coston2, in both directions where it has two: an accusation that stands and one that is answered, a challenge that lands and a copy of it that is refused.
| KYA-OS / Checkpoint | ACTA / ASQAV | AP2 mandates | OAP (pre-action) | DELICTI | |
|---|---|---|---|---|---|
| Effector-signed receipt | yes | gateway/operator | yes | gateway | consumes theirs |
| Commitment before the act | – | – | payments only | policy hash | on-chain, with budget + delegation tree |
| Independent confirmation the effect happened | – | – | – | – | FDC (second witness) |
| Detects structuring across many small calls | – | – | – | admitted gap | sum over sequence |
| Consequence without a court | – | – | – | – | bonded slash |
| Survives the operator's bankruptcy | if they keep logs | Bitcoin/Rekor anchor | – | – | neutral chain |
A receipt proves registration. "A false claim can be immutably registered." DELICTI proves the deed — or proves the receipt lied.
| Piece | State |
|---|---|
MandateRegistry.sol — commitments, delegation tree, monotonic narrowing, revocation |
tests pass |
AnchorLog.sol — per-mandate sequence of Merkle roots over receipts; refuses dead mandates |
tests pass |
Bond.sol — challengeFalsePayment: anchored receipt × FDC ReferencedPaymentNonexistence → slash |
tests pass (mock FDC); live FdcVerification resolution verified on a Coston2 fork |
Receipts.sol — normalized "overt act" leaf bound to the original third-party receipt |
done |
scripts/contradicted-deed.sh — live end-to-end on Coston2: testXRP nonexistence → FDC proof → slash |
done, executed on Coston2 |
flario x402_receipt v2 with mandate_ref + effector-side mandate gate (DELICTI_REGISTRY, DELICTI_REQUIRE_MANDATE) on both the MCP and the HTTP path |
shipped in flario |
Bond.challengeBudgetOverrunERC20 — the real x402 case: settlement is transferWithAuthorization on the token, native value is 0, the deed is the Transfer event inside the FDC proof |
tests pass |
tools/delicti.py — normalize a flario receipt into a leaf (bit-identical to Receipts.hash), evidence class, sorted-pair Merkle tree + proofs |
done |
scripts/x402-structuring.sh — end-to-end with real EIP-3009 settlements, real flario v2 receipts, FDC proofs with events, ERC-20 challenge |
done, executed on Coston2 |
Bond.challengeBudgetOverrun + scripts/structuring.sh — five 1-FLR deeds under a 4-FLR budget, each corroborated by FDC EVMTransaction, sum convicts |
done, executed on Coston2 |
scripts/mcp-structuring.sh — the same salami, but every deed is a paid MCP call to a running flario server: witness 1 is emitted by the effector process, not hand-built |
done, executed on Coston2 |
scripts/brake-test.sh — the effector-side brake (SPEC §7) live: a live mandate pays; a missing, revoked or borrowed mandate is refused before any funds move |
done, executed on Coston2 |
Bond.accuseUnanchoredDeed / answerAccusation / resolveAccusation + MandateRegistry.declareExclusive — the deed nobody wrote down (SPEC §6.4) |
done, executed on Coston2 (both outcomes) |
SpendMeter.sol + Bond.challengeUnderReportedSpend — the running tally that refuses structuring in real time (SPEC §7.1), and convicts the effector whose tally lied (§6.5) |
done, executed on Coston2 |
Bond.commitChallenge — commit–reveal on all five challenges and on the accusation, so the 10 % belongs to whoever detected the violation rather than to whoever copied the calldata (SPEC §6.7) |
done, executed on Coston2 (both directions) |
Deployment: MandateRegistry 0xC6c27d29Ff33Bb6760e5033f52666fD1acc21c3f, AnchorLog 0x1A1A7E88C65643F5B91F6C86F1a0b6D2A459EDc0, SpendMeter 0x91384a94f6FFd0383BEFB27f3097dc51A4C4A005, Bond 0x3bb15155E55d8200Cc275204631d6B9033a5f38C. Testnet timers: commitLead 120 s (production 10 min), responseWindow 600 s, anchorGrace 300 s. COMMIT_TTL is 3600 s and is a constant, not a deployment choice. The voting-round clock resolves to Flare's live ProtocolsV2 at 0xA90Db6D10F856799b10ef2A77EBCbF460aC71e52 — Bond.protocols() returns it, so the rule below is enforced against Flare's own clock and not against anything this repo wrote down.
Five 1-C2FLR deeds under a 4-C2FLR mandate, the salami of §6.2, run twice: once by the address that found it, once by a copier.
The honest challenger. Committed at t = 1789422302 — a bare hash, naming nothing. The FDC voting round that produced the evidence, 1455472, began at t = 1789422480: 178 seconds later, against a required lead of 120. Reveal: 0xdbf70a53…9a7628 (358,026 gas) → bondOf = 0, slashed = true, mandate revoked, 10 % plus the remainder credited for pull.
The copier. Same five proofs, same calldata, a commitment made at t = 1789422665 — 185 seconds after that round had already opened, which is the earliest anyone watching FdcHub could have known the case existed. Submitted with a forced gas limit so the refusal would be recorded rather than disappear into a local revert: 0xab529327…4da115 → status 0, revert data 0xc9e9ac51 = CommittedTooLate().
Three details worth more than the headline:
- The copier burned 271,152 gas before the gate bit. The commitment is checked after the FDC proofs are verified, because
votingRoundis only trustworthy once the proof has been checked against the Relay root. So a copy is not free to attempt — it pays for the verification and then loses. - Its commitment is still sitting there, unspent, at
committedAt0xa67f9966…6e2d05. The honest one is gone: consumed on reveal, single-use, as designed. - The whole sequence — five deeds, commit, wait out the lead, five attestation requests, five proofs, one refusal, one slash — ran unattended in about ten minutes. Script:
scripts/structuring.shwithSNIPE=1(the default).
For scale, the last native structuring challenge on-chain cost 291,855 gas on the v0.4 deployment; this one cost 358,026. That span covers v0.5's hardening and v0.6/v0.7 as well as commit–reveal, so it is not a clean measurement of this feature — but the order of magnitude is the answer to "what does this cost": one extra ~31k-gas transaction, and a reveal that is tens of thousands of gas heavier.
Two things this does not fix, stated in SPEC §10 rather than glossed over: a squatter willing to pay rent on every candidate deed set forever can still hold a live commitment, and the agent — which by construction knows of its own violation first — can self-slash ahead of a real watcher.
Deployment: MandateRegistry 0x3b53a646E5450F4b525e30F2aA59be95AF77657b, AnchorLog 0xd5EECFAFE96fE9eec7E126F4B318DB139c9396bf, SpendMeter 0xD64465B95A1E83DC292AcB1e5b66dD416ADeA55C, Bond 0x3557Ae63bC3868165E605685b45506116E2CE1e6 (testnet timers: responseWindow 600 s, anchorGrace 300 s; production values are 24 h and 1 h).
Structuring refused in real time (mandate #1). Four settlements of 0.01 C2FLR against a 0.04 budget, each preceded by a wouldExceed call and followed by note: 0x5bcd06f8…4fd525, 0xc38c2a1d…191acb, 0x10a8a73a…f9a61c, 0xd5f59139…2d8351. The meter then reads spent = 0.04, headroom = 0, wouldExceed = true. The fifth slice has no transaction hash, because it never happened — the refusal is an eth_call, so there is nothing to link to but the meter itself. That is the point of this one: no FDC round, no waiting, no funds moved, and the bond untouched (slashed = false). Script: scripts/spend-meter.sh (MODE=brake).
The tally that lied (mandate #2). The effector recorded two of five settlements and stayed silent about three; the meter said 0.02 while the world showed 0.05. Five FDC EVMTransaction proofs, no anchored leaves needed — challengeUnderReportedSpend: 0x53664b9f…0b20ff (280,836 gas) → slashed. Script: scripts/spend-meter.sh (MODE=underreport).
The deed nobody wrote down (mandate #5). A deed from an exclusive agent's address with no receipt behind it, accused after the anchor grace: 0x3ea01514…e27772 (221,524 gas). Nobody answered, the response window closed, and anyone could resolve it — 0x432ca347…72490b (118,872 gas) → slashed, mandate revoked, the challenger's 10 % and stake credited for pull. Script: scripts/unanchored-deed.sh (MODE=silence).
The same accusation, answered (mandate #4). The agent had anchored the receipt in time, so the accusation was dismissed on the evidence and the accuser's stake was forfeited to the principal: accusation 0x8f0a4d3c…8a99ec (220,684 gas), answer 0x1bbcaf3f…6ae48b (76,075 gas), slashed = false. Script: scripts/unanchored-deed.sh (MODE=answer).
MandateRegistry 0x401C07e28db3464ab2013C36Babf4701cD8dC6bd, AnchorLog 0x2FbcF31FC3a66BbfbA30743aab932d7AE78FDf56, Bond 0xc42A87F8E005B231819b16E46B119b90228b86A6. Testnet timers: responseWindow = 600 s, anchorGrace = 300 s (production values are 24 h and 1 h — both are constructor immutables so the whole loop can be shown in one sitting).
What is not claimed yet: no accusation has been resolved on-chain. scripts/unanchored-deed.sh reached the FDC step and the data-availability layer kept answering attestation request not found for the deed's voting round, far past the 2–4 minutes these rounds usually take, with Coston2 gas at 1500–2000 gwei instead of the usual ~25. The contracts are covered by 42 tests; the live demonstration is pending, and this section will say so until it is not.
The same loop, against the hardened deployment (mandate #1). Five paid MCP calls through the flario server, five FDC proofs with Transfer events, challengeBudgetOverrunERC20: 0xc3ec3064…8c5fc4 (361,940 gas) → slashed. The proceeds are now credited, not pushed: claim() 0x45885501…8a0bf3. Deployment: MandateRegistry 0x1e85be1CD6D499f5E8AE12C6Fa1336949188FbB7, AnchorLog 0x14E65D83032b85241B764f5fbbEE532a90403D23, Bond 0x9bDFE9C95980E7676E64779Cabe1948Ce6F04ae8.
The hardening, verified on-chain rather than only in tests. revoke() followed immediately by withdraw() reverts CoolingWindow() (0x3f93322d) and the bond stays posted; revokeByBond from an address that is not the registered Bond reverts NotBond() (0x799e4159). CHANGELOG v0.5.0 says what each of those was protecting against.
The loop through a running effector (mandate #8). Every deed here is a paid MCP tool call: the agent is an MCP client, the flario server is the effector, it settles the EIP-3009 authorization itself and answers with its own flario-receipt/2 carrying mandate_ref. Nothing about witness 1 is hand-built. Five calls of 1 mUSDT0 under a 4 mUSDT0 mandate, each corroborated by an FDC EVMTransaction proof carrying its Transfer event — challengeBudgetOverrunERC20: 0x118bc486…d922f0 (327,144 gas) → slashed, mandate revoked. Script: scripts/mcp-structuring.sh.
The brake (SPEC §7), live. Four calls against the same server: a live mandate with the correct agent pays (0x2b59d401…277664, mandate #5); a call with no mandate_id under DELICTI_REQUIRE_MANDATE=1, a call under revoked mandate #6, and a call under mandate #7 belonging to another address are all refused before any funds move — the payer's token balance is unchanged across all three. Script: scripts/brake-test.sh.
The real x402 salami (v0.3 deployment, mandate #3): five genuine EIP-3009 transferWithAuthorization settlements of 1 mUSDT0 each (agent signs typed data, facilitator submits — exactly flario's x402 path), each wrapped in a genuine flario-receipt/2 carrying mandate_ref (witness 1), each corroborated by an FDC EVMTransaction proof carrying the Transfer event (witness 2), under a 4 mUSDT0 mandate — challengeBudgetOverrunERC20: 0x19c73850…014ed1 (325,410 gas) → slashed, mandate revoked. Script: scripts/x402-structuring.sh. Deployment: MandateRegistry 0x73109d769878cA2Cf0Ba180CF4f1a24b404F3f48, AnchorLog 0x8eC9C70f9615804259c16e811dC428Db7a1522Fe, Bond 0xBA146240AC394E64ca50CaC40100A2cdAE241e4e, MockUSDT0 (EIP-3009, public mint) 0x9Eea43feA502609d0D88DAfd1d64B4e929BF18C2.
Current deployment (v0.2, Receipts.Leaf.ref): MandateRegistry 0x52A61f0B9312042c514B0aC5C053747B0EdF0C17, AnchorLog 0x10F4e4bc90d483B9E1D6c90EE6d6275FF825D2ae, Bond 0x84Da6082Ba9f453d6aE59A0A3f868F6A1C35046E.
Structuring proven (mandate #4): five transfers of 1 C2FLR to 0x2222…2222 under a 4 C2FLR budget — each one legal alone, each corroborated by its own FDC EVMTransaction proof (rounds 1448936–1448937, sourceAddress == the mandated agent) — then challengeBudgetOverrun with all five: 0xa547ebad…96557f (291,855 gas) → slashed, mandate revoked. This is the pattern pre-action gates cannot see, because every call passes on its own.
First contradicted deed (v0.1 deployment): MandateRegistry 0x307cF47DB74a48CFC9813c59F29B1a2c546746d5, AnchorLog 0xed65258EC80fAE6b780215aA17E1AB7A321d41E8, Bond 0x6b4Dc7E1F6eda9B8D2ECa97dDF35e1E19A3E1ed2.
- FDC request (
ReferencedPaymentNonexistence, testXRP, round 1448919):0x2fb195a3…60d13e - mandate #1 committed:
0x8e6bae06…393c92; false receipt anchored:0x43c8bd62…f95ace; bond 1 C2FLR:0x0bc4f479…62181b - challenge with the real FDC proof → slashed, mandate revoked:
0x91bb1909…5fdc91(159,787 gas)
Verified on Coston2 (chain 114): FdcVerification 0x906507E0B64bcD494Db73bd0459d1C667e14B933, Relay 0xa10B672D1c62e5457b17af63d4302add6A99d7dE, FDC protocol id 200.
See CHANGELOG.md. Proposal to the receipt ecosystems: docs/proposals/kya-os-mandate-ref.md.
The vocabulary, trust model, evidence classes, challenge invariants, and non-claims are fixed in SPEC.md (v0.1 draft).
npm install # pulls @flarenetwork/flare-periphery-contracts
forge build
forge test # unit tests with a mock FDC
forge test --fork-url coston2 --match-contract Coston2ForkTest -vv # live wiringDeploy to Coston2: forge script script/Deploy.s.sol --rpc-url coston2 --broadcast --private-key $PK
flario (an MCP server for Flare) is the first effector that speaks DELICTI. The paying agent adds mandate_id (and optionally mandate_registry) to its x402 payload; the server binds it into the receipt (flario-receipt/2, field mandate_ref) and, if DELICTI_REGISTRY is set, refuses the payment before funds move when the mandate is not live or the payer is not the mandated agent. The effector is the final common pathway — a deed with no mandate is a muscle moving with no signal, so the effector may simply not move.
x402 payload → flario checks MandateRegistry.isLive + agent → settles → x402_receipt{mandate_ref, fdc_attestation_ref}
tools/delicti.py normalize receipt.json → leaf + leafHash (== Receipts.hash on-chain)
tools/delicti.py tree leaf*.json → root for AnchorLog.anchor + per-leaf proofs for Bond
DELICTI names what it can and cannot prove:
- A — two witnesses agree (receipt + FDC attestation of the effect).
- B — one witness; the effect is not observable in the world (e.g. an email sent).
- C — self-report only.
- A compromised effector can sign false receipts. That is exactly why witness two exists: FDC does not trust the effector.
- FDC finality is minutes, not seconds. DELICTI is evidence after the fact, not a real-time brake (a mandate check hook is left in the spec for effectors that want one).
- Web2 effects need allow-listed sources on FDC; Sprint 0 corroborates on-chain and XRPL/BTC/DOGE effects only.
- Whitehat only. Everything runs on Coston2 / forks. Never real mainnet with other people's funds.
MIT — Dziuba Technology.