Overview

Spend Permissions let you designate a trusted spender that can move assets out of a user’s Base Account on their behalf. After the user signs the permission, the spender can initiate transfers within the limits you define — no additional prompts, pop-ups, or signatures needed from the user. This powers seamless experiences such as subscription renewals, algorithmic trading, and automated payouts. Read more about the Spend Permission Manager contract and supported chains on GitHub.

Usage

Request a Spend Permission

You create an EIP-712 payload that describes the permission and ask the user to sign it. Store the resulting signature along with the permission data so you can register the permission on-chain later. The SDK helper below handles construction and signing for you.
Field NameTypeDescription
accountaddressSmart account this spend permission is valid for
spenderaddressEntity that can spend account’s tokens
tokenaddressToken address (ERC-7528 native token or ERC-20 contract)
allowanceuint160Maximum allowed value to spend within each period
perioduint48Time duration for resetting used allowance on a recurring basis (seconds)
startuint48Timestamp this spend permission is valid starting at (inclusive, unix seconds)
enduint48Timestamp this spend permission is valid until (exclusive, unix seconds)
saltuint256Arbitrary data to differentiate unique spend permissions with otherwise identical fields
extraDatabytesArbitrary data to attach to a spend permission which may be consumed by the spender
import { requestSpendPermission } from "@base-org/account/spend-permission";

const permission = await requestSpendPermission({
  account: "0x...",
  spender: "0x...",
  token: "0x...",
  chainId: 8453, // or any other supported chain
  allowance: 1_000_000n,
  periodInDays: 30,
});

console.log("Spend Permission:", permission);

Use the Spend Permission

Using a permission is 2 steps:
  1. Prepare the calls — Call prepareSpendCallData with the permission and an amount.
  2. Submit the calls — Submit the calls using your app’s spender account.
prepareSpendCallData returns an array of calls needed to spend the tokens:
  • approveWithSignature — When the permission is not yet registered onchain, this call would be prepended to the spend call.
  • spend — The call to spend the tokens.
This array should have 1 or 2 calls, submit them in order using your app’s spender account.
import { prepareSpendCallData } from "@base-org/account/spend-permission";

// returns [approveWithSignatureCall, spendCall]
const spendCalls = await prepareSpendCallData({
  permission,
  amount, // optional; omit to spend the remaining allowance
});

// If your app spender account supports wallet_sendCalls, submit them in batch using wallet_sendCalls
// this is an example on how to do it using wallet_sendCalls in provider interface
await provider.request({
  method: "wallet_sendCalls",
  params: [
    {
      version: "2.0",
      atomicRequired: true,
      from: spender,
      calls: spendCalls,
    },
  ],
});

// If your app spender account doesn't support wallet_sendCalls, submit them in order using eth_sendTransaction
// this is an example on how to do it using eth_sendTransaction in provider interface
await Promise.all(
  spendCalls.map((call) =>
    provider.request({
      method: "eth_sendTransaction",
      params: [
        {
          ...call,
          from: spender,
        },
      ],
    })
  )
);

Revoke a Spend Permission

You can revoke a permission in two ways:
  • Request user approval via request to user’s Base Account using requestRevoke.
  • Revoke silently from your app’s spender by submitting the call returned from prepareRevokeCallData.
import {
  requestRevoke,
  prepareRevokeCallData,
} from "@base-org/account/spend-permission";

// Option A: User-initiated revoke (wallet popup)
try {
  const hash = await requestRevoke(permission);
  console.log("Revoke succeeded", hash);
} catch {
  console.warn("Revoke was rejected or failed");
}

// Option B: Silent revoke by your app's spender account
const revokeCall = await prepareRevokeCallData(permission);

// Submit the revoke call using your app's spender account
// this is an example on how to do it using wallet_sendCalls in provider interface
await provider.request({
  method: "wallet_sendCalls",
  params: [
    {
      version: "2.0",
      atomicRequired: true,
      from: spender,
      calls: [revokeCall],
    },
  ],
});

// If your app spender account doesn't support wallet_sendCalls, submit the revoke call using eth_sendTransaction
// this is an example on how to do it using eth_sendTransaction in provider interface
await provider.request({
  method: "eth_sendTransaction",
  params: [
    {
      ...revokeCall,
      from: spender,
    },
  ],
});

API Reference

Complete Integration Example

import {
  fetchPermissions,
  getPermissionStatus,
  prepareSpendCallData,
  requestSpendPermission,
  requestRevoke,
  prepareRevokeCallData,
} from "@base-org/account/spend-permission";

const spender = "0xAppSpenderAddress";

// 1) Fetch available permissions
const permissions = await fetchPermissions({
  account: "0xUserBaseAccountAddress",
  chainId: 84532,
  spender,
});

// ========================================
// When there IS an existing permission
// ========================================

// 2. find the permission to use, potentially filtering by token
const permission = permissions.at(0);

// 3. check the status of permission
try {
  const { isActive, remainingSpend } = await getPermissionStatus(permission);
  const amount = 1000n;

  if (!isActive || remainingSpend < amount) {
    throw new Error("No spend permission available");
  }
} catch {
  throw new Error("No spend permission available");
}

// 4. prepare the calls
const [approveCall, spendCall] = await prepareSpendCallData({
  permission,
  amount,
});

// 5. execute the calls using your app's spender account
// this is an example using wallet_sendCalls, in production it could be using eth_sendTransaction.
await provider.request({
  method: "wallet_sendCalls",
  params: [
    {
      version: "2.0",
      atomicRequired: true,
      from: spender,
      calls: [approveCall, spendCall],
    },
  ],
});

// ========================================
// When there is NOT an existing permission
// ========================================

// 2. request a spend permission to use
const newPermission = await requestSpendPermission({
  account: "0xUserBaseAccountAddress",
  spender,
  token: "0xTokenContractAddress",
  chainId: 84532,
  allowance: 1_000_000n,
  periodInDays: 30,
});

// 3. prepare the calls
const spendCalls = await prepareSpendCallData({
  permission: newPermission,
  amount: 1_000n,
});

// 4. execute the calls using your app's spender account
// this is an example using eth_sendTransaction. If your app account supports wallet_sendCalls, use wallet_sendCalls to batch the calls instead.
await Promise.all(
  spendCalls.map((call) =>
    provider.request({
      method: "eth_sendTransaction",
      params: [
        {
          ...call,
          from: spender,
        },
      ],
    })
  )
);

// ========================================
// Request user to revoke spend permission
// ========================================

try {
  const hash = await requestRevoke(permission);
  console.log("Revoke succeeded", hash);
} catch {
  throw new Error("Revoke failed");
}

// ========================================
// Revoke spend permission in the background
// ========================================

const revokeCall = await prepareRevokeCallData(permission);

await provider.request({
  method: "wallet_sendCalls",
  params: [
    {
      version: "2.0",
      atomicRequired: true,
      from: spender,
      calls: [revokeCall],
    },
  ],
});