Foundry/Solidity package implementing the ERC-8257 Agent Tool Registry standard.
cd packages/tool-registry
forge build # Compile contracts
forge test # Run all tests (including interface ID pins)
forge fmt --check # Check formatting
forge fmt # Auto-format
forge lint src/ examples/ # Lint Solidity (also enforced via lint-staged)Solidity version: 0.8.28 (set in foundry.toml).
Formatter: 120 char line width, 4-space tabs, no bracket spacing.
| File | Role |
|---|---|
src/interfaces/IToolRegistry.sol |
Canonical interface — all public functions the registry exposes |
src/interfaces/IAccessPredicate.sol |
Predicate interface — hasAccess() + name() |
src/interfaces/IRequirementTypes.sol |
Requirement-type selectors (IERC721Holding, IERC1155Holding, ISubscription, IERC7496Trait, IERC20Balance) for predicate kind values |
src/ToolRegistry.sol |
Reference implementation |
examples/ |
Canonical predicate implementations (ERC721, ERC1155, Subscription, Composite, TraitGated, ERC20Balance) |
test/ |
Foundry tests; mocks live in test/mocks/ for edge-case predicate behavior |
script/Deploy.s.sol |
Deterministic CREATE2 deployment script |
scripts/generate-test-vectors.ts |
TypeScript helper to produce ABI-encoded test inputs (its own pnpm project) |
scripts/check-abi-sync.sh |
CI script verifying TypeScript ABIs in tool-sdk match Forge build output |
| ERC-8257 spec (currently in review: ethereum/ERCs#1723) | Standard — code must mirror its interface IDs and behavior |
lib/ |
Foundry dependencies via git submodules (forge-std, OpenZeppelin, create2-helpers, shipyard-core) |
When reviewing changes to this package, verify:
-
ERC-165 interface IDs: Adding or removing a function from
IToolRegistryorIAccessPredicatechanges the interface ID (XOR of function selectors). If the interface changed:- The pinned interface ID test in
test/ToolRegistry.t.solmust be updated - The ERC-8257 spec must reflect the new ID — changes go through the upstream ERCs PR (ethereum/ERCs#1723 while in review; switch to https://ercs.ethereum.org/ERCS/erc-8257 once merged)
- This is a breaking change for third-party implementors — document it in the changeset
- The pinned interface ID test in
-
Cross-file sync: These files must stay in sync:
- Solidity interfaces (
src/interfaces/) ↔ TypeScript ABIs (../tool-sdk/src/lib/onchain/abis.ts) - Deployed addresses in
README.md↔../tool-sdk/src/lib/onchain/chains.ts - Every function in the Solidity interface should have a corresponding entry in the TypeScript ABI
- Requirement-type selectors in
src/interfaces/IRequirementTypes.sol↔kindvalues in../tool-sdk/skill/references/known-predicates.md - Deployed predicate addresses in
README.md↔../tool-sdk/skill/SKILL.md"Deployed Contracts" table and../tool-sdk/skill/references/known-predicates.md - New predicates added to
examples/must get a corresponding entry in../tool-sdk/skill/references/known-predicates.md - Deploying to a new chain: add the chain ID to
foundry.toml([rpc_endpoints]+[etherscan]), theREADME.md"Chains" column, everyDeployment.chainsarray in../tool-sdk/src/lib/onchain/chains.ts, and every prose chain enumeration in the skill docs /predicate-gating-guide.md. See../tool-sdk/AGENTS.mdchecklist item 2 for the full list. Abstract (2741) is a ZK Stack chain but is EVM-equivalent, so CREATE2 resolves the canonical addresses with nochains.tsoverridesneeded — confirm with aforge scriptdry-run that predicted addresses match before broadcasting.
- Solidity interfaces (
-
Access control on state-mutating functions: Only the tool creator should be able to call
updateToolMetadata,setAccessPredicate, and predicate configuration functions likesetCollections. VerifyNotToolCreator/ creator checks exist. -
Multi-transaction atomicity: Registration flows that require multiple transactions (e.g.,
registerToolthensetCollections) can leave the tool in a partially configured state if the second TX fails. The CLI must print recovery instructions for the manual completion step. -
Predicate validation:
_validatePredicateuses ERC-165supportsInterfaceto verify predicates. Changes toIAccessPredicatechange the expected interface ID, breaking existing predicates. Call this out explicitly in changesets. -
Pure/view correctness:
name(),version(), andhasAccess()must bevieworpure. State-reading predicates usestaticcall— any attempt to mutate state inhasAccesswill revert.
- Predicate contracts are multi-tenant: one deployment per chain, configured per
toolId. - Predicates key their config by
toolIdand pull the authoritative creator from the registry — no separate admin role. address(0)asaccessPredicatemeans open access. Always-deny is a separate predicate contract, not a flag.- Interface-only additions to
IAccessPredicateorIToolRegistryare breaking changes (interface ID changes). - The versioning scheme is
MAJOR.MINOR(not semver)."0.1"= pre-release,"1.0"= first stable.
- Interface ID pin tests:
test_interfaceId_*_pinnedtests assert exact interface ID values. Update them when interfaces change. - Mock predicates:
test/mocks/contains edge-case predicates (reverting, gas-burning, deny-all, etc.) for testing registry resilience. - Run the full suite (
forge test) after any change — interface ID tests catch accidental signature changes.