A prompt is not suitability. When an AI agent trades for you, the only thing standing between "what you meant" and "what it did" is usually a sentence in a system prompt. Warrant turns that sentence into an on-chain object: a versioned, revocable, expiring warrant that a principal grants to an agent, plus a preflight that scores every proposed trade against it and an on-chain attestation that the agent saw that scorecard before it acted.
Warrant reveals, it does not enforce. It never custodies funds, never blocks a trade and never tries to bypass a wallet's own guard rails. What it adds is verifiability: after the fact anyone can check what the agent was allowed to do, and prove it was told the trade was out of bounds.
Demo videos: Monad (2:31: real quote, mm-signed attestation, ERC-8004, audit) https://youtu.be/qOab45ZLuJg · Avalanche Fuji (2:44) https://youtu.be/yitnbGgzKOA · Pitch deck (EN/中文): docs/warrant-pitch.pdf
Renamed from "Mandate" on 2026-09-26 to avoid confusion with an unrelated project of the same name. Contract, command and package names changed; nothing else did.
The MetaMask Agent Wallet already ships a policy layer (Guard Mode). Its policy file expresses three
things: address allow/block lists, allowed chains, and a rolling 24-hour USD outflow limit
(read from mm wallet policy template, @metamask/agent-wallet 6.2.0). That is a spend limit.
It cannot say "never accept more than 1% slippage", "not into a pool this thin", "not if fees eat
0.8% of the trade", or "no new token approvals". Those are suitability limits, and four of
Warrant's seven dimensions exist only here.
| Dimension | Guard Mode policy | Warrant |
|---|---|---|
| Trade size | 24h outflow total (partial) | per-trade USD cap |
| Slippage tolerance | — | ✓ |
| Price impact | — | ✓ |
| Fee load | — | ✓ |
| Recipient is self | address allowlist (partial) | ✓ |
| Cross-chain route | allowed chains (partial) | ✓ |
| New ERC-20 approval | — | ✓ |
Warrant is a complement, not a replacement: Guard Mode enforces spend, Warrant reveals suitability and leaves a public record.
- Principals who let an agent trade (a person, a fund's risk desk, a DAO treasury): they set the limits once on-chain and can revoke them at any time without touching the agent.
- Agent builders on the MetaMask Agent Wallet: one command before each swap, and an audit trail their users can check.
- Anyone reviewing an agent after the fact: every attestation is public, so "was the agent told this trade was out of bounds?" has a verifiable answer.
ERC-8226 ("RAMS", Draft, by Brickken) lets a verified principal delegate scoped, time-bounded, financially capped authority to an on-chain agent, and has a regulated token check that mandate before an agent-initiated action. It overlaps with Warrant's first half (principal → agent, revocable, expiring, capped, EIP-712) and answers a different question:
| ERC-8226 RAMS | Warrant | |
|---|---|---|
| Question | Was this agent authorised to act for this principal on this asset? | Was this particular trade suitable, and was the agent told before it acted? |
| Limits | asset address, action type, per-tx and cumulative quantity, validity window | slippage, price impact, fee load, recipient, new approvals, cross-chain route, USD size |
| Where it applies | tokens that implement the RAMS gate (regulated assets) | any swap an mm agent is about to make |
| Mode | enforces: the gated token reverts | reveals: signed disclosure, then audit |
| Principal eligibility | IComplianceProvider (KYC / investor status) |
out of scope |
A RAMS mandate can hold and a trade still be unsuitable (a 3% slippage sale of a bond the agent was
allowed to sell); a Warrant scorecard can pass for an asset the agent was never mandated to touch.
They are complements. mm warrant preflight --rams <registry> reads a RAMS mandate into the
scorecard when the trade touches a RAMS-gated asset (see below).
principal ──setWarrant / revoke──▶ WarrantRegistry (on-chain, per principal→agent, versioned)
│ getWarrant
agent ── mm swap quote ──▶ mm warrant preflight ──▶ scorecard (pass / warn / fail)
│
agent ──attestPreflight(quoteHash, version, verdict, scorecardHash)──▶ PreflightAttested event
or
agent ──mm wallet sign-typed-data (EIP-712, off-chain)──▶ anyone ──submit──▶ WarrantAttestor
(checks signer + version) ──▶ SignedPreflightAttested
WarrantReputation ──giveFeedback──▶ ERC-8004 Reputation (agent's identity)
| Layer | What | Where |
|---|---|---|
| Contract | WarrantRegistry: set / revoke / read warrants; attestPreflight emits an event bound to the current warrant version (stale or revoked versions revert) |
contracts/src/WarrantRegistry.sol |
| Contract | WarrantAttestor: relays an agent-signed EIP-712 attestation; checks the signer is the agent and the warrant is active at the signed version (reads the registry, never writes it); rejects replays and malleable signatures |
contracts/src/WarrantAttestor.sol |
| Scoring | 7-dimension scorer + worst-case loss, pure functions | src/lib/score.ts |
| Chain I/O | read the warrant, canonical scorecard hash, quote hash | src/lib/onchain.ts |
| CLI | mm warrant preflight plugin command |
src/commands/warrant/preflight.ts |
| Agent instructions | when to preflight, stop, ask, attest | skills/warrant/SKILL.md |
| Live walkthrough | grant → preflight → attest → revoke on a real testnet | scripts/demo-onchain.mjs |
| Relayer | put an agent-signed attestation on-chain | scripts/submit-signed.mjs |
| RPC shim | lets mm wallet send-transaction reach Monad testnet (see Limitations) |
scripts/mm-rpc-shim.mjs |
| Contract | WarrantReputation: each verified attestation becomes one ERC-8004 feedback entry on the agent's identity (see below) |
contracts/src/WarrantReputation.sol |
| ERC-8004 identity | mint the agent's identity and bind its agentWallet to the mm wallet |
scripts/register-agent.mjs |
| Audit | violation detector: trades without an active warrant, without a disclosure, or after disclosing FAIL | src/lib/audit.ts, scripts/audit.mjs |
| ERC-8226 | --rams <registry>: when the sold asset is RAMS-gated, the agent's mandate check becomes a scorecard dimension |
readRamsCheck in src/lib/onchain.ts; vendored reference in rams/; scripts/demo-rams.mjs |
Tech stack: Solidity 0.8.28 + Foundry 1.8.3 · TypeScript on Node 22+ · viem 2.56 ·
MetaMask Agent Wallet plugin SDK (@metamask/agent-wallet 6.2.0, oclif) · Arc mainnet · Monad testnet ·
Avalanche Fuji C-Chain.
WarrantRegistry has the same address on both chains (same deployer, same nonce):
0x37cdFe2a144993dC3145367305fF66E29302E673
| Chain | Deploy tx | Explorer |
|---|---|---|
| Monad testnet (10143) | 0x62d54a4690013a4a5c2fec2653d7442dfa3741f3bf0845c625eb37911f3d08a8 |
contract |
| Avalanche Fuji C-Chain (43113) | 0xd224000c67d4c0146e70726481582c5a0e8a3e51f5521d0ac2c46a89309c68e9 |
contract |
Deployment records: contracts/broadcast/Deploy.s.sol/{10143,43113}/. The records also contain the
first deployment under the old name (MandateRegistry, 0xf0145a8b…1f48), kept as history.
WarrantAttestor is also at the same address on both chains, pointing at the registry above:
0xC356ac5ebD7d249102D3C9c25764A8815c1718aA (deploy txs: Monad 0xa65b871f…1a92, Fuji
0x75d86fbf…a859; records in contracts/broadcast/DeployAttestor.s.sol/).
The whole stack runs on Arc mainnet (chain 5042, USDC as gas), wired to Arc's own ERC-8004 v2.0.0 registries, and one real quote went the whole way:
| Address / transaction | |
|---|---|
WarrantRegistry |
0x80B435fF87276f1302BfCAB4D7202555EeCDe73f |
WarrantAttestor |
0x33B7B8db2927783dEbb58a589CF5c7B95824fae1 |
WarrantReputation |
0x30906fFDE26d87868B602D34DA96f2C0006e3962 (feedback client of ERC-8004 ReputationRegistry 0x8004BAa1…9b63) |
Deploy + warrant v1 to the mm wallet |
contracts/script/DeployArc.s.sol; records in contracts/broadcast/DeployArc.s.sol/5042/ (4 txs, ~1.99M gas) |
| Agent identity | ERC-8004 agentId 1419 on IdentityRegistry 0x8004A169…a432: register, agentWallet := mm wallet (signed by the mm wallet) |
| Disclosure, relayed and rated | 0x2123414c…11f6 |
The quote is a real mm swap quote on Arc: 0.5 USDC → EURC through Uniswap
(0xb5a7cebf…9ad3), price impact 0.08%, slippage 50 bps. Preflight against the warrant on Arc
returned FAIL on one dimension, fee load: $0.01 (199 bps) against a 50 bps limit, because on
Arc the gas is paid in the coin being sold and the quote prices it (0.00997 USDC network fee on a
$0.50 trade). The mm server wallet signed that scorecard (mm wallet sign-typed-data, chain
5042), and one relayed transaction recorded it in WarrantAttestor with the quote hash
0xac086309…b68c and scorecard hash 0x3b065817…a596 that preflight printed, and posted ERC-8004
feedback warrant-preflight / fail for agent 1419. No swap was executed: the agent disclosed FAIL
and stopped. Before deploying, contracts/test/ArcMainnet.fork.t.sol ran the same flow on an Arc
mainnet fork against the live registries.
The agent here is the mm server wallet 0x299e298ded14b36412a7857ed02b844e7fb58e0e (Guard Mode),
granted warrant v1 on both chains. Two ways to attest, both tested live:
Transaction (mm wallet send-transaction → attestPreflight) |
Signature (mm wallet sign-typed-data → relayed to WarrantAttestor) |
|
|---|---|---|
| Fuji | PASS 0xb828…72c5, FAIL 0xea6a…9b03 | PASS 0x0f5a…0cfd, FAIL 0x8721…38f7 |
| Monad testnet | PASS 0xb362…8905 (through the RPC shim) | PASS 0x0540…1f6f |
| Agent pays gas | yes (~31k–35k) | no; the relayer pays (67,577 gas on Fuji; 88,716 billed on Monad, which charges the gas limit) |
| Guard Mode approval | one email approval per transaction (these testnets are not in the default allowed_chains) |
none: signed immediately, in every attempt |
| Monad RPC shim needed | yes | no |
The signature path is what an unattended agent would use: it signs every scorecard for free and without waiting for a human, and the evidence becomes public once anyone relays it.
ERC-8004 (Draft) gives agents an on-chain identity
(an ERC-721) and a reputation registry that any client can post feedback to. Its v2.0.0 registries
are live at the same addresses on both testnets (Identity 0x8004A818…BD9e, Reputation
0x8004B663…8713). Warrant plugs into both:
- Identity. The trading agent is an ERC-8004 identity whose
agentWalletis the mm wallet. The registry only changesagentWalletwith that wallet's own EIP-712 signature; here it came frommm wallet sign-typed-data(no transaction, no approval prompt), submitted by the agent's operator (scripts/register-agent.mjs). - Reputation.
WarrantReputation(0x0FAf92b84f00201e888210B62ef5055Cc3BbefED, both chains) is the feedback client.submitAndRate(attestation, signature, agentId)accepts only an attestation whose agent is that identity'sagentWallet, records it inWarrantAttestor(or finds it already recorded, so front-running the attestor cannot cost the agent its feedback), and posts one entry: value 1,tag1 = "warrant-preflight",tag2= the verdict,feedbackHash= the attestation digest. Anyone can then ask the ERC-8004 registry itself:getSummary(agentId, [WarrantReputation], "warrant-preflight", "fail")= how many trades this agent disclosed as out of bounds before acting.
| Monad testnet | Avalanche Fuji | |
|---|---|---|
Agent identity (owner: operator 0xeb11…789E) |
agentId 1937, register | agentId 252, register |
agentWallet := mm server wallet, signed by mm |
0x6ce8…dca2 | 0xcd02…cda5 |
| PASS attestation rated | 0xc89e…4879 (already on-chain → rated only) | 0x3e46…27d2 (already on-chain → rated only) |
| FAIL attestation rated | 0x108c…7427 (new: recorded + rated in one tx) | 0x5c7c…f5cf (already on-chain → rated only) |
getSummary read from the ERC-8004 registry |
2 entries: 1 pass, 1 fail |
2 entries: 1 pass, 1 fail |
WarrantReputation deploy |
0xd0b34514…3332 |
0x75ca407c…77cc |
What this reputation measures is disclosure, not trading skill: that the agent checked each
trade against a principal's warrant and signed the result before acting. A principal is only an
address, so a consumer should weigh entries by principal (the SignedPreflightAttested event
names it); the count alone can be inflated by an agent that grants itself warrants.
Disclosure only matters if someone checks it against what the agent actually did.
scripts/audit.mjs reads a principal → agent pair from chain and classifies every transaction the
agent sent in a block range:
| Verdict | Meaning |
|---|---|
ok |
an active warrant, and the latest disclosure before the trade was PASS |
NO_ACTIVE_WARRANT |
traded while the warrant was never granted, revoked or expired |
UNATTESTED |
traded with no unused disclosure before it (one disclosure covers one trade) |
EXECUTED_AFTER_FAIL |
the latest disclosure before the trade said FAIL |
EXECUTED_ON_WARN |
review, not a violation: WARN needs a human yes, which is not visible on-chain |
Everything comes from the chain: warrant events and disclosures from logs, the warrant's state
before the range from getWarrant at the previous block, and the agent's transactions from the
blocks themselves. The agent's nonce delta over the range is the denominator, so the report
says how many of its outgoing transactions it examined; an empty result never claims more than that.
Transactions to Warrant or ERC-8004 contracts are bookkeeping, not trades. Exit code 2 on violations.
scripts/demo-violations.mjs stages one of each outcome with the testnet agent key (the "trades"
are 1e-6 native-token transfers to 0x…dEaD, standing in for swaps; the audit does not depend on
what a trade is). The audit then found them without seeing the script's labels:
| Staged step | Fuji | Monad testnet | Audit verdict |
|---|---|---|---|
| grant warrant | 0x8b53…3b50 | 0x3da5…dfdd | |
| disclose PASS | 0x6ed9…a1fe | 0x89e8…50a4 | bookkeeping |
| trade 1 | 0x91ac…ed7e | 0x5de3…6a87 | ok |
| trade 2 (no disclosure) | 0x245d…828f | 0xca7a…bd91 | UNATTESTED |
| disclose FAIL | 0x940d…e665 | 0x98f8…b724 | bookkeeping |
| trade 3 | 0x6b6f…fbd7 | 0x73d7…a15f | EXECUTED_AFTER_FAIL |
| revoke | 0xa2c0…5ef8 | 0x77e0…d713 | |
| trade 4 | 0xf82a…684c | 0x45da…42e3 | NO_ACTIVE_WARRANT |
Both chains: 4 actions, 3 violations, coverage 6 of 6. Reproduce:
node scripts/audit.mjs fuji --principal 0xe4ebDEbd84f80bF592ca61C6eA56d10568D23aeA --agent 0xeb114deDc3883A4300fa0bBC29F5590607c0789E --from-block 58782113 --to-block 58782127
node scripts/audit.mjs monad --principal 0xe4ebDEbd84f80bF592ca61C6eA56d10568D23aeA --agent 0xeb114deDc3883A4300fa0bBC29F5590607c0789E --from-block 66108417 --to-block 66108463What it cannot see: whether a trade matched the quote that was disclosed (the swap transaction does not carry the quote id), and when a signed disclosure was signed (the EIP-712 payload has no timestamp, so for signed disclosures it proves existence, not order; the report says so). It scans every block in range, so it is meant for bounded windows (up to 20,000 blocks), not whole histories.
The unmodified ERC-8226 reference implementation is deployed on Monad testnet (addresses and
provenance in rams/README.md); its ERC-7943 mock asset stands in for a
tokenized bond ("wDBOND"). The principal holds 1,000 wDBOND and grants the agent both a RAMS
mandate (transferFrom only, ≤100 per trade, ≤250 in total) and a Warrant (≤100 bps slippage,
etc.). scripts/demo-rams.mjs then preflights three sales with readRamsCheck, the same call
mm warrant preflight --rams makes, so the RAMS answer is inside the attested scorecard hash:
| Sale | RAMS canExecute |
Warrant | Attestation | What happened |
|---|---|---|---|---|
| A. 50 wDBOND, 0.5% slippage | OK |
PASS | 0x9de6…6285 | agent sold: 0x65e9…a56d |
| B. 80 wDBOND, 3% slippage | OK |
FAIL (slippage) | 0x5385…0bd3 | agent stopped; RAMS alone would have allowed it |
| C. 150 wDBOND, 0.5% slippage | OVER_TX_CAP |
FAIL (mandate) | 0x8a36…3bd5 | the token itself reverts MandateBlocked(OVER_TX_CAP) (checked with eth_call) |
Setup transactions: mandate 0xe7d4…7671,
warrant v5 0x845d…5287.
Sale B is the point: a mandate that holds does not make a trade suitable. Limits of this demo: the
quotes are synthetic (no DEX lists wDBOND), the "sale" settles as transferFrom to a venue address,
one key plays principal, compliance operator and token admin, and the --rams CLI flag has been
exercised through the same library functions but not yet against a real mm stored quote of a
RAMS-gated asset. test/rams.test.mjs checks on anvil that every RAMS verdict the plugin reports
matches what the gated token does with the same call (accept, OVER_TX_CAP, OVER_CUMULATIVE_CAP).
scripts/demo-onchain.mjs executed on both chains: grant → preflight against the on-chain warrant
→ agent attests a PASS and a FAIL scorecard → principal revokes. Every transaction below returned
status = success with one event log, sent to the registry above.
| Step | Monad testnet | Avalanche Fuji |
|---|---|---|
setWarrant (principal) |
0xf864…fbf8 | 0x50a8…df54 |
attestPreflight PASS (agent) |
0x5fc8…9d5e | 0xc719…cbca |
attestPreflight FAIL (agent) |
0x18d4…9a0e | 0x27a2…bfc9 |
revoke (principal) |
0xc7db…5921 | 0x90e7…4400 |
Principal 0xe4ebDEbd84f80bF592ca61C6eA56d10568D23aeA, agent 0xeb114deDc3883A4300fa0bBC29F5590607c0789E
(throwaway testnet wallets).
The contract is plain Solidity 0.8.28 with no chain-specific precompiles; the same bytecode runs on both networks. What differs is why each chain matters for this use case.
Monad. Warrant is built as a plugin for the MetaMask Agent Wallet (mm) CLI, and the on-chain
half lives on Monad testnet. An agent that trades at machine frequency needs a pre-trade
attestation per quote; that is only reasonable on a chain with high throughput and fast blocks.
Measured cost of one attestPreflight on Monad testnet: 35,130 gas at 102 gwei = 0.0036 MON.
Avalanche. Same contract on the Fuji C-Chain. Snowman consensus fixes the order of accepted
blocks in under a second with no reorgs, so an attestation accepted before the trade stays before
it. Since the Helicon upgrade (ACP-194; scheduled by avalanchego for Fuji on 2026-07-28 and mainnet
on 2026-09-22, both before the Fuji transactions in this README) C-Chain execution is asynchronous:
a block is accepted first, executed after, and its results settled about τ = 5 s later. So order
is final at acceptance, but an agent that waits for the attestation's receipt before trading is
waiting for execution, not acceptance. Measured cost of one attestPreflight on Fuji: 30,788 gas at a
160 wei gas price, i.e. effectively free. This build does not use Avalanche L1s, ICM or x402.
Seven dimensions, each pass / warn / fail against the warrant:
| Dimension | Warrant field |
|---|---|
| Trade size (USD) | maxUsdPerTrade |
| Slippage tolerance | maxSlippageBps |
| Price impact | maxPriceImpactBps |
| Fee load | maxFeeBps |
| Recipient is the wallet itself | recipientMustBeSelf |
| Cross-chain route | allowCrossChain |
| New ERC-20 approval | allowNewApproval |
Plus a worst-case realised loss (slippage to minDestAmount) and a note when the trade would
likely trip the wallet's native Guard Mode 2FA. If the on-chain warrant is missing, revoked or
expired, the verdict is forced to fail: the agent is not authorised.
Requirements: Node 22.18+, mm (npm i -g @metamask/agent-wallet), Foundry.
npm install
npm run relink # point the local @metamask/agent-wallet at the global mm copy (see below)
npm run build
mm config set experimentalPlugins true
mm config set experimentalAllowUnverifiedInstalls true
mm plugins install "file:<absolute path to this repo>" --accept-permissionsnpm run relink matters: npm install puts a second physical copy of @metamask/agent-wallet
in node_modules, so the host's instanceof PluginCommand check fails with
PLUGIN_INVALID_BASE. relink.cjs replaces it with a junction to the global copy. Re-run it after
every npm install.
Grant a warrant (principal), on either chain:
cast send 0x37cdFe2a144993dC3145367305fF66E29302E673 \
"setWarrant(address,(uint64,uint16,uint16,uint16,bool,bool,bool,uint64))" \
<AGENT> "(100000,100,150,50,true,true,true,0)" \
--rpc-url https://api.avax-test.network/ext/bc/C/rpc --private-key $PRINCIPAL_KEYPreflight a pending quote against it (agent):
mm swap quote ... # creates a stored quote
mm warrant preflight \
--registry 0x37cdFe2a144993dC3145367305fF66E29302E673 \
--principal <PRINCIPAL> \
--rpc-url https://testnet-rpc.monad.xyz \
--attestor 0xC356ac5ebD7d249102D3C9c25764A8815c1718aA # optional: also emit the EIP-712 attestationWith --attestor, the output also carries attestation.signed: the typed data and a ready-to-run
mm wallet sign-typed-data command. Sign, then relay with any funded key:
node scripts/submit-signed.mjs typed-data.json <signature> # checks the signer locally, then submitsThe output ends with a ready-to-run mm wallet send-transaction … attestPreflight(…) command, so
the mm wallet that will trade signs the disclosure itself (JSON output carries the same call under
attestation.mm). Grant the warrant to that wallet's address (mm wallet list). A cast send
variant is printed too, for agents that hold their own key. Without --registry, preflight falls
back to a local warrant.json (see warrant.example.json).
On Monad testnet, mm 6.2.0 needs a local RPC shim because MetaMask's RPC proxy does not serve chain 10143 yet (the preflight output says so and adds explicit gas and fees to the payload):
node scripts/mm-rpc-shim.mjs & # loopback only; logs chain + method
MM_INFURA_RPC_BASE_URL=http://127.0.0.1:47812 mm wallet send-transaction --chain-id 10143 --payload '…'Tested with the server wallet in Guard Mode: Monad testnet
0xb3628760…8905,
Fuji 0xb828bf71…72c5 (PASS) and 0xea6abf03…9b03 (FAIL). Each needed one email approval, because
neither testnet is in the wallet's default allowed_chains.
Live walkthrough without mm (uses synthetic quotes, real chain):
cp contracts/.env.example contracts/.env # then put two funded testnet keys in it
node scripts/demo-onchain.mjs monad # or: fujiskills/warrant/SKILL.md tells an agent to run preflight before every swap, stop on fail,
ask the human on warn, attest before executing, and never split or reshape a trade to get under
a threshold.
npm test # scoring (14) + attestation builders (11) + audit (18) + on-chain integration on a local anvil (8) + ERC-8226 on anvil (8)
git clone --depth 1 https://github.com/foundry-rs/forge-std contracts/lib/forge-std # once
(cd rams && git clone --depth 1 --branch v5.4.0 https://github.com/OpenZeppelin/openzeppelin-contracts lib/openzeppelin-contracts && git clone --depth 1 https://github.com/foundry-rs/forge-std lib/forge-std) # once, for the ERC-8226 test
cd contracts && forge test # WarrantRegistry (13) + WarrantAttestor (14) + WarrantReputation on a Fuji fork of the live ERC-8004 registries (5) + the full stack on an Arc mainnet fork (1)
cd ../rams && forge test # the vendored ERC-8226 reference suite (105), unmodifiedThe integration test deploys to a local anvil, writes a warrant with cast, reads it back through
the plugin, attests with the hashes the plugin produces, and revokes. Its assertions are anchored
to the values written with cast, not to the plugin's own conversion code.
- Reveal, not enforce. A plugin cannot intercept
mm's native commands; an agent can skip preflight. The attestation makes skipping detectable, not impossible. - Attestation signer. The
mmserver wallet can signattestPreflightthroughmm wallet send-transaction: tested on Fuji, PASS0xb828bf71…72c5and FAIL0xea6abf03…9b03, both sent from the server wallet (~30,800 gas each). Two caveats as of mm 6.2.0: Guard Mode asks for email/MFA approval for every transaction on a chain outside the wallet'sallowed_chains(neither testnet is in the default list), and on Monad testnet (10143) MetaMask's RPC backend answersInvalid chainId, so mm needsscripts/mm-rpc-shim.mjs(0xb3628760…8905went through it). Because of the per-transaction approval, the unattendedscripts/demo-onchain.mjssigns with a separate agent EOA instead. - Fee load counts gas only when the quote prices it. Since 2026-10-05 the network fee is added
to the fee load when it is paid in the coin being sold (USDC on Arc, the native coin when selling
it), valued at the quote's own sell-side rate; otherwise it is left out rather than guessed (a
quote from an empty wallet on Monad carries no network fee). The worst-case loss still covers
only the fill at
minDestAssetAmount, not gas. - Prices are off-chain inputs. USD values come from the quote. An attestation proves what the agent was shown, not that the price data was correct.
- Scripted demos use synthetic quotes; the plugin has run on a real one.
mm swap quoteworks with an empty wallet: on 2026-09-27 a real Monad mainnet quote (1 MON → USDC, quote0x13d2c3f1…e2f7) went through the installed plugin,mm warrant preflight --registry … --attestor …, scored against the on-chain warrant on Monad testnet: PASS (size $0.03, slippage 50 bps, price impact −2.39%, fees $0). mm'spriceImpactis(fromUsd − toUsd) / fromUsd(reproduced from that quote's own fields), so a negative value is in the trader's favour. The scripted walkthroughs (demo-onchain.mjs,demo-violations.mjs) score the fixtures intest/fixtures.mjsso they stay reproducible; every warrant read, write and attestation is real. A second real quote (0x2ade8da4…5256) went the whole way: preflight PASS →mm wallet sign-typed-databy the server wallet → relayed throughWarrantReputationon Monad testnet in one tx,0x397aee33…7295, whoseSignedPreflightAttestedcarries the quote hash0xaffb6c2f…d016and scorecard hash0x2020c94f…91e1that preflight printed, plus ERC-8004 feedback #3 for agent 1937. No real swap has been executed yet (on Arc the warrant and the quote are both on mainnet; the agent disclosed FAIL and did not trade). - Mainnet, but not audited. The Arc deployment is mainnet; Monad and Fuji are testnets. The contracts hold no funds (they record warrants and disclosures only) and have not been audited.
Both hackathons allow a pre-existing foundation if it is disclosed. Timeline:
| Date | Work | Evidence |
|---|---|---|
| 2026-09-03 | Scaffolding cloned from MetaMask's agent-wallet-plugin-template (MIT): build config, plugin wiring, a hello ping sample and a security-scan workflow. The sample and workflow were removed on 2026-09-26. |
LICENSE keeps MetaMask's notice |
| 2026-09-03 → 09-14 | Preflight command, 7-dimension scoring (src/lib/score.ts), limits loader (src/lib/spec.ts), unit tests, offline demo. |
File modification dates; snapshot commit 10d4f25 |
| 2026-09-25 → | Registry contract and tests, on-chain preflight (src/lib/onchain.ts), anvil integration test, deployments to Monad testnet and Fuji, live demo, agent skill, this README. |
Commits from b923d94 on |
| 2026-09-26 | Renamed Mandate → Warrant; redeployed as WarrantRegistry. |
This commit and later |
| 2026-09-27 → 09-28 | Signed attestations (WarrantAttestor), ERC-8004 identity and reputation, audit, real mm quotes, ERC-8226 (RAMS) integration. |
Commits and deployments listed above |
Version control started late. The repository was only put under git on 2026-09-25, so work from 09-03 to 09-14 appears as one snapshot commit instead of its own history. No commit is backdated.
- Monad Metropolis (build window 2026-09-01 → 10-13): all of the above falls inside the window; the only pre-existing code is the MetaMask template scaffolding.
- Avalanche Buildathon (2026-09-14 → 09-30): the preflight plugin predates the window. The work done for this event is the on-chain layer: the contract, on-chain preflight, the Fuji deployment and the live demo.
This project was built with AI coding tools, as both hackathons require to be disclosed:
- Claude Code (Anthropic; Claude Opus models, Opus 5.5 for the work from 2026-09-25) wrote most
of the code, tests, scripts and this README under the author's direction; commits it co-authored
carry a
Co-Authored-Bytrailer. - The design was critiqued twice by a panel of other AI models ("council" reviews, 2026-09-14 and
2026-09-26). The first is why Warrant reveals instead of enforcing: an
mmplugin cannot intercept native commands, so an enforcement layer would be bypassable by construction. The second surfaced the naming collision and the Guard Mode positioning above. - Product thesis, scope, and every go/no-go decision are the author's. Each on-chain claim in this README was checked against transaction receipts.
MIT. See LICENSE.