From 8981c9f3dcd4dcc2f8a924ffc9a8418568162e0e Mon Sep 17 00:00:00 2001 From: Patrick Hughes Date: Tue, 17 Dec 2024 11:19:53 -0600 Subject: [PATCH 01/16] add spend permission tutorial to base docs --- .../docs/1_smart-wallet-spend-permissions.md | 398 ++++++++++++++++++ 1 file changed, 398 insertions(+) create mode 100644 apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md diff --git a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md new file mode 100644 index 0000000000..943499bbcd --- /dev/null +++ b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md @@ -0,0 +1,398 @@ +--- +title: 'Create Subscription Payments with Spend Permissions' +slug: /create-subscription-payments-with-spend-permissions +description: Learn how to implement a smart wallet signer for a subscription payment application. +author: hughescoin +keywords: [smart wallet, onchain, spend permissions, smart account, account abstraction] +tags: ['frontend', 'account abstraction'] +difficulty: medium +hide_table_of_contents: false +displayed_sidebar: null +--- + +# Create an App Wallet using Smart Wallet + +Before Smart Wallets, onchain apps primarily prompted users to sign and approve transactions. Now, apps can have their own wallets (App Wallets), enabling them to do more onchain. This is possible through Smart Wallets (ERC-4337). + +In this guide, we'll integrate an App Wallet to facilitate subscription payments for users purchasing goods from a fictitious e-commerce store. + +Your App Wallet can be any public/private keypair wallet. However, our App Wallet will be an ERC-4337 smart account. Using a smart wallet provides advantages such as the programmability to facilitate payouts, gas sponsorship with paymasters, and the ability to avoid storing funds directly in the wallet. Additionally, with a paymaster and a smart wallet, gas costs can be covered by the app or funded using the user's payment method, including ERC-20 tokens. + +## Prerequisites: + +### Coinbase CDP account[​](https://docs.base.org/tutorials/gasless-transaction-on-base-using-a-paymaster/#coinbase-cdp-account "Direct link to Coinbase CDP account") + +This is your access point to the Coinbase Cloud Developer Platform, where you can manage projects and utilize tools like the Paymaster. + +### Familiarity with Smart Accounts and ERC 4337[​](https://docs.base.org/tutorials/gasless-transaction-on-base-using-a-paymaster/#familiarity-with-smart-accounts-and-erc-4337 "Direct link to Familiarity with Smart Accounts and ERC 4337") + +Understand the basics of Smart Accounts and the ERC-4337 standard for advanced transaction patterns and account abstractions. + +### 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 accounts, functions, etc. It easily allows onchain developers to use the same skillsets from Javascript/typescript and frontend development and bring it onchain. + + +## Objectives + +* Create a smart account from a public/private keypair. +* Enable a smart account to receive subscription payments. +* Set up the App Wallet with a paymaster. + +## Template Project + +Let's first start at a common place. Clone the template e-commerce store: + +```bash +git clone https://github.com/hughescoin/healing-honey.git + +bun install +``` +If you don’t have an existing keypair, follow these steps to generate one using Foundry: + +Install foundry if you don't have it. +```sh +curl -L https://foundry.paradigm.xyz | bash +``` + +Then, create a private key pair: +```bash +cast wallet new +``` + +It should output something similar to this: +```bash +Successfully created new keypair. + +Address: 0x48155Eca1EC9e6986Eef6129A0024f84B8483B59 + +Private key: 0xcd57753bb4e308ba0c6f574e8af04a7bae0ca0aff5750ddd6275460f49635527 +``` + +Excellent, now that you have your keypair, let's start building out our `.env` file with the appropriate values. + +Open the `.env` file from Healing Honey project and add the private key to the file + +```bash +SPENDER_PRIVATE_KEY=0xcd57753bb4e308ba0c6f574e8af04a7bae0ca0aff5750ddd6275460f49635527 +``` +Now let's get the other environment variable values. + +Navigate to [CDP](https://portal.cdp.coinbase.com/) to get your Paymaster URL and API key. + +They can be found by navigating to Onchain Tools > [Paymaster] > Configuration. + +![cdp-config](apps/base-docs/assets/images/paymaster-tutorials/cdp-copy-endpoint.png) + +Copy the **Base Sepolia** (Base testnet) Paymaster URL and API Key then update your .env file: + +```bash +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 +``` + +:::tip +For your `CDP_API_KEY` and `NEXT_PUBLIC_ONCHAINKIT_API_KEY`, extract the alphanumeric string that comes **after the `base-sepolia` path** in your Paymaster URL. + +For example, if your Paymaster URL is: `https://api.developer.coinbase.com/rpc/v1/base-sepolia/JJ8uIiSMZWgCOyL0EpJgNAf0qPegLMC0` + + +The API key would be: `JJ8uIiSMZWgCOyL0EpJgNAf0qPegLMC0` +::: + + +Your `.env` file should look like this: + +```bash +COINBASE_COMMERCE_API_KEY="f3cbce52-6f03-49b1-ab34-4fe9e1311d9a" + +CDP_API_KEY="JJ8uIiSMZWgCOyL0EpJgNAf0qPegLMC0" + +NEXT_PUBLIC_ENVIRONMENT=localhost + +SPENDER_PRIVATE_KEY=0xa72d316dd59a9e9a876b80fa2bbe825a9836e66fd45d87a2ea3c9924a5b131a1 + +NEXT_PUBLIC_SPENDER_ADDRESS= + +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 +``` + +You'll notice that the `NEXT_PUBLIC_SPENDER_ADDRESS` is empty. this is because we will need to compute the public address. Let's do that now + +To create a Smart Contract Account (also known as [Smart Wallets](https://www.coinbase.com/wallet/smart-wallet)) from a private key, we'll need to use the account abstraction tools provided by Viem that takes our `SPENDER_PRIVATE_KEY` as the parameter and converts. this to a smart account. Once the smart account is created, we'll log it's address in our console and then add it to your `.env` file. + +First, update the `tsconfig.jso`n file: Change the `module` type to `commonjs`: + +```bash +"module": "commonjs" +``` + +With your `module` set to `commonjs` you can now create a file to run the script. + +In the root of your project create a file called `generateSmartWalletAddress.ts` and add the following code: +```ts +import { createPublicClient, Hex, http } from 'viem'; +import { baseSepolia } from 'viem/chains'; +import { privateKeyToAccount } from 'viem/accounts'; +import { toCoinbaseSmartAccount } from 'viem/account-abstraction'; +import dotenv from 'dotenv'; + +dotenv.config(); + +export async function logSpenderSmartContractWalletAddress() { + const client = createPublicClient({ + chain: baseSepolia, + transport: http(), + }); + + const spenderAccountOwner = privateKeyToAccount( + process.env.SPENDER_PRIVATE_KEY! as Hex + ); + console.log('spenderAccountOwner', spenderAccountOwner.address); + + const spenderAccount = await toCoinbaseSmartAccount({ + client, + owners: [spenderAccountOwner], + }); + console.log('Spender Smart Wallet Address:', spenderAccount.address); +} + +async function main() { + await logSpenderSmartContractWalletAddress(); +} + +if (require.main === module) { + main().catch((error) => { + console.error(error); + process.exit(1); + }); + } +``` + +Save it then run the following: + +```bash +bun install -g ts-node typescript @types/node dotenv && ts-node generateSmartWalletAddress.ts +``` + +This script installs ts-node along with typescript and runs the script made in the previous step. + +Your terminal should output something similar to this: + +```bash +spenderAccountOwner 0x2a83b0e4462449660B6E7567b2C808Bud04d877D +Spender Smart Wallet Address: 0x2269Cc26eBEc78C0fB04c26edDdc65FE15807C8E +``` + +You may now add the Spender Smart Wallet Address to the `.env` file : + +```bash +NEXT_PUBLIC_SPENDER_ADDRESS=0x2269Cc26eBEc78C0fB04c26edDdc65FE15807C8E +``` + +Revert the module changes made in the `tsconfig.json` file back to `esnext` + +## Creating the Smart Spender + +Create a file `smartSpender.ts` under `app/lib` + +```ts +import { createPublicClient, createWalletClient, Hex, http } from 'viem'; +import { baseSepolia } from 'viem/chains'; +import { privateKeyToAccount } from 'viem/accounts'; +import { + createBundlerClient, + createPaymasterClient, + toCoinbaseSmartAccount, +} from 'viem/account-abstraction'; + +export async function getPublicClient() { + const client = createPublicClient({ + chain: baseSepolia, + transport: http(), + }); + return client; +} +export async function getSpenderBundlerClient() { + const client = createPublicClient({ + chain: baseSepolia, + transport: http(), + }); + + const spenderAccountOwner = privateKeyToAccount( + process.env.SPENDER_PRIVATE_KEY! as Hex + ); + + console.log({ spenderAccountOwner }); + const spenderAccount = await toCoinbaseSmartAccount({ + client, + owners: [spenderAccountOwner], + }); + console.log({ spenderAccount: spenderAccount }); + const paymasterClient = createPaymasterClient({ + transport: http(process.env.BASE_SEPOLIA_PAYMASTER_URL), + }); + + const spenderBundlerClient = createBundlerClient({ + account: spenderAccount, + client, + paymaster: paymasterClient, + transport: http(process.env.BASE_SEPOLIA_PAYMASTER_URL), + }); + + return spenderBundlerClient; +} +``` + +Now that you have a `smartSpender` set up with an address you will need to create a function that tells your app to transfer the user's subscription payment to the `smartSpender`'s wallet in the `collect/route.tsx` file. + +Currently the `route.tsx` file is programmed to have our Spender make onchain actions using a Externally Owned Account (EOA) which is the more common/traditional wallet. This can be observed by looking at the `getSpenderWalletClient()` which requries a private key in order to interact onchain. + +:::tip + +An easy way to tell if a function is making onchain calls using a Smart Wallet is if it uses a [userOperation](https://www.biconomy.io/post/decoding-entrypoint-and-useroperation-with-erc-4337-part1). EOAs do not use userOperations; instead, they sign transactions themselves and post them directly onchain. + +::: + +You want the application to use the Smart Account while still maintaining functionality for EOAs in the future. To achieve this, let's create two functions: one for making calls using an EOA and another for using a Smart Account. + +Create a function called `transactUsingEOA` that takes two parameters: `spendPermission` (of type `any`) and `signature` (of type `any`). Then, paste the logic from the current `POST` function into the `transactUsingEOA` function. + +Your `transactUsingEOA` function should look like this: + + +```ts +async function transactUsingEOA(spendPermission: any, signature: any) { + const spenderBundlerClient = await getSpenderWalletClient(); + const publicClient = await getPublicClient(); + + const approvalTxnHash = await spenderBundlerClient.writeContract({ + address: spendPermissionManagerAddress, + abi: spendPermissionManagerAbi, + functionName: "approveWithSignature", + args: [spendPermission, signature], + }); + + const approvalReceipt = await publicClient.waitForTransactionReceipt({ + hash: approvalTxnHash, + }); + + const spendTxnHash = await spenderBundlerClient.writeContract({ + address: spendPermissionManagerAddress, + abi: spendPermissionManagerAbi, + functionName: "spend", + args: [spendPermission, "1"], + }); + + const spendReceipt = await publicClient.waitForTransactionReceipt({ + hash: spendTxnHash, + }); + + return { + success: spendReceipt.status == "success", + transactionHash: spendReceipt.transactionHash, + }; +} +``` + +Now let's create a function for transacting using the Smart Account you created earlier. + +In the same file, create a function named `transactUsingSmartWallet` that takes the same parameters. + + +```ts +async function transactUsingSmartWallet(spendPermission: any, signature: any) { + const spenderBundlerClient = await getSpenderBundlerClient(); + const userOpHash = await spenderBundlerClient.sendUserOperation({ + calls: [ + { + abi: spendPermissionManagerAbi, + functionName: "approveWithSignature", + to: spendPermissionManagerAddress, + args: [spendPermission, signature], + }, + { + abi: spendPermissionManagerAbi, + functionName: "spend", + to: spendPermissionManagerAddress, + args: [spendPermission, BigInt(1)], // spend 1 wei + }, + ], + }); + + const userOpReceipt = await spenderBundlerClient.waitForUserOperationReceipt({ + hash: userOpHash, + }); + + return { + success: userOpReceipt.success, + transactionHash: userOpReceipt.receipt.transactionHash, + }; +} +``` + +You'll notice that this function uses userOperations, a clear giveaway that you are using a Smart Wallet and **not** an EOA. + +Now that we have separate functions for transacting with an EOA and a Smart Wallet, let's update the `POST` function to use the Smart Wallet for transactions. + +```ts +export async function POST(request: NextRequest) { + const spenderBundlerClient = await getSpenderWalletClient(); + const publicClient = await getPublicClient(); + try { + const body = await request.json(); + const { spendPermission, signature } = body; + const { success, transactionHash } = await transactUsingSmartWallet( + spendPermission, + signature + ); + return NextResponse.json({ + status: success ? "success" : "failure", + transactionHash: transactionHash, + transactionUrl: `https://sepolia.basescan.org/tx/${transactionHash}`, + }); + } catch (error) { + console.error(error); + return NextResponse.json({}, { status: 500 }); + } +} +``` +> The full code sample is available [here](https://github.com/ilikesymmetry/spend-permissions-quickstart/blob/main/app/collect/route.tsx#L38) + +Excellent! You just added a Smart Wallet 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 `transactUsingSmartWallet` function. + +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](https://gist.github.com/hughescoin/d1566557f85cb2fafd281833affbe022). + + +```bash +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 +In this tutorial you created a smart wallet by converting a private key (EOA) to a smart wallet, enableed functionality of your app to have a backend signer be a smartWallet or a traditional EOA. + +Remember, our app is designed to allow for users to purchase a good repeatedly without having to sign for the transaction or make additional requests. + +--- +[Paymaster]: https://portal.cdp.coinbase.com/products/bundler-and-paymaster \ No newline at end of file From 26471eea98b18affc6b4f623cb1f4f1de1a2d26d Mon Sep 17 00:00:00 2001 From: Patrick Hughes Date: Tue, 17 Dec 2024 11:20:02 -0600 Subject: [PATCH 02/16] add spend permission tutorial to base docs --- .../tutorials/docs/1_smart-wallet-spend-permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md index 943499bbcd..ed4321de06 100644 --- a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md +++ b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md @@ -10,7 +10,7 @@ hide_table_of_contents: false displayed_sidebar: null --- -# Create an App Wallet using Smart Wallet +# Create Subscription Payments with Spend Permissions Before Smart Wallets, onchain apps primarily prompted users to sign and approve transactions. Now, apps can have their own wallets (App Wallets), enabling them to do more onchain. This is possible through Smart Wallets (ERC-4337). From 9dc93e60e1e0ee9a01f046b2637b1d80b307aacf Mon Sep 17 00:00:00 2001 From: Patrick Hughes Date: Tue, 17 Dec 2024 11:21:04 -0600 Subject: [PATCH 03/16] update title --- .../tutorials/docs/1_smart-wallet-spend-permissions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md index ed4321de06..1064c58e14 100644 --- a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md +++ b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md @@ -1,5 +1,5 @@ --- -title: 'Create Subscription Payments with Spend Permissions' +title: 'Create Onchain Subscription Payments with Spend Permissions' slug: /create-subscription-payments-with-spend-permissions description: Learn how to implement a smart wallet signer for a subscription payment application. author: hughescoin @@ -10,7 +10,7 @@ hide_table_of_contents: false displayed_sidebar: null --- -# Create Subscription Payments with Spend Permissions +# Create Onchain Subscription Payments with Spend Permissions Before Smart Wallets, onchain apps primarily prompted users to sign and approve transactions. Now, apps can have their own wallets (App Wallets), enabling them to do more onchain. This is possible through Smart Wallets (ERC-4337). From f0591ea8a84e72b5c0e01900eceef91e6e7d817f Mon Sep 17 00:00:00 2001 From: Patrick Hughes Date: Tue, 17 Dec 2024 18:04:57 -0600 Subject: [PATCH 04/16] add more detail about spend permissions and how they function in the tutorial overview --- .../docs/1_smart-wallet-spend-permissions.md | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md index 1064c58e14..d9c47d68a9 100644 --- a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md +++ b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md @@ -12,11 +12,43 @@ displayed_sidebar: null # Create Onchain Subscription Payments with Spend Permissions -Before Smart Wallets, onchain apps primarily prompted users to sign and approve transactions. Now, apps can have their own wallets (App Wallets), enabling them to do more onchain. This is possible through Smart Wallets (ERC-4337). +## Overview -In this guide, we'll integrate an App Wallet to facilitate subscription payments for users purchasing goods from a fictitious e-commerce store. +Spend Permissions are a new onchain primitive that allows any user to grant an application permission to pull a specified amount of funds from their account. 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. + +For Spend Permissions to work, the user must have a **Smart Account**. Newly created Smart Accounts add the Permission Manager during account creation, and it cannot be removed. Existing Smart Wallets must manually enable Spend Permissions by adding the Permission Manager via a one-time approval flow when an app requests them. + +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 scoped parameters. +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, pulling 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 pull 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. + +--- + +### What You'll Learn in This Tutorial + +In this tutorial, we’ll walk through a demo application that uses Spend Permissions to enable onchain subscription payments. Specifically, you will: + +- 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. -Your App Wallet can be any public/private keypair wallet. However, our App Wallet will be an ERC-4337 smart account. Using a smart wallet provides advantages such as the programmability to facilitate payouts, gas sponsorship with paymasters, and the ability to avoid storing funds directly in the wallet. Additionally, with a paymaster and a smart wallet, gas costs can be covered by the app or funded using the user's payment method, including ERC-20 tokens. ## Prerequisites: From ce7b34f84e833d0121181b41ad1f0d88628ad6de Mon Sep 17 00:00:00 2001 From: Patrick Hughes Date: Tue, 17 Dec 2024 18:10:39 -0600 Subject: [PATCH 05/16] move objectives at the beginning and replace previous learning section --- .../docs/1_smart-wallet-spend-permissions.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md index d9c47d68a9..83ad7d26cb 100644 --- a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md +++ b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md @@ -26,7 +26,6 @@ A typical flow is as follows: At any point, the user can revoke their Spend Permission. ---- ### Use Cases for Spend Permissions @@ -39,17 +38,20 @@ Spend Permissions allow for the following onchain functionalities: --- -### What You'll Learn in This Tutorial +## 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 account 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: ### Coinbase CDP account[​](https://docs.base.org/tutorials/gasless-transaction-on-base-using-a-paymaster/#coinbase-cdp-account "Direct link to Coinbase CDP account") @@ -65,12 +67,6 @@ Understand the basics of Smart Accounts and the ERC-4337 standard for advanced t 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 accounts, functions, etc. It easily allows onchain developers to use the same skillsets from Javascript/typescript and frontend development and bring it onchain. -## Objectives - -* Create a smart account from a public/private keypair. -* Enable a smart account to receive subscription payments. -* Set up the App Wallet with a paymaster. - ## Template Project Let's first start at a common place. Clone the template e-commerce store: From 89a3ad721d35710347beb6a5e3cbdd809af7010b Mon Sep 17 00:00:00 2001 From: Patrick Hughes Date: Wed, 18 Dec 2024 11:21:45 -0600 Subject: [PATCH 06/16] update sample repo link --- .../tutorials/docs/1_smart-wallet-spend-permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md index 83ad7d26cb..bb2e46e264 100644 --- a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md +++ b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md @@ -72,7 +72,7 @@ Wagmi/viem are two libraries that enable smart contract interaction using typesc Let's first start at a common place. Clone the template e-commerce store: ```bash -git clone https://github.com/hughescoin/healing-honey.git +git clone https://github.com/hughescoin/learn-spend-permissions.git bun install ``` From 7cc1f874c640598711025a0030e782e7a884c037 Mon Sep 17 00:00:00 2001 From: Patrick Hughes Date: Thu, 19 Dec 2024 07:09:44 -0600 Subject: [PATCH 07/16] restructure tutorial to focus on the basics on spend permissions --- .../docs/1_smart-wallet-spend-permissions.md | 308 +++++------------- 1 file changed, 76 insertions(+), 232 deletions(-) diff --git a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md index bb2e46e264..8cff7fbcc8 100644 --- a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md +++ b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md @@ -1,7 +1,7 @@ --- title: 'Create Onchain Subscription Payments with Spend Permissions' slug: /create-subscription-payments-with-spend-permissions -description: Learn how to implement a smart wallet signer for a subscription payment application. +description: Implement a smart wallet signer for a subscription payment application. author: hughescoin keywords: [smart wallet, onchain, spend permissions, smart account, account abstraction] tags: ['frontend', 'account abstraction'] @@ -74,6 +74,8 @@ Let's first start at a common place. Clone the template e-commerce store: ```bash git clone https://github.com/hughescoin/learn-spend-permissions.git +cd learn-spend-permissions + bun install ``` If you don’t have an existing keypair, follow these steps to generate one using Foundry: @@ -88,7 +90,7 @@ Then, create a private key pair: cast wallet new ``` -It should output something similar to this: +Your terminal should output something similar to this: ```bash Successfully created new keypair. @@ -97,22 +99,21 @@ Address: 0x48155Eca1EC9e6986Eef6129A0024f84B8483B59 Private key: 0xcd57753bb4e308ba0c6f574e8af04a7bae0ca0aff5750ddd6275460f49635527 ``` -Excellent, now that you have your keypair, let's start building out our `.env` file with the appropriate values. +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. -Open the `.env` file from Healing Honey project and add the private key to the file +Start by opening the `.env` file in the Healing Honey project and adding your private key: ```bash SPENDER_PRIVATE_KEY=0xcd57753bb4e308ba0c6f574e8af04a7bae0ca0aff5750ddd6275460f49635527 ``` -Now let's get the other environment variable values. +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 account 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. -Navigate to [CDP](https://portal.cdp.coinbase.com/) to get your Paymaster URL and API key. +With your Spender Client set up, ensure all other required environment variables are configured for the app to work when running the dev server. -They can be found by navigating to Onchain Tools > [Paymaster] > Configuration. +Head over to [Coinbase Developer Platform](https://portal.cdp.coinbase.com/) to retrieve your Paymaster URL and API Key. These can be found under **Onchain Tools > Paymaster > Configuration**. +![cdp-config]() -![cdp-config](apps/base-docs/assets/images/paymaster-tutorials/cdp-copy-endpoint.png) - -Copy the **Base Sepolia** (Base testnet) Paymaster URL and API Key then update your .env file: +Copy the **Base Sepolia** (Base testnet) Paymaster URL and API Key, then update your `.env` file as follows: ```bash BASE_SEPOLIA_PAYMASTER_URL=https://api.developer.coinbase.com/rpc/v1/base-sepolia/YOUR_API_KEY @@ -120,19 +121,22 @@ CDP_API_KEY=YOUR_API_KEY NEXT_PUBLIC_ONCHAINKIT_API_KEY=YOUR_API_KEY ``` -:::tip -For your `CDP_API_KEY` and `NEXT_PUBLIC_ONCHAINKIT_API_KEY`, extract the alphanumeric string that comes **after the `base-sepolia` path** in your Paymaster URL. +:::tip 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` +For example, if your Paymaster URL is: https://api.developer.coinbase.com/rpc/v1/base-sepolia/JJ8uIiSMZWgCOyL0EpJgNAf0qPegLMC0 +The API Key would be: `JJ8uIiSMZWgCOyL0EpJgNAf0qPegLMC0` +::: -The API key would be: `JJ8uIiSMZWgCOyL0EpJgNAf0qPegLMC0` +:::warning +Please do not use these API Keys ::: -Your `.env` file should look like this: +Your .env file should now look like this: -```bash +``` COINBASE_COMMERCE_API_KEY="f3cbce52-6f03-49b1-ab34-4fe9e1311d9a" CDP_API_KEY="JJ8uIiSMZWgCOyL0EpJgNAf0qPegLMC0" @@ -141,258 +145,94 @@ NEXT_PUBLIC_ENVIRONMENT=localhost SPENDER_PRIVATE_KEY=0xa72d316dd59a9e9a876b80fa2bbe825a9836e66fd45d87a2ea3c9924a5b131a1 -NEXT_PUBLIC_SPENDER_ADDRESS= - 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 -``` -You'll notice that the `NEXT_PUBLIC_SPENDER_ADDRESS` is empty. this is because we will need to compute the public address. Let's do that now - -To create a Smart Contract Account (also known as [Smart Wallets](https://www.coinbase.com/wallet/smart-wallet)) from a private key, we'll need to use the account abstraction tools provided by Viem that takes our `SPENDER_PRIVATE_KEY` as the parameter and converts. this to a smart account. Once the smart account is created, we'll log it's address in our console and then add it to your `.env` file. - -First, update the `tsconfig.jso`n file: Change the `module` type to `commonjs`: - -```bash -"module": "commonjs" ``` -With your `module` set to `commonjs` you can now create a file to run the script. +To ensure your app communicates with the correct server when a user interacts with their wallet, open the src/components/OnchainProviders.tsx file. -In the root of your project create a file called `generateSmartWalletAddress.ts` and add the following code: -```ts -import { createPublicClient, Hex, http } from 'viem'; -import { baseSepolia } from 'viem/chains'; -import { privateKeyToAccount } from 'viem/accounts'; -import { toCoinbaseSmartAccount } from 'viem/account-abstraction'; -import dotenv from 'dotenv'; - -dotenv.config(); - -export async function logSpenderSmartContractWalletAddress() { - const client = createPublicClient({ - chain: baseSepolia, - transport: http(), - }); - - const spenderAccountOwner = privateKeyToAccount( - process.env.SPENDER_PRIVATE_KEY! as Hex - ); - console.log('spenderAccountOwner', spenderAccountOwner.address); - - const spenderAccount = await toCoinbaseSmartAccount({ - client, - owners: [spenderAccountOwner], - }); - console.log('Spender Smart Wallet Address:', spenderAccount.address); -} - -async function main() { - await logSpenderSmartContractWalletAddress(); -} - -if (require.main === module) { - main().catch((error) => { - console.error(error); - process.exit(1); - }); - } -``` +Replace the // TODO comment with the following value for the keysUrl property: -Save it then run the following: - -```bash -bun install -g ts-node typescript @types/node dotenv && ts-node generateSmartWalletAddress.ts +```json +keysUrl: "https://keys-dev.coinbase.com/connect" ``` -This script installs ts-node along with typescript and runs the script made in the previous step. +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. -Your terminal should output something similar to this: - -```bash -spenderAccountOwner 0x2a83b0e4462449660B6E7567b2C808Bud04d877D -Spender Smart Wallet Address: 0x2269Cc26eBEc78C0fB04c26edDdc65FE15807C8E -``` +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. -You may now add the Spender Smart Wallet Address to the `.env` file : +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: -```bash -NEXT_PUBLIC_SPENDER_ADDRESS=0x2269Cc26eBEc78C0fB04c26edDdc65FE15807C8E +```ts +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. -Revert the module changes made in the `tsconfig.json` file back to `esnext` +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. -## Creating the Smart Spender +For this demo, we'll set: + - `period`: 2629743 seconds (equivalent to one month) + - `end`: 1767291546 (Unix timestamp for January 1, 2026) -Create a file `smartSpender.ts` under `app/lib` +Now, update the message constant to include these parameters. It should look like this: ```ts -import { createPublicClient, createWalletClient, Hex, http } from 'viem'; -import { baseSepolia } from 'viem/chains'; -import { privateKeyToAccount } from 'viem/accounts'; -import { - createBundlerClient, - createPaymasterClient, - toCoinbaseSmartAccount, -} from 'viem/account-abstraction'; - -export async function getPublicClient() { - const client = createPublicClient({ - chain: baseSepolia, - transport: http(), - }); - return client; -} -export async function getSpenderBundlerClient() { - const client = createPublicClient({ - chain: baseSepolia, - transport: http(), - }); - - const spenderAccountOwner = privateKeyToAccount( - process.env.SPENDER_PRIVATE_KEY! as Hex - ); - - console.log({ spenderAccountOwner }); - const spenderAccount = await toCoinbaseSmartAccount({ - client, - owners: [spenderAccountOwner], - }); - console.log({ spenderAccount: spenderAccount }); - const paymasterClient = createPaymasterClient({ - transport: http(process.env.BASE_SEPOLIA_PAYMASTER_URL), - }); - - const spenderBundlerClient = createBundlerClient({ - account: spenderAccount, - client, - paymaster: paymasterClient, - transport: http(process.env.BASE_SEPOLIA_PAYMASTER_URL), - }); - - return spenderBundlerClient; -} +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; ``` -Now that you have a `smartSpender` set up with an address you will need to create a function that tells your app to transfer the user's subscription payment to the `smartSpender`'s wallet in the `collect/route.tsx` file. - -Currently the `route.tsx` file is programmed to have our Spender make onchain actions using a Externally Owned Account (EOA) which is the more common/traditional wallet. This can be observed by looking at the `getSpenderWalletClient()` which requries a private key in order to interact onchain. - -:::tip +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. -An easy way to tell if a function is making onchain calls using a Smart Wallet is if it uses a [userOperation](https://www.biconomy.io/post/decoding-entrypoint-and-useroperation-with-erc-4337-part1). EOAs do not use userOperations; instead, they sign transactions themselves and post them directly onchain. +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. -::: - -You want the application to use the Smart Account while still maintaining functionality for EOAs in the future. To achieve this, let's create two functions: one for making calls using an EOA and another for using a Smart Account. +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. -Create a function called `transactUsingEOA` that takes two parameters: `spendPermission` (of type `any`) and `signature` (of type `any`). Then, paste the logic from the current `POST` function into the `transactUsingEOA` function. - -Your `transactUsingEOA` function should look like this: +### Step 1: Fix the Approval Transaction +The `approvalTxnHash` function is responsible for calling the `approveWithSignature` method on the `Spend Permission Manager` contract. Update it with the following properties and values: ```ts -async function transactUsingEOA(spendPermission: any, signature: any) { - const spenderBundlerClient = await getSpenderWalletClient(); - const publicClient = await getPublicClient(); - - const approvalTxnHash = await spenderBundlerClient.writeContract({ - address: spendPermissionManagerAddress, - abi: spendPermissionManagerAbi, - functionName: "approveWithSignature", - args: [spendPermission, signature], - }); - - const approvalReceipt = await publicClient.waitForTransactionReceipt({ - hash: approvalTxnHash, - }); - - const spendTxnHash = await spenderBundlerClient.writeContract({ - address: spendPermissionManagerAddress, - abi: spendPermissionManagerAbi, - functionName: "spend", - args: [spendPermission, "1"], - }); - - const spendReceipt = await publicClient.waitForTransactionReceipt({ - hash: spendTxnHash, - }); - - return { - success: spendReceipt.status == "success", - transactionHash: spendReceipt.transactionHash, - }; -} +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. -Now let's create a function for transacting using the Smart Account you created earlier. - -In the same file, create a function named `transactUsingSmartWallet` that takes the same parameters. - +Next, we need to call the `spend` function to utilize the user's approved funds. Update the `spendTxnHash` function with the following code: ```ts -async function transactUsingSmartWallet(spendPermission: any, signature: any) { - const spenderBundlerClient = await getSpenderBundlerClient(); - const userOpHash = await spenderBundlerClient.sendUserOperation({ - calls: [ - { - abi: spendPermissionManagerAbi, - functionName: "approveWithSignature", - to: spendPermissionManagerAddress, - args: [spendPermission, signature], - }, - { - abi: spendPermissionManagerAbi, - functionName: "spend", - to: spendPermissionManagerAddress, - args: [spendPermission, BigInt(1)], // spend 1 wei - }, - ], - }); - - const userOpReceipt = await spenderBundlerClient.waitForUserOperationReceipt({ - hash: userOpHash, - }); - - return { - success: userOpReceipt.success, - transactionHash: userOpReceipt.receipt.transactionHash, - }; -} +const spendTxnHash = await spenderBundlerClient.writeContract({ + address: spendPermissionManagerAddress, + abi: spendPermissionManagerAbi, + functionName: 'spend', + args: [spendPermission, BigInt(1)], +}); ``` -You'll notice that this function uses userOperations, a clear giveaway that you are using a Smart Wallet and **not** an EOA. - -Now that we have separate functions for transacting with an EOA and a Smart Wallet, let's update the `POST` function to use the Smart Wallet for transactions. +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. -```ts -export async function POST(request: NextRequest) { - const spenderBundlerClient = await getSpenderWalletClient(); - const publicClient = await getPublicClient(); - try { - const body = await request.json(); - const { spendPermission, signature } = body; - const { success, transactionHash } = await transactUsingSmartWallet( - spendPermission, - signature - ); - return NextResponse.json({ - status: success ? "success" : "failure", - transactionHash: transactionHash, - transactionUrl: `https://sepolia.basescan.org/tx/${transactionHash}`, - }); - } catch (error) { - console.error(error); - return NextResponse.json({}, { status: 500 }); - } -} -``` -> The full code sample is available [here](https://github.com/ilikesymmetry/spend-permissions-quickstart/blob/main/app/collect/route.tsx#L38) +--- -Excellent! You just added a Smart Wallet 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 `transactUsingSmartWallet` function. +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. 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. @@ -423,4 +263,8 @@ In this tutorial you created a smart wallet by converting a private key (EOA) to Remember, our app is designed to allow for users to purchase a good repeatedly without having to sign for the transaction or make additional requests. --- -[Paymaster]: https://portal.cdp.coinbase.com/products/bundler-and-paymaster \ No newline at end of file +[Paymaster]: https://portal.cdp.coinbase.com/products/bundler-and-paymaster +[Spender]: https://www.smartwallet.dev/guides/spend-permissions/api-reference/spendpermissionmanager#:~:text=spender,%27s%20tokens. +[Wallet Client]: https://viem.sh/docs/clients/wallet.html +[scopes]: https://www.smartwallet.dev/guides/spend-permissions/overview#the-spendpermission-details +[EIP-712]: https://eips.ethereum.org/EIPS/eip-712 \ No newline at end of file From 0102e58ea76cf82969877e8e263a6c54343065e0 Mon Sep 17 00:00:00 2001 From: Patrick Hughes Date: Thu, 19 Dec 2024 07:11:39 -0600 Subject: [PATCH 08/16] prompt user to creat .env file from example --- .../tutorials/docs/1_smart-wallet-spend-permissions.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md index 8cff7fbcc8..870048d248 100644 --- a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md +++ b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md @@ -78,6 +78,13 @@ cd learn-spend-permissions bun install ``` + +Create a .env file from the example provided: + +```bash +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. From 4dda19b4d8ec503922fd641d436f064503b7297c Mon Sep 17 00:00:00 2001 From: Patrick Hughes Date: Thu, 19 Dec 2024 07:13:19 -0600 Subject: [PATCH 09/16] use spend instead of pull --- .../tutorials/docs/1_smart-wallet-spend-permissions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md index 870048d248..d7b207513c 100644 --- a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md +++ b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md @@ -14,7 +14,7 @@ displayed_sidebar: null ## Overview -Spend Permissions are a new onchain primitive that allows any user to grant an application permission to pull a specified amount of funds from their account. 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. +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 account. 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. For Spend Permissions to work, the user must have a **Smart Account**. Newly created Smart Accounts add the Permission Manager during account creation, and it cannot be removed. Existing Smart Wallets must manually enable Spend Permissions by adding the Permission Manager via a one-time approval flow when an app requests them. @@ -22,7 +22,7 @@ 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 scoped parameters. 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, pulling funds from the user's Smart Wallet under the granted scope. +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. @@ -32,7 +32,7 @@ At any point, the user can revoke their Spend Permission. 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 pull funds directly for purchases without popup interruptions. +- **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. From 5c94e08849dbc3c6f9f49cf2e27b32aee7c54ec1 Mon Sep 17 00:00:00 2001 From: Patrick Hughes Date: Thu, 19 Dec 2024 07:16:56 -0600 Subject: [PATCH 10/16] change smart account -> smart wallet --- .../docs/1_smart-wallet-spend-permissions.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md index d7b207513c..ea7b43c0e6 100644 --- a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md +++ b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md @@ -3,7 +3,7 @@ title: 'Create Onchain Subscription Payments with Spend Permissions' slug: /create-subscription-payments-with-spend-permissions description: Implement a smart wallet signer for a subscription payment application. author: hughescoin -keywords: [smart wallet, onchain, spend permissions, smart account, account abstraction] +keywords: [smart wallet, onchain, spend permissions, smart wallet, account abstraction] tags: ['frontend', 'account abstraction'] difficulty: medium hide_table_of_contents: false @@ -14,9 +14,9 @@ displayed_sidebar: null ## 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 account. 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. +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. -For Spend Permissions to work, the user must have a **Smart Account**. Newly created Smart Accounts add the Permission Manager during account creation, and it cannot be removed. Existing Smart Wallets must manually enable Spend Permissions by adding the Permission Manager via a one-time approval flow when an app requests them. +For Spend Permissions to work, the user must have a **Smart Wallet**. Newly created Smart Wallets add the Permission Manager during wallet creation, and it cannot be removed. Existing Smart Wallets must manually enable Spend Permissions by adding the Permission Manager via a one-time approval flow when an app requests them. A typical flow is as follows: 1. The user logs into an app with their Smart Wallet. @@ -44,7 +44,7 @@ Spend Permissions allow for the following onchain functionalities: 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 account from a public/private keypair. +- 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. @@ -54,17 +54,17 @@ By the end of this tutorial, your application will seamlessly request and utiliz ## Prerequisites: -### Coinbase CDP account[​](https://docs.base.org/tutorials/gasless-transaction-on-base-using-a-paymaster/#coinbase-cdp-account "Direct link to Coinbase CDP account") +### Coinbase CDP account[](https://docs.base.org/tutorials/gasless-transaction-on-base-using-a-paymaster/#coinbase-cdp-account "Direct link to Coinbase CDP account") This is your access point to the Coinbase Cloud Developer Platform, where you can manage projects and utilize tools like the Paymaster. -### Familiarity with Smart Accounts and ERC 4337[​](https://docs.base.org/tutorials/gasless-transaction-on-base-using-a-paymaster/#familiarity-with-smart-accounts-and-erc-4337 "Direct link to Familiarity with Smart Accounts and ERC 4337") +### Familiarity with Smart Wallets and ERC 4337[​](https://docs.base.org/tutorials/gasless-transaction-on-base-using-a-paymaster/#familiarity-with-smart-accounts-and-erc-4337 "Direct link to Familiarity with Smart Accounts and ERC 4337") -Understand the basics of Smart Accounts and the ERC-4337 standard for advanced transaction patterns and account abstractions. +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 accounts, functions, etc. It easily allows onchain developers to use the same skillsets from Javascript/typescript and frontend development and bring it onchain. +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 @@ -113,7 +113,7 @@ Start by opening the `.env` file in the Healing Honey project and adding your pr ```bash 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 account 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. +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. From 441c2aff8ea15a37382ba6c9f9990dd36c2315de Mon Sep 17 00:00:00 2001 From: Patrick Hughes Date: Thu, 19 Dec 2024 07:46:10 -0600 Subject: [PATCH 11/16] use prod link to keys.coinbase.com --- .../tutorials/docs/1_smart-wallet-spend-permissions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md index ea7b43c0e6..1cb01191ee 100644 --- a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md +++ b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md @@ -20,7 +20,7 @@ For Spend Permissions to work, the user must have a **Smart Wallet**. Newly crea 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 scoped parameters. +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. @@ -165,7 +165,7 @@ To ensure your app communicates with the correct server when a user interacts wi Replace the // TODO comment with the following value for the keysUrl property: ```json -keysUrl: "https://keys-dev.coinbase.com/connect" +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. From 90a33834f918c467d2c436ef601d1dad6e16668d Mon Sep 17 00:00:00 2001 From: Patrick Hughes Date: Thu, 19 Dec 2024 07:49:16 -0600 Subject: [PATCH 12/16] update paragraph on spend permission wallet creation flow --- .../tutorials/docs/1_smart-wallet-spend-permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md index 1cb01191ee..b7753dee2c 100644 --- a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md +++ b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md @@ -16,7 +16,7 @@ displayed_sidebar: null 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. -For Spend Permissions to work, the user must have a **Smart Wallet**. Newly created Smart Wallets add the Permission Manager during wallet creation, and it cannot be removed. Existing Smart Wallets must manually enable Spend Permissions by adding the Permission Manager via a one-time approval flow when an app requests them. +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. From 12153b3b6b582f44a8a087fab01b5b359a15d69d Mon Sep 17 00:00:00 2001 From: Patrick Hughes Date: Thu, 19 Dec 2024 07:52:22 -0600 Subject: [PATCH 13/16] remove step --- .../docs/1_smart-wallet-spend-permissions.md | 67 +++++++++---------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md index b7753dee2c..451a7d7228 100644 --- a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md +++ b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md @@ -18,47 +18,45 @@ Spend Permissions are a new onchain primitive that allows any user to grant an a 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. +A typical flow is as follows: -At any point, the user can revoke their Spend Permission. +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 +### 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. +- **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. +- 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: -### Coinbase CDP account[](https://docs.base.org/tutorials/gasless-transaction-on-base-using-a-paymaster/#coinbase-cdp-account "Direct link to Coinbase CDP account") +### Coinbase CDP account[](https://docs.base.org/tutorials/gasless-transaction-on-base-using-a-paymaster/#coinbase-cdp-account 'Direct link to Coinbase CDP account') -This is your access point to the Coinbase Cloud Developer Platform, where you can manage projects and utilize tools like the Paymaster. +This is your access point to the Coinbase Cloud Developer Platform, where you can manage projects and utilize tools like the Paymaster. -### Familiarity with Smart Wallets and ERC 4337[​](https://docs.base.org/tutorials/gasless-transaction-on-base-using-a-paymaster/#familiarity-with-smart-accounts-and-erc-4337 "Direct link to Familiarity with Smart Accounts and ERC 4337") +### Familiarity with Smart Wallets and ERC 4337[​](https://docs.base.org/tutorials/gasless-transaction-on-base-using-a-paymaster/#familiarity-with-smart-accounts-and-erc-4337 'Direct link to Familiarity with Smart Accounts and ERC 4337') Understand the basics of Smart Wallets and the ERC-4337 standard for advanced transaction patterns and account abstraction. @@ -66,7 +64,6 @@ Understand the basics of Smart Wallets and the ERC-4337 standard for advanced tr 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: @@ -88,16 +85,19 @@ 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. + ```sh curl -L https://foundry.paradigm.xyz | bash ``` Then, create a private key pair: + ```bash cast wallet new ``` Your terminal should output something similar to this: + ```bash Successfully created new keypair. @@ -113,6 +113,7 @@ Start by opening the `.env` file in the Healing Honey project and adding your pr ```bash 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. @@ -136,11 +137,10 @@ For example, if your Paymaster URL is: https://api.developer.coinbase.com/rpc/v1 The API Key would be: `JJ8uIiSMZWgCOyL0EpJgNAf0qPegLMC0` ::: -:::warning +:::warning Please do not use these API Keys ::: - Your .env file should now look like this: ``` @@ -162,7 +162,7 @@ BASE_SEPOLIA_PAYMASTER_URL=https://api.developer.coinbase.com/rpc/v1/base-sepoli 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: +Replace the // TODO comment with the following value for the keysUrl property: ```json keysUrl: "https://keys.coinbase.com/connect" @@ -176,17 +176,17 @@ Spend Permissions rely on [EIP-712] signatures and include several parameters, o ```ts const subscriptionAmountInEther = price ? subscriptionAmount / price : 0; -const subscriptionAmountInWei = parseEther( - subscriptionAmountInEther.toString() -); +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) + +- `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: @@ -210,8 +210,6 @@ You may have noticed that when the user clicks the **Subscribe** button, it send 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. -### Step 1: Fix the Approval Transaction - The `approvalTxnHash` function is responsible for calling the `approveWithSignature` method on the `Spend Permission Manager` contract. Update it with the following properties and values: ```ts @@ -222,6 +220,7 @@ const approvalTxnHash = await spenderBundlerClient.writeContract({ 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: @@ -237,7 +236,7 @@ const spendTxnHash = await spenderBundlerClient.writeContract({ 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. @@ -245,7 +244,6 @@ I know what you're thinking: how can I see the valid (non-revoked) spend permiss 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](https://gist.github.com/hughescoin/d1566557f85cb2fafd281833affbe022). - ```bash curl --location 'https://rpc.wallet.coinbase.com' \ --header 'Content-Type: application/json' \ @@ -263,15 +261,16 @@ curl --location 'https://rpc.wallet.coinbase.com' \ }' ``` +## Conclusion -## Conclusion In this tutorial you created a smart wallet by converting a private key (EOA) to a smart wallet, enableed functionality of your app to have a backend signer be a smartWallet or a traditional EOA. -Remember, our app is designed to allow for users to purchase a good repeatedly without having to sign for the transaction or make additional requests. +Remember, our app is designed to allow for users to purchase a good repeatedly without having to sign for the transaction or make additional requests. --- + [Paymaster]: https://portal.cdp.coinbase.com/products/bundler-and-paymaster [Spender]: https://www.smartwallet.dev/guides/spend-permissions/api-reference/spendpermissionmanager#:~:text=spender,%27s%20tokens. [Wallet Client]: https://viem.sh/docs/clients/wallet.html [scopes]: https://www.smartwallet.dev/guides/spend-permissions/overview#the-spendpermission-details -[EIP-712]: https://eips.ethereum.org/EIPS/eip-712 \ No newline at end of file +[EIP-712]: https://eips.ethereum.org/EIPS/eip-712 From 98af4620f29924e940bb60c75458b5866c64c1d2 Mon Sep 17 00:00:00 2001 From: Patrick Hughes Date: Thu, 19 Dec 2024 08:00:18 -0600 Subject: [PATCH 14/16] add conclusion --- .../docs/1_smart-wallet-spend-permissions.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md index 451a7d7228..e0c11e1709 100644 --- a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md +++ b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md @@ -236,10 +236,14 @@ const spendTxnHash = await spenderBundlerClient.writeContract({ 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: + +```bash +bun run dev +``` + 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](https://gist.github.com/hughescoin/d1566557f85cb2fafd281833affbe022). @@ -263,9 +267,11 @@ curl --location 'https://rpc.wallet.coinbase.com' \ ## Conclusion -In this tutorial you created a smart wallet by converting a private key (EOA) to a smart wallet, enableed functionality of your app to have a backend signer be a smartWallet or a traditional EOA. +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. -Remember, our app is designed to allow for users to purchase a good repeatedly without having to sign for the transaction or make additional requests. +We can’t wait to see what you’ll build. When you implement Spend Permissions, tag us on X/Farcaster [@Base](https://x.com/base) to share your creations. Let’s make 2025 the year of onchain apps—together! 🚀 --- From d6281581d5e5fb57b3f9ccedcd8e57b7786ea0a2 Mon Sep 17 00:00:00 2001 From: Patrick Hughes Date: Thu, 19 Dec 2024 08:09:14 -0600 Subject: [PATCH 15/16] add recalling spend permissions section header --- .../tutorials/docs/1_smart-wallet-spend-permissions.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md index e0c11e1709..f3cdf129be 100644 --- a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md +++ b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md @@ -244,6 +244,8 @@ 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](https://gist.github.com/hughescoin/d1566557f85cb2fafd281833affbe022). From 349ddc32b04d99ba3e05c20bfedff575f93078ec Mon Sep 17 00:00:00 2001 From: Patrick Hughes Date: Thu, 19 Dec 2024 14:46:46 -0600 Subject: [PATCH 16/16] add image --- .../tutorials/docs/1_smart-wallet-spend-permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md index f3cdf129be..42f8f3de07 100644 --- a/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md +++ b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md @@ -119,7 +119,7 @@ Next, navigate to the `src/app/lib/spender.ts` file. Here, you'll see the `priva 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](https://portal.cdp.coinbase.com/) to retrieve your Paymaster URL and API Key. These can be found under **Onchain Tools > Paymaster > Configuration**. -![cdp-config]() +![cdp-config](../../assets/images/paymaster-tutorials/cdp-copy-endpoint.png) Copy the **Base Sepolia** (Base testnet) Paymaster URL and API Key, then update your `.env` file as follows: