Skip to main content

Create Onchain Subscription Payments with Spend Permissions

Overview

Spend Permissions are a new onchain primitive that allows any user to grant an application permission to spend a specified amount of funds from their wallet. Spend Permissions are similar to Session Keys, where temporary permissions enable seamless user interaction without repeatedly prompting signatures. However, Spend Permissions are more secure because they are scoped and controlled by parameters such as token, start time, end time, period, and allowance, which a user signs off on when approving a Spend Permission.

Existing Smart Wallets without Spend Permissions enabled will be asked to enable Spend Permissions the first time they interact with an application that requests a Spend Permission approval. Enabling Spend Permissions is easily done via a one-click, one-time approval flow.

A typical flow is as follows:

  1. The user logs into an app with their Smart Wallet.
  2. The app requests approval by presenting the user with the spend permissions.
  3. The user reviews the scopes and either confirms or denies the request.
  4. Upon approval, the app calls the SpendPermission singleton contract to initiate transactions, spending funds from the user's Smart Wallet under the granted scope.

At any point, the user can revoke their Spend Permission.

Use Cases for Spend Permissions

Spend Permissions allow for the following onchain functionalities:

  • Subscription Payments: Apps can collect recurring payments (e.g., monthly subscriptions) without requiring the user to re-sign each time.
  • Seamless In-App Purchases: E-commerce stores and apps can spend funds directly for purchases without popup interruptions.
  • Gas Sponsorship: Spend Permissions can be used alongside paymasters to sponsor gas fees for user transactions.
  • One-Click Mints: Users can allocate an amount of funds for an app to spend on their behalf, enabling a series of onchain actions without requiring repeated approvals.

Objectives

In this tutorial, we’ll walk through a demo application that uses Spend Permissions to enable onchain subscription payments. Specifically, you will:

  • Create a smart wallet from a public/private keypair.
  • Enable an EOA to receive subscription payments.
  • Implement a Subscribe button that:
    • Calls the spend function to initiate transactions.
    • Adds the SpendPermission singleton contract as an owner to the user’s Smart Wallet.

By the end of this tutorial, your application will seamlessly request and utilize Spend Permissions to facilitate recurring onchain payments.

Prerequisites:

This is your access point to the Coinbase Cloud Developer Platform, where you can manage projects and utilize tools like the Paymaster.

Understand the basics of Smart Wallets and the ERC-4337 standard for advanced transaction patterns and account abstraction.

Familiarity with wagmi/viem

Wagmi/viem are two libraries that enable smart contract interaction using typescript. It makes onchain development smoother and what you will use to create smart wallets, functions, etc. It easily allows onchain developers to use the same skillsets from Javascript/typescript and frontend development and bring it onchain.

Template Project

Let's first start at a common place. Clone the template e-commerce store:

git clone https://github.com/hughescoin/learn-spend-permissions.git

cd learn-spend-permissions

bun install

Create a .env file from the example provided:

cp env.local.example .env

If you don’t have an existing keypair, follow these steps to generate one using Foundry:

Install foundry if you don't have it.

curl -L https://foundry.paradigm.xyz | bash

Then, create a private key pair:

cast wallet new

Your terminal should output something similar to this:

Successfully created new keypair.

Address: 0x48155Eca1EC9e6986Eef6129A0024f84B8483B59

Private key: 0xcd57753bb4e308ba0c6f574e8af04a7bae0ca0aff5750ddd6275460f49635527

Now that you have your keypair, it's time to create a "Spender client". The Spender is the account that will receive funds from users granting Spend Permissions. We'll use the keypair generated earlier to set this up.

Start by opening the .env file in the Healing Honey project and adding your private key:

SPENDER_PRIVATE_KEY=0xcd57753bb4e308ba0c6f574e8af04a7bae0ca0aff5750ddd6275460f49635527

Next, navigate to the src/app/lib/spender.ts file. Here, you'll see the privateKeyToAccount function from Viem in use. This function creates an wallet from the private key, enabling it to sign transactions and messages. The generated account is then used to create a Wallet Client, which allows signing and executing onchain transactions to interact with the Spend Permission contract.

With your Spender Client set up, ensure all other required environment variables are configured for the app to work when running the dev server.

Head over to Coinbase Developer Platform to retrieve your Paymaster URL and API Key. These can be found under Onchain Tools > Paymaster > Configuration. cdp-config

Copy the Base Sepolia (Base testnet) Paymaster URL and API Key, then update your .env file as follows:

BASE_SEPOLIA_PAYMASTER_URL=https://api.developer.coinbase.com/rpc/v1/base-sepolia/YOUR_API_KEY
CDP_API_KEY=YOUR_API_KEY
NEXT_PUBLIC_ONCHAINKIT_API_KEY=YOUR_API_KEY
CDP API KEYS

For the CDP_API_KEY and NEXT_PUBLIC_ONCHAINKIT_API_KEY, extract the alphanumeric string from the Paymaster URL after the base-sepolia path.

For example, if your Paymaster URL is: https://api.developer.coinbase.com/rpc/v1/base-sepolia/JJ8uIiSMZWgCOyL0EpJgNAf0qPegLMC0

The API Key would be: JJ8uIiSMZWgCOyL0EpJgNAf0qPegLMC0

danger

Please do not use these API Keys

Your .env file should now look like this:

COINBASE_COMMERCE_API_KEY="f3cbce52-6f03-49b1-ab34-4fe9e1311d9a"

CDP_API_KEY="JJ8uIiSMZWgCOyL0EpJgNAf0qPegLMC0"

NEXT_PUBLIC_ENVIRONMENT=localhost

SPENDER_PRIVATE_KEY=0xa72d316dd59a9e9a876b80fa2bbe825a9836e66fd45d87a2ea3c9924a5b131a1

NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME=Healing Honey Shop

NEXT_PUBLIC_ONCHAINKIT_API_KEY="JJ8uIiSMZWgCOyL0EpJgNAf0qPegLMC0"

BASE_SEPOLIA_PAYMASTER_URL=https://api.developer.coinbase.com/rpc/v1/base-sepolia/JJ8uIiSMZWgCOyL0EpJgNAf0qPegLMC0

To ensure your app communicates with the correct server when a user interacts with their wallet, open the src/components/OnchainProviders.tsx file.

Replace the // TODO comment with the following value for the keysUrl property:

keysUrl: "https://keys.coinbase.com/connect"

With these steps complete, your environment and Spender Client are ready to support onchain interactions. Now, let's move on to building the Subscribe button.

Navigate to src/components/Subscribe.tsx. You'll notice that the component is incomplete and currently shows multiple errors. We'll address these issues to enable Spend Permission functionality.

Spend Permissions rely on EIP-712 signatures and include several parameters, or scopes. One key scope is the allowance, which defines the amount an app can spend on behalf of the user. For our application, this will be set to 85% of the user's cart total, reflecting a 15% subscription discount. To achieve this, add the following code to line 95 to calculate the subscriptionAmountInWei variable:

const subscriptionAmountInEther = price ? subscriptionAmount / price : 0;
const subscriptionAmountInWei = parseEther(subscriptionAmountInEther.toString());

By adding these lines of code, we enable the discounted price to be passed as the allowance in the Spend Permission.

Next, we need to define the period and end parameters. The period specifies the time interval for resetting the used allowance (recurring basis), and the end specifies the Unix timestamp until which the Spend Permission remains valid.

For this demo, we'll set:

  • period: 2629743 seconds (equivalent to one month)
  • end: 1767291546 (Unix timestamp for January 1, 2026)

Now, update the message constant to include these parameters. It should look like this:

const message = {
account: accountAddress,
spender: process.env.NEXT_PUBLIC_SPENDER_ADDRESS! as Address,
token: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' as Address,
allowance: subscriptionAmountInWei,
period: 2629743,
start: Math.floor(Date.now() / 1000),
end: 1767291546,
salt: BigInt(0),
extraData: '0x' as Hex,
} as const;

By setting these values, we have defined the essential parameters for the Spend Permission, allowing our Subscribe button to handle recurring payments with ease. Let's continue enhancing the functionality in the next steps.

You may have noticed that when the user clicks the Subscribe button, it sends data to the /collect route. However, this route is currently broken. Let's address this issue to complete the functionality of our application.

In its current state, the /collect route contains incomplete logic for interacting with the Spend Permission Manager singleton contract. Specifically, we need to update the approvalTxnHash and spendTxnHash functions to properly handle user approvals and spending operations.

The approvalTxnHash function is responsible for calling the approveWithSignature method on the Spend Permission Manager contract. Update it with the following properties and values:

const approvalTxnHash = await spenderBundlerClient.writeContract({
address: spendPermissionManagerAddress,
abi: spendPermissionManagerAbi,
functionName: 'approveWithSignature',
args: [spendPermission, signature],
});

Once the approval transaction completes, the app will have the user's permission to spend their funds.

Next, we need to call the spend function to utilize the user's approved funds. Update the spendTxnHash function with the following code:

const spendTxnHash = await spenderBundlerClient.writeContract({
address: spendPermissionManagerAddress,
abi: spendPermissionManagerAbi,
functionName: 'spend',
args: [spendPermission, BigInt(1)],
});

These updates ensure that the /collect route correctly processes both the approval and spending steps, enabling seamless interaction with the Spend Permission Manager. With these fixes in place, the backend can fully support the Spend Permission flow.

Excellent! You just added a Spender Client as a backend app wallet. Now, when users click the Subscribe button, the component will call the handleCollectSubscription function, and the request will be handled by the route function.

Go ahead and run your app locally to see your hard work come to life:

bun run dev

Obtaining Wallet Spend Permissions (Optional)

I know what you're thinking: how can I see the valid (non-revoked) spend permissions for each user (wallet)? That's an easy one. Base provides an endpoint that allows you to retrieve valid spend permissions for an account by polling the utility API at: https://rpc.wallet.coinbase.com.

An optional step you can take is to create a "My Subscriptions" tab on your site to present users with their valid spend permissions. Below is an example of the curl request to the RPC endpoint. A sample response can be found here.

curl --location 'https://rpc.wallet.coinbase.com' \
--header 'Content-Type: application/json' \
--data '{
"jsonrpc": "2.0",
"method": "coinbase_fetchPermissions",
"params": [
{
"account": "0xfB2adc8629FC9F54e243377ffcECEb437a42934C",
"chainId": "0x14A34",
"spender": "0x2a83b0e4462449660b6e7567b2c81ac6d04d877d"
}
],
"id": 1
}'

Conclusion

And there you have it - an onchain subscription application enabled by Spend Permissions. By combining Smart Wallets with scoped permissions, you’ve seen how we can streamline recurring payments, enable one-click purchases, and revolutionize how users interact with decentralized applications.

Now, it’s your turn! The code and concepts we’ve explored today are just the beginning. Start experimenting, integrate Spend Permissions into your app, and redefine what’s possible with blockchain technology.

We can’t wait to see what you’ll build. When you implement Spend Permissions, tag us on X/Farcaster @Base to share your creations. Let’s make 2025 the year of onchain apps—together! 🚀


We use cookies and similar technologies on our websites to enhance and tailor your experience, analyze our traffic, and for security and marketing. You can choose not to allow some type of cookies by clicking . For more information see our Cookie Policy.