Security · S3
Token approval scope requested by the SDK
How large an allowance does the SDK ask users to grant, and for how long?
Why it matters
Unbounded approvals turn any future bug or key compromise in the spender into a drain of every approving wallet.
A score of 3 needs evidence naming the distributed package (name, version and integrity hash) or a network trace, not only source code in a repository.
Scoring anchors
| Exact-amount approvals, or Permit2/EIP-2612 with expiry, by default. | |
| A bounded allowance by default (capped or refilling, limited to one receiver), or exact-amount with an unbounded opt-in clearly labelled in code. | |
| Unbounded by default, disclosed. | |
| Unbounded, undisclosed. | |
| No admissible evidence submitted yet. Counts as 0 in the pillar. |
Admissible evidence
- Approval call sites in the SDK at a pinned commit verified
- A sample approval transaction verified
Common failures
- Flows that avoid approvals entirely (plain transfers to a deposit address) scored as unknown rather than assessed.
Current ratings
| Provider | Score | Finding |
|---|---|---|
| Aleo (credits.aleo private transfers) | There are no allowances for native credits; each payment is executed by the owner, so nothing can pull funds later. Judgment call: better than exact-amount approvals in substance, but 3 needs a distributed-package or trace record, so we take 2. | |
| AnomaPay | Deposits use Permit2 signature transfers of the exact amount with expiry (the docs example uses 30 minutes), which fits anchor 3. A 3 needs a package or trace, and we did not see how large a base approval to Permit2 the closed app requests. Judgment call: 2. | |
| Avalanche Card | No admissible evidence yet. | |
| Avici Card | No admissible evidence yet. | |
| Aztec (Alpha mainnet private token transfers) | The reference token uses one-time authorisations for an exact action instead of allowances, and a direct transfer needs none. Judgment call: anchor 3 in substance, but 3 needs a distributed-package or trace record for the client, so we take 2; third-party tokens may differ. | |
| Bitget Wallet Card | Almost all Bitget cardholders on this program have an unlimited allowance to the authorizer, which only Fiat24's key can spend through. Neither Bitget nor Fiat24 discloses that. Judgment call: the app's default is inferred from onchain allowances, not from Bitget code (closed); 188 of 195 sampled accounts are unbounded, so "unbounded, undisclosed", 0. | |
| Blanksquare (Shielder) | The SDK leaves approval to the integrator; the public reference dapp approves the exact amount. That fits anchor 3, but a 3 needs a package or trace and we did not inspect the closed live app. Judgment call: 2. | |
| COCA Card | No admissible evidence yet. | |
| Coinbase Commerce (Onchain Payment Protocol) | The SDK asks for an exact-amount approval to the Transfers contract. The contract's other token paths use Permit2 or EIP-2612 permits for the exact amount, with a deadline. The hosted checkout bundles the Permit2 SDK. Its one-time token approval to Permit2 is not readable from the minified code, and that approval may be unbounded. Permit2 still enforces the per-payment amount and expiry. | |
| Curvy | Payers fund a portal by plain transfer. The optional direct-shield path, currently disabled onchain, approves exactly the amount. That fits anchor 3 in substance, but we have no package or trace evidence for the published build, and npm latest (0.0.7) lags the pinned source (0.0.8-rc.2). Judgment call: scored 2. | |
| Daimo Pay | Re-assessed under the updated anchor; the score is unchanged and now rests on the distributed package as well as the source. In the default crypto flow the SDK asks for no token allowance: the payer sends the exact amount straight to the session's deposit address, both at the pinned commit and in the published @daimo/sdk 1.8.8 bundle. The only other route in the contracts, pullFromPermit, uses EIP-2612 with a deadline and the deposit address as spender. Limit: the optional fiat/account flow (off unless the merchant enables fiat) has the Daimo-provisioned Privy wallet sign server-supplied EIP-712 "routing authorization" data or send a server-supplied sponsored transaction (packages/sdk/src/common/account.ts L857-L876, web/hooks/useDraftDeposit.ts L231-L256); the scope of those authorizations is set by the closed API and cannot be read from client code. Judgment call: a default flow with no approval at all is stricter than the anchor-3 bar of exact-amount approvals, so we score it 3. | |
| Deblock Card | No admissible evidence yet. | |
| Ethena Pay | There are no ERC-20 approvals; the wallet grants Ethena Pay's executor a refilling daily allowance of 10,000 USDe and 10,000 USDC by default, spendable to any address. Judgment call: bounded and refilling, but not limited to one receiver and not disclosed to users outside verified source, so the lower anchor 1. | |
| ether.fi Cash | There is no token approval. Instead, ether.fi's wallet role can pull up to a refilling daily/monthly limit that the user sets, routed only to the settlement dispatchers. Judgment call: this is bounded and narrowly routed but not exact-amount, so 2 (the bounded-allowance anchor). The sample limit ($100k/day) is high, and any default module can move Safe funds outside this limit. | |
| Exa Card | The card's spending authority is an unbounded keeper permission built into the account, not a capped allowance. It is disclosed in docs and code. Unbounded by default, disclosed, so 1. | |
| Fluidkey | Receiving needs no approval, and the auto-earn path approves exact amounts. Judgment call: approvals the closed app may request for swaps or other features cannot be checked, so 2. | |
| Fuse Card | The allowance is bounded and refilling, but it is not limited to one receiver, which anchor 2 requires. Judgment call: between 1 and 2, so I took the lower. It is disclosed, capped and user-adjustable, but the destination is open. | |
| Gnosis Pay | There is no ERC-20 approval. Instead, a Roles allowance lets Gnosis Pay's spender pull up to a daily limit, which the user sets and the UI caps at 8000. It refills every period, applies to one token and transfer() only, and is locked to the settlement receiver; the setup events cited under S1 confirm that scope onchain. The revised anchor 2 names exactly this case ("a bounded allowance by default, capped or refilling, limited to one receiver"), so this is 2 without a judgment call. It is not 3 because it is neither exact-amount nor expiring. Note (red team R7): a single delegate key can draw on this allowance without the spender Safe's 2-of-3 threshold (see C1). | |
| HesabPay | No admissible evidence yet. | |
| Hinkal | The SDK asks for an exact-amount approval to the Hinkal contract. That fits anchor 3 in substance, but the only source is the npm build output; there is no source repository at a commit, which makes this documented-tier evidence. Judgment call: capped at 1 by the evidence tier, not by the behaviour. | |
| Holyheld Card | The observed default is a permit with an expiry for slightly more than the top-up amount. That is a bounded allowance, not exact-amount. The approve fallback's amount is set inside the private library and cannot be checked. Judgment call: 2 (bounded) rather than 3, because the permit exceeds the amount and the fallback is unverifiable. | |
| Houdini Swap | The private flow uses a plain transfer of the exact amount to a deposit address, so no token allowance is granted. That is at least as tight as an exact-amount approval. Judgment call: held at 2 rather than 3, because the main payer client (the web app) is closed and not inspected, and no package or trace was recorded. | |
| Hyperbeat Card | A user-set spending limit bounds what the Operator can settle, per the docs. Judgment call: bounded allowance would be 2, but documented-only evidence caps at 1. | |
| Jupiter Card | No admissible evidence yet. | |
| M-Pesa | No admissible evidence yet. | |
| MetaMask Card | The default approval is effectively unbounded (2.2 trillion tokens), with no expiry, to a spender that operator EOAs can pull from. A restricted amount is available as an opt-in. The default is labelled "Full access" in the UI, and the support page tells users they set a spending cap. Judgment call: labelled in the UI, so "unbounded by default, disclosed" (1) rather than 0. | |
| Opera MiniPay | No admissible evidence yet. | |
| Monero | There are no token approvals; each payment is a signed transaction and nothing can pull funds later. Judgment call: this beats exact-amount approvals in substance, but 3 needs a distributed-package or trace record for the client, so we take 2. | |
| Nullmask | No admissible evidence yet. | |
| Oobit Card | No admissible evidence yet. | |
| Payy Card | No standing allowance exists in the card path. Deposits support exact-amount, expiring EIP-3009 authorisations, and card debits are funded one note at a time. Judgment call: this design meets anchor 3, but the app that chooses between mint and mintWithAuthorization is closed and no package or trace is available, so 2. | |
| Phantom Cash | No admissible evidence yet. | |
| Plasma One | The observed card account grants an unbounded USDT0 approval to a collateral contract whose logic a Rain EOA can replace; neither the Terms nor the card terms disclose an unbounded approval. Unbounded and undisclosed, so 0. Judgment call: the sample is a Rain collateral on Plasma not confirmed as a Plasma One account, and the app code that sets the approval is not public. | |
| Privacy Pools (0xbow) | ERC20 deposits approve the exact amount, so the code meets anchor 3. Judgment call: the frontend is not a distributed package and we ran no trace, so we cap at 2. | |
| Railgun | Railway asks for an unlimited allowance to the upgradeable RAILGUN proxy when shielding, and we found no disclosure of that in the UI code. The SDK has no approval helper; integrators choose. Scored 0 on the reference wallet. | |
| Request Network | By default the SDK and the reference widget ask the payer for an unlimited allowance. The only disclosure is a JSDoc line ("with no limit") aimed at developers, and nothing tells the payer. An integrator can pass an exact amount. Unlimited approval to the immutable ERC20FeeProxy is limited by that contract's fixed code. Unlimited approval to Erc20ConversionProxy is exposed to whatever code its owner Safe later points it at (see S1, S2). We did not assess the hosted API's approval calldata. | |
| Rizon Card | No admissible evidence yet. | |
| SafePal Card | Most SafePal cardholders have an unlimited allowance to the authorizer, which only Fiat24's key can spend through. Neither SafePal nor Fiat24 discloses that. Judgment call: the app's default is inferred from onchain allowances, not from SafePal code (closed); 10 of 12 sampled accounts are unbounded, so "unbounded, undisclosed", 0. | |
| Solayer Card (Emerald Card / Solayer Pay) | No admissible evidence yet. | |
| Tongo (confidential ERC-20 on Starknet) | The SDK asks for exact-amount approvals, and transfers need none. The ledger's own MAX approval to its Vault is contract-to-contract. Judgment call: the code fits 3, but 3 needs a distributed package or trace, so 2. | |
| Tornado Cash (Classic pools) | The UI asks by default for an unlimited allowance to TornadoRouter and shows the choice in its approval dialog. The router is not upgradeable, and deposit pulls only the denomination from msg.sender, but any allowance left over remains exposed to the router's fixed code. The core client approves exactly the deposit amount, which would score higher. Judgment call: we score the UI default because it is the published frontend, and take 1 (unbounded by default, disclosed). | |
| Tria | No admissible evidence yet. | |
| Tuyo Card | No admissible evidence yet. | |
| Umbra Cash | The app requests an unlimited allowance by default without saying so. The spender is immutable and pulls only from msg.sender, which limits the exposure, but the anchor concerns the scope requested. Scored 0. | |
| Wirex One | Onboarding gives the Wirex FundsManagement contract unlimited allowances on every supported token, and I found no user-facing disclosure. Unbounded, undisclosed, so 0. | |
| Wise | No admissible evidence yet. | |
| WORM (BETH proof-of-burn) | The default ETH payment path needs no approval. The ERC-20 path uses Permit2 with a bounded, expiring permit to the router, on top of the standard unlimited approval to Permit2. In mintCoin, BETH approves the hook for the exact hook allowance and resets it to zero. Judgment call: the substance is close to 3, but 3 needs a distributed package or trace, so 2. | |
| XPlace Card | No admissible evidence yet. | |
| Zcash (shielded transfers) | There are no token approvals: each payment is a signed transaction spending exact notes, and nothing can pull funds later. Judgment call: this beats exact-amount approvals in substance, but 3 needs a distributed-package or trace record for the client, so we take 2. | |
| zERC20 (zBNB on BNB Chain) | The SDK requests exact-amount approvals, and the zBNB payment path needs none. Gasless redeem uses a signed fee authorization rather than an allowance. Judgment call: the code fits 3, but it is read from a compiled npm package, not a public repository, so the documented cap gives 1. |