Give an AI agent real authority over a real tokenized asset, and take it back in one transaction.
Passport Kit node is an identity and authorization layer for AI agents operating tokenized real-world assets.
Every surface : a permissioned token, an ENS resolver, a Uniswap v4 hook, reads one eligibility gate that's re-derived at each action, never cached. So one revocation makes every surface refuse at once, humans and agents together; and an action the agent was never authorized to take is refused too.
Personhood via World ID with minimal disclosure ("18+"); agents inherit their human's eligibility, never holding a claim of their own. Live on Ethereum Sepolia.
Stack
Solidity and Foundry for the contracts, deployed and verified on Ethereum Sepolia. EIP-712 for claim signatures. Privy for embedded wallets. World ID for both verification steps. ENS NameWrapper with a custom resolver. A Uniswap v4 hook. React frontend, Node backend for identity creation and agent linking. Licensed Apache-2.0.
Architecture
The system has four layers and a single decision point.
Trust. An issuer registry records which issuer is trusted for which claim topic and exposes an enumerable list per topic. A claim issuer contract signs claims off-chain over EIP-712 and holds the revocation levers. Validity is re-checked at read time against encoding, expiry, signer authorization and revocation state.
Identity. One ERC-734/735 identity contract per user, with the user's own wallet as the MANAGEMENT key. submitClaim validates that the issuer is trusted for the topic, then validates the signature. An identity factory holds the wallet-to-identity mapping that every surface resolves through, along with agent linking.
Decision. A single eligibility gate. isEligible(identity, policyId) returns a boolean and a bytes32 reason code. For each topic a policy requires, it pulls the trusted issuers from the registry, reads the matching claim off the identity, and re-verifies it with the issuer.
Surfaces. A permissioned ERC-20 consults the gate inside _update. A custom ENS resolver computes text(node, key) live. A subname registrar mints through NameWrapper and binds node to identity in one call. A Uniswap v4 hook consults the gate in beforeSwap.
Three design decisions
No privileged writer. The issuer signs; the user submits their own claim. Nobody can write to another user's identity, which removes the griefing vector at the key gate rather than patching it downstream. It also means the platform cannot silently mint a credential onto someone.
Revocation is a latch checked at read time, not a signature. Signature-based revocation can be bypassed by issuing a fresh signature. Because the gate re-asks the issuer on every read, and because the latch prevents new claims from landing while it is engaged, the identity owner cannot route around a platform decision. A separate global signer flag retires every signature a compromised key produced.
The gate cannot be griefed. It iterates a controlled issuer list rather than every claim that has landed on an identity, which an attacker could otherwise inflate until verification exhausts gas. Each issuer read is wrapped in try/catch, so a malformed claim is ignored rather than disabling the surface. An unset policy fails closed.
Partner technologies
World ID performs two distinct functions, and separating them is deliberate. Selfie Check proves a live human without an orb and returns only a nullifier; this gates agent spawning, because a majority vote is meaningless if one person controls three wallets. Identity Check is document-backed and gates full eligibility, but we request a single predicate: eighteen or over. Not name, not document number, not country. On-chain claim data is abi.encode(dataHash, expiresAt, nonce), so there is no personal data to leak.
ENS is an enforcement surface rather than a label. text(node, key) is computed inside the eth_call rather than stored, so there is no setText, no synchronization keeper, and no cache to invalidate. After a revocation, the next lookup returns REVOKED. setTenant(parentNode, gate, policyId, controller) allows one resolver to serve multiple tenants, each with its own policy and controller. Agents receive subnames beneath their principal, so luis.casaazul.eth identifies who the agent answers to before any contract is read.
Uniswap v4 is where an agent's action meets the perimeter. Existing compliant-pool designs hardcode an allowlist into the hook, which means multiple pools hold multiple stale copies of a fact that changes without notice. Our hook calls the same isEligible that the token and the ENS name call, so a policy change propagates to every surface at once. beforeSwap consults the gate; beforeRemoveLiquidity does not, because compliance restricts movement to a counterparty and never restricts withdrawal of a user's own position.
Privy addresses the barrier that typically undermines a compliance demonstration: wallet onboarding. Email sign-in produces an embedded wallet with no seed phrase and no browser extension.
Implementation notes
The ENS record does not exist as stored state. compliance.status is derived during the read. Each lookup performs several SLOADs and a signature recovery, which is costly by resolver standards and free to the caller because it occurs inside an eth_call. This is the reason revocation propagates without any process running.
The pool required its own identity. The permissioned ERC-20 checks both parties to a transfer, so during a swap the pool is a counterparty, and every swap reverted, including compliant ones. Rather than special-casing the address in a conditional, we issued the pool an identity holding the required claims. The exemption is therefore visible on-chain rather than embedded in code.
Two refusals share one gate. Revocation means the principal no longer qualifies; a mandate limit means the delegate was never permitted. Both route through the same isEligible call rather than a parallel permission system, which is why revoking a principal mid-transaction also changes what their delegates may do, with nothing recomputed.

