Skip to content

Latest commit

 

History

History
259 lines (201 loc) · 19.9 KB

File metadata and controls

259 lines (201 loc) · 19.9 KB

Mapae Live Demo - GIWA Sepolia

Run at 2026-07-28T23:30:25.546Z · chain 91342 · every hash below is clickable and public.

The delegation: the agent 0x8D62720694c8f71095202E049C31C25B48496232 may pay the merchant up to ₩50,000/day from the principal's MapaeAccount for 7 days - and only while the principal 0x2875B01Abf0E5EB98253274d62Db08FA7630B783 holds a live Dojang attestation from the testnet faucet issuer.

Cast

Role Address
Principal (human; holds the Dojang attestation; signs the delegation) 0x2875B01Abf0E5EB98253274d62Db08FA7630B783
MapaeAccount (holds funds and delegation state; owner = principal) 0x28e3ad56826F42Ca4d602766BcBc39Ff60A49a99
Agent (delegate; redeems) 0x8D62720694c8f71095202E049C31C25B48496232
Merchant (allowed payee) 0x8ACD1cB724AAe8CDCD737aC97A5aF4414012a617
Attacker (unlisted payee) 0x4Fe81416D2534Eea4c94F6b6dB21faC58eF6d336

Transactions

# What happened Expected On-chain result Tx
S1 Create MapaeAccount (owner EIP-712 consent) success success 0x18d4315f…
S2 Fund account with 1,000,000 mKRW success success 0x0c2b865b…
S3 Principal already holds a live Dojang attestation - skipped -
T1 Authorized payment: 30,000 mKRW -> merchant success success 0xa01e6e86…
T2 Over daily cap: +30,000 (total would be 60,000 > 50,000) revert: transfer-amount-exceeded reverted Error(ERC20PeriodTransferEnforcer:transfer-amount-exceeded) 0xe7563dfa…
T3 Unlisted payee: 10,000 -> attacker revert: PayeeNotAllowed reverted PayeeNotAllowed(0x4Fe81416D2534Eea4c94F6b6dB21faC58eF6d336) 0x131e9744…
T4 Issuer discrimination: delegation demands UPBIT KOREA revert: NotDojangVerified reverted NotDojangVerified(0x2875B01Abf0E5EB98253274d62Db08FA7630B783, 0xd99b42e778498aa3c9c1f6a012359130252780511687a35982e8e52735453034) 0xfaf9880c…
T5a Principal disables the delegation success success 0x580e3165…
T5 Payment while disabled (identity still LIVE) revert: CannotUseADisabledDelegation reverted CannotUseADisabledDelegation() 0x0dbdc013…
T6a Principal re-enables the delegation success success 0x5f6a50c9…
T6 Payment after re-enable: 10,000 -> merchant success success 0xd0b461f6…
T7a Principal revokes their Dojang attestation success success 0xf4042891…
T7 Payment after identity revocation (delegation ENABLED, cap unspent, window open) revert: NotDojangVerified reverted NotDojangVerified(0x2875B01Abf0E5EB98253274d62Db08FA7630B783, 0xaa92f8c143657dde575de430aecaea6ca91f2e6072339b16932d426895d8d678) 0xd3843e1f…
T8a Principal re-issues their attestation success success 0x1a5e8ddd…
T8 Payment after re-issuance: 10,000 -> merchant success success 0x250b424e…

Final balances (this run): merchant +₩50000 · account ₩950000 · attacker ₩0

Why T7 is the thesis

At T7 the delegation signature is valid, the daily cap has ₩40,000 unspent, the time window is open, the payee is allowed, and the delegation is enabled. The payment still fails - purely because the principal revoked their real-world identity attestation, in a transaction that never touched a Mapae contract. Identity revocation is a kill switch the delegation layer does not even see; it is inherited from Dojang reading liveness at the moment of use.

T5-T8 form a 2x2 matrix: each kill switch (disable-delegation / revoke-identity) blocks alone while the other is untouched, and each is reversible. No ordering assumptions - the manager checks disabled-state before any caveat, so the errors never bleed into each other.

The per-payment ceiling - the sixth condition, proven live

One delegation carrying all five payment conditions at once - identity, 50,000/day period cap, 10,000 per-payment ceiling, one allowlisted payee, 7-day window - redeemed twice. The pair is chosen so only the ceiling can refuse: the day budget had 40,000 of room when the second payment was attempted.

Step Expected On-chain Tx
pay 10,000 - exactly at the ceiling success success 0x3e922d55…
pay 10,001 - one over revert: PerPaymentCapExceeded reverted PerPaymentCapExceeded(10001, 10000) 0xdac9db98…

Delegation 0xbecc5c5f7790413bf21d87c99861553021c890408d2546c97ab3f7af28415260. Reproduce with pnpm tsx scripts/perpayment-proof.ts.

Traceback

pnpm trace 0xa01e6e8696d4fe4d505c8636ed1f09a0a0da3d4dcf01bd045f0046d99757e568 resolves T1's hash backwards: payment -> delegation hash -> principal -> attestation uid -> issuer, entirely from public state.

Reproduce

cp .env.example .env   # two fresh keys; fund the principal from the faucets
pnpm install && pnpm fixtures && pnpm demo

x402 facilitator - the erc7710 path, live

An HTTP facilitator (gosuda/x402-facilitator, branch feature/erc7710) settling Mapae delegations. The client signed two typed-data payloads and spoke HTTP - it broadcast nothing and needed no gas. The facilitator held no policy and no funds: verification is simulation of the delegation manager, and every cap, payee, window and identity check ran on-chain.

Chain: principal -> agent (4 caveats) -> facilitator settlement signer 0x0Cde5B7742B2C67c5BF6f5aEa339db868684336a.

Step Result Tx
F1 /verify 20,000 isValid=true, payer=0x28e3ad56826F42Ca4d602766BcBc39Ff60A49a99 (the account, not the agent) -
F2 /verify 60,000 isValid=false, reason=delegation_cap_exceeded - the facilitator evaluated NO policy; the chain did -
F3 /settle 20,000 success, gas paid by facilitator, funds account->merchant 0x9fbe7b2b…
F4 /settle 20,000 again success - one payload, second settlement. An EIP-3009 authorization cannot do this 0x8a1fba25…
F5 /settle third 20,000 rejected before broadcast: reason=delegation_cap_exceeded - fee payer wasted zero gas -

Merchant received exactly ₩40,000 across two settlements of ONE signed payload - multi-use is the property that distinguishes erc7710 from eip3009/permit2 in the x402 exact/EVM spec.

The MCP surface - an agent paid, chose, and was refused over stdio

mcp/harness.ts plays both roles: phase 1 is the human, signing a fresh authority - ₩5,000/day, ₩1,000 per payment, TWO allowed payees; phase 2 speaks only stdio JSON-RPC to the built server, the way a model's MCP client does. Amounts are chosen so exactly one condition can refuse each step.

Step Result Tx
pay 700 PAID to the first allowed payee - the default 0x18ee370c…
pay 300, payee=second PAID - the argument chose among the SIGNED list 0x26a8e5ac…
pay 100 to an outsider NOT_IN_POLICY - rejected before any transaction existed, zero gas -
pay 1,500 REFUSED on-chain: PerPaymentCapExceeded(1500, 1000) - the day budget had ₩4,000 of room 0x8091a929…
redelegate cap 500, per-payment 200 a two-hop child carrying both narrowing caveats, signed with no transaction -

The package on npm (mapae-mcp@0.6.0) was then verified cold from the registry - fresh npm cache, throwaway profile - confirming the request link it composes carries the whole policy shape (perTx ceiling, named payees), which the Composer arms on open.

Reproduce: pnpm tsx mcp/harness.ts (uses the demo keys from .env.example).

The product path - a person signed these in a browser

Every transaction above was produced by a script that built its own delegation and then spent it: two programs agreeing with themselves. These four are different. A human connected MetaMask, composed a permission in the Composer, read it back as a sentence, and signed EIP-712 in the extension. The delegation hash the browser displayed - 0x7e74004e13…c680 - is character-for- character the hash pnpm redeem computed from the copied bytes, which is what proves the two halves encode the same thing.

The permission: agent 0x8D6272…6232 may pay one address up to ₩5,000 per day for 7 days, only while 0x2875B0…B783 holds a live testnet-faucet attestation.

Step Amount Result Tx
P1 first payment 1,250 paid 0xf320b785…
P2 after the owner clicked Disable in My Permissions 1,250 refused - CannotUseADisabledDelegation 0x97a3376c…
P3 after the owner clicked Re-enable 3,750 paid 0xd8f49b02…
P4 one won past the cap 1 refused - ERC20PeriodTransferEnforcer:transfer-amount-exceeded 0xd75079a8…

Four things fall out of the sequence, and the third is the one worth pausing on.

The kill switch is reversible. P2 blocked and P3 went through. Without that, "two independent kill switches" is unprovable: you could only show that throwing one stops payment, never that it stopped payment by itself.

Re-enabling is a resume, not a reset. P3 was for 3,750 - the remainder of a ₩5,000 cap with 1,250 already spent - and it succeeded, then P4's single won did not. Had the budget reset, disable → enable would be an unlimited-spend bypass: spend the cap, toggle, spend it again. It does not reset because the enforcer keys spend by (manager, delegationHash), and that hash excludes the signature, so re-signing the same delegation returns to the same budget. A fresh budget requires a different salt, which is what salt is for.

The boundary is exact. 3,750 passed and the next single won was refused. Not approximately the cap.

The total is the signed number. Across two refusals, a disable and a re-enable, the payee received exactly ₩5,000. Not a digit more.

Reproduce it yourself:

pnpm preflight <your wallet>     # gas, attestation, account, funds - each with the fix
cd explorer && pnpm dev          # issue one, copy the permission context
pnpm redeem <context> [amount]   # spend it as the agent; payee and policy come from the signature

x402 facilitator - the erc7710 path, live

An HTTP facilitator (gosuda/x402-facilitator, feat/giwa-erc7710) settling Mapae delegations. The client signed two typed-data payloads and spoke HTTP - it broadcast nothing and needed no gas. The facilitator held no policy and no funds: verification is simulation of the delegation manager, and every cap, payee, window and identity check ran on-chain.

Chain: principal -> agent (4 caveats) -> facilitator settlement signer 0x0Cde5B7742B2C67c5BF6f5aEa339db868684336a.

Step Result Tx
F1 /verify 20,000 isValid=true, payer=0x4b6B26Cc68011FB4e9c9B6C0a4E15df040BFc23e (the account, not the agent) -
F2 /verify 60,000 isValid=false, reason=delegation_cap_exceeded - the facilitator evaluated NO policy; the chain did -
F3 /settle 20,000 success, gas paid by facilitator, funds account->merchant 0xf91e431c…
F4 /settle 20,000 again success - one payload, second settlement. An EIP-3009 authorization cannot do this 0x4c792c10…
F5 /settle third 20,000 rejected before broadcast: reason=delegation_cap_exceeded - fee payer wasted zero gas -

Merchant received exactly ₩40,000 across two settlements of ONE signed payload - multi-use is the property that distinguishes erc7710 from eip3009/permit2 in the x402 exact/EVM spec.

x402 facilitator - the erc7710 path, live

An HTTP facilitator (gosuda/x402-facilitator, feat/giwa-erc7710) settling Mapae delegations. The client signed two typed-data payloads and spoke HTTP - it broadcast nothing and needed no gas. The facilitator held no policy and no funds: verification is simulation of the delegation manager, and every cap, payee, window and identity check ran on-chain.

Chain: principal -> agent (4 caveats) -> facilitator settlement signer 0x0Cde5B7742B2C67c5BF6f5aEa339db868684336a.

Step Result Tx
F1 /verify 20,000 isValid=true, payer=0x4b6B26Cc68011FB4e9c9B6C0a4E15df040BFc23e (the account, not the agent) -
F2 /verify 60,000 isValid=false, reason=delegation_cap_exceeded - the facilitator evaluated NO policy; the chain did -
F3 /settle 20,000 success, gas paid by facilitator, funds account->merchant 0xbf506e8f…
F4 /settle 20,000 again success - one payload, second settlement. An EIP-3009 authorization cannot do this 0x63bdf487…
F5 /settle third 20,000 rejected before broadcast: reason=delegation_cap_exceeded - fee payer wasted zero gas -

Merchant received exactly ₩40,000 across two settlements of ONE signed payload - multi-use is the property that distinguishes erc7710 from eip3009/permit2 in the x402 exact/EVM spec.

Graduated autonomy - how much may the agent decide alone?

One authority, three rungs. The person signs a root that carries the identity condition, a ₩50,000 day budget, one merchant and a seven-day window, then signs each rung off it. Every rung inherits all of that and narrows one thing further.

Rung Per payment Human in the loop
1 ₩10,000 no
2 ₩30,000 no
3 ₩200,000 yes

Who signs a rung decides whether its gate means anything. The root delegates to the person, not to the agent, and the person signs each rung. Conditions can only be added going down a chain, never removed - so a rung the agent signed would bind nobody, because the agent could sign a second rung without the gate and redeem through that instead. Both arrangements are pinned in test/integration/TierBudget.t.sol.

What the agent tried Outcome on chain Transaction
₩10,000 on rung 1 settled 0xd17f165d…
₩30,000 on rung 1, above its ₩10,000 ceiling refused PerPaymentCapExceeded(30000, 10000) 0x49b9dcba…
₩5,000 to an unlisted payee refused PayeeNotAllowed(0x8627eEE60293Eb53646d30031186c5B83D5889ae) 0x6429c669…
₩150,000 on rung 3, presenting a code nobody confirmed refused CodeNotVerified(0xcde44ed867b9bce97f3e687eb4537822a871742b6fb41147551659f06749ff07, mapae.tiers.demo, 0xaa92f8c143657dde575de430aecaea6ca91f2e6072339b16932d426895d8d678) 0xfdeaf01b…
₩5,000 after the identity was revoked refused NotDojangVerified(0x2875B01Abf0E5EB98253274d62Db08FA7630B783, 0xaa92f8c143657dde575de430aecaea6ca91f2e6072339b16932d426895d8d678) 0xef36af5d…
₩5,000 after the identity was restored settled 0x87459ecc…
₩30,000 on rung 2 settled 0x832fa392…
₩20,000 on rung 2, inside its ceiling but past the shared budget refused Error(ERC20PeriodTransferEnforcer:transfer-amount-exceeded) 0xc949e811…
₩5,000 on rung 2, exactly the remaining budget settled 0x12600cf4…
₩5,000 on rung 1, after rung 2 spent the day refused Error(ERC20PeriodTransferEnforcer:transfer-amount-exceeded) 0x2e7d9c44…

The last three rows are the reason this is one authority rather than three. Rung 2 spends the day down to nothing; rung 1 is then refused even though it never once exceeded its own ₩10,000 ceiling. A rung cannot be read on its own - the budget belongs to the root they hang from, and ERC20PeriodTransferEnforcer keys its ledger on the delegation hash it is attached to.

Signed as siblings instead - two roots, each saying ₩50,000 a day - the same ladder would spend ₩100,000, because two delegation hashes mean two ledgers. Nothing reverts; the chain does exactly what was signed. The gap is between what was signed and what the person believes they signed, which is why it is a test and not a footnote.

Row 4 is the other one worth reading twice. That payment is inside every limit, from a live identity, on an enabled delegation, to an allowed merchant. It is refused because no live human confirmation stands behind it. Verified Codes are issued by exchanges through their own channels, so the confirming half cannot be staged here - the refusal is the half that can be proven, and it is the half that matters.

Reproduce with pnpm tsx scripts/tiers-demo.ts <account>.