Skip to content

feat(transaction): add useTokenAllowance and useRevokeAllowance hooks - #2635

Open
lau90eth wants to merge 1 commit into
coinbase:mainfrom
lau90eth:feat/token-allowance-security-v2
Open

feat(transaction): add useTokenAllowance and useRevokeAllowance hooks#2635
lau90eth wants to merge 1 commit into
coinbase:mainfrom
lau90eth:feat/token-allowance-security-v2

Conversation

@lau90eth

@lau90eth lau90eth commented May 1, 2026

Copy link
Copy Markdown

Summary

Infinite token allowances are one of the most common exploit vectors on Base.
Users approve spending and forget — leaving funds permanently exposed to
contracts that may later be compromised or malicious.

OnchainKit has no way to read or revoke existing allowances.

This PR adds useTokenAllowance and useRevokeAllowance — two security hooks
that let users audit and revoke ERC-20 approvals in one click.

// Audit existing allowances
const { allowances, isLoading } = useTokenAllowance({
  tokens: ['0xUSDC', '0xDAI'],
  spenders: ['0xSUSPICIOUS'],
});

// Revoke in one click
const { revoke, isRevoking } = useRevokeAllowance();
await revoke({ token: '0xUSDC', spender: '0xSUSPICIOUS' });

What changed

  • useTokenAllowance hook — reads ERC-20 allowances for a connected
    wallet across multiple tokens and spenders. Detects and flags infinite
    (type(uint256).max) approvals explicitly.
  • useRevokeAllowance hook — revokes an allowance by calling
    approve(spender, 0) via useWriteContract. Exposes isRevoking state
    for UI feedback during the transaction.
  • getTokenAllowance utility — onchain read using viem erc20Abi and
    readContract. No external API dependency — reads directly from chain.
  • TokenAllowance type — standardized shape for allowance data including
    infinite detection flag.
  • Public exports — added to src/transaction/index.ts

Usage

// Read allowances
const { allowances, isLoading, error } = useTokenAllowance({
  tokens: [USDC_ADDRESS, DAI_ADDRESS],
  spenders: [UNISWAP_ROUTER, SUSPICIOUS_CONTRACT],
});

// allowances shape:
// [{
//   token: Address,
//   spender: Address,
//   amount: bigint,
//   isInfinite: boolean,   // true if amount === MaxUint256
// }]

// Render infinite approvals as warnings
{allowances
  .filter(a => a.isInfinite)
  .map(a => (
    <div key={`${a.token}-${a.spender}`}>
      ⚠️ Infinite approval to {a.spender}
      <button onClick={() => revoke({ token: a.token, spender: a.spender })}>
        {isRevoking ? 'Revoking...' : 'Revoke'}
      </button>
    </div>
  ))
}

// Revoke
const { revoke, isRevoking, error } = useRevokeAllowance();

await revoke({ 
  token: USDC_ADDRESS, 
  spender: SUSPICIOUS_CONTRACT 
});

API

type UseTokenAllowanceOptions = {
  tokens: Address[];    // ERC-20 token addresses to check
  spenders: Address[];  // spender addresses to check against
};

type TokenAllowance = {
  token: Address;
  spender: Address;
  amount: bigint;
  isInfinite: boolean;  // true if amount === MaxUint256
};

type UseTokenAllowanceResult = {
  allowances: TokenAllowance[];
  isLoading: boolean;
  error: APIError | null;
};

type UseRevokeAllowanceResult = {
  revoke: (params: { token: Address; spender: Address }) => Promise<void>;
  isRevoking: boolean;
  error: APIError | null;
};

Notes to reviewers

  • No external API dependencyuseTokenAllowance reads directly onchain
    via viem readContract + erc20Abi. Works on any EVM chain configured
    in OnchainKitProvider.
  • Infinite allowance detection — flags amount === MaxUint256 explicitly
    as isInfinite: true. This is the most common dangerous pattern.
  • Revoke mechanism — calls approve(spender, 0) which is the ERC-20
    standard for revoking. Compatible with all standard ERC-20 tokens.
  • useRevokeAllowance uses useWriteContract from wagmi — same pattern as
    existing OnchainKit transaction hooks.
  • Follows the same APIError pattern used across existing utilities.
  • Both hooks auto-refetch when address changes.

Testing

  • useTokenAllowance.test.ts — 4 tests
  • useRevokeAllowance.test.ts — 2 tests
  • All 390 test files pass (2699 tests)

Test cases covered:

  • Empty state when no address connected
  • Normal allowance amount correctly read
  • Infinite allowance detected and flagged
  • API/onchain read error — graceful fallback
  • revoke() calls approve(spender, 0) correctly
  • isRevoking state exposed during transaction

Risk

Low.

useTokenAllowance is read-only — zero side effects, cannot modify state.

useRevokeAllowance writes onchain only when explicitly called by the user.
The only state change is approve(spender, 0) — reducing an allowance,
never increasing it. This is strictly safer than the current state.

No existing hooks, components, or utilities are modified.

Adds ERC-20 token allowance security hooks for OnchainKit.

- useTokenAllowance: reads existing token spending allowances
- useRevokeAllowance: revokes allowance by approving 0
- getTokenAllowance utility: reads onchain allowance via viem
- Risk detection: flags infinite (max uint256) allowances
- 5 tests covering: empty state, normal allowance, infinite allowance,
  API error, revoke action, revoking state

Refs: coinbase#2572
@vercel

vercel Bot commented May 1, 2026

Copy link
Copy Markdown

@lau90eth is attempting to deploy a commit to the Coinbase Team on Vercel.

A member of the Team first needs to authorize it.

@cb-heimdall

Copy link
Copy Markdown

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants