Passport Agent Protocol
Your agent has a passport. You stamp the visas.
Created on 29th September 2026
•
Passport Agent Protocol
Your agent has a passport. You stamp the visas.
The problem Passport Agent Protocol solves
Watch the demo (3 min): : a real agent against production and HashKey Chain testnet.
AI agents like Claude Code can now write code, call APIs and move money. To do it, they get a private key or an API key in .env: full access, forever, with no human check, one prompt injection away from draining a wallet or leaking a credential. The only alternative is giving them nothing and copy-pasting every transaction by hand.
There is no middle ground: no way for a person to approve this specific action from a device they trust, and no public record of who authorized which agent to do what.
The solution
Passport Agent Protocol (PAP) gives every agent an on-chain passport (an ERC-8004 identity owned by a human) and lets the human stamp visas on it from their phone: scoped, limited, expiring permissions enforced by a smart contract on HashKey Chain. The agent never holds a spending key. It can only ask.
- Connect. The agent shows a QR code; the phone registers it as an ERC-8004 agent and grants a visa, for example up to 100 demoUSDT for 7 days.
- Ask. The agent requests a payment or a secret, with a reason.
- Approve. The phone shows the request in plain language; the human approves with Face ID.
- Enforce. One transaction on HashKey Chain.
AgentPassportchecks scope, limit and expiry; over the limit it reverts withLimitExceeded(). The rule lives in the contract, not in the app.
What people can use it for
- Let an agent pay safely: every payment approved from the phone, or autonomous payments inside a visa the contract still bounds.
- Hand an API key to an agent without handing it over: the key is sealed in two layers (phone, then agent), the server only stores ciphertext, and every read needs the agent's signature plus your Face ID and is counted on-chain.
- Let services verify an agent without knowing its owner: an x402-style gate answers
402, the agent signs a challenge, and one on-chain call (canAct) proves a real human authorized it. Compliant but private. - Use it from any agent: MCP server and Claude Code plugin with Agent Skills, a CLI, JSON-RPC 2.0, and a local Ethereum signer so
cast, viem, ethers or web3.py work without a private key.
Try it
- Demo video:
- Web and phone app: https://pap.devcristobalvc.com
- In Claude Code:
/plugin marketplace add DevCristobalvc/ethereum-builders-tour-cali, then/plugin install pap@pap, then ask "connect to PAP". - Source code: https://github.com/DevCristobalvc/ethereum-builders-tour-cali
Deployed contracts (HashKey Chain testnet, chainId 133)
All six are verified on the HSK explorer.
| Contract | Role | Address |
|---|---|---|
| AgentPassport | Visas: grant, pay, record, canAct | 0xCE112FD67B0E19a2eeD894dDD3a5B445989A6e7B |
| IdentityRegistry | ERC-8004 agent identity (human owns the NFT) | 0x9a074B1BD632D93b20BF468Cd23C0a9794F2e50F |
| ReputationRegistry | ERC-8004 feedback | 0xf935f364f797AF2336FfDb3ee06431e1616B7c6C |
| DemoUSDT | Test stablecoin used for payments | 0xdD8FB4B51aa492b9E0Ae5AD10de65B146797Cd84 |
| PassportRegistry | ZK membership (roadmap) | 0x4bB5791e89b4B2aA9D435CCB7F428626c790f10C |
| Groth16Verifier | ZK proof verifier (roadmap) | 0xEBB0A2188451e702c4905168d465FCCbdf44B229 |
Proof on-chain
- Payment approved from an iPhone with Face ID (
AgentPassport.pay, 10 demoUSDT). - Payment made by the agent alone, inside its visa (no human signature; limit and expiry still enforced).
- 500 demoUSDT against a 100 limit reverts on-chain with
LimitExceeded().
Tested
37 contract tests, 19 JSON-RPC checks, 27 sealed-secret checks, 20 local-signer checks and an on-chain end-to-end run against production. Full ticket log in backlog.md; architecture in docs/ARCHITECTURE.md; threat model in docs/SECURITY.md.
Challenges I ran into
1. Passkeys cannot sign Ethereum transactions. WebAuthn passkeys use P-256; EVM accounts use secp256k1, and HashKey Chain has no P-256 precompile. We keep a secp256k1 key on the phone, encrypted with a secret derived from the passkey's PRF extension (iOS 18+): every signature needs Face ID and the key never leaves the device. RIP-7212 plus account abstraction is the path to make the passkey the signer directly.
2. Stale nonces from a load-balanced RPC. Onboarding sends 4 transactions in a row from the phone. Right after a receipt, another RPC node sometimes still returned the old nonce, and the next transaction failed with replacement transaction underpriced. Our on-chain end-to-end test reproduced it every time; tracking nonces locally (viem nonceManager) on the phone and on the gas sponsor fixed it.
3. eth_getLogs range limits. The public HSK RPC rejects log queries over a few thousand blocks, so the passport's stamp history and the live stats silently came back empty. History now comes from the explorer's Etherscan-style API, with the RPC as a recent-window fallback.
4. Delivering a secret that neither the server nor the phone can read. The first design was "the phone decrypts and re-encrypts for the agent". We inverted the layers (phone outside, agent inside), so the phone only removes its own layer and never sees the plaintext, and bound the agent address and secret name as AES-GCM additional data so a blob cannot be replayed for another agent.
5. Existing tools assume a private key. cast send estimates gas first, and that reverts because the agent holds no tokens. The local signer answers eth_estimateGas for the transfers PAP brokers, and refuses anything else with EIP-1193 error 4200, so nothing unexpected is ever signed.
6. Autonomy without Face ID. iOS does not run web apps in the background, so "read a secret inside the visa without asking" cannot rely on the phone. We documented five options and recommend a TEE custodian that records each read on-chain before releasing it.
7. Protecting a free gas sponsor. New phone wallets get a little testnet gas automatically. A signature proves nothing when anyone can create keys, so the sponsor is protected with per-address, per-IP and global limits.
Tracks Applied (3)
