Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.cloud.coinbase.com/llms.txt

Use this file to discover all available pages before exploring further.

Spend Permissions let you designate a trusted spender that can spend tokens on behalf of your Smart Account. After you sign the permission, the spender can initiate token spending within the limits you define. You can define limits based on token, time period, and amount.
Smart Accounts must have spend permissions enabled at the time of creation. You cannot create spend permissions on accounts that were created without spend permissions enabled.
Spend Permissions utilize the Spend Permission Manager contract deployed on Base and other networks. Some use cases this feature enables:
  • Subscription payments - Enable recurring payments for SaaS, content subscriptions, or membership fees
  • Agentic payments - Control your agent’s spending limits for autonomous operations
  • Algorithmic trading - Allow trading bots to execute trades within predefined limits
  • Automated payouts - Schedule regular distributions or reward payments
  • Allowance management - Give team members or family controlled access to funds
  • Dollar-cost averaging - Automate periodic investment purchases

How it works

There are two parties involved in a Spend Permission:
  • Account - The smart account that creates the Spend Permission and approves it onchain.
  • Spender - The entity that can spend tokens on behalf of the account within the limits defined by a Spend Permission. Can be a Smart Account or a regular account.

Anatomy of a spend permission

  • Spender - The entity that can spend tokens on behalf of the account.
  • Token - The token that the Spend Permission is for. Use "eth" or "usdc" as shortcuts (Base and Base Sepolia only), or provide an ERC-20 contract address for other tokens.
  • Allowance - The amount of the token the spender is allowed to spend, in the token’s smallest unit (e.g. wei for ETH, 6-decimal units for USDC).
  • Time period - Use periodInDays for simple daily/weekly limits, or period, start, and end for advanced rolling windows and fixed time ranges.
  • Salt - A random value to differentiate between permissions with the same parameters. Generated automatically by the SDK.
  • Extra Data - Arbitrary data for additional information about the permission.

Enable spend permissions

Spend permissions must be enabled when creating the smart account.
Set enableSpendPermissions: true in your CDPHooksProvider config:
import { CDPHooksProvider } from "@coinbase/cdp-hooks";

function App() {
  return (
    <CDPHooksProvider
      config={{
        projectId: "your-project-id",
        ethereum: {
          createOnLogin: "smart",
          enableSpendPermissions: true,
        },
      }}
    >
      <YourApp />
    </CDPHooksProvider>
  );
}

Create a spend permission

Use useCreateSpendPermission to create a permission. Creating a permission is a user operation that requires gas — use useCdpPaymaster: true on Base or provide a paymasterUrl.
import { useCreateSpendPermission } from "@coinbase/cdp-hooks";
import { parseUnits } from "viem";

function CreateSpendPermission() {
  const { createSpendPermission, status, data, error } = useCreateSpendPermission();

  const handleCreate = async () => {
    await createSpendPermission({
      network: "base-sepolia",
      spender: "0x1399BE2B2E4186C209617053822C67173E8DFe5c",
      token: "usdc",
      allowance: parseUnits("10", 6), // 10 USDC
      periodInDays: 7,
      useCdpPaymaster: true,
    });
  };

  return (
    <div>
      <button onClick={handleCreate} disabled={status === "pending"}>
        {status === "pending" ? "Creating..." : "Create Permission"}
      </button>
      {status === "success" && data && <p>Tx: {data.transactionHash}</p>}
      {error && <p>Error: {error.message}</p>}
    </div>
  );
}

Use a spend permission (spender side)

Once a permission is created, the designated spender lists the account’s permissions to find theirs, then calls useSpendPermission to spend within the defined limits.
import { parseUnits } from "@coinbase/cdp-sdk";

const allPermissions = await cdp.evm.listSpendPermissions({
  address: smartAccount.address,
});

const permissions = allPermissions.spendPermissions.filter(
  (p) => p.permission.spender.toLowerCase() === spender.address.toLowerCase()
);

if (permissions.length === 0) {
  console.log("No spend permissions found for this spender");
  process.exit(1);
}

const spend = await spender.useSpendPermission({
  spendPermission: permissions[0].permission,
  value: parseUnits("0.005", 6),
  network: "base-sepolia",
});

const receipt = await spender.waitForUserOperation(spend);
console.log("Spend completed:", receipt);

List spend permissions

useListSpendPermissions automatically lists permissions for the authenticated user’s smart account.
import { useListSpendPermissions } from "@coinbase/cdp-hooks";

function ListPermissions() {
  const { refetch, data, status, error } = useListSpendPermissions();

  return (
    <div>
      <button onClick={refetch} disabled={status === "pending"}>
        Refresh
      </button>
      {data?.spendPermissions?.map((sp) => (
        <div key={sp.permissionHash}>
          <p>Spender: {sp.permission.spender}</p>
          <p>Token: {sp.permission.token}</p>
          <p>Allowance: {sp.permission.allowance}</p>
          <p>Revoked: {sp.revoked ? "Yes" : "No"}</p>
        </div>
      ))}
      {error && <p>Error: {error.message}</p>}
    </div>
  );
}

Revoke a spend permission

import { useRevokeSpendPermission } from "@coinbase/cdp-hooks";

function RevokePermission({ permissionHash }: { permissionHash: string }) {
  const { revokeSpendPermission, status, data, error } = useRevokeSpendPermission();

  const handleRevoke = async () => {
    await revokeSpendPermission({
      network: "base-sepolia",
      permissionHash,
      useCdpPaymaster: true,
    });
  };

  return (
    <div>
      <button onClick={handleRevoke} disabled={status === "pending"}>
        {status === "pending" ? "Revoking..." : "Revoke"}
      </button>
      {status === "success" && data && <p>Revoked. Tx: {data.transactionHash}</p>}
      {error && <p>Error: {error.message}</p>}
    </div>
  );
}

Check remaining allowance

Query the getCurrentPeriod function on the Spend Permission Manager contract to see how much of an allowance remains in the current period.
Node (TypeScript)
import { createPublicClient, http, type Address } from "viem";
import { baseSepolia } from "viem/chains";

const SPEND_PERMISSION_MANAGER_ADDRESS = "0xf85210B21cC50302F477BA56686d2019dC9b67Ad";

const SPEND_PERMISSION_MANAGER_ABI = [
  {
    inputs: [
      {
        components: [
          { name: "account", type: "address" },
          { name: "spender", type: "address" },
          { name: "token", type: "address" },
          { name: "allowance", type: "uint160" },
          { name: "period", type: "uint48" },
          { name: "start", type: "uint48" },
          { name: "end", type: "uint48" },
          { name: "salt", type: "uint256" },
          { name: "extraData", type: "bytes" },
        ],
        name: "spendPermission",
        type: "tuple",
      },
    ],
    name: "getCurrentPeriod",
    outputs: [
      { name: "start", type: "uint48" },
      { name: "end", type: "uint48" },
      { name: "spend", type: "uint160" },
    ],
    stateMutability: "view",
    type: "function",
  },
] as const;

const client = createPublicClient({ chain: baseSepolia, transport: http() });

const permissions = await cdp.evm.listSpendPermissions({ address: smartAccount.address });
const permission = permissions.spendPermissions[0].permission;

const [, , amountSpent] = await client.readContract({
  address: SPEND_PERMISSION_MANAGER_ADDRESS,
  abi: SPEND_PERMISSION_MANAGER_ABI,
  functionName: "getCurrentPeriod",
  args: [permission],
});

const remaining = BigInt(permission.allowance) - amountSpent;
console.log(`Spent: ${amountSpent}, Remaining: ${remaining}`);

Spend Permissions vs Policies

Spend PermissionsPolicies
EvaluationOnchain via smart contractsOff-chain via Coinbase TEE infrastructure
ScopeToken spending on EVMAny transaction type
PlatformEVM onlyEVM and Solana
Account scopeAny onchain account, including outside your CDP projectAccounts within your CDP project only

Supported networks

Mainnets

Arbitrum, Avalanche, Base, Ethereum, Optimism, Polygon

Testnets

Base Sepolia, Ethereum Sepolia
The Spend Permission Manager contract is deployed at 0xf85210B21cC50302F477BA56686d2019dC9b67Ad on all supported networks.