Proof-of-Agent is an economic-security primitive: agents stake $POA to register as
isVerified(agent), their stake is slashable for objectively onchain-verifiable
offenses, and stakers earn a pro-rata share of registration fees. Agent identity is the
agent's onchain wallet address, so Virtuals ERC-6551 Token Bound Accounts, Bankr agents,
and Robinhood Agentic Accounts all register the same way — by calling the registry.
Built with OpenZeppelin v5 + Foundry for Robinhood Chain (EVM L2, chain ID 4663).
⚠️ Status: unaudited — do not deploy with real funds. This code has been pre-screened (an adversarial self-review + three independent model labs; all HIGH/MEDIUM findings fixed) but has not had a paid independent security audit and is not deployed to mainnet. A paid audit is the hard gate before any mainnet launch with non-trivial stake. Seedocs/SECURITY.mdfor the full risk posture, andAUDIT_SCOPE.mdfor the reviewer brief.
Why this exists. Robinhood Chain launched July 1 2026 with a live agent economy (Virtuals, Bankr, Robinhood Agentic Accounts) and no native chain token — leaving a vacuum for a native agent/utility token. No one provides a stake-and-slash reputation primitive for agents. $POA is genuinely consumed (staked + burned on slash), so it passes the "is the token actually needed" test that kills most "AI coins."
┌─────────────────────┐ stake + fee ┌─────────────────────────────────────────┐
│ Agent wallet │ ───────────────────► │ ProofOfAgent (the registry) │
│ (EOA or ERC-6551 │ │ - stake state, isVerified(agent) │
│ TBA or any addr) │ ◄── isVerified ──── │ - register / heartbeat / withdraw │
└─────────────────────┘ (the seam) │ - challenge → slash → escrow → claim │
▲ │ - Synthetix-style fee distribution │
│ └─────────────────────────────────────────┘
marketplaces / agent-launchers query ▲
isVerified() to badge trusted agents │ fees (pro-rata to stakers)
Registration. An agent wallet calls register(stakeAmount, signingPubKey). The
signingPubKey is an EOA key decoupled from the agent wallet — required because
ERC-6551 TBAs are smart contracts that cannot sign EIP-712. The agent posts heartbeats
(signed EIP-712 Heartbeat messages) to prove liveness, and signs attestations
(Attestation messages) for its offchain work.
Slashing (two V1 offenses).
DOUBLE_SIGN— two valid EIP-712Attestationsignatures by the agent's signer on the samenoncebut differentclaimHash. Cryptographically irrebutable; no refute path.MISSED_HEARTBEAT— optimistic: the agent failed to heartbeat withinheartbeatWindow. The agent mayrefutewith a fresh heartbeat signature within the challenge window; a successful refute returns the challenger's bond to the agent (anti-griefing: the agent is paid to defend).
A challenger bonds challengeBond POA to file a challenge. After the withdrawalDelay window
(an optimistic challenge window that doubles as the exit delay), anyone calls
resolveChallenge. A valid slash reduces the agent's stake by slashBps (default 20%),
escrows the slashed amount for escrowDelay, then claimSlash pays a bountyBps fraction to
the challenger and burns the rest — permanently destroying $POA.
Rewards. Registration fees are distributed to stakers pro-rata via a Synthetix-style cumulative-reward-per-stake index. When there are no stakers, fees route to the timelock (protocol reserve) so the first staker cannot capture pre-staking fees.
The withdrawal-lock ordering. requestDeregister opens a withdrawalDelay window during
which anyone may challenge. withdraw requires the window to have passed and
openChallenges == 0. A successful slash cancels any pending deregistration, forcing the
slashed agent back into a fresh 7-day window before withdrawing the remainder — closing the
"front-run a slash by withdrawing" and "slash-then-immediately-exit" races.
Key-rotation fix. A naive design lets a caught double-signer escape by rotating its signing
key between challenge and resolve (the snapshotted sigs no longer match the live signer). We
snapshot signer into the Challenge struct at challenge time and resolve against the
snapshot. We also add a signer cooldown: a rotated-away signer stays attributable to the
agent for one withdrawalDelay, so rotating before a challenge is filed cannot escape
either. These two fixes are regression-tested (test_DoubleSign_AgentEscapesByRotatingKey_FixedBySnapshot,
test_DoubleSign_RotatesBeforeChallenge_SignerCooldown).
Conservation invariant (fuzz-tested). At all times:
token.balanceOf(registry) == totalStaked + rewardPoolReserve + totalActiveBonds + totalUnclaimedEscrow
Every POA in the registry maps to exactly one bucket; every POA-moving function updates them
in lockstep. testFuzz_TokenConservationInvariant runs the full slash lifecycle across
randomized agent counts/stakes and asserts the invariant at every step.
| Allocation | % | Recipient | Mechanism |
|---|---|---|---|
| Liquidity | 50% | Deployer → Uniswap v2 POA/WETH pool | LaunchProofOfAgent.s.sol addLiquidityETH |
| Community airdrop | 35% | AirdropClaimer (Merkle-claim) |
claim(index, amount, proof); sweep to reserve after deadline |
| Team | 10% | VestingWallet (2y linear) |
OZ VestingWallet, beneficiary-controlled |
| Protocol reserve | 5% | TimelockController |
early-registrant bootstrap + V2 incentives |
- Supply: 100M $POA, minted to deployer at construction, no mint after.
- Token: vanilla OZ
ERC20 + ERC20Permit+ permissionlessburn. No transfer tax, no auto-swap-liquify, no anti-bot limits, no trading gate — utility token, free transfers. - Ownership:
POATokenownership renounced post-launch.ProofOfAgent+AirdropClaimerownership transferred to aTimelockController(they need ongoing parameter updates and cannot be fully renounced).slashBpsis capped at 5000 (50%) so even rogue governance can't drain everything in one slash.
| Contract | Role | Access control |
|---|---|---|
POAToken |
OZ ERC20+ERC20Permit, 100M mint, permissionless burn |
Ownable → renounced post-launch |
ProofOfAgent |
The primitive: stake state, isVerified, register/heartbeat/withdraw, full challenge lifecycle, fee distribution |
Ownable → TimelockController |
AirdropClaimer |
Merkle-claim for the 35% community bucket; sweep() to reserve after deadline |
Ownable → TimelockController |
TimelockController (OZ) |
Governance owner of the registry + claimer. PROPOSER = deployer initially (rotate to 3/5 multisig) | OZ roles |
VestingWallet (OZ) |
2y linear vest of the 10% team allocation | beneficiary-controlled |
Requires Foundry (forge). Install dependencies first, then build + test:
forge install OpenZeppelin/openzeppelin-contracts # fetch the OZ dependency into lib/
forge build --force # Compiler run successful!
forge test -vv # 81 tests, all green
forge coverage # (optional) coverage reportTest suites:
test/POAToken.t.sol— token basics + EIP-712 permit (9 tests)test/ProofOfAgent.t.sol— register/stake/withdraw/heartbeat/rewards/views/setters (36 tests)test/Slashing.t.sol— full challenge lifecycle, key-rotation regression, cooldown, reentrancy, fuzz conservation (20 tests)test/AirdropClaimer.t.sol— Merkle claim, sweep, deadline (12 tests)test/AgentWalletIntegration.t.sol— the ERC-6551 seam: a contract-wallet agent through the full lifecycle (2 tests)test/MerkleGenerator.t.sol— end-to-end: the Solidity Merkle generator's root + proofs are accepted by the realAirdropClaimer(2 tests)test/mocks/MaliciousToken.sol— re-enters the registry on transfer (reentrancy guard proof)
The deploy is a real-money, irreversible action. You run it with your own keystore; Claude will never handle your private key or fire the launch for you.
- Install Foundry. Import your deployer keystore once (you type the seed phrase in your own
terminal — never in this chat):
cast wallet import poa-deployer --mnemonic < /path/to/your/seed-file echo 'YOUR_KEYSTORE_PASSWORD' > pw.txt # gitignored; ONLY the keystore password
- Fund
poa-deployerwith ETH on Robinhood Chain (~0.05 ETH gas + your liquidity ETH). - Generate the airdrop Merkle tree offchain and copy the root into
.envasMERKLE_ROOT. - Fill
.env(copy from.env.example):DEPLOYER_ADDRESS,TEAM_WALLET,MERKLE_ROOT,ROUTER,LIQ_*. Confirm the real Uniswap v2 router on the explorer first.
source .env
forge script script/DeployProofOfAgent.s.sol:DeployProofOfAgent \
--rpc-url $RPC_URL --account poa-deployer --password-file pw.txt --broadcastDeploys POAToken → VestingWallet → TimelockController → ProofOfAgent → AirdropClaimer,
distributes the supply, and transfers registry+claimer ownership to the timelock. Note the
ProofOfAgent address printed in the logs.
forge verify-contract <POAToken addr> src/POAToken.sol:POAToken --chain 4663 --verifier blockscout
forge verify-contract <registry addr> src/ProofOfAgent.sol:ProofOfAgent --chain 4663 --verifier blockscout
forge verify-contract <claimer addr> src/AirdropClaimer.sol:AirdropClaimer --chain 4663 --verifier blockscout
# repeat for VestingWallet, TimelockControllerSet TOKEN_ADDR to the deployed POAToken address, then:
source .env
forge script script/LaunchProofOfAgent.s.sol:LaunchProofOfAgent \
--rpc-url $RPC_URL --account poa-deployer --password-file pw.txt --broadcastAdds POA/WETH liquidity and (if RENOUNCE=1) renounces POAToken ownership.
- Lock or burn your LP tokens (recommended for trust).
- Rotate the timelock's PROPOSER role to a 3/5 multisig and renounce the deployer's admin role.
The deploy script is validated on a local Anvil node:
~/.foundry/bin/anvil --chain-id 4663 --block-time 1 & # in one terminal
# in another, with an anvil test key as DEPLOYER_ADDRESS:
export DEPLOYER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
export TEAM_WALLET=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
export MERKLE_ROOT=0x0000000000000000000000000000000000000000000000000000000000000001
export AIRDROP_DEADLINE=$(( $(date +%s) + 7776000 ))
export PK=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
forge script script/DeployProofOfAgent.s.sol:DeployProofOfAgent \
--rpc-url http://127.0.0.1:8545 --private-key $PK --broadcastThe full agent lifecycle (register → heartbeat → challenge → refute → slash → claim → withdraw)
is covered by test/AgentWalletIntegration.t.sol and test/Slashing.t.sol, including the
conservation invariant and the ERC-6551 contract-wallet seam.
- Slashing bug = direct loss of staker funds. Highest severity. Mitigations: conservation-
invariant fuzz test,
nonReentranton every POA-mover, the signer-snapshot + cooldown fixes, per-(agent, Offense)spam cap, and a paid independent audit before mainnet with non-trivial stake. The test suite is the reviewer's first read; V1 scope is frozen (no V2 creep). - Audit need is real and not free. Slashing + EIP-712 + rewards is subtle — the design review itself caught a real escape bug. Budget time + money for an independent reviewer.
- Bootstrapping chicken-and-egg. No agent registers until marketplaces query
isVerified; no marketplace queries until agents register. The 5% protocol reserve funds an early-registrant bonus (timelock-disbursed) for the first N agents. Without this, V1 likely launches to crickets. - Legal posture. Stake-and-slash may attract securities scrutiny. Frame $POA as a utility/registration token (fees = "registration fee share," not "dividends"); get a legal opinion before launch.
- Timelock governance capture. A single-EOA proposer = de facto centralized. Mitigation:
proposers = 3/5 multisig from day one;
slashBpscapped at 5000; min-delay (3d) + withdrawal delay (7d) give stakers a window to exit if governance goes rogue. - Sequencer censorship (Arbitrum stack). The refute-before-deadline race is subject to sequencer ordering. Document that refutes must use adequate priority fee and be sent well before the deadline.
DAO court for subjective disputes; broader offense catalog (STALE_ATTESTATION, witness-based
offchain slash); vePOA governance / on-chain voting; automated heartbeat relayers; stake
delegation / liquid staking; SlashingController extraction; time-weighted reward drip;
ERC-1271 contract-signer support; multi-token staking; Uniswap v3/v4 active liquidity.
The contract is necessary but ~30% of the work. The other 70% — integration + distribution — decides whether this is revolutionary or unused:
- Reach Virtuals Protocol (their agents are ERC-6551 TBAs → our address-keyed registry fits)
and propose
isVerified-badging for their agents. - Reach Bankr and Robinhood's Agentic team.
- Show up in the Robinhood Chain Discord/forums daily; build a docs site as the registry's front door.
- Seed the first N agents via the early-registrant bonus; publish verified-agent addresses as the trust signal.
The contract above delivers the auditable, composable primitive. Both halves are required.
MIT — see LICENSE.