Skip to content

proof-of-agent/protocol

Repository files navigation

Proof-of-Agent ($POA) — the trust layer for AI agents on Robinhood Chain

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. See docs/SECURITY.md for the full risk posture, and AUDIT_SCOPE.md for 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."


How it works

┌─────────────────────┐   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-712 Attestation signatures by the agent's signer on the same nonce but different claimHash. Cryptographically irrebutable; no refute path.
  • MISSED_HEARTBEAT — optimistic: the agent failed to heartbeat within heartbeatWindow. The agent may refute with 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.


Tokenomics

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 + permissionless burn. No transfer tax, no auto-swap-liquify, no anti-bot limits, no trading gate — utility token, free transfers.
  • Ownership: POAToken ownership renounced post-launch. ProofOfAgent + AirdropClaimer ownership transferred to a TimelockController (they need ongoing parameter updates and cannot be fully renounced). slashBps is capped at 5000 (50%) so even rogue governance can't drain everything in one slash.

Contracts

Contract Role Access control
POAToken OZ ERC20+ERC20Permit, 100M mint, permissionless burn Ownablerenounced post-launch
ProofOfAgent The primitive: stake state, isVerified, register/heartbeat/withdraw, full challenge lifecycle, fee distribution OwnableTimelockController
AirdropClaimer Merkle-claim for the 35% community bucket; sweep() to reserve after deadline OwnableTimelockController
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

Build & test

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 report

Test 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 real AirdropClaimer (2 tests)
  • test/mocks/MaliciousToken.sol — re-enters the registry on transfer (reentrancy guard proof)

Deploy (mainnet — YOU run this, not Claude)

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.

0. Prerequisites

  1. 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
  2. Fund poa-deployer with ETH on Robinhood Chain (~0.05 ETH gas + your liquidity ETH).
  3. Generate the airdrop Merkle tree offchain and copy the root into .env as MERKLE_ROOT.
  4. Fill .env (copy from .env.example): DEPLOYER_ADDRESS, TEAM_WALLET, MERKLE_ROOT, ROUTER, LIQ_*. Confirm the real Uniswap v2 router on the explorer first.

1. Deploy the system

source .env
forge script script/DeployProofOfAgent.s.sol:DeployProofOfAgent \
  --rpc-url $RPC_URL --account poa-deployer --password-file pw.txt --broadcast

Deploys POAToken → VestingWallet → TimelockController → ProofOfAgent → AirdropClaimer, distributes the supply, and transfers registry+claimer ownership to the timelock. Note the ProofOfAgent address printed in the logs.

2. Verify sources on Blockscout

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, TimelockController

3. Launch liquidity

Set 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 --broadcast

Adds POA/WETH liquidity and (if RENOUNCE=1) renounces POAToken ownership.

4. Post-launch (governance hygiene)

  • 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.

Anvil dry-run (before mainnet)

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 --broadcast

The 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.


Security & risk

  1. Slashing bug = direct loss of staker funds. Highest severity. Mitigations: conservation- invariant fuzz test, nonReentrant on 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).
  2. 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.
  3. 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.
  4. 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.
  5. Timelock governance capture. A single-EOA proposer = de facto centralized. Mitigation: proposers = 3/5 multisig from day one; slashBps capped at 5000; min-delay (3d) + withdrawal delay (7d) give stakers a window to exit if governance goes rogue.
  6. 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.

V2 roadmap (explicitly deferred — keep V1 shippable)

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 honest non-code gate

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.

License

MIT — see LICENSE.

About

Stake-and-slash trust primitive for AI agents on Robinhood Chain. Unaudited, pre-mainnet — see SECURITY.md.

Topics

Resources

License

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors