Contracts and addresses
Retainer deploys an unmodified instance of Coinbase's audited router and transacts against Coinbase's canonical manager. Nothing here is Retainer's to audit.
Base Sepolia — chain 84532
| Contract | Address | Who deployed it |
|---|---|---|
SpendRouter (Retainer's instance) | 0x337099eE403C090388A66cc9370F7b0Fe4CDcC79 | Retainer. Verified source on Basescan; logic untouched. |
SpendPermissionManager | 0xf85210B21cC50302F477BA56686d2019dC9b67Ad | Coinbase. Canonical; same address on Base mainnet. Not redeployed. |
PublicERC6492Validator | 0xcfCE48B757601F3f351CB6f434CB0517aEEE293D | Coinbase. Canonical. |
| USDC (testnet) | 0x036CbD53842c5426634e7929541eC2318f3dCF7e | Circle. |
CoinbaseSmartWalletFactory | 0x0BA5ED0c6AA8c49038F819E587E2633c4A9F428a | Coinbase. Used by the scripted test account. |
The router deployment
- Deployment transaction: 0x761cad85bf7a350b2c1c20352382a8c33f6550764d5485bc1d346257f691becb — block 46564847, 1,631,793 gas.
- Compiler 0.8.30, optimiser on, 999,999 runs, EVM
cancun. Verified through the Etherscan V2 API independently of the deploy tool's own report. contracts/deployments/base-sepolia.json - Deployed with plain
CREATE. Upstream's deploy script usesCREATE2through0x4e59…eb8, which has no code on Base Sepolia or Base mainnet. contracts/script/DeployRouter.s.sol - The deploy script refuses any chain but 84532. contracts/script/DeployRouter.s.sol:33
Provenance
The three contract sources are copied verbatim from coinbase/spend-permissions at commite0004e63edc4e17de7aa978293800ac7a16892e5. A script re-checks them against upstream by sha256 and fails if anything drifted.contracts/src/PROVENANCE.md contracts/script/verify-vendor.sh
| File | sha256 |
|---|---|
SpendPermissionManager.sol | 2a5a0d72f06cd1b66099d4e8ee9129dc5c52c75c46a4b42eeebf415b4fb2a231 |
SpendRouter.sol | 104e1a47fb47e3ebc92710721ac37a3b7c999081c1257b34608ab3aaf8d636f2 |
PublicERC6492Validator.sol | f3cb49c5864badf20ccc7dfaccb0c98aa28c2a01520bf923d2087394f00433ca |
The deployed manager's on-chain SPEND_PERMISSION_TYPEHASH equals keccak256of the type string in that vendored source, which is what makes “the code we read is the code we transact against” a checked statement.test_typehash_matchesDeployedManager
Audits — upstream, not Retainer's
| Scope | Dates | Firm |
|---|---|---|
SpendPermissionManager | October, November, December 2024 | Cantina |
SpendRouter | 2026-03-18 and 2026-03-21 | Cantina |
Reports live in upstream's audits/ directory. Retainer has commissioned no audit of its own: its Solidity is limited to a deploy script and tests, and its off-chain code has not been reviewed by a third party.
Events the reconciler indexes
SpendPermissionUsed(bytes32,address,address,address,(uint48,uint48,uint160))— topic0xbcba65b4…6753cSpendRouted(address,address,address,bytes32,address,uint256)— topic0x92c3f8f6…c9dae
Both topics, and every custom error selector the classifier decodes, are pinned by test so an upstream change cannot drift past unnoticed. contracts/test/retainer/ErrorSelectors.t.sol