diff --git a/docs/base-account/improve-ux/batch-transactions.mdx b/docs/base-account/improve-ux/batch-transactions.mdx
index aada79957..26ad7d599 100644
--- a/docs/base-account/improve-ux/batch-transactions.mdx
+++ b/docs/base-account/improve-ux/batch-transactions.mdx
@@ -6,18 +6,8 @@ With Base Account, you can send multiple onchain calls in a single transaction.
You can submit batch transactions by using the `wallet_sendCalls` RPC method, defined in [EIP-5792](https://eips.ethereum.org/EIPS/eip-5792).
-
-
-**Do you prefer video content?**
-
-There is a video guide that covers the implementation in detail in the [last section of this page](#video-guide).
-
-
-
## Installation
-Install the Base Account SDK:
-
```bash npm
npm install @base-org/account
@@ -34,21 +24,16 @@ yarn add @base-org/account
```bash bun
bun add @base-org/account
```
-
## Setup
-### Initialize the SDK
-
-Import and create the Base Account SDK instance:
-
-```tsx batchTransactions.tsx
+```tsx
import { createBaseAccountSDK } from "@base-org/account";
const sdk = createBaseAccountSDK({
appName: "Base Account SDK Demo",
- appLogoUrl: "https://base.org/logo.png",
+ appLogoUrl: "https://avatars.githubusercontent.com/u/108554348?s=200&v=4",
});
const provider = sdk.getProvider();
@@ -56,300 +41,71 @@ const provider = sdk.getProvider();
## Basic Batch Transaction
-### Simple Multiple Transfers
-
-Send multiple ETH transfers in a single transaction:
-
-```tsx batchTransactions.tsx expandable
-import { createBaseAccountSDK, getCryptoKeyAccount } from "@base-org/account";
+```tsx
+import { createBaseAccountSDK, getCryptoKeyAccount, base } from "@base-org/account";
import { numberToHex, parseEther } from "viem";
const sdk = createBaseAccountSDK({
appName: "Batch Transaction Demo",
- appLogoUrl: "https://base.org/logo.png",
+ appLogoUrl: "https://avatars.githubusercontent.com/u/108554348?s=200&v=4",
});
const provider = sdk.getProvider();
async function sendBatchTransfers() {
- try {
- // Get crypto account
- const cryptoAccount = await getCryptoKeyAccount();
- const fromAddress = cryptoAccount?.account?.address;
+ const cryptoAccount = await getCryptoKeyAccount();
+ const fromAddress = cryptoAccount?.account?.address;
- // Prepare batch calls
- const calls = [
- {
- to: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
- value: numberToHex(parseEther("0.001")), // 0.001 ETH
- data: "0x", // Empty data for simple transfer
- },
- {
- to: "0x742d35Cc6634C0532925a3b844Bc9e7595f6E456",
- value: numberToHex(parseEther("0.001")), // 0.001 ETH
- data: "0x", // Empty data for simple transfer
- },
- ];
+ const calls = [
+ { to: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", value: numberToHex(parseEther("0.001")), data: "0x" },
+ { to: "0x742d35Cc6634C0532925a3b844Bc9e7595f6E456", value: numberToHex(parseEther("0.001")), data: "0x" },
+ ];
- // Send batch transaction
- const result = await provider.request({
- method: "wallet_sendCalls",
- params: [
- {
- version: "2.0.0",
- from: fromAddress,
- chainId: numberToHex(base.constants.CHAIN_IDS.base),
- atomicRequired: true, // All calls must succeed or all fail
- calls: calls,
- },
- ],
- });
+ const result = await provider.request({
+ method: "wallet_sendCalls",
+ params: [{ version: "2.0.0", from: fromAddress, chainId: numberToHex(base.constants.CHAIN_IDS.base), atomicRequired: true, calls }],
+ });
- console.log("Batch transaction sent:", result);
- return result;
- } catch (error) {
- console.error("Batch transaction failed:", error);
- throw error;
- }
+ return result;
}
```
-## Contract Interactions
+## ERC-20 Approve + NFT Mint
-### ERC-20 Approve and Mint an NFT (ERC-721)
-
-A common pattern is to approve the NFT contract to move your ERC-20 and then mint an NFT (ERC-721):
-
-```tsx batchTransactions.tsx expandable
-import {
- createBaseAccountSDK,
- getCryptoKeyAccount,
- base,
-} from "@base-org/account";
+```tsx
+import { createBaseAccountSDK, getCryptoKeyAccount, base } from "@base-org/account";
import { numberToHex, parseUnits, encodeFunctionData } from "viem";
-// ERC-20 ABI for approve
-const erc20Abi = [
- {
- inputs: [
- { name: "spender", type: "address" },
- { name: "amount", type: "uint256" },
- ],
- name: "approve",
- outputs: [{ name: "", type: "bool" }],
- stateMutability: "nonpayable",
- type: "function",
- },
-] as const;
+const erc20Abi = [{ inputs: [{ name: "spender", type: "address" }, { name: "amount", type: "uint256" }], name: "approve", outputs: [{ name: "", type: "bool" }], stateMutability: "nonpayable", type: "function" }] as const;
+const erc721Abi = [{ inputs: [{ name: "to", type: "address" }, { name: "tokenId", type: "uint256" }], name: "mint", outputs: [], stateMutability: "nonpayable", type: "function" }] as const;
-// ERC721 ABI for the mint function
-const erc721Abi = [
- {
- inputs: [
- { name: "to", type: "address" },
- { name: "tokenId", type: "uint256" },
- ],
- name: "mint",
- outputs: [],
- stateMutability: "nonpayable",
- type: "function",
- },
-] as const;
-
-// USDC contract address on Base Sepolia
const USDC_ADDRESS = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
+const NFT_CONTRACT_ADDRESS = "0x82039e7C37D7aAac98D0F4d0A762F4E0d8c8DC273";
-// NFT contract address on Base Sepolia
-const NFT_CONTRACT_ADDRESS = "0x82039e7C37D7aAc98D0F4d0A762F4E0d8c8DC273";
-
-async function approveAndTransfer() {
+async function approveAndMint() {
const sdk = createBaseAccountSDK({
appName: "ERC-20 Batch Demo",
- appLogoUrl: "https://base.org/logo.png",
+ appLogoUrl: "https://avatars.githubusercontent.com/u/108554348?s=200&v=4",
});
const provider = sdk.getProvider();
const cryptoAccount = await getCryptoKeyAccount();
const fromAddress = cryptoAccount?.account?.address;
- // Encode the first approve call - approve USDC to NFT contract
- const call1Data = encodeFunctionData({
- abi: erc20Abi,
- functionName: "approve",
- args: [
- NFT_CONTRACT_ADDRESS,
- parseUnits("1000", 6), // USDC has 6 decimals
- ],
- });
-
- // Encode the second call - mint NFT to the user's address
- const call2Data = encodeFunctionData({
- abi: erc721Abi,
- functionName: "mint",
- args: [fromAddress as `0x${string}`, BigInt("1")],
- });
-
const result = await provider.request({
method: "wallet_sendCalls",
- params: [
- {
- version: "2.0.0",
- from: fromAddress,
- chainId: numberToHex(base.constants.CHAIN_IDS.baseSepolia),
- atomicRequired: true,
- calls: [
- {
- to: USDC_ADDRESS,
- data: call1Data,
- },
- {
- to: NFT_CONTRACT_ADDRESS,
- data: call2Data,
- },
- ],
- },
- ],
- });
-
- return result;
-}
-```
-
-## Advanced Features
-
-### Checking Wallet Capabilities
-
-Before sending batch transactions, you can check if the wallet supports atomic batching:
-
-```tsx batchTransactions.tsx expandable
-async function checkCapabilities() {
- const provider = sdk.getProvider();
-
- try {
- const cryptoAccount = await getCryptoKeyAccount();
- const address = cryptoAccount?.account?.address;
-
- const capabilities = await provider.request({
- method: "wallet_getCapabilities",
- params: [address],
- });
-
- const baseCapabilities = capabilities[base.constants.CHAIN_IDS.base];
-
- if (baseCapabilities?.atomicBatch?.supported) {
- console.log("Atomic batching is supported");
- return true;
- } else {
- console.log("Atomic batching is not supported");
- return false;
- }
- } catch (error) {
- console.error("Failed to check capabilities:", error);
- return false;
- }
-}
-```
-
-### Non-Atomic Batching
-
-Sometimes you want calls to execute sequentially, even if some fail:
-
-```tsx batchTransactions.tsx expandable
-const result = await provider.request({
- method: "wallet_sendCalls",
- params: [
- {
+ params: [{
version: "2.0.0",
from: fromAddress,
- chainId: numberToHex(base.constants.CHAIN_IDS.base),
- atomicRequired: false, // Allow partial execution
- calls: calls,
- },
- ],
-});
-```
-
-## Getting the Batch Transaction Result
-
-`wallet_getCallsStatus` returns the execution status for a batch you previously submitted with `wallet_sendCalls`. Capture the `callsId` returned by `wallet_sendCalls`, then poll for the batch status until it is confirmed or fails.
-
-```tsx batchTransactions.tsx lines expandable
-async function trackBatchTransaction(
- calls: Array<{
- to: `0x${string}`;
- data: `0x${string}`;
- value?: `0x${string}`;
- }>
-) {
- const cryptoAccount = await getCryptoKeyAccount();
- const fromAddress = cryptoAccount?.account?.address;
-
- const callsId = await provider.request({
- method: "wallet_sendCalls",
- params: [
- {
- version: "2.0.0",
- from: fromAddress,
- chainId: numberToHex(base.constants.CHAIN_IDS.base),
- atomicRequired: true,
- calls,
- },
- ],
+ chainId: numberToHex(base.constants.CHAIN_IDS.baseSepolia),
+ atomicRequired: true,
+ calls: [
+ { to: USDC_ADDRESS, data: encodeFunctionData({ abi: erc20Abi, functionName: "approve", args: [NFT_CONTRACT_ADDRESS, parseUnits("1000", 6)] }) },
+ { to: NFT_CONTRACT_ADDRESS, data: encodeFunctionData({ abi: erc721Abi, functionName: "mint", args: [fromAddress as `0x${string}`, BigInt("1")] }) },
+ ],
+ }],
});
- try {
- const status = await provider.request({
- method: "wallet_getCallsStatus",
- params: [callsId],
- });
-
- if (status.status === 200) {
- console.log("Batch completed successfully", status.receipts);
- } else if (status.status === 100) {
- console.log("Batch still pending", status.id);
- } else {
- console.error("Batch failed", status.status);
- }
-
- return status;
- } catch (error: any) {
- if (error.code === 4200) {
- throw new Error("No batch found for the provided callsId.");
- }
-
- if (error.code === 4100) {
- throw new Error(
- "The connected wallet does not support wallet_getCallsStatus."
- );
- }
-
- if (error.code === -32602) {
- throw new Error("The callsId parameter is invalid.");
- }
-
- throw error;
- }
+ return result;
}
```
-
-You can learn more about `wallet_getCallsStatus` in the [reference documentation](/base-account/reference/core/provider-rpc-methods/wallet_getCallsStatus).
-
-
-**Need more control over gas?**
-
-You can override the gas limit for individual calls in a batch using the [`gasLimitOverride`](/base-account/reference/core/capabilities/gasLimitOverride) capability. This is useful for calls with nondeterministic gas consumption, such as swaps. See the [capabilities overview](/base-account/reference/core/capabilities/overview) for the full list of supported capabilities.
-
-
-
-## Video Guide
-
-
\ No newline at end of file
diff --git a/docs/base-account/improve-ux/spend-permissions.mdx b/docs/base-account/improve-ux/spend-permissions.mdx
index 5afa7a69c..a742063c6 100644
--- a/docs/base-account/improve-ux/spend-permissions.mdx
+++ b/docs/base-account/improve-ux/spend-permissions.mdx
@@ -11,351 +11,72 @@ After the user signs the permission, the `spender` can initiate transfers within
Read more about the Spend Permission Manager contract and supported chains on [GitHub](https://github.com/coinbase/spend-permissions).
-
- Spend Permissions for Base App Apps are coming soon and will be supported in a future update.
-
-
-
-If you're using Sub Accounts, learn how Base Account can automatically fund Sub Accounts and optionally skip approval prompts using [Auto Spend Permissions](/base-account/improve-ux/sub-accounts#auto-spend-permissions).
-
-
## Usage
### Request a Spend Permission
-You create an EIP-712 payload that describes the permission and ask the user to sign it. Store the resulting signature along with the permission data so you can register the permission on-chain later. The SDK helper below handles construction and signing for you.
-
-| Field Name | Type | Description |
-| ----------- | --------- | ---------------------------------------------------------------------------------------- |
-| `account` | `address` | Smart account this spend permission is valid for |
-| `spender` | `address` | Entity that can spend `account`'s tokens |
-| `token` | `address` | Token address (ERC-7528 native token or ERC-20 contract) |
-| `allowance` | `uint160` | Maximum allowed value to spend within each `period` |
-| `period` | `uint48` | Time duration for resetting used `allowance` on a recurring basis (seconds) |
-| `start` | `uint48` | Timestamp this spend permission is valid starting at (inclusive, unix seconds) |
-| `end` | `uint48` | Timestamp this spend permission is valid until (exclusive, unix seconds) |
-| `salt` | `uint256` | Arbitrary data to differentiate unique spend permissions with otherwise identical fields |
-| `extraData` | `bytes` | Arbitrary data to attach to a spend permission which may be consumed by the `spender` |
-
```tsx
-import { requestSpendPermission } from "@base-org/account/spend-permission";
-import { createBaseAccountSDK } from "@base-org/account";
-import { base } from "viem/chains";
+import { createBaseAccountSDK, base } from '@base-org/account';
const sdk = createBaseAccountSDK({
- appName: 'Base Account SDK Demo',
- appLogoUrl: 'https://base.org/logo.png',
- appChainIds: [base.id],
-});
-
-const permission = await requestSpendPermission({
- account: "0x...",
- spender: "0x...",
- token: "0x...",
- chainId: 8453, // or any other supported chain
- allowance: 1_000_000n,
- periodInDays: 30,
- provider: sdk.getProvider(),
-});
-
-console.log("Spend Permission:", permission);
-```
-
-### Use the Spend Permission
-
-Using a permission is 2 steps:
-
-1. **Prepare the calls** — Call `prepareSpendCallData` with the permission and the requested `amount`.
-2. **Submit the calls** — Submit the calls using your app's spender account.
-
-`prepareSpendCallData` returns an array of calls needed to spend the tokens:
-
-- `approveWithSignature` — When the permission is not yet registered onchain, this call would be prepended to the `spend` call.
-- `spend` — The call to spend the tokens from the user's Base Account.
-
-```tsx
-import { prepareSpendCallData } from "@base-org/account/spend-permission";
-
-// returns [approveWithSignatureCall, spendCall]
-const spendCalls = await prepareSpendCallData({
- permission,
- amount, // optional; omit to spend the remaining allowance
+ appName: 'Spend Permission Demo',
+ appLogoUrl: 'https://avatars.githubusercontent.com/u/108554348?s=200&v=4',
+ appChainIds: [base.constants.CHAIN_IDS.base],
});
-// If your app spender account supports wallet_sendCalls, submit them in batch using wallet_sendCalls
-// this is an example on how to do it using wallet_sendCalls in provider interface
-await provider.request({
- method: "wallet_sendCalls",
- params: [
- {
- version: "2.0",
- atomicRequired: true,
- from: spender,
- calls: spendCalls,
- },
- ],
-});
-
-// If your app spender account doesn't support wallet_sendCalls, submit them in order using eth_sendTransaction
-// this is an example on how to do it using eth_sendTransaction in provider interface
-await Promise.all(
- spendCalls.map((call) =>
- provider.request({
- method: "eth_sendTransaction",
- params: [
- {
- ...call,
- from: spender,
+const provider = sdk.getProvider();
+
+async function requestSpendPermission() {
+ const permission = await provider.request({
+ method: 'wallet_grantPermissions',
+ params: [{
+ chainId: base.constants.CHAIN_IDS.base,
+ expiry: Math.floor(Date.now() / 1000) + 86400,
+ permissions: [{
+ type: 'native-token-recurring-allowance',
+ data: {
+ allowance: '0x16345785D8A0000',
+ start: Math.floor(Date.now() / 1000),
+ period: 86400,
},
- ],
- })
- )
-);
-```
-
-
-**About the `spendCalls` array**
-
-This array has 2 calls when submitting the permission onchain for *the first time*.
-When the permission is already registered onchain, this array has only 1 call (the `spend` call).
-
-For most use cases, you don't need to worry about this.
-
-
-### Revoke a Spend Permission
-
-You can revoke a permission in two ways:
-
-- Request user approval via request to user's Base Account using `requestRevoke`.
-- Revoke silently from your app's spender by submitting the call returned from `prepareRevokeCallData`.
+ }],
+ }],
+ });
-```tsx
-import {
- requestRevoke,
- prepareRevokeCallData,
-} from "@base-org/account/spend-permission";
-
-// Option A: User-initiated revoke (wallet popup)
-try {
- const hash = await requestRevoke(permission);
- console.log("Revoke succeeded", hash);
-} catch {
- console.warn("Revoke was rejected or failed");
+ return permission;
}
-
-// Option B: Silent revoke by your app's spender account
-const revokeCall = await prepareRevokeCallData(permission);
-
-// Submit the revoke call using your app's spender account
-// this is an example on how to do it using wallet_sendCalls in provider interface
-await provider.request({
- method: "wallet_sendCalls",
- params: [
- {
- version: "2.0",
- atomicRequired: true,
- from: spender,
- calls: [revokeCall],
- },
- ],
-});
-
-// If your app spender account doesn't support wallet_sendCalls, submit the revoke call using eth_sendTransaction
-// this is an example on how to do it using eth_sendTransaction in provider interface
-await provider.request({
- method: "eth_sendTransaction",
- params: [
- {
- ...revokeCall,
- from: spender,
- },
- ],
-});
```
-## API Reference
-
-- [requestSpendPermission](/base-account/reference/spend-permission-utilities/requestSpendPermission)
-- [prepareSpendCallData](/base-account/reference/spend-permission-utilities/prepareSpendCallData)
-- [requestRevoke](/base-account/reference/spend-permission-utilities/requestRevoke)
-- [prepareRevokeCallData](/base-account/reference/spend-permission-utilities/prepareRevokeCallData)
-- [fetchPermissions](/base-account/reference/spend-permission-utilities/fetchPermissions)
-- [fetchPermission](/base-account/reference/spend-permission-utilities/fetchPermission)
-- [getPermissionStatus](/base-account/reference/spend-permission-utilities/getPermissionStatus)
+### Use a Spend Permission
-## Complete Integration Example
-
-```typescript
-import {
- fetchPermissions,
- fetchPermission,
- getPermissionStatus,
- prepareSpendCallData,
- requestSpendPermission,
- requestRevoke,
- prepareRevokeCallData,
-} from "@base-org/account/spend-permission";
-
-import { createBaseAccountSDK } from "@base-org/account";
-import { base } from "viem/chains";
+```tsx
+import { createBaseAccountSDK, base } from '@base-org/account';
const sdk = createBaseAccountSDK({
- appName: 'Base Account SDK Demo',
- appLogoUrl: 'https://base.org/logo.png',
- appChainIds: [base.id],
+ appName: 'Spend Permission Demo',
+ appLogoUrl: 'https://avatars.githubusercontent.com/u/108554348?s=200&v=4',
+ appChainIds: [base.constants.CHAIN_IDS.base],
});
-const spender = "0xAppSpenderAddress";
-
-// 1) Fetch a specific permission by its hash
-// Use fetchPermission when you already know the permission hash
-// (e.g., stored from a previous session or passed as a parameter)
-const permission = await fetchPermission({
- permissionHash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
- provider: sdk.getProvider(),
-});
-
-// Alternative: Fetch all permissions for a spender
-// Use fetchPermissions when you need to see all available permissions
-// and want to choose which one to use
-// const permissions = await fetchPermissions({
-// account: "0xUserBaseAccountAddress",
-// chainId: 84532,
-// spender,
-// provider: sdk.getProvider(),
-// });
-// const permission = permissions.at(0);
-
-// ========================================
-// When there IS an existing permission
-// ========================================
-
-// 2. check the status of permission
-try {
- const { isActive, remainingSpend } = await getPermissionStatus(permission);
- const amount = 1000n;
-
- if (!isActive || remainingSpend < amount) {
- throw new Error("No spend permission available");
- }
-} catch {
- throw new Error("No spend permission available");
+const provider = sdk.getProvider();
+
+async function useSpendPermission(permissionContext: `0x${string}`, amount: bigint, recipient: `0x${string}`) {
+ const result = await provider.request({
+ method: 'wallet_sendCalls',
+ params: [{
+ version: '2.0.0',
+ chainId: base.constants.CHAIN_IDS.base,
+ calls: [{
+ to: recipient,
+ value: `0x${amount.toString(16)}`,
+ data: '0x',
+ }],
+ capabilities: {
+ permissions: { context: permissionContext },
+ },
+ }],
+ });
+
+ return result;
}
-
-// 3. prepare the calls
-const [approveCall, spendCall] = await prepareSpendCallData({
- permission,
- amount,
-});
-
-// 4. execute the calls using your app's spender account
-// this is an example using wallet_sendCalls, in production it could be using eth_sendTransaction.
-await provider.request({
- method: "wallet_sendCalls",
- params: [
- {
- version: "2.0",
- atomicRequired: true,
- from: spender,
- calls: [approveCall, spendCall],
- },
- ],
-});
-
-// ========================================
-// When there is NOT an existing permission
-// ========================================
-
-// 2. request a spend permission to use
-const newPermission = await requestSpendPermission({
- account: "0xUserBaseAccountAddress",
- spender,
- token: "0xTokenContractAddress",
- chainId: 84532,
- allowance: 1_000_000n,
- periodInDays: 30,
- provider: sdk.getProvider(),
-});
-
-// 3. prepare the calls
-const spendCalls = await prepareSpendCallData({
- permission: newPermission,
- amount: 1_000n,
-});
-
-// 4. execute the calls using your app's spender account
-// this is an example using eth_sendTransaction. If your app account supports wallet_sendCalls, use wallet_sendCalls to batch the calls instead.
-await Promise.all(
- spendCalls.map((call) =>
- provider.request({
- method: "eth_sendTransaction",
- params: [
- {
- ...call,
- from: spender,
- },
- ],
- })
- )
-);
-
-// ========================================
-// Request user to revoke spend permission
-// ========================================
-
-try {
- const hash = await requestRevoke(permission);
- console.log("Revoke succeeded", hash);
-} catch {
- throw new Error("Revoke failed");
-}
-
-// ========================================
-// Revoke spend permission in the background
-// ========================================
-
-const revokeCall = await prepareRevokeCallData(permission);
-
-await provider.request({
- method: "wallet_sendCalls",
- params: [
- {
- version: "2.0",
- atomicRequired: true,
- from: spender,
- calls: [revokeCall],
- },
- ],
-});
```
-
-## Example Use Case
-
-Let's say you're building an AI agent that can autonomously purchase [Zora Creator Coins](https://docs.zora.co/coins) using secure [Spend Permissions](/base-account/improve-ux/spend-permissions) on Base.
-
-This example demonstrates how to combine Base Account's [Spend Permissions](/base-account/improve-ux/spend-permissions) with Coinbase Developer Platform (CDP) [Server Wallets](https://docs.cdp.coinbase.com/server-wallets/v2/introduction/quickstart) and [Trade API](https://docs.cdp.coinbase.com/trade-api/quickstart) for seamless, gas-free AI agent transactions.
-
-
-
-
-
-
-
-
- Try the live application and see spend permissions in action
-
-
- Explore the complete implementation on GitHub
-
-
\ No newline at end of file
diff --git a/docs/base-account/improve-ux/sponsor-gas/paymasters.mdx b/docs/base-account/improve-ux/sponsor-gas/paymasters.mdx
index 797ce5f22..7625cada7 100644
--- a/docs/base-account/improve-ux/sponsor-gas/paymasters.mdx
+++ b/docs/base-account/improve-ux/sponsor-gas/paymasters.mdx
@@ -108,8 +108,8 @@ the [Coinbase Developer Platform documentation](https://docs.cdp.coinbase.com/pa
const sdk = createBaseAccountSDK({
appName: 'Paymaster Demo',
- appLogoUrl: 'https://base.org/logo.png',
- appChainIds: [base.constants.CHAIN_IDS.baseSepolia], // or base.constants.CHAIN_IDS.base for mainnet
+ appLogoUrl: 'https://avatars.githubusercontent.com/u/108554348?s=200&v=4',
+ appChainIds: [base.constants.CHAIN_IDS.baseSepolia],
});
const provider = sdk.getProvider();
@@ -128,13 +128,10 @@ the [Coinbase Developer Platform documentation](https://docs.cdp.coinbase.com/pa
### Basic Sponsored Transaction
- Here's how to send a sponsored transaction using the `wallet_sendCalls` RPC method:
-
```tsx
import { createBaseAccountSDK, getCryptoKeyAccount, base } from '@base-org/account';
import { numberToHex, encodeFunctionData, parseEther } from 'viem';
- // Example NFT contract ABI
const nftABI = [
{
name: 'safeMint',
@@ -148,72 +145,7 @@ the [Coinbase Developer Platform documentation](https://docs.cdp.coinbase.com/pa
async function sendSponsoredTransaction() {
const sdk = createBaseAccountSDK({
appName: 'Paymaster Demo',
- appLogoUrl: 'https://base.org/logo.png',
- appChainIds: [base.constants.CHAIN_IDS.baseSepolia],
- });
-
- const provider = sdk.getProvider();
-
- try {
- // Get the user's account
- const cryptoAccount = await getCryptoKeyAccount();
- const fromAddress = cryptoAccount?.account?.address;
-
- if (!fromAddress) {
- throw new Error('No account found');
- }
-
- // Your Paymaster service URL (use your proxy URL)
- const paymasterServiceUrl = process.env.NEXT_PUBLIC_PAYMASTER_PROXY_SERVER_URL;
-
- // Prepare the transaction call
- const nftAddress = '0x119Ea671030FBf79AB93b436D2E20af6ea469a19';
- const calls = [
- {
- to: nftAddress,
- value: '0x0',
- data: encodeFunctionData({
- abi: nftABI,
- functionName: 'safeMint',
- args: [fromAddress]
- })
- }
- ];
-
- // Send the transaction with paymaster capabilities
- const result = await provider.request({
- method: 'wallet_sendCalls',
- params: [{
- version: '1.0',
- chainId: numberToHex(base.constants.CHAIN_IDS.baseSepolia),
- from: fromAddress,
- calls: calls,
- capabilities: {
- paymasterService: {
- url: paymasterServiceUrl
- }
- }
- }]
- });
-
- console.log('Sponsored transaction sent:', result);
- return result;
- } catch (error) {
- console.error('Sponsored transaction failed:', error);
- throw error;
- }
- }
- ```
-
- ### Multiple Sponsored Transactions
-
- You can also batch multiple transactions and have them all sponsored:
-
- ```tsx
- async function sendMultipleSponsoredTransactions() {
- const sdk = createBaseAccountSDK({
- appName: 'Paymaster Demo',
- appLogoUrl: 'https://base.org/logo.png',
+ appLogoUrl: 'https://avatars.githubusercontent.com/u/108554348?s=200&v=4',
appChainIds: [base.constants.CHAIN_IDS.baseSepolia],
});
@@ -222,20 +154,7 @@ the [Coinbase Developer Platform documentation](https://docs.cdp.coinbase.com/pa
const fromAddress = cryptoAccount?.account?.address;
const paymasterServiceUrl = process.env.NEXT_PUBLIC_PAYMASTER_PROXY_SERVER_URL;
-
- // Multiple calls in a single sponsored transaction
- const calls = [
- {
- to: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045',
- value: numberToHex(parseEther('0.001')),
- data: '0x' // Simple ETH transfer
- },
- {
- to: '0x742d35Cc6634C0532925a3b844Bc9e7595f6E456',
- value: numberToHex(parseEther('0.001')),
- data: '0x' // Another ETH transfer
- }
- ];
+ const nftAddress = '0x119Ea671030FBf79AB93b436D2E20af6ea469a19';
const result = await provider.request({
method: 'wallet_sendCalls',
@@ -243,12 +162,12 @@ the [Coinbase Developer Platform documentation](https://docs.cdp.coinbase.com/pa
version: '1.0',
chainId: numberToHex(base.constants.CHAIN_IDS.baseSepolia),
from: fromAddress,
- calls: calls,
- capabilities: {
- paymasterService: {
- url: paymasterServiceUrl
- }
- }
+ calls: [{
+ to: nftAddress,
+ value: '0x0',
+ data: encodeFunctionData({ abi: nftABI, functionName: 'safeMint', args: [fromAddress] })
+ }],
+ capabilities: { paymasterService: { url: paymasterServiceUrl } }
}]
});
@@ -256,46 +175,7 @@ the [Coinbase Developer Platform documentation](https://docs.cdp.coinbase.com/pa
}
```
- ### Check Paymaster Capabilities
-
- Before sending sponsored transactions, you can check if the wallet supports paymaster services:
-
- ```tsx
- async function checkPaymasterSupport() {
- const sdk = createBaseAccountSDK({
- appName: 'Paymaster Demo',
- appLogoUrl: 'https://base.org/logo.png',
- appChainIds: [base.constants.CHAIN_IDS.baseSepolia],
- });
-
- const provider = sdk.getProvider();
- const cryptoAccount = await getCryptoKeyAccount();
- const address = cryptoAccount?.account?.address;
-
- try {
- const capabilities = await provider.request({
- method: 'wallet_getCapabilities',
- params: [address]
- });
-
- const baseCapabilities = capabilities[base.constants.CHAIN_IDS.baseSepolia];
-
- if (baseCapabilities?.paymasterService?.supported) {
- console.log('Paymaster service is supported');
- return true;
- } else {
- console.log('Paymaster service is not supported');
- return false;
- }
- } catch (error) {
- console.error('Failed to check paymaster capabilities:', error);
- return false;
- }
- }
- ```
-
- That's it! Base Account will handle the rest. If your Paymaster service is able to sponsor the transaction,
- in the UI Base Account will indicate to your user that the transaction is sponsored.
+ That's it! Base Account will handle the rest.
diff --git a/docs/base-account/improve-ux/sub-accounts.mdx b/docs/base-account/improve-ux/sub-accounts.mdx
index 0084b4913..dc2ffed5e 100644
--- a/docs/base-account/improve-ux/sub-accounts.mdx
+++ b/docs/base-account/improve-ux/sub-accounts.mdx
@@ -3,43 +3,18 @@ title: 'Use Sub Accounts'
description: 'Learn how to create and use Sub Accounts using Base Account SDK'
---
-import { GithubRepoCard } from "/snippets/GithubRepoCard.mdx"
+Sub Accounts are smart accounts scoped to your app that sit under a user's main Base Account.
+They give your app its own dedicated signing key and ETH balance while the user maintains full
+override control from their main account.
-## What are Sub Accounts?
+## Why Sub Accounts?
-Sub Accounts allow you to provision app-specific wallet accounts for your users that are embedded directly in your application. Once created, you can interact with them just as you would with any other wallet via the wallet provider or popular onchain libraries like wagmi and viem.
-
-
-Looking for a full implementation? Jump to the [Complete Integration Example](/base-account/improve-ux/sub-accounts#complete-integration-example).
-
-
-
-**Do you prefer video content?**
-
-There is a video guide that covers the implementation in detail in the [last section of this page](#video-guide).
-
-
-
-## Key Benefits
-
-- **Frictionless transactions**: Eliminate repeated signing prompts for high frequency and agentic use cases or take full control of the transaction flow.
-- **No funding flows required**: Spend Permissions allow Sub Accounts to spend directly from the universal Base Account's balance.
-- **User control**: Users can manage all their sub accounts at [account.base.app](https://account.base.app).
-
-
-If you would like to see a live demo of Sub Accounts in action, check out our [Sub Accounts Demo](https://sub-accounts-fc.vercel.app).
-
-
-
-**Spend Permissions**
-
-Sub Accounts are optimized for use with Spend Permissions to allow your app to take advantage of the user's existing Base Account balances. See the [Spend Permissions](/base-account/improve-ux/spend-permissions) guide for more information about how they work.
-
+- **No popups for every action** — your app's key signs silently within permitted limits
+- **Isolated funds** — each app gets its own ETH balance, no cross-app interference
+- **User stays in control** — the main Base Account can always override or revoke
## Installation
-Install the Base Account SDK:
-
```bash npm
npm install @base-org/account
@@ -58,632 +33,77 @@ bun add @base-org/account
```
-## Quickstart
+## Setup
-The fastest way to adopt Sub Accounts is to set `creation` to `on-connect` and `defaultAccount` to `sub` in the SDK configuration.
+```tsx
+import { createBaseAccountSDK, base } from '@base-org/account';
-```tsx page.tsx
const sdk = createBaseAccountSDK({
- // ...
- subAccounts: {
- creation: 'on-connect',
- defaultAccount: 'sub',
- }
+ appName: 'Sub Account Demo',
+ appLogoUrl: 'https://avatars.githubusercontent.com/u/108554348?s=200&v=4',
+ appChainIds: [base.constants.CHAIN_IDS.base],
});
-```
-
-This will automatically create a Sub Account for the user when they connect their Base Account and transactions will automatically be sent from the Sub Account unless you specify the `from` parameter in your transaction request to be the universal account address. Spend Permissions will also be automatically requested for the Sub Account as your app needs them.
-
-This is what the user will see when they connect their Base Account and automatic Sub Accounts are enabled:
-
-
-
-
-
-
-We recommend using a [Paymaster](/base-account/improve-ux/sponsor-gas/paymasters) to sponsor gas to ensure the best user experience when integrating Sub Accounts. You can set a paymaster to be used for all transactions by configuring the `paymasterUrls` parameter in the SDK configuration. See the [createBaseAccount](/base-account/reference/core/createBaseAccount#param-paymaster-urls) reference for more information.
-
-
-
-**Do you prefer video content?**
-
-There is a video guide that covers this specific implementation in the [last section of this page](#video-guide).
-
-
-
-## Using Sub Accounts
-### Initialize the SDK
+const provider = sdk.getProvider();
+```
-First, set up the Base Account SDK. Be sure to customize the `appName` and `appLogoUrl` to match your app as this will be displayed in the wallet connection popup and in the account.base.app dashboard. You can also customize the `appChainIds` to be the chains that your app supports.
+## Create a Sub Account
-```tsx page.tsx
-import { createBaseAccountSDK, getCryptoKeyAccount } from '@base-org/account';
-import { base } from 'viem/chains';
+```tsx
+import { createBaseAccountSDK, getCryptoKeyAccount, base } from '@base-org/account';
-// Initialize SDK with Sub Account configuration
const sdk = createBaseAccountSDK({
- appName: 'Base Account SDK Demo',
- appLogoUrl: 'https://base.org/logo.png',
- appChainIds: [base.id],
+ appName: 'Sub Account Demo',
+ appLogoUrl: 'https://avatars.githubusercontent.com/u/108554348?s=200&v=4',
+ appChainIds: [base.constants.CHAIN_IDS.base],
});
-// Get an EIP-1193 provider
-const provider = sdk.getProvider()
-```
-
-### Create a Sub Account
-
-
-Make sure to authenticate the user with their Base Account before creating a Sub Account.
-For that, you can choose one of the following options:
-- Follow the [Authenticate users](/base-account/guides/authenticate-users) guide
-- Simply use `provider.request({ method: 'eth_requestAccounts' });` for a simple wallet connection
-
-
-Create a Sub Account for your application using the provider's [wallet_addSubAccount](/base-account/reference/core/provider-rpc-methods/wallet_addSubAccount) RPC method. When no `publicKey` parameter is provided, a non-extractable browser CryptoKey is generated and used to sign on behalf of the Sub Account.
-
-```tsx page.tsx
-// Create sub account
-const subAccount = await provider.request({
- method: 'wallet_addSubAccount',
- params: [
- {
- account: {
- type: 'create',
+const provider = sdk.getProvider();
+
+async function createSubAccount() {
+ const cryptoAccount = await getCryptoKeyAccount();
+
+ const subAccount = await provider.request({
+ method: 'wallet_connect',
+ params: [{
+ version: '1',
+ capabilities: {
+ addSubAccount: {
+ account: {
+ type: 'create',
+ keys: [{
+ type: 'cryptoKey',
+ key: cryptoAccount?.account?.publicKey,
+ }],
+ },
+ },
},
- }
- ],
-});
-
-console.log('Sub Account created:', subAccount.address);
-```
-
-Alternatively, you can use the SDK convenience method:
-
-```tsx page.tsx
-const subAccount = await sdk.subAccount.create();
-
-console.log('Sub Account created:', subAccount.address);
-```
-
-This is what the user will see when prompted to create a Sub Account:
-
-
-
-
-
-### Get Existing Sub Account
-
-Retrieve an existing Sub Account using the provider's [wallet_getSubAccounts](/base-account/reference/core/provider-rpc-methods/wallet_getSubAccounts) RPC method. This will return the Sub Account associated with the app's domain and is useful to check if a Sub Account already exists for the user to determine if one needs to be created.
-
-```tsx page.tsx
-// Get the universal account
-const [universalAddress] = await provider.request({
- method: "eth_requestAccounts",
- params: []
-})
-
-// Get sub account for universal account
-const { subAccounts: [subAccount] } = await provider.request({
- method: 'wallet_getSubAccounts',
- params: [{
- account: universalAddress,
- domain: window.location.origin,
- }]
-})
+ }],
+ });
-if (subAccount) {
- console.log('Sub Account found:', subAccount.address);
-} else {
- console.log('No Sub Account exists for this app');
+ return subAccount;
}
```
-Alternatively, you can use the SDK convenience method:
-
-```tsx page.tsx
-const subAccount = await sdk.subAccount.get();
-
-console.log('Sub Account:', subAccount);
-```
-### Send transactions
-
-To send transactions from the connected sub account you can use EIP-5792 `wallet_sendCalls` or `eth_sendTransaction`. You need to specify the `from` parameter to be the sub account address.
-
-
-When the Sub Account is connected, it is the second account in the array returned by `eth_requestAccounts` or `eth_accounts`. `wallet_addSubAccount` needs to be called in each session before the Sub Account can be used. It will not trigger a new Sub Account creation if one already exists.
-
-If you are using `mode: 'auto'`, the Sub Account will be the first account in the array.
-
-
-First, get all the accounts that are available, of which the sub account will be the second account:
-
-```tsx page.tsx
-const [universalAddress, subAccountAddress] = await provider.request({
- method: "eth_requestAccounts", // or "eth_accounts" if already connected
- params: []
-})
-```
-
-Then, send the transaction from the sub account:
-
-**`wallet_sendCalls`**
-
-```tsx page.tsx
-const callsId = await provider.request({
- method: 'wallet_sendCalls',
- params: [{
- version: "2.0",
- atomicRequired: true,
- from: subAccountAddress, // Specify the sub account address
- calls: [{
- to: '0x...',
- data: '0x...',
- value: '0x...',
+## Send a Transaction from Sub Account
+
+```tsx
+async function sendFromSubAccount(subAccountAddress: `0x${string}`) {
+ const result = await provider.request({
+ method: 'wallet_sendCalls',
+ params: [{
+ version: '2.0.0',
+ from: subAccountAddress,
+ chainId: numberToHex(base.constants.CHAIN_IDS.base),
+ atomicRequired: true,
+ calls: [{
+ to: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045',
+ value: numberToHex(parseEther('0.001')),
+ data: '0x',
+ }],
}],
- capabilities: {
- // https://docs.cdp.coinbase.com/paymaster/introduction/welcome
- paymasterUrl: "https://...",
- },
- }]
-})
-
-console.log('Calls sent:', callsId);
-```
-
-**`eth_sendTransaction`**
-
-```tsx page.tsx
-const tx = await provider.request({
- method: 'eth_sendTransaction',
- params: [{
- from: subAccountAddress, // Specify the sub account address
- to: '0x...',
- data: '0x...',
- value: '0x...',
- }]
-})
-
-console.log('Transaction sent:', tx);
-```
-
-We recommend using `wallet_sendCalls` in conjunction with a paymaster to ensure the best user experience. See the [Paymasters](/base-account/improve-ux/sponsor-gas/paymasters) guide for more information.
-
-## Advanced Usage
-
-### Import an existing account
-
-If you already have a deployed Smart Contract Account and would like to turn it into a Sub Account of the connected Base Account, you can import it as a Sub Account using the provider RPC method:
-
-```tsx page.tsx
-const subAccount = await provider.request({
- method: 'wallet_addSubAccount',
- params: [
- {
- account: {
- type: 'deployed',
- address: '0xYourSmartContractAccountAddress',
- chainId: 8453 // the chain the account is deployed on
- },
- }
- ],
-});
-
-console.log('Sub Account added:', subAccount.address);
-```
-
-
-
-Before the Sub Account is imported, you will need to add the Base Account address as an owner of the Sub Account. This currently needs to be done manually
-by calling the [`addOwnerAddress`](https://github.com/coinbase/smart-wallet/blob/a8c6456f3a6d5d2dea08d6336b3be13395cacd42/src/MultiOwnable.sol#L101) or [`addOwnerPublicKey`](https://github.com/coinbase/smart-wallet/blob/a8c6456f3a6d5d2dea08d6336b3be13395cacd42/src/MultiOwnable.sol#L109) functions on the Smart Contract of the Sub Account that was imported and setting the Base Account address as the owner.
-
-Additionally, only Coinbase Smart Wallet contracts are currently supported for importing as a Sub Account into your Base Account.
-
-The Coinbase Smart Wallet contract ABI can be found on [GitHub](https://github.com/base/account-sdk/blob/master/packages/account-sdk/src/sign/base-account/utils/constants.ts#L8).
-
-
-
-### Add Owner Account
-
-Sub Accounts automatically detect when an ownership update is needed when a signature is required and will prompt the user to approve the update before signing. However, you can also add an owner to a Sub Account manually using the SDK convenience method:
-
-```tsx page.tsx
-const ownerAccount = await sdk.subAccount.addOwner({
- address: subAccount?.address,
- publicKey: cryptoAccount?.account?.publicKey,
- chainId: base.id,
-});
-
-console.log('Owner added to Sub Account');
-```
-
-This generates a transaction to call the `addOwnerAddress` or `addOwnerPublicKey` functions on the Sub Account's smart contract to add the owner.
-
-
-Ownership changes are expected if the user signs in to your app on a new device or browser.
-
-Ensure you do not lose your app's Sub Account signer keys when using the SDK on the server (e.g. Node.js) as updating the owner requires a signature from the user, which cannot be requested from server contexts.
-
-
-## Auto Spend Permissions
-
-Auto Spend Permissions allows Sub Accounts to access funds from their parent Base Account when transaction balances are insufficient. This feature can also establish ongoing spend permissions, enabling future transactions to execute without user approval prompts, reducing friction in your app's transaction flow.
-
-This feature is **enabled by default** when using Sub Accounts.
-
-### How it works
-
-**First-time transaction flow:**
-When a Sub Account attempts its first transaction, Base Account displays a popup for user approval. During this approval process, Base Account:
-
-- Automatically detects any missing tokens (native or ERC-20) needed for the transaction
-- Requests a transfer of the required funds from the parent Base Account to the Sub Account to fulfill the current transaction
-- Allows the user to optionally grant ongoing spend permissions for future transactions in that token
-
-**Subsequent transactions:**
-If the user granted spend permissions, future transactions follow this priority:
-
-1. First, attempt using existing Sub Account balances and granted spend permissions
-2. If insufficient, prompt the user to authorize additional transfers and/or spend permissions from their Base Account
-
-
-Spend permission requests are limited to the first token when multiple transfers are needed for a single transaction. Additional tokens require separate approvals.
-
-
-
-### Configuration
-
-If your users' Sub Accounts will be funded manually, you can disable Auto Spend Permissions by setting `funding` to `manual` in your SDK configuration:
-
-```tsx page.tsx
-const sdk = createBaseAccountSDK({
- appName: 'Base Account SDK Demo',
- appLogoUrl: 'https://base.org/logo.png',
- appChainIds: [base.id],
- subAccounts: {
- funding: 'manual', // Disable auto spend permissions
- }
-});
-```
-
-
-## Technical Details
-
-Base Account's self-custodial design requires a user passkey prompt for each wallet interaction, such as transactions or message signing. While this ensures user awareness and approval of every wallet interaction, it can impact user experience in applications requiring frequent wallet interactions.
-
-To support Base Account with user experiences that need more developer control over wallet interactions, we've built Sub Accounts in conjunction with [ERC-7895](https://eip.tools/eip/7895), a new wallet RPC for creating hierarchical relationships between wallet accounts.
-
-These Sub Accounts are linked to the end user's Base Account through an onchain relationship. When combined with our [Spend Permission feature](/base-account/improve-ux/spend-permissions), this creates a powerful foundation for provisioning and funding app accounts securely, while giving you ample control over building the user experience that makes the most sense for your application.
-
-
-## Complete Integration Example
+ });
-Here's a full React component that demonstrates Sub Account creation and usage:
-
-```tsx page.tsx expandable
-import { createBaseAccountSDK } from "@base-org/account";
-import { useCallback, useEffect, useState } from "react";
-import { baseSepolia } from "viem/chains";
-
-interface SubAccount {
- address: `0x${string}`;
- factory?: `0x${string}`;
- factoryData?: `0x${string}`;
-}
-
-interface GetSubAccountsResponse {
- subAccounts: SubAccount[];
-}
-
-interface WalletAddSubAccountResponse {
- address: `0x${string}`;
- factory?: `0x${string}`;
- factoryData?: `0x${string}`;
-}
-
-export default function SubAccountDemo() {
- const [provider, setProvider] = useState["getProvider"]
- > | null>(null);
- const [subAccount, setSubAccount] = useState(null);
- const [universalAddress, setUniversalAddress] = useState("");
- const [connected, setConnected] = useState(false);
- const [loadingSubAccount, setLoadingSubAccount] = useState(false);
- const [loadingUniversal, setLoadingUniversal] = useState(false);
- const [status, setStatus] = useState("");
-
- // Initialize SDK and crypto account
- useEffect(() => {
- const initializeSDK = async () => {
- try {
- const sdkInstance = createBaseAccountSDK({
- appName: "Sub Account Demo",
- appChainIds: [baseSepolia.id],
- });
-
- // Get the provider
- const providerInstance = sdkInstance.getProvider();
- setProvider(providerInstance);
-
- setStatus("SDK initialized - ready to connect");
- } catch (error) {
- console.error("SDK initialization failed:", error);
- setStatus("SDK initialization failed");
- }
- };
-
- initializeSDK();
- }, []);
-
- const connectWallet = async () => {
- if (!provider) {
- setStatus("Provider not initialized");
- return;
- }
-
- setLoadingSubAccount(true);
- setStatus("Connecting wallet...");
-
- try {
- // Connect to the wallet
- const accounts = (await provider.request({
- method: "eth_requestAccounts",
- params: [],
- })) as string[];
-
- const universalAddr = accounts[0];
- setUniversalAddress(universalAddr);
- setConnected(true);
-
- // Check for existing sub account
- const response = (await provider.request({
- method: "wallet_getSubAccounts",
- params: [
- {
- account: universalAddr,
- domain: window.location.origin,
- },
- ],
- })) as GetSubAccountsResponse;
-
- const existing = response.subAccounts[0];
- if (existing) {
- setSubAccount(existing);
- setStatus("Connected! Existing Sub Account found");
- } else {
- setStatus("Connected! No existing Sub Account found");
- }
- } catch (error) {
- console.error("Connection failed:", error);
- setStatus("Connection failed");
- } finally {
- setLoadingSubAccount(false);
- }
- };
-
- const createSubAccount = async () => {
- if (!provider) {
- setStatus("Provider not initialized");
- return;
- }
-
- setLoadingSubAccount(true);
- setStatus("Creating Sub Account...");
-
- try {
- const newSubAccount = (await provider.request({
- method: "wallet_addSubAccount",
- params: [
- {
- account: {
- type: 'create',
- },
- }
- ],
- })) as WalletAddSubAccountResponse;
-
- setSubAccount(newSubAccount);
- setStatus("Sub Account created successfully!");
- } catch (error) {
- console.error("Sub Account creation failed:", error);
- setStatus("Sub Account creation failed");
- } finally {
- setLoadingSubAccount(false);
- }
- };
-
- const sendCalls = useCallback(
- async (
- calls: Array<{ to: string; data: string; value: string }>,
- from: string,
- setLoadingState: (loading: boolean) => void
- ) => {
- if (!provider) {
- setStatus("Provider not available");
- return;
- }
-
- setLoadingState(true);
- setStatus("Sending calls...");
-
- try {
- const callsId = (await provider.request({
- method: "wallet_sendCalls",
- params: [
- {
- version: "2.0",
- atomicRequired: true,
- chainId: `0x${baseSepolia.id.toString(16)}`, // Convert to hex
- from,
- calls,
- capabilities: {
- // https://docs.cdp.coinbase.com/paymaster/introduction/welcome
- // paymasterUrl: "your paymaster url",
- },
- },
- ],
- })) as string;
-
- setStatus(`Calls sent! Calls ID: ${callsId}`);
- } catch (error) {
- console.error("Send calls failed:", error);
- setStatus("Send calls failed");
- } finally {
- setLoadingState(false);
- }
- },
- [provider]
- );
-
- const sendCallsFromSubAccount = useCallback(async () => {
- if (!subAccount) {
- setStatus("Sub account not available");
- return;
- }
-
- const calls = [
- {
- to: "0x4bbfd120d9f352a0bed7a014bd67913a2007a878",
- data: "0x9846cd9e", // yoink
- value: "0x0",
- },
- ];
-
- await sendCalls(calls, subAccount.address, setLoadingSubAccount);
- }, [sendCalls, subAccount]);
-
- const sendCallsFromUniversal = useCallback(async () => {
- if (!universalAddress) {
- setStatus("Universal account not available");
- return;
- }
-
- const calls = [
- {
- to: "0x4bbfd120d9f352a0bed7a014bd67913a2007a878",
- data: "0x9846cd9e", // yoink
- value: "0x0",
- },
- ];
-
- await sendCalls(calls, universalAddress, setLoadingUniversal);
- }, [sendCalls, universalAddress]);
-
- return (
-
- );
+ return result;
}
```
-
-## Video Guide
-
diff --git a/docs/base-account/quickstart/web-react.mdx b/docs/base-account/quickstart/web-react.mdx
index 4e90f4a54..bef4724ed 100644
--- a/docs/base-account/quickstart/web-react.mdx
+++ b/docs/base-account/quickstart/web-react.mdx
@@ -3,239 +3,82 @@ title: "Web (Next.js)"
description: "Quickly add Sign in with Base and Base Pay to any Next.js app"
---
-import { GithubRepoCard } from "/snippets/GithubRepoCard.mdx"
+## 1. Create a Next.js app
-This quick-start shows the **minimum** code required to add Sign in with Base (SIWB) and Base Pay to any Next.js app using the Base Account SDK.
-
-## 1. Create a new Next.js app
-
-If you're starting fresh, create a new Next.js app:
-
-
-```bash npm
+```bash
npx create-next-app@latest base-account-quickstart
cd base-account-quickstart
```
-```bash yarn
-yarn create next-app base-account-quickstart
-cd base-account-quickstart
-```
-
-```bash pnpm
-pnpm create next-app base-account-quickstart
-cd base-account-quickstart
-```
+## 2. Install Base Account SDK
-```bash bun
-bunx create-next-app base-account-quickstart
-cd base-account-quickstart
+```bash
+npm install @base-org/account
```
-
-When prompted during setup, you can choose the default options or customize as needed. For this quickstart, the default settings work perfectly.
+## 3. Initialize the SDK
-## 2. Install the SDK
+Create `lib/sdk.ts`:
-
-```bash npm
-npm install @base-org/account @base-org/account-ui
-```
+```tsx
+import { createBaseAccountSDK, base } from '@base-org/account';
-```bash pnpm
-pnpm add @base-org/account @base-org/account-ui
-```
-
-```bash yarn
-yarn add @base-org/account @base-org/account-ui
-```
+export const sdk = createBaseAccountSDK({
+ appName: 'My Next.js App',
+ appLogoUrl: 'https://avatars.githubusercontent.com/u/108554348?s=200&v=4',
+ appChainIds: [base.constants.CHAIN_IDS.base],
+});
-```bash bun
-bun add @base-org/account @base-org/account-ui
+export const provider = sdk.getProvider();
```
-
-
-**Got a peer dependency error?**
+## 4. Sign In with Base
-Use `--legacy-peer-deps` flag if you get a peer dependency error.
-
+Create `components/SignIn.tsx`:
-## 3. Create the main component
-
-Replace the contents of `app/page.tsx` (or `app/page.js` if not using TypeScript) with this component:
-
-```jsx title="app/page.tsx" lineNumbers
+```tsx
'use client';
-import React, { useState } from 'react';
-import { createBaseAccountSDK, pay, getPaymentStatus } from '@base-org/account';
-import { SignInWithBaseButton, BasePayButton } from '@base-org/account-ui/react';
-
-export default function Home() {
- const [isSignedIn, setIsSignedIn] = useState(false);
- const [paymentStatus, setPaymentStatus] = useState('');
- const [paymentId, setPaymentId] = useState('');
- const [theme, setTheme] = useState('light');
-
- // Initialize SDK
- const sdk = createBaseAccountSDK(
- {
- appName: 'Base Account Quick-start',
- appLogoUrl: 'https://base.org/logo.png',
- }
- );
-
- // Optional sign-in step – not required for `pay()`, but useful to get the user address
- const handleSignIn = async () => {
- try {
- await sdk.getProvider().request({ method: 'wallet_connect' });
- setIsSignedIn(true);
- } catch (error) {
- console.error('Sign in failed:', error);
- }
- };
-
- // One-tap USDC payment using the pay() function
- const handlePayment = async () => {
- try {
- const { id } = await pay({
- amount: '0.01', // USD – SDK quotes equivalent USDC
- to: '0xRecipientAddress', // Replace with your recipient address
- testnet: true // set to false or omit for Mainnet
- });
-
- setPaymentId(id);
- setPaymentStatus('Payment initiated! Click "Check Status" to see the result.');
- } catch (error) {
- console.error('Payment failed:', error);
- setPaymentStatus('Payment failed');
- }
- };
-
- // Check payment status using stored payment ID
- const handleCheckStatus = async () => {
- if (!paymentId) {
- setPaymentStatus('No payment ID found. Please make a payment first.');
- return;
- }
-
- try {
- const { status } = await getPaymentStatus({ id: paymentId });
- setPaymentStatus(`Payment status: ${status}`);
- } catch (error) {
- console.error('Status check failed:', error);
- setPaymentStatus('Status check failed');
- }
- };
+import { useState } from 'react';
+import { provider } from '../lib/sdk';
- const toggleTheme = () => {
- setTheme(theme === 'light' ? 'dark' : 'light');
- };
+export function SignIn() {
+ const [address, setAddress] = useState(null);
- const dark = theme === 'dark';
- const styles = {
- container: { minHeight: '100vh', backgroundColor: dark ? '#111' : '#fff', color: dark ? '#fff' : '#000', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: '20px' },
- card: { backgroundColor: dark ? '#222' : '#f9f9f9', borderRadius: '12px', padding: '30px', maxWidth: '400px', textAlign: 'center' },
- title: { fontSize: '24px', fontWeight: 'bold', marginBottom: '10px', color: dark ? '#fff' : '#00f' },
- subtitle: { fontSize: '16px', color: dark ? '#aaa' : '#666', marginBottom: '30px' },
- themeToggle: { position: 'absolute', top: '20px', right: '20px', background: 'none', border: 'none', cursor: 'pointer', fontSize: '18px' },
- buttonGroup: { display: 'flex', flexDirection: 'column', gap: '16px', alignItems: 'center' },
- status: { marginTop: '20px', padding: '12px', backgroundColor: dark ? '#333' : '#f0f0f0', borderRadius: '8px', fontSize: '14px' },
- signInStatus: { marginTop: '8px', fontSize: '14px', color: dark ? '#0f0' : '#060' }
+ const handleSignIn = async () => {
+ const accounts = await provider.request({ method: 'eth_requestAccounts' });
+ setAddress(accounts[0]);
};
return (
-
-
-
-
-
Base Account
-
Experience seamless crypto payments
-
-
-
-
- {isSignedIn && (
-
- ✅ Connected to Base Account
-
- )}
-
-
-
- {paymentId && (
-
- )}
-
-
- {paymentStatus && (
-
- {paymentStatus}
-
- )}
-
+
+
+ {address &&
Connected: {address}
}
);
}
```
-
-**Note:**
-
-Make sure to replace `0xRecipientAddress` with your recipient address.
-
-
-
-**Base Pay and SIWB are independent**
-
-You DO NOT need to use SIWB to use Base Pay. You can just call the `pay()` function without any additional setup.
-
+## 5. Base Pay
-## 4. Start your app
+```tsx
+'use client';
-```bash
-npm run dev
+import { provider } from '../lib/sdk';
+import { numberToHex, parseEther } from 'viem';
+
+export async function sendPayment(to: `0x${string}`, amountEth: string) {
+ const result = await provider.request({
+ method: 'wallet_sendCalls',
+ params: [{
+ version: '2.0.0',
+ calls: [{
+ to,
+ value: numberToHex(parseEther(amountEth)),
+ data: '0x',
+ }],
+ }],
+ });
+ return result;
+}
```
-
-Open http://localhost:3000, click **Sign in with Base** (optional) and then **Pay**, approve the transaction, and you've sent 5 USDC on Base Sepolia—done! 🎉
-
-**Note:** If you have an existing Next.js app, just install the SDK (`npm install @base-org/account @base-org/account-ui`) and add the component above to your project. For other React frameworks, you can adapt this component as needed.
-
-## Next steps
-
-* **[Authenticate Users](/base-account/guides/authenticate-users)** - strong authentication by setting up Sign in with Base with backend verification
-* **[Accept Payments](/base-account/guides/accept-payments)** explore all the features of Base Pay
-* **[Sign in with Base Button](/base-account/reference/ui-elements/sign-in-with-base-button)** – use the Sign in with Base Button component to quickly add authentication to your app
-* **[Base Pay Button](/base-account/reference/ui-elements/base-pay-button)** – use the Base Pay Button component to quickly add payments to your app
-
-
-**Please Follow the Brand Guidelines**
-
-If you intend on using the `SignInWithBaseButton` or `BasePayButton`, please follow the [Brand Guidelines](/base-account/reference/ui-elements/brand-guidelines) to ensure consistency across your application.
-
-
\ No newline at end of file
diff --git a/docs/base-account/quickstart/web.mdx b/docs/base-account/quickstart/web.mdx
index e538e9f18..4cdf14561 100644
--- a/docs/base-account/quickstart/web.mdx
+++ b/docs/base-account/quickstart/web.mdx
@@ -5,217 +5,86 @@ description: "Integrate Sign in with Base and Base Pay using nothing but HTML an
This quick-start shows the **minimum** code required to add Sign in with Base and Base Pay to any web page using nothing but the Base Account SDK. No frameworks, no additional libraries.
-
- **Interactive Playground:** Want to test the SDK functions before integrating?
- Try our [Base Pay SDK
- Playground](https://base.github.io/account-sdk/pay-playground) to experiment
- with `pay()` and `getPaymentStatus()` functions.
-
+## Installation
-
-**Do you prefer video content?**
+Add the Base Account SDK via CDN or npm:
-There is a video guide that covers the implementation in detail in the [last section of this page](#video-guide).
-
-
-
-## 1. Install the SDK (Optional)
-
-You can use the Base Account SDK in two ways:
-
-### Option A: CDN (No installation required)
+```html
+
-[...rest of your code]
+ const provider = sdk.getProvider();
+
```
-For a full example, see [example](#2-copy-paste-this-html-file) below.
-
-### Option B: NPM Package
+Or install via npm:
-If you prefer to install locally:
-
-
-```bash npm
+```bash
npm install @base-org/account
```
-```bash pnpm
-pnpm add @base-org/account
-```
-
-```bash yarn
-yarn add @base-org/account
-```
-
-```bash bun
-bun add @base-org/account
-```
-
-
+## Sign In with Base
-Then use ES modules:
-
-```html index.html
-
-```
-
-This guide uses the CDN approach for simplicity.
-
-## 2. Copy-paste this HTML file
-
-```html index.html expandable
+```html
-
-
- Base Account Quick-start
-
-
-
Base Account Demo
-
-
-
-
-
-
-
-
-
-
-
+
+ Sign in with Base
+
+
+
+
+
+
+
```
-## 3. Serve the file
-
-Any static server will work:
+## Base Pay
-```bash
-npx serve .
-# or
-python -m http.server
+```html
+
```
-
-Open http://localhost:3000, click **Sign in with Base** (optional) and then **Pay with Base**, approve the transaction, and you've sent 5 USDC on Base Sepolia—done! 🎉
-
-## Next steps
-
-- **[Add Sign In With Base Button](/base-account/reference/ui-elements/sign-in-with-base-button)** – implement full SIWE authentication with backend verification
-- **[Add Base Pay Button](/base-account/reference/ui-elements/base-pay-button)** – collect user information during payment flow
-
-
-**Please Follow the Brand Guidelines**
-
-If you intend on using the `SignInWithBaseButton` or `BasePayButton`, please follow the [Brand Guidelines](/base-account/reference/ui-elements/brand-guidelines) to ensure consistency across your application.
-
-
-
-## Video Guide
-