Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

86 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HumanMandate

An allowance bound to a person, not an address.

We do not issue Proof of Human. We enforce spending rules for a unique human that World already attested (Achieving Proof of Human Stage 3 — authentication / relying party).

Credit cards bind to a card. ERC-20 allowances bind to an address. Revoke either and the counterparty spins up a new credential.

HumanMandate binds authority to a World AgentBook humanId and enforces it in Solidity:

  • The rolling 24h cap belongs to the person — two of their agents share one budget
  • Revoke cuts off the person — a brand-new agent address still reverts
  • Raising the cap or moving the payee requires a fresh Selfie Check. Routine spend inside the cap does not
  • Spending can be converted through Uniswap without widening what may be spent

Built at ETHGlobal Lisbon 2026.

How it works

Every branch that ends in a refusal is a real mainnet transaction, linked further down.

flowchart LR
    A["Any address<br/>they operate"] --> G{"Who is<br/>behind it?"}
    K[("World AgentBook<br/>read on-chain")] -.-> G
    G -->|"nobody"| R1["NotHumanBacked"]
    G -->|"the authorised<br/>person"| C{"Inside the<br/>cap?"}
    C -->|"no"| R2["CapExceeded"]
    C -->|"yes"| P["Paid — only to<br/>the one locked payee"]

    classDef q fill:#ffffff,stroke:#2d2c2c,color:#2d2c2c,stroke-width:1.5px;
    classDef no fill:#fdecea,stroke:#b3261e,color:#b3261e,stroke-width:1.5px;
    classDef ok fill:#e8f5ee,stroke:#1f7a4d,color:#1f7a4d,stroke-width:1.5px;
    classDef n fill:#f9f9f8,stroke:#8b8987,color:#2d2c2c;
    class G,C q;
    class R1,R2 no;
    class P ok;
    class A,K n;
Loading

Revoking cuts off the person. They come back tomorrow on a brand-new address and enter at the same first gate — and are refused again.

Two properties fall out of this shape:

The budget belongs to the person. Two different addresses operated by the same human draw on one rolling cap, and revoking cuts off every address that person will ever open.

Why this is new

Projects on agentbook.world use AgentKit to gate HTTP endpoints. None we measured use AgentBook inside a contract to guard money. We read the live AgentBook from Solidity and revert on-chain.

Live on World Chain mainnet

Contract Address
HumanMandate 0x7fcEc100ADc4e89b09a92e3f7931161791D06054
MandateSwapper 0x4054fC0708799B906276984575cDfaBbe1Df45e9
AgentBookRegistry 0x9Ac36746eFbb8192b0D5BB8C0774026bff1b9aB4
World AgentBook (official) 0xA23aB2712eA7BBa896930544C7d6636a96b944dA

The mandate, proved on one contract

A revert here is a designed refusal, mined deliberately so it can be verified. Every selector was recovered with cast run and matched against cast sig — not guessed.

What it proves Result Tx
An address the mandate never named spends anyway ok 0xb7fa49a1…4620
A wallet with no human behind it 0x203ac8ca NotHumanBacked 0x9ebb088a…1f0e
A second agent of the same person shares the budget ok 0x7da5b4ba…767e
One payment over the per-payment cap 0xcb0bcbd5 PerTxCapExceeded 0x33e79b96…c650
One wei past the rolling 24h cap 0x2e8b3b3b CapExceeded 0xe91dcc5f…f2be
Raising the limit with no Selfie Check 0x6aaa9349 LivenessRequired 0x61f5ccf4…cdd5
Raising it with a fresh attestation ok 0xb1e64600…e603
Replaying that attestation 0x6c866211 LivenessAlreadyUsed 0x1fa5fa90…11dc
The revoked person returns on a fresh address 0xa4e1a97e NotAuthorized 0x23db1c10…5dab

Rows one and two are the discriminating pair. Either alone proves nothing; together they prove the registry read is the actual gate.

Uniswap integration

Where the money is spent. A mandate on its own moves one token to one payee. MandateSwapper lets a mandated agent convert what it is allowed to spend into the asset the payer chose, routed by the Uniswap Trading API, without widening what it may spend.

Developer feedback for this integration: FEEDBACK.md.

The problem this exists to solve. A cap counts what leaves the payer. Put a swap in the middle and an agent can stay under the cap forever while still draining value — route through a bad pool, or sandwich itself. The amount spent looks obedient; the amount received does not. A cap with no floor on the output is not a cap.

The whole path, both contracts:

flowchart LR
    A["Any address<br/>the person operates"] --> B{"Human<br/>behind it?"}
    B -->|no| R1["NotHumanBacked"]
    B -->|yes| C{"The person the<br/>payer authorised?"}
    C -->|no| R2["WrongHuman"]
    C -->|yes| D{"Within the per-payment<br/>and rolling 24h caps?"}
    D -->|no| R3["PerTxCapExceeded<br/>CapExceeded"]
    D -->|yes| E["Paid to the payee<br/>fixed at authorisation"]

    K[("World AgentBook<br/>lookupHuman()")] -.->|"answers both gates"| B

    E --> F["Uniswap Trading API<br/>route"]
    F --> G{"Did the payee receive<br/>at least the floor?"}
    G -->|no| R4["SlippageTooHigh"]
    G -->|yes| H["Payee paid in the<br/>asset the payer chose"]

    classDef default fill:#f9f9f8,stroke:#8b8987,color:#2d2c2c,stroke-width:1px;
    classDef gate fill:#ffffff,stroke:#2d2c2c,color:#2d2c2c,stroke-width:1.5px;
    classDef refuse fill:#fdecea,stroke:#b3261e,color:#b3261e,stroke-width:1.5px;
    classDef allow fill:#e8f5ee,stroke:#1f7a4d,color:#1f7a4d,stroke-width:1.5px;
    classDef ext fill:#efedea,stroke:#8b8987,color:#2d2c2c,stroke-dasharray:3 3;
    class B,C,D,G gate;
    class R1,R2,R3,R4 refuse;
    class E,H allow;
    class K ext;
Loading

So the contract measures the actual balance delta and refuses below the floor:

Where File Lines
Output floor enforced on the measured delta contracts/src/MandateSwapper.sol 154–159
Router is immutable (the agent cannot name its own "router") contracts/src/MandateSwapper.sol 61
Payer fixes the asset and payee; the agent picks only timing and route contracts/src/MandateSwapper.sol 81–92
Trading API /quote and /swap calls scripts/swap.ts 21, 95, 143
Routing-aware /swap body (UniswapX vs CLASSIC) scripts/swap.ts 67–79
AgentBook read that gates every spend contracts/src/HumanMandate.sol 105–111, 210–212

Proved on-chain

Real route from the Trading API (CLASSIC, 526 bytes of calldata), real liquidity, real refusal.

Step Result Tx
Payer opens the mandate, paying out to the swapper ok 0xf8c784f4…3d6e
Payer declares what it converts into, and who receives it ok 0x64d5c3c2…914f
The agent spends 0.0005 WETH, under the cap ok 0x92cf187c…8473
Settle demanding a floor the route cannot pay 0x76baadda SlippageTooHigh 0x354bd126…4ea1f
Settle with an honest floor — payee receives 0.939127 USDC.e ok 0x33ad7da0…09a5

The refusal is the point. Quoted output was 939042 base units; demanding 1878084 was refused with both figures in the revert data.

The same code path on real money

The run above spends WETH. The contract does not care which token it is, so the same functions run a second card denominated in real USDC.e — a $1.50 daily cap with a $0.50 ceiling per payment, converting to WETH for the payee. Nothing about the contract changed; the token address is a parameter.

Step Result Tx
The agent spends 0.50 USDC.e, under the cap ok 0x96276665…6219d
Settle demanding a floor the route cannot pay 0x76baadda SlippageTooHigh 0x392bddb6…9dce85
Settle with an honest floor — payee receives the WETH ok 0xaef46ee0…db2820

This is the shape a dollar-cost-averaging mandate takes: the agent converts a fixed daily amount and the proceeds land at an address the payer fixed. The agent holds neither side.

Developer feedback for this integration: FEEDBACK.md.

Tests

cd contracts
./bootstrap.sh     # pinned forge-std + OpenZeppelin, ~11 MB
forge test

48/48 passing, including 4 fork tests against the live World Chain AgentBook (pinned block 32845385). The one that matters most:

  • test_a_bad_route_is_refused_even_though_the_cap_was_respected — the cap is obeyed and the transaction is still refused

AI usage disclosure

Built with Claude Code / Cursor as pair programmers for scaffolding, tests, and doc-driven integration. Architecture, product calls, and verification of every on-chain claim were made by the team. Every transaction hash in this README was executed for real.

About

An allowance bound to a person, not an address. World AgentBook read from inside a Solidity payment contract, with Uniswap execution and an on-chain slippage floor. Live on World Chain mainnet.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages