diff --git a/docs/base-account/framework-integrations/cdp.mdx b/docs/base-account/framework-integrations/cdp.mdx index 0c259d39..8563000d 100644 --- a/docs/base-account/framework-integrations/cdp.mdx +++ b/docs/base-account/framework-integrations/cdp.mdx @@ -1,12 +1,617 @@ --- title: "Coinbase Developer Platform" -description: "Integrate Base Account with CDP Embedded Wallets" +description: "Build onchain apps supporting both Base Account and CDP Embedded Wallets" --- +# Integrate Base Account with CDP Embedded Wallets -We are working to integrate Base Account with [CDP Embedded Wallets](https://docs.cdp.coinbase.com/embedded-wallets/welcome), and will publish guidance soon. +Learn how to build an onchain app that seamlessly supports both **existing Base Account users** and **new users** through CDP Embedded Wallets, providing unified authentication and wallet management. -In the meantime, you can use the connector from the Base Account [Wagmi guide](/base-account/framework-integrations/wagmi/setup) -with CDP Embedded Wallets [Wagmi integration](https://docs.cdp.coinbase.com/embedded-wallets/wagmi). +## Overview +This integration enables your app to serve two distinct user types: +- **Existing Base users**: Connect with their Base Account for a familiar experience +- **New onchain users**: Create CDP Embedded Wallets via email, mobile, or social authentication +Both user types get the same app functionality while using their preferred wallet type. + +## What you'll build + +- **Unified authentication flow**: Single sign-in supporting both wallet types +- **Automatic wallet detection**: Smart routing based on user's existing wallet status +- **Consistent user experience**: Both wallet types access the same app features + +## Prerequisites + +- Node.js 18+ installed +- React application (Next.js recommended) +- [CDP Portal account](https://portal.cdp.coinbase.com/) with Project ID +- Basic familiarity with Wagmi and React hooks + +## Installation + +Install the required packages for both CDP Embedded Wallets and Base Account support: + +```bash +npm install @coinbase/cdp-core @coinbase/cdp-hooks @base-org/account @tanstack/react-query viem wagmi +``` + +## Step-by-step implementation + +Since native CDP + Base Account integration is under development, this guide uses a **dual connector approach** where both wallet types are supported through separate, coordinated connectors. + +You can use the Base Account Wagmi connector alongside CDP's React provider system to create a unified experience that properly handles wallet persistence for both wallet types. + +### Step 1: Environment configuration + +Create environment variables for your CDP project: + +```bash +# .env.local +NEXT_PUBLIC_CDP_PROJECT_ID=your_cdp_project_id +NEXT_PUBLIC_APP_NAME="Your App Name" +``` + +Get your CDP Project ID from the [CDP Portal](https://portal.cdp.coinbase.com/). + +⚠️ **Critical**: Without a valid `NEXT_PUBLIC_CDP_PROJECT_ID`, the app will fail to load with "Project ID is required" errors. Also configure your domain in CDP Portal → Wallets → Embedded Wallet settings for CORS. + +### Step 2: Configure Wagmi for Base Account support + +Set up Wagmi with the Base Account connector (embedded wallets will be handled separately via CDP React providers): + +```typescript +// config/wagmi.ts +import { createConfig, http } from 'wagmi'; +import { base, baseSepolia } from 'wagmi/chains'; +import { baseAccount } from 'wagmi/connectors'; + +// Base Account connector +const baseAccountConnector = baseAccount({ + appName: process.env.NEXT_PUBLIC_APP_NAME || 'Your App', +}); + +// Wagmi config (only for Base Account - embedded wallets handled by CDP React providers) +export const wagmiConfig = createConfig({ + connectors: [baseAccountConnector], + chains: [baseSepolia, base], // Put baseSepolia first for testing + transports: { + [base.id]: http(), + [baseSepolia.id]: http(), + }, +}); +``` + +### Step 3: Set up application providers + +Wrap your application with the necessary providers. **Important**: Use `CDPHooksProvider` to properly manage embedded wallet authentication state: + +```typescript +// app/layout.tsx +'use client'; + +import { WagmiProvider } from 'wagmi'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { CDPHooksProvider } from '@coinbase/cdp-hooks'; +import { wagmiConfig } from '../config/wagmi'; + +const queryClient = new QueryClient(); + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + + + + {children} + + + + + + ); +} +``` + +### Step 4: Create unified authentication hook + +Build a custom hook to manage both wallet types. Using `CDPHooksProvider` ensures users get their existing embedded wallets when they sign in again, rather than creating new ones each time. + + +```typescript +// hooks/useUnifiedAuth.ts +import { useAccount, useConnect, useDisconnect } from 'wagmi'; +import { useSignInWithEmail, useVerifyEmailOTP, useIsSignedIn, useEvmAddress, useSignOut } from '@coinbase/cdp-hooks'; +import { useState, useEffect } from 'react'; + +export type WalletType = 'base_account' | 'embedded' | 'none'; + +export function useUnifiedAuth() { + // Wagmi hooks for Base Account + const { address: wagmiAddress, isConnected: wagmiConnected, connector } = useAccount(); + const { connect, connectors } = useConnect(); + const { disconnect: wagmiDisconnect } = useDisconnect(); + + // CDP hooks for embedded wallet - these work with CDPHooksProvider + const { signInWithEmail, isLoading: isSigningIn } = useSignInWithEmail(); + const { verifyEmailOTP, isLoading: isVerifying } = useVerifyEmailOTP(); + const { isSignedIn: cdpSignedIn } = useIsSignedIn(); + const { evmAddress: cdpAddress } = useEvmAddress(); + const { signOut } = useSignOut(); + + const [walletType, setWalletType] = useState('none'); + const [flowId, setFlowId] = useState(''); + + // Determine which wallet is active and prioritize the active one + const address = wagmiConnected ? wagmiAddress : cdpAddress; + const isConnected = wagmiConnected || cdpSignedIn; + + useEffect(() => { + if (wagmiConnected && connector?.name === 'Base Account') { + setWalletType('base_account'); + } else if (cdpSignedIn && cdpAddress) { + setWalletType('embedded'); + } else { + setWalletType('none'); + } + }, [wagmiConnected, cdpSignedIn, connector, cdpAddress]); + + const connectBaseAccount = () => { + const baseConnector = connectors.find(c => c.name === 'Base Account'); + if (baseConnector) { + connect({ connector: baseConnector }); + } + }; + + const signInWithEmbeddedWallet = async (email: string) => { + try { + const response = await signInWithEmail({ email }); + + // Capture flowId for OTP verification + if (response && typeof response === 'object' && 'flowId' in response) { + setFlowId(response.flowId as string); + } + + return true; + } catch (error) { + console.error('Failed to sign in with email:', error); + return false; + } + }; + + const verifyOtpAndConnect = async (otp: string) => { + try { + // With CDPReactProvider, verifyEmailOTP automatically signs the user in + await verifyEmailOTP({ flowId, otp }); + return true; + } catch (error) { + console.error('Failed to verify OTP:', error); + return false; + } + }; + + const disconnect = async () => { + if (wagmiConnected) { + wagmiDisconnect(); + } + + if (cdpSignedIn || walletType === 'embedded') { + try { + await signOut(); + } catch (error) { + console.error('CDP sign out failed:', error); + } + } + }; + + return { + address, + isConnected, + walletType, + connectBaseAccount, + signInWithEmbeddedWallet, + verifyOtpAndConnect, + disconnect, + isSigningIn, + isVerifying, + }; +} +``` + +### Step 5: Build authentication component + +Create a component that presents both authentication options: + +```typescript +// components/WalletAuthButton.tsx +'use client'; + +import { useState } from 'react'; +import { useUnifiedAuth } from '../hooks/useUnifiedAuth'; + +export function WalletAuthButton() { + const { + address, + isConnected, + walletType, + connectBaseAccount, + signInWithEmbeddedWallet, + verifyOtpAndConnect, + disconnect, + isSigningIn, + isVerifying, + } = useUnifiedAuth(); + + const [authStep, setAuthStep] = useState<'select' | 'email' | 'otp'>('select'); + const [email, setEmail] = useState(''); + const [otp, setOtp] = useState(''); + + // Connected state + if (isConnected && address) { + const walletDisplay = { + base_account: { name: 'Base Account', icon: '🟦' }, + embedded: { name: 'Embedded Wallet', icon: '📱' }, + }[walletType] || { name: 'Connected', icon: '✅' }; + + return ( +
+ {walletDisplay.icon} +
+
{walletDisplay.name}
+
+ {address.slice(0, 6)}...{address.slice(-4)} +
+
+ +
+ ); + } + + // OTP verification + if (authStep === 'otp') { + return ( +
+
+

Check your email

+

Enter the code sent to {email}

+
+ + setOtp(e.target.value)} + placeholder="000000" + maxLength={6} + className="w-full px-3 py-2 border rounded text-center font-mono" + /> + +
+ + + +
+
+ ); + } + + // Email input + if (authStep === 'email') { + return ( +
+

Create account

+ + setEmail(e.target.value)} + placeholder="your@email.com" + className="w-full px-3 py-2 border rounded" + /> + +
+ + + +
+
+ ); + } + + // Initial selection + return ( +
+

Connect Your Wallet

+ + + + +
+ ); +} +``` + +### Step 6: Handle transactions for each wallet type + +Create a transaction component that adapts to each wallet type: + +```typescript +// components/SendTransaction.tsx +import { useState } from 'react'; +import { parseEther } from 'viem'; +import { useSendTransaction, useWaitForTransactionReceipt, useAccount, useSwitchChain } from 'wagmi'; +import { base, baseSepolia } from 'wagmi/chains'; +import { useUnifiedAuth } from '../hooks/useUnifiedAuth'; + +export function SendTransaction() { + const { address, walletType } = useUnifiedAuth(); + const { chain } = useAccount(); + const { switchChain } = useSwitchChain(); + const [amount, setAmount] = useState(''); + const [recipient, setRecipient] = useState(''); + + const { data: hash, sendTransaction, isPending, error } = useSendTransaction(); + const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash }); + + const handleTransaction = async () => { + if (!address || !amount || !recipient) return; + + try { + sendTransaction({ + to: recipient as `0x${string}`, + value: parseEther(amount), + }); + } catch (error) { + console.error('Transaction failed:', error); + } + }; + + // Show different guidance based on wallet type + const getTransactionGuidance = () => { + switch (walletType) { + case 'base_account': + return { + title: 'Base Account Transaction', + description: 'You\'ll be prompted to confirm with your passkey', + icon: '🔐' + }; + case 'embedded': + return { + title: 'Embedded Wallet Transaction', + description: 'Transaction will be signed automatically', + icon: '⚡' + }; + default: + return { title: 'Send Transaction', description: '', icon: '💸' }; + } + }; + + const guidance = getTransactionGuidance(); + + if (!address) return null; + + return ( +
+
+
{guidance.icon}
+

{guidance.title}

+

{guidance.description}

+ + {/* Network indicator and switch */} +
+
+ + Network: {chain?.name || 'Unknown'} + +
+ {chain?.id !== baseSepolia.id && ( + + )} + {chain?.id !== base.id && ( + + )} +
+
+
+
+ +
+
+ + setAmount(e.target.value)} + placeholder="0.001" + step="0.001" + className="w-full px-3 py-2 border border-gray-300 rounded" + /> +
+ +
+ + setRecipient(e.target.value)} + placeholder="0x..." + className="w-full px-3 py-2 border border-gray-300 rounded font-mono text-sm" + /> +
+ + {error && ( +
+

Error: {error.message}

+
+ )} + + + + {isSuccess && hash && ( +
+

✅ Transaction Confirmed!

+ + View on {chain?.id === baseSepolia.id ? 'Sepolia ' : ''}Basescan → + +
+ )} +
+
+ ); +} +``` + +### Step 7: Complete your app + +Put everything together in your main application: + +```typescript +// app/page.tsx +'use client'; + +import { WalletAuthButton } from '../components/WalletAuthButton'; +import { SendTransaction } from '../components/SendTransaction'; +import { useAccount } from 'wagmi'; + +export default function HomePage() { + const { isConnected } = useAccount(); + + return ( +
+
+
+

CDP + Base Account Demo

+

+ One app supporting both Base Account and embedded wallet users +

+
+ +
+ + {isConnected && } +
+
+
+ ); +} +``` + +## Troubleshooting + +### Common Issues + +**Base Account connector not appearing** +- Verify the Base Account SDK, `@base-org/account`, is installed and up-to-date +- Check wagmi configuration includes Base Account connector +- Ensure app is running on Base or Base Sepolia network + +**CDP Embedded Wallet authentication failing** +- Verify CDP Project ID is correct in environment variables +- **Critical**: Add your domains (e.g., `http://localhost:3000`, `http://localhost:3001`) to CDP Portal → Wallets → Embedded Wallet settings → Allowed domains +- Ensure all required CDP packages (see above) are installed + +**New wallet created each time instead of signing into existing wallet** +- Ensure you're using `CDPHooksProvider` with proper config in your layout +- Verify CDP Project ID is correctly configured +- Check that hooks are imported from `@coinbase/cdp-hooks` consistently + +**Users can't switch between wallet types** +- Implement proper disconnect flow before connecting different type +- Clear any cached authentication state when switching +- Provide clear UI guidance for wallet type selection + +## Enhanced integration coming soon + +We are actively working on native Base Account integration with CDP Embedded Wallets that will enable: + +- **Unified connector**: Single CDP connector to handle both wallet types seamlessly +- **Spend permissions**: Sub Accounts will be able to access parent Base Account balance with limits +- **Sub Account creation**: Base Account users will be able to create app-specific Sub Accounts + +## Resources + +- [CDP Embedded Wallets Documentation](https://docs.cdp.coinbase.com/embedded-wallets/) +- [CDP React Components Documentation](https://docs.cdp.coinbase.com/embedded-wallets/components) +- [Base Account Wagmi Setup](/base-account/framework-integrations/wagmi/setup) +- [CDP Portal](https://portal.cdp.coinbase.com/) +- [Wagmi Documentation](https://wagmi.sh/) + +Monitor the [CDP documentation](https://docs.cdp.coinbase.com/) for updates on enhanced Embedded Wallet Base Account integration features. \ No newline at end of file