From 76de6b6177b580326530bda26e91c501f771bb32 Mon Sep 17 00:00:00 2001 From: Pat Date: Fri, 20 Dec 2024 15:53:19 -0600 Subject: [PATCH 1/8] [Tutorial]: Create Onchain Subscription Payments with Spend Permissions (#1410) * add spend permission tutorial to base docs * add spend permission tutorial to base docs * update title * add more detail about spend permissions and how they function in the tutorial overview * move objectives at the beginning and replace previous learning section * update sample repo link * restructure tutorial to focus on the basics on spend permissions * prompt user to creat .env file from example * use spend instead of pull * change smart account -> smart wallet * use prod link to keys.coinbase.com * update paragraph on spend permission wallet creation flow * remove step * add conclusion * add recalling spend permissions section header * add image --- .../docs/1_smart-wallet-spend-permissions.md | 284 ++++++++++++++++++ 1 file changed, 284 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 00000000000..42f8f3de073 --- /dev/null +++ b/apps/base-docs/tutorials/docs/1_smart-wallet-spend-permissions.md @@ -0,0 +1,284 @@ +--- +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 wallet, account abstraction] +tags: ['frontend', 'account abstraction'] +difficulty: medium +hide_table_of_contents: false +displayed_sidebar: null +--- + +# 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: + +### 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 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. + +### 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: + +```bash +git clone https://github.com/hughescoin/learn-spend-permissions.git + +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. + +```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. + +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: + +```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. + +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](../../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: + +```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 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` +::: + +:::warning +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: + +```json +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: + +```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. + +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: + +```ts +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: + +```ts +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: + +```ts +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: + +```bash +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). + +```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 + +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](https://x.com/base) to share your creations. Let’s make 2025 the year of onchain apps—together! 🚀 + +--- + +[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 From 4598f7a3b671d1a71a57d8486471f561b66f3ba7 Mon Sep 17 00:00:00 2001 From: Bilog WEB3 <155262265+Bilogweb3@users.noreply.github.com> Date: Sat, 21 Dec 2024 01:37:46 +0100 Subject: [PATCH 2/8] Update route.ts (#1444) --- .../app/(basenames)/api/basenames/avatar/ipfsUpload/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/app/(basenames)/api/basenames/avatar/ipfsUpload/route.ts b/apps/web/app/(basenames)/api/basenames/avatar/ipfsUpload/route.ts index 68b4955057b..d59ffe35642 100644 --- a/apps/web/app/(basenames)/api/basenames/avatar/ipfsUpload/route.ts +++ b/apps/web/app/(basenames)/api/basenames/avatar/ipfsUpload/route.ts @@ -14,7 +14,7 @@ export const MAX_IMAGE_SIZE_IN_MB = 1; // max 1mb export async function POST(request: NextRequest) { try { - // Rerrer validation + // Referer validation const requestUrl = new URL(request.url); // Username must be provided From 5300ed6c4aee2316e2587a0be4461a1ef0f1c1ae Mon Sep 17 00:00:00 2001 From: planetBoy <140164174+Guayaba221@users.noreply.github.com> Date: Sat, 21 Dec 2024 01:39:56 +0100 Subject: [PATCH 3/8] Update next.config.js (#1443) --- apps/bridge/next.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/bridge/next.config.js b/apps/bridge/next.config.js index 34e69868831..2b320c92708 100644 --- a/apps/bridge/next.config.js +++ b/apps/bridge/next.config.js @@ -36,7 +36,7 @@ const baseConfig = { // Enable strict mode in development reactStrictMode: !isProdEnv, - // Minifiy for production builds + // Minify for production builds swcMinify: true, }; From 47a703f338020a61a803515b2ad6f8c26f0537a9 Mon Sep 17 00:00:00 2001 From: Danbo <140512416+dannbbb1@users.noreply.github.com> Date: Fri, 20 Dec 2024 19:44:47 -0500 Subject: [PATCH 4/8] Fix typos and spelling errors in documentation (#1447) * Update opengraph-image.tsx * Update cardImage.svg.tsx * Update [tokenId].ts --- apps/web/app/(basenames)/name/[username]/opengraph-image.tsx | 2 +- apps/web/pages/api/basenames/[name]/assets/cardImage.svg.tsx | 2 +- apps/web/pages/api/basenames/metadata/[tokenId].ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/web/app/(basenames)/name/[username]/opengraph-image.tsx b/apps/web/app/(basenames)/name/[username]/opengraph-image.tsx index eb9f0f744f3..47b7500a2b8 100644 --- a/apps/web/app/(basenames)/name/[username]/opengraph-image.tsx +++ b/apps/web/app/(basenames)/name/[username]/opengraph-image.tsx @@ -71,7 +71,7 @@ export default async function OpenGraphImage(props: ImageRouteProps) { const chain = getChainForBasename(username as Basename); let imageSource = domainName + profilePicture.src; - // NOTE: Do we want to fail if the name doesn't exists? + // NOTE: Do we want to fail if the name doesn't exist? try { const client = getBasenamePublicClient(chain.id); const avatar = await client.getEnsText({ diff --git a/apps/web/pages/api/basenames/[name]/assets/cardImage.svg.tsx b/apps/web/pages/api/basenames/[name]/assets/cardImage.svg.tsx index ee348597192..d8ec858f712 100644 --- a/apps/web/pages/api/basenames/[name]/assets/cardImage.svg.tsx +++ b/apps/web/pages/api/basenames/[name]/assets/cardImage.svg.tsx @@ -73,7 +73,7 @@ export default async function handler(request: NextRequest) { logger.error('Error fetching basename Avatar:', error); } - // Using Satori for a SVG response + // Using Satori for an SVG response const svg = await satori(
Date: Sat, 21 Dec 2024 07:57:09 +0700 Subject: [PATCH 5/8] DackieSwap update logo and description (#1423) Co-authored-by: andreapn.eth --- apps/web/public/images/partners/dackie.webp | Bin 4902 -> 4440 bytes apps/web/src/data/ecosystem.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/public/images/partners/dackie.webp b/apps/web/public/images/partners/dackie.webp index 98472afb8ad56f7f963d7674c757368419a23620..c21760e59e387b87234a0bacc5f1df8f642ba5de 100644 GIT binary patch literal 4440 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 Y1u50gpKmIpn=t;%JE~Ed^xyyh0J`Ul>Hq)$ diff --git a/apps/web/src/data/ecosystem.json b/apps/web/src/data/ecosystem.json index 3dee494a711..2bc9195f7d7 100644 --- a/apps/web/src/data/ecosystem.json +++ b/apps/web/src/data/ecosystem.json @@ -1346,7 +1346,7 @@ { "name": "Dackieswap", "url": "https://www.dackieswap.xyz", - "description": "DackieSwap is the native and community-owned DEX/Launchpad on Base. It offers great liquidity, simple features, friendly interface and unlimited benefits.", + "description": "The Premier Multichain DEX with AI Agent Technology.", "category": "defi", "subcategory": "dex", "imageUrl": "/images/partners/dackie.webp" From 03b3fc596b82aaa5f5d88f5cc822cd9f00b489d0 Mon Sep 17 00:00:00 2001 From: maradini77 <140460067+maradini77@users.noreply.github.com> Date: Sat, 21 Dec 2024 02:03:15 +0100 Subject: [PATCH 6/8] Update useSetPrimaryBasename.ts (#1433) --- apps/web/src/hooks/useSetPrimaryBasename.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/src/hooks/useSetPrimaryBasename.ts b/apps/web/src/hooks/useSetPrimaryBasename.ts index f4230fb4328..f1f9555f6c7 100644 --- a/apps/web/src/hooks/useSetPrimaryBasename.ts +++ b/apps/web/src/hooks/useSetPrimaryBasename.ts @@ -15,10 +15,10 @@ import { useUsernameProfile } from 'apps/web/src/components/Basenames/UsernamePr /* A hook to set a name as primary for resolution. - Responsabilities: + Responsibilities: - Get and validate the primary username against the new username - Write the new name to the contract & Wait for the transaction to be processed - - Refetch basename on successfull request + - Refetch basename on successful request */ type UseSetPrimaryBasenameProps = { From b68c57a009dda48528c7f2e86da205cef0196162 Mon Sep 17 00:00:00 2001 From: favour Date: Sat, 21 Dec 2024 11:20:16 +0100 Subject: [PATCH 7/8] typo improvement (#1450) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "// use ⌘-K S to format without saving": It might be clearer to say "// use ⌘-K S (or Ctrl-K S) to format without saving", to ensure users who aren't using macOS can also understand the keyboard shortcut. --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index aaca799c8d4..7ce34dee131 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { // Prettier "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true, // use ⌘-K S to format without saving + "editor.formatOnSave": true, // use ⌘-K S (or Ctrl-K S) to format without saving // Disable built-in formatters "html.format.enable": false, From 4e8b7689d81e6f33c68592f5392b4a561fa7f811 Mon Sep 17 00:00:00 2001 From: sashaodessa <140454972+sashaodessa@users.noreply.github.com> Date: Sat, 21 Dec 2024 11:28:28 +0100 Subject: [PATCH 8/8] Update logger.ts (#1455) --- apps/web/src/utils/logger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/utils/logger.ts b/apps/web/src/utils/logger.ts index d2c8fff0e53..e8f3cf704ae 100644 --- a/apps/web/src/utils/logger.ts +++ b/apps/web/src/utils/logger.ts @@ -30,7 +30,7 @@ class CustomLogger { let traceId: string | undefined; let spanId: string | undefined; - //TODO: initialice ddTrace through dd-tracer + //TODO: initialize ddTrace through dd-tracer if (ddTrace) { // Access trace information server-side const currentSpan = ddTrace.scope().active();