Retainer
Welcome

Current limitations

Stated plainly. If a thing is not on this page, it is either true or listed elsewhere as proven — not quietly assumed.

LimitationDetailStatus
Base Sepolia onlyChain 84532. Every address and transaction in these docs is testnet. There is no mainnet deployment, no production claim, and the test keys are disposable.By decision
Base Account consent blocked upstreamA newly created Base Account cannot sign a spend permission on Base Sepolia: Coinbase's hosted screen refuses with “This chain is not supported.” See below. It no longer blocks testnet. A browser wallet signs through a smart account it owns, which never reaches that screen — proven with MetaMask, permission #18 0xa647cbb0…. See signing with any wallet.base/account-sdk#363
No fee mechanism in the routerThe router forwards the full value to one recipient. Retainer cannot take a percentage without becoming custodial. Revenue must be a flat merchant fee, outside the flow.Structural
No auth on the dashboard/dashboard is public, has no authentication, and shows every merchant record to anyone who reaches it. Its five review actions are the only writes, and they are switched off unless RETAINER_ENABLE_REVIEW_WRITES=true, which the public deployment does not set — so there it is read-only. Merchant sign-in, with every query scoped to the signed-in merchant, is designed and not built.Not built
One EIP-7702 delegate is trusted, by decisionMetaMask converts customer accounts to EIP-7702 accounts by itself, inside the revoke 0x2b19810b…, which would otherwise stop a customer who cancelled from ever registering again. So, for registration only, exactly one delegate is accepted as an owner: MetaMask's EIP7702StatelessDeleGator, whose verified source checks signatures with plain ECDSA against the account's own address — the same property as a plain account. It is hardcoded, and pinned to the hash of the code that was reviewed; if that code ever differs, the owner is refused. Every other delegate, and every other contract account, is still refused for registration. Sign-in does not consult delegates at all. See signing with any wallet.Deliberate, scoped exception
Merchant names on links are not verifiedA billing link names its merchant, and the pay page shows that name — always beside the address the money goes to, with plain wording that the name is what the link calls the merchant. Today every link is created by the operator, so the name is only as trustworthy as the operator. Stage 2 requirement: once merchants create their own links, an unverified name is an attack surface — anyone could name a link after a company a customer trusts. Merchant links must carry an identity bound to the signed-in merchant wallet, and the page must not present a name as more than what it is.Stage 2 requirement
A link schedules only the first chargeA billing link's plan decides when the first charge falls due — at signup, at the end of the first period, or not at all — and the customer sees that rule in the terms before signing. Later charges are still created by the operator; recurring schedules belong to the merchant surface.Partly built
Signing in is not privacyA customer's permissions and charges are served only to a browser that has signed in as that wallet. That stops Retainer handing one customer's records to another. It does not hide them: they are on a public chain, and anyone can look an address up. See signing in.By nature
A contract account with no key cannot sign inSigning in proves one thing — that the person holds the wallet's key — and checks it by plain signature recovery. Plain accounts and every EIP-7702-upgraded account can sign in, whatever their delegate. A true contract account, such as a Safe or a smart wallet used directly, has no key and cannot. It loses nothing by it: such an account cannot register a permission either, and pays by plain transfer instead, which the matcher attributes. See signing in.By design
Wallet-owned accounts start emptyOn the wallet-owned path, charges draw from a smart account that is a different address from the customer's wallet, and it starts with no funds. The sign page makes funding an explicit step. A gasless top-up by signature (EIP-3009) has been simulated, not built.Known friction
Reads are not pinned to a block in productionThe public RPC can answer from a node a block behind the one that returned a receipt. The drills pin every post-transaction read to the receipt's block; the server's owner-code check and the dashboard read latest. A keyed RPC endpoint would remove most of it.Stated, not built
Rate limits key on an unsalted IP hashRegistration limits use sha256 of the client IP, never the IP itself. The IPv4 space is small enough to reverse, so this is a limiter, not a privacy property.Stated
The indexer stores other parties' eventsSpendPermissionUsed is indexed for every spender on the shared manager, not only ours. Confirmation matches on our own transaction hashes, so it is noise in the table, not an error.Known
No invoices, plans, customers, tax, proration or refundsRetainer is a charge engine. None of these objects exist. Refunds in particular cannot be executed by a non-custodial layer, only instructed.Not built
No re-authorisation or usage forecastingA cap that is too small is reported, not renegotiated. These are the next product decisions and depend on merchant conversations that have not happened.Not built
Single executor, single workerOne executor key per deployment, and one worker at a time: a worker runs only while it holds a lease, and every transaction that creates or retires a charge attempt re-checks it. Multiple executors are not supported.By design, for now
Reorgs are detectable, not handledIncoming transfers are indexed at three confirmations and store their block_hash, so a reorg beneath an indexed transfer can be detected. Nothing automatically unwinds a match whose transfer no longer exists — a human would have to reverse it.Stated, not built
Watch matching never guessesOnly an exact remaining amount from a sender already linked to a customer is matched automatically. Everything else — ambiguous ties, amount mismatches, unknown senders — waits in a review queue. This is deliberate, but it means a merchant with many unlinked senders does manual work until the links are learned.By design
Usage metering is storage onlyA usage_records table and a sum at charge time. No rating, tiers or aggregation windows.By decision

The hosted-consent block, precisely

This affects the Base Account path only. On testnet the working route is a browser wallet owning a smart account — see signing with any wallet.

Coinbase's keys.coinbase.com signing screen rejects Base Sepolia for newly created Base Accounts with the message“This chain is not supported. Base Sepolia is not supported. Please try a different chain.”The message is misleading: Base Sepolia is in the popup's supported-chains map and supplies the display name in that very error. The operative check isisTestnet inside the wallet-upgrade path — testnet delegation provisioning for EIP-7702 accounts, wearing chain-support copy.README.md:102

Account typeOn-chain codeBase Sepolia consent
ERC-4337 (factory-deployed contract)0x363d3d37…works — every charge in these docs was made this way
EIP-7702 (delegated EOA)0xef0100…refused
  • Open since 2026-07-10. No maintainer response as of 2026-09-09. A documentation-only PR (#390) has been open and unmerged since 2026-08-21.
  • It also blocks Coinbase's own documented pay({ testnet: true }) flow, so it is not specific to Retainer.
  • Unknown: whether Base Accounts created before the 7702 provisioning change still pass. The issue thread reports that they do; Retainer has not been able to test it because no such account was available.
  • Unknown: whether or when Coinbase will change this. No statement has been made.

Retainer deliberately did not route around it. Going to mainnet to dodge a testnet bug would have meant a permanent deployment and a real-money key set months ahead of need.

Distribution inside the Base App

Base's documentation for spend permissions carries the note that “Spend Permissions for Base App Apps are coming soon and will be supported in a future update.” That is Coinbase's statement, not Retainer's. Today the primitive is reachable only from external web apps using the Base Account SDK. When, or whether, it reaches Base App mini-apps is unknown.

Reorgs

Watch mode indexes incoming transfers at three confirmations, the same depth the charge reconciler uses, and stores each transfer's block_hash alongside its block number. That makes a reorg beneath an already-indexed transferdetectable: a later scan finding a different hash at the same height means the transfer, and any payment matched from it, may no longer exist on chain. detectReorgs

Detection is not handling. Nothing automatically unwinds a match whose underlying transfer has vanished, and nothing re-opens an expected payment that was settled by one. On Base at three confirmations this is unlikely rather than impossible, and the honest position is to say so rather than to imply a guarantee the code does not provide.

What has and has not been verified

  • Verified: everything on the evidence section of the landing page, the six failure modes, both crash-recovery branches, the custody invariant in tests and on live balances.
  • Not verified: behaviour under sustained load, behaviour with many concurrent permissions, Base fee spikes beyond the 30-day window sampled, or any mainnet condition.
  • Not verified: the real-account browser path end to end — blocked as above. The scripted ERC-4337 path proves the state machine; the real one would prove the integration.
Reporting standard
Where these docs say a number, it was read from the chain or the database before being written. Where they say “reportedly,” the source is someone else's report. Where they say unknown, nobody has checked.