Foundry reference implementation of the ERC-8257 Agent Tool Registry, a minimal onchain registry for AI agent tools with predicate-based access control.
@opensea/tool-sdk is the TypeScript SDK and CLI for writing tool manifests, registering tools, and gating tool endpoints against this registry.
Anyone can register a tool in the registry, and anyone can read it. Each tool has an optional access predicate:
- If
accessPredicateisaddress(0), anyone can invoke the tool. - Otherwise it points to a contract implementing
IAccessPredicate. NFT gating, subscriptions, allowlists, DAO votes, and reputation scores can all be written as predicates without changing the registry.
Payment, cross-chain gating, and subscription billing are out of scope for the standard.
| Contract | Interfaces | Description |
|---|---|---|
ToolRegistry.sol |
IToolRegistry |
Tool registration, metadata updates, access delegation |
ToolRegistry stores tools and their metadata. For a gated tool it checks access with a staticcall to IAccessPredicate(accessPredicate).hasAccess(toolId, account, data). There is no pause flag: to disable a tool temporarily, point accessPredicate at a predicate that always denies.
cd packages/tool-registry
forge install
forge buildforge testforge test --gas-reportThe predicates in examples/ are references, not part of the ERC. Each is multi-tenant: deploy it once per chain and every tool keeps its own config, keyed by toolId. On each config write the predicate reads the tool's creator from the registry, so creators configure their own tools without an admin role.
| Contract | Gate |
|---|---|
ERC721OwnerPredicate.sol |
Account owns at least one token (balanceOf > 0) in any of up to 10 configured ERC-721 collections |
ERC1155OwnerPredicate.sol |
Account owns at least one of any configured (collection, tokenId) pair, across up to 10 ERC-1155 collections with up to 16 token IDs each |
SubscriptionPredicate.sol |
Subscription: an NFT tier with an expiration time |
CompositePredicate.sol |
Combines up to 3 IAccessPredicate contracts with AND or OR, optionally negating each term. Fails closed if a sub-call fails |
TraitGatedPredicate.sol |
ERC-721 ownership plus a matching ERC-7496 dynamic trait value. The traits can live on a separate contract (such as a renderer). Each tool sets the trait key and up to 32 allowed values |
ERC20BalancePredicate.sol |
Account holds at least minBalance of a configured ERC-20 token (balanceOf >= minBalance) |
script/Deploy.s.sol deploys ToolRegistry, ERC721OwnerPredicate, ERC1155OwnerPredicate, SubscriptionPredicate, and TraitGatedPredicate through the Arachnid keyless CREATE2 factory (0x4e59...956C, already deployed on every major chain), so addresses match across chains. Re-running with the same salt does nothing where the address is already occupied. Changing _SALT (for a vanity address, say) deploys the new address on chains that lack it and leaves existing deployments alone.
ERC20BalancePredicate and TraitGatedPredicate also have standalone scripts, so you can deploy either one without the rest:
REGISTRY=0x265BB2DBFC0A8165C9A1941Eb1372F349baD2cf1 \
NETWORKS=base forge script script/DeployTraitGatedPredicate.s.sol --sig "run()" -vvv \
--account beta-deployer --sender $DEPLOYER --broadcast --verifyREGISTRY=0x265BB2DBFC0A8165C9A1941Eb1372F349baD2cf1 \
NETWORKS=base forge script script/DeployERC20BalancePredicate.s.sol --sig "run()" -vvv \
--account beta-deployer --sender $DEPLOYER --broadcast --verifyCanonical v0.2 deployments, at the same CREATE2 address on every supported chain.
| Contract | Address | Chains |
|---|---|---|
ToolRegistry (v0.2) |
0x265BB2DBFC0A8165C9A1941Eb1372F349baD2cf1 |
Ethereum mainnet, Base, Shape, Abstract, Monad, Robinhood Chain |
ERC721OwnerPredicate (v0.2) |
0xc8721c9A776958FfFfEb602DA1b708bf1D318379 |
Ethereum mainnet, Base, Shape, Abstract, Monad, Robinhood Chain |
ERC1155OwnerPredicate (v0.2) |
0x77373Dc3c1AE9A1e937eF3e5E08F4807D47c7c11 |
Ethereum mainnet, Base, Shape, Abstract, Monad, Robinhood Chain |
SubscriptionPredicate (v0.2) |
0xCBe0cd9B1d99d95Baa9c58f2767246C52e461f25 |
Ethereum mainnet, Base, Shape, Abstract, Monad, Robinhood Chain |
TraitGatedPredicate (v0.2) |
0x10abF07CfA34Bf22372C57f27e8bd9C2DCF93fA1 |
Ethereum mainnet, Base, Shape, Abstract, Monad, Robinhood Chain |
ERC20BalancePredicate (v0.2) |
0x1a834FC48B5f6e119c62C12a98b32137bCFA77cD |
Ethereum mainnet, Base, Shape, Abstract, Monad, Robinhood Chain |
The registry reports its identity onchain through name() and version(), and each predicate through name(). The EIP draft defines the version string format.
cp .env.example .env # fill in BASE_RPC_URL, ETHERSCAN_API_KEY, and one of DEPLOYER (+ keystore) or DEPLOYER_PRIVATE_KEY
# Dry-run (simulation only)
NETWORKS=base forge script script/Deploy.s.sol --sig "run()" -vvv
# Broadcast + verify (keystore-based, preferred)
cast wallet import beta-deployer --interactive # one-time keystore import
NETWORKS=base forge script script/Deploy.s.sol --sig "run()" -vvv \
--account beta-deployer --sender $DEPLOYER --broadcast --verify
# Broadcast + verify (raw private key, one-shot)
DEPLOYER_PRIVATE_KEY=0x... NETWORKS=base forge script script/Deploy.s.sol \
--sig "run()" -vvv --broadcast --verifyNETWORKS is a comma-separated list of keys from [rpc_endpoints] in foundry.toml, and the script forks each network in turn. Verification uses one Etherscan v2 API key (ETHERSCAN_API_KEY), which covers every Etherscan-supported chain, Base included.
- OpenZeppelin Contracts: ERC-165
- Forge Std: testing utilities
- create2-helpers: CREATE2 deploy script base
Read-only mirror of a private monorepo, updated on each release. Pull requests are welcome, and we bring accepted changes into the monorepo.
Found a vulnerability? Report it through OpenSea's Bugcrowd program at https://bugcrowd.com/engagements/opensea rather than opening a public issue. See SECURITY.md.