cdp-wallet-onramp-kit
Version:
Complete toolkit for Coinbase Developer Platform (CDP) Embedded Wallets and Onramp integration with reusable components, utilities, and documentation
922 lines (716 loc) • 28.8 kB
Markdown
# Add Coinbase Onramp to Existing Next.js App
**Purpose:** Comprehensive instructions for integrating [Coinbase Developer Platform (CDP) Onramp](https://docs.cdp.coinbase.com/onramp-offramp/docs) into an existing Next.js (App Router) application using local documentation and best practices.
**Scope:** All AI-generated advice or code related to CDP Onramp must follow these guardrails and utilize the provided documentation.
---
## **🚨 ULTRA-CRITICAL: USE CDP SDK FROM THE START 🚨**
**BEFORE YOU DO ANYTHING ELSE:** Install and use the CDP SDK. Do NOT try to manually implement JWT generation or key format conversion.
```bash
# Install the CDP SDK FIRST - this handles everything automatically
npm install @coinbase/cdp-sdk @coinbase/cdp-react @coinbase/cdp-hooks
```
**WHY THIS IS CRITICAL:**
- ✅ **Automatic Key Format Handling**: Works with PEM, base64, or any CDP key format
- ✅ **No Manual JWT Generation**: SDK handles all authentication complexity
- ✅ **Built-in Error Handling**: Proper error messages and format detection
- ✅ **Proven Implementation**: Same code used in production CDP apps
**NEVER DO THIS:**
```tsx
// ❌ DON'T manually implement JWT generation
import { sign } from 'jsonwebtoken';
// ❌ DON'T manually convert key formats
const keyBuffer = Buffer.from(keySecretBase64, 'base64');
```
**ALWAYS DO THIS:**
```tsx
// ✅ USE CDP SDK for everything
import { generateJwt } from '@coinbase/cdp-sdk/auth';
const jwt = await generateJwt({ apiKeyId, apiKeySecret, ... });
```
---
## **STEP 1: Install Documentation Package**
**IMPORTANT:** Before starting the integration, install the onramp documentation package to access comprehensive guides and examples:
```bash
npx -p onramp-docs-cli onramp-docs setup
```
Or use the interactive setup:
```bash
npx -p onramp-docs-cli onramp-setup
```
This will create `doc/onramp/` in your project with the following documentation:
- `onramp/intro.md` - API authentication and CDP setup
- `onramp/onramp-overview.md` - Introduction and supported countries
- `onramp/generating-onramp-url.md` - URL generation and session tokens
- `onramp/one-click-buy-url.md` - Pre-filled buy experience
- `onramp/generating-quotes.md` - Getting price quotes
- `onramp/countries-and-currencies.md` - Supported regions and payment methods
- `onramp/secure-init.md` - **MANDATORY** secure initialization requirements
- `onramp/create-session-token.md` - Session token API details
- `onramp/transaction-status.md` - Transaction monitoring
**CRITICAL:** Reference these documentation files throughout the integration process for specific implementation details, troubleshooting, and best practices.
---
## **🚨 CRITICAL INSTRUCTIONS FOR AI CODING AGENTS 🚨**
### **PRE-IMPLEMENTATION REQUIREMENTS**
**BEFORE** writing any code, you **MUST** read these documentation files in order:
1. **READ FIRST**: `doc/onramp/onramp/secure-init.md`
- **Learn**: Session token mandatory migration (7/31/2025)
- **Learn**: Session token properties (5-minute expiration, single-use)
- **Learn**: Portal configuration toggle requirement
2. **READ SECOND**: `doc/onramp/onramp/intro.md`
- **Learn**: JWT authentication setup for server-side operations
- **Learn**: Secret vs Client API key differences
- **Learn**: Environment variable setup patterns
3. **READ THIRD**: `doc/onramp/onramp/countries-and-currencies.md`
- **Learn**: Country/state validation requirements (US states have asset restrictions)
- **Learn**: Payment method limits by region
- **Learn**: Required config and options API calls
### **ABSOLUTE REQUIREMENTS FOR NEXT.JS**
You **MUST** follow these requirements or the implementation will **BREAK**:
1. **MANDATORY Session Token Usage** - See `doc/onramp/onramp/secure-init.md` for complete details:
```tsx
// ❌ WRONG - Will fail after 7/31/2025
const url = `https://pay.coinbase.com/buy/select-asset?appId=${appId}&addresses=${addresses}`;
// ✅ CORRECT - Uses session token (see secure-init.md)
const sessionToken = await getSessionToken();
const url = `https://pay.coinbase.com/buy/select-asset?sessionToken=${sessionToken}`;
```
2. **NEVER expose Secret API Keys on client** - Only in server actions/API routes (see `doc/onramp/onramp/intro.md`):
```tsx
// ❌ WRONG - Secret key exposed on client
"use client";
const SECRET_KEY = process.env.CDP_SECRET_KEY; // SECURITY VIOLATION
// ✅ CORRECT - Server-side only (see intro.md JWT section)
export async function getSessionToken() {
'use server';
const jwt = await generateJWT(); // Server-side operation
}
```
3. **ALWAYS validate user location FIRST** - See `doc/onramp/onramp/countries-and-currencies.md`:
- US users MUST provide state (NY has asset restrictions)
- Check supported countries via config API
- Validate payment limits per region
4. **ALWAYS add `"use client"` directive** for browser APIs/React hooks
5. **ALWAYS use App Router structure** (`app/` directory, not `pages/`)
### **CRITICAL ERROR PATTERNS TO AVOID**
Reference `doc/onramp/onramp/` files to avoid these failures:
```tsx
// ❌ MISSING USER LOCATION VALIDATION - Will fail for restricted regions
// Must read countries-and-currencies.md first
// ❌ REUSING SESSION TOKENS - Single-use only (secure-init.md)
const token = await getSessionToken();
window.open(`...?sessionToken=${token}`); // First use
window.open(`...?sessionToken=${token}`); // WILL FAIL - token already used
// ❌ NO PAYMENT LIMIT VALIDATION - Different limits per country/method
// Must check countries-and-currencies.md for min/max amounts
// ❌ MISSING GUEST CHECKOUT HANDLING - See onramp-overview.md
// Users without Coinbase accounts → guest flow (different limits/methods)
```
---
## **STEP 2: Choose Integration Approach**
**FIRST**: Read `doc/onramp/onramp/onramp-overview.md` to understand supported countries and user flows.
Select ONE approach based on your UX requirements:
### **Option A: Progressive Enhancement (Recommended)**
1. **Start with country validation** (see `doc/onramp/onramp/countries-and-currencies.md`)
2. **Show appropriate payment methods** per user's location
3. **Generate session tokens** with location-appropriate assets
- **Best for**: Production apps with global users
- **Docs to read**: `countries-and-currencies.md` → `generating-onramp-url.md`
### **Option B: One-Click-Buy (Best UX for Known Intent)**
- **When to use**: User already selected asset + amount in your app
- **Requirements**: Must validate payment limits per user's country first
- **Docs to read**: `one-click-buy-url.md` + `generating-quotes.md`
- **Critical**: Read quote limitations in `generating-quotes.md`
### **Option C: OnchainKit Components (Rapid Development)**
- **Built-in handling**: Country validation, session tokens, error states
- **Limitation**: Less customization control
- **Still required**: User location detection for asset filtering
- **Integration docs**: OnchainKit + `doc/onramp/onramp/generating-onramp-url.md`
---
## **STEP 3: Installation and Setup**
### **Install Required Packages**
```bash
# 🚨 CRITICAL: Install CDP SDK FIRST - this handles everything
npm install @coinbase/cdp-sdk @coinbase/cdp-react @coinbase/cdp-hooks
# Additional packages for specific features
npm install @coinbase/onchainkit # For OnchainKit integration (Option C only)
npm install @vercel/edge # For user location detection (REQUIRED for all options)
```
### **Environment Configuration**
Create or update your `.env.local` file:
```bash
# Project ID from https://portal.cdp.coinbase.com
NEXT_PUBLIC_CDP_PROJECT_ID=your-project-id-here
# Secret API Key (server-side only - see intro.md for creation)
# 🚨 NOTE: CDP SDK handles ANY key format automatically (PEM, base64, etc.)
CDP_SECRET_KEY=your-secret-api-key-here
CDP_SECRET_KEY_NAME=organizations/your-org/apiKeys/your-key-id
# Alternative variable names (CDP SDK works with any of these):
CDP_API_KEY_NAME=your-key-id
CDP_API_KEY_PRIVATE_KEY=your-base64-or-pem-key
```
### **Portal Configuration (MANDATORY)**
**BEFORE any code works**, configure your project:
1. **Create Secret API Key**: Follow `doc/onramp/onramp/intro.md` setup instructions
2. **Enable Secure Init**: Read `doc/onramp/onramp/secure-init.md` Step 5 - toggle "Enforce secure initialization" in Portal
3. **Save API key securely** (never commit to version control)
**Reference**: `doc/onramp/onramp/secure-init.md` for complete portal setup requirements.
---
## **STEP 4: CDP Provider Setup (CRITICAL)**
**🚨 CRITICAL:** You MUST set up the CDP provider correctly or hooks won't work.
### **Create CDP Provider**
```tsx
// components/cdp-provider.tsx
"use client";
import { CDPReactProvider } from '@coinbase/cdp-react';
const cdpConfig = {
projectId: process.env.NEXT_PUBLIC_CDP_PROJECT_ID!,
basePath: "https://api.cdp.coinbase.com/platform",
useMock: false,
debugging: true, // Enable for development
};
const appConfig = {
name: "Your App Name",
logoUrl: "/logo.png",
};
export function CDPProvider({ children }: { children: React.ReactNode }) {
return (
<CDPReactProvider config={cdpConfig} app={appConfig}>
{children}
</CDPReactProvider>
);
}
```
### **Add to Layout**
```tsx
// app/layout.tsx
import { CDPProvider } from '@/components/cdp-provider';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<CDPProvider>
{children}
</CDPProvider>
</body>
</html>
);
}
```
**🚨 WHY THIS IS CRITICAL:** Without the CDP provider, all hooks will return `false` for `isInitialized` and `isSignedIn`, even after successful authentication.
---
## **STEP 5: Server-Side Setup (Using CDP SDK)**
### **Create JWT Authentication Utility (Using CDP SDK)**
**🚨 CRITICAL:** Use the CDP SDK instead of manual JWT generation:
```tsx
// lib/auth.ts
import { generateJwt } from '@coinbase/cdp-sdk/auth'
/**
* Generate a JSON Web Token (JWT) for CDP API authentication
* Required for all CDP Onramp API calls
*/
export async function generateJWT(): Promise<string> {
const privateKeyString = process.env.CDP_SECRET_KEY || process.env.CDP_API_KEY_PRIVATE_KEY
const keyName = process.env.CDP_SECRET_KEY_NAME || process.env.CDP_API_KEY_NAME
if (!privateKeyString || !keyName) {
throw new Error('CDP_SECRET_KEY and CDP_SECRET_KEY_NAME must be set')
}
try {
// 🚨 USE CDP SDK - handles all key formats automatically
const jwt = await generateJwt({
apiKeyId: keyName,
apiKeySecret: privateKeyString, // Works with PEM, base64, or any format
requestMethod: 'POST',
requestHost: 'api.developer.coinbase.com',
requestPath: '/onramp/v1/token',
expiresIn: 120 // 2 minutes
})
return jwt
} catch (error) {
console.error('JWT generation failed:', error)
console.error('Key format detected:', privateKeyString.startsWith('-----BEGIN') ? 'PEM' : 'Base64')
throw new Error('Failed to generate JWT. Please check your CDP API key format.')
}
}
/**
* Create a session token for initializing the onramp widget
* This token is single-use and expires after 5 minutes
*/
export async function createSessionToken(
jwt: string,
params: {
addresses: Array<{
address: string
blockchains: string[]
}>
assets?: string[]
}
): Promise<string> {
const response = await fetch('https://api.developer.coinbase.com/onramp/v1/token', {
method: 'POST',
headers: {
'Authorization': `Bearer ${jwt}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(params)
})
if (!response.ok) {
const error = await response.text()
throw new Error(`Failed to create session token: ${error}`)
}
const data = await response.json()
return data.token || data.data?.token
}
/**
* Generate a complete onramp URL with session token
* Use this for programmatic onramp URL generation
*/
export async function generateOnrampUrl(params: {
addresses: Array<{
address: string
blockchains: string[]
}>
assets?: string[]
defaultAsset?: string
defaultNetwork?: string
presetFiatAmount?: number
presetCryptoAmount?: number
redirectUrl?: string
partnerUserId?: string
}): Promise<string> {
// Generate JWT and session token
const jwt = await generateJWT()
const sessionToken = await createSessionToken(jwt, {
addresses: params.addresses,
assets: params.assets
})
// Build URL with session token and optional parameters
const url = new URL('https://pay.coinbase.com/buy/select-asset')
url.searchParams.set('sessionToken', sessionToken)
if (params.defaultAsset) {
url.searchParams.set('defaultAsset', params.defaultAsset)
}
if (params.defaultNetwork) {
url.searchParams.set('defaultNetwork', params.defaultNetwork)
}
if (params.presetFiatAmount) {
url.searchParams.set('presetFiatAmount', params.presetFiatAmount.toString())
}
if (params.presetCryptoAmount) {
url.searchParams.set('presetCryptoAmount', params.presetCryptoAmount.toString())
}
if (params.redirectUrl) {
url.searchParams.set('redirectUrl', params.redirectUrl)
}
if (params.partnerUserId) {
url.searchParams.set('partnerUserId', params.partnerUserId)
}
return url.toString()
}
```
### **Create Session Token API Route**
```tsx
// app/api/onramp/session-token/route.ts
import { generateJWT } from '@/lib/auth';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
try {
const { addresses, assets } = await request.json();
// Generate JWT for authentication
const jwt = await generateJWT();
// Call CDP Session Token API
const response = await fetch('https://api.developer.coinbase.com/onramp/v1/token', {
method: 'POST',
headers: {
'Authorization': `Bearer ${jwt}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
addresses,
assets: assets || ['ETH', 'USDC', 'BTC'],
}),
});
if (!response.ok) {
throw new Error(`Session token API failed: ${response.status}`);
}
const data = await response.json();
return NextResponse.json({
token: data.data.token,
channelId: data.data.channel_id
});
} catch (error) {
console.error('Session token generation failed:', error);
return NextResponse.json(
{ error: 'Failed to generate session token' },
{ status: 500 }
);
}
}
```
---
## **STEP 6: Client-Side Integration**
### **Option A: Direct URL Integration**
**IMPORTANT:** Create a client component for onramp functionality:
```tsx
// components/OnrampButton.tsx
"use client";
import { useState } from 'react';
interface OnrampButtonProps {
walletAddress: string;
className?: string;
children?: React.ReactNode;
}
export function OnrampButton({
walletAddress,
className = '',
children = 'Buy Crypto'
}: OnrampButtonProps) {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleOnramp = async () => {
if (!walletAddress) {
setError('Wallet address is required');
return;
}
setIsLoading(true);
setError(null);
try {
// Generate session token via API route
const response = await fetch('/api/onramp/session-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
addresses: [
{
address: walletAddress,
blockchains: ['base', 'ethereum']
}
],
assets: ['ETH', 'USDC', 'BTC']
}),
});
if (!response.ok) {
throw new Error('Failed to generate session token');
}
const { token } = await response.json();
// Construct onramp URL with session token
const onrampUrl = new URL('https://pay.coinbase.com/buy/select-asset');
onrampUrl.searchParams.set('sessionToken', token);
// Optional: Add default parameters for better UX
onrampUrl.searchParams.set('defaultAsset', 'USDC');
onrampUrl.searchParams.set('defaultNetwork', 'base');
// Redirect to onramp
window.open(onrampUrl.toString(), '_blank');
} catch (error) {
console.error('Onramp error:', error);
setError(error instanceof Error ? error.message : 'Failed to open onramp');
} finally {
setIsLoading(false);
}
};
return (
<div>
<button
onClick={handleOnramp}
disabled={isLoading || !walletAddress}
className={`px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 ${className}`}
>
{isLoading ? 'Opening...' : children}
</button>
{error && (
<p className="mt-2 text-sm text-red-600">{error}</p>
)}
</div>
);
}
```
### **Option B: OnchainKit Integration**
```tsx
// components/OnchainKitFund.tsx
"use client";
import { FundButton, FundCard } from '@coinbase/onchainkit/fund';
import { base } from 'viem/chains';
interface OnchainKitFundProps {
walletAddress: string;
}
export function OnchainKitFund({ walletAddress }: OnchainKitFundProps) {
return (
<div className="space-y-4">
{/* Option 1: Fund Button */}
<FundButton
fundingUrl={`https://pay.coinbase.com/buy/select-asset?appId=${process.env.NEXT_PUBLIC_CDP_PROJECT_ID}&addresses=[{"address":"${walletAddress}","blockchains":["base"]}]`}
/>
{/* Option 2: Fund Card (full UI) */}
<FundCard
fundingUrl={`https://pay.coinbase.com/buy/select-asset?appId=${process.env.NEXT_PUBLIC_CDP_PROJECT_ID}&addresses=[{"address":"${walletAddress}","blockchains":["base"]}]`}
className="w-full max-w-md"
/>
</div>
);
}
```
### **Option C: One-Click-Buy Implementation**
```tsx
// components/OneClickBuy.tsx
"use client";
import { useState } from 'react';
interface OneClickBuyProps {
walletAddress: string;
asset?: string;
amount?: number;
currency?: string;
}
export function OneClickBuy({
walletAddress,
asset = 'USDC',
amount = 10,
currency = 'USD'
}: OneClickBuyProps) {
const [isLoading, setIsLoading] = useState(false);
const handleOneClickBuy = async () => {
setIsLoading(true);
try {
// Generate session token
const response = await fetch('/api/onramp/session-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
addresses: [{ address: walletAddress, blockchains: ['base'] }],
assets: [asset]
}),
});
const { token } = await response.json();
// Create one-click-buy URL
const onrampUrl = new URL('https://pay.coinbase.com/buy/select-asset');
onrampUrl.searchParams.set('sessionToken', token);
onrampUrl.searchParams.set('defaultAsset', asset);
onrampUrl.searchParams.set('presetFiatAmount', amount.toString());
onrampUrl.searchParams.set('fiatCurrency', currency);
onrampUrl.searchParams.set('defaultPaymentMethod', 'CARD');
window.open(onrampUrl.toString(), '_blank');
} catch (error) {
console.error('One-click buy failed:', error);
} finally {
setIsLoading(false);
}
};
return (
<button
onClick={handleOneClickBuy}
disabled={isLoading}
className="px-6 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 disabled:opacity-50"
>
{isLoading ? 'Loading...' : `Buy ${amount} ${currency} of ${asset}`}
</button>
);
}
```
---
## **STEP 7: Integration into Your App**
### **Add to Your Existing Components**
```tsx
// app/wallet/page.tsx or your existing wallet component
import { OnrampButton } from '@/components/OnrampButton';
import { OneClickBuy } from '@/components/OneClickBuy';
export default function WalletPage() {
// Assume you have wallet address from your existing wallet integration
const walletAddress = "0x1234..."; // Your user's wallet address
return (
<div className="space-y-6">
<h1>Your Wallet</h1>
{/* Basic onramp integration */}
<div>
<h2>Fund Your Wallet</h2>
<OnrampButton walletAddress={walletAddress} />
</div>
{/* One-click buy options */}
<div>
<h2>Quick Buy</h2>
<div className="space-y-2">
<OneClickBuy
walletAddress={walletAddress}
asset="USDC"
amount={10}
currency="USD"
/>
<OneClickBuy
walletAddress={walletAddress}
asset="ETH"
amount={50}
currency="USD"
/>
</div>
</div>
</div>
);
}
```
---
## **STEP 8: Advanced Features**
### **Transaction Status Monitoring**
For transaction status updates, reference `doc/onramp/onramp/transaction-status.md`:
```tsx
// lib/onramp-status.ts
import { generateJWT } from './auth';
export async function getTransactionStatus(transactionId: string) {
const jwt = await generateJWT('GET', 'api.developer.coinbase.com', `/onramp/v1/transactions/${transactionId}`);
const response = await fetch(`https://api.developer.coinbase.com/onramp/v1/transactions/${transactionId}`, {
headers: {
'Authorization': `Bearer ${jwt}`,
},
});
return response.json();
}
```
### **Quote Generation**
For getting price quotes before purchase, see `doc/onramp/onramp/generating-quotes.md`:
```tsx
// app/api/onramp/quote/route.ts
import { generateJWT } from '@/lib/auth';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
try {
const quoteParams = await request.json();
const jwt = await generateJWT('POST', 'api.developer.coinbase.com', '/onramp/v1/buy/quote');
const response = await fetch('https://api.developer.coinbase.com/onramp/v1/buy/quote', {
method: 'POST',
headers: {
'Authorization': `Bearer ${jwt}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(quoteParams),
});
const data = await response.json();
return NextResponse.json(data);
} catch (error) {
return NextResponse.json({ error: 'Quote generation failed' }, { status: 500 });
}
}
```
---
## **STEP 9: Testing and Verification**
### **Development Testing**
1. **Start your development server:**
```bash
npm run dev
```
2. **Test the integration:**
- Click the onramp button
- Verify session token generation
- Test the onramp flow in sandbox mode
- Verify wallet address is correctly passed
3. **Check console for errors** - Monitor both client and server logs
4. **Test different scenarios:**
- Different wallet addresses
- Various asset types
- Different amount presets
### **Common Issues and Solutions**
Reference `doc/onramp/onramp/` documentation for troubleshooting:
1. **Session Token Errors:** Check `intro.md` for authentication setup
2. **Missing Wallet Address:** Ensure wallet is connected before showing onramp
3. **CORS Issues:** Verify domain is whitelisted in CDP dashboard
4. **Rate Limiting:** Implement proper error handling for 429 responses
---
## **CRITICAL LOCATION VALIDATION PATTERN**
**MANDATORY:** Always validate user location before showing onramp (will fail without this):
```tsx
// components/LocationAwareOnramp.tsx
"use client";
import { useState, useEffect } from 'react';
export function LocationAwareOnramp({ walletAddress }: { walletAddress: string }) {
const [locationData, setLocationData] = useState<any>(null);
const [isSupported, setIsSupported] = useState<boolean | null>(null);
useEffect(() => {
async function checkLocation() {
try {
// Get user location (implement your geo detection)
const location = await getUserLocation(); // { country: 'US', subdivision: 'NY' }
// Validate support via your API route
const response = await fetch(`/api/onramp/validate-location?country=${location.country}&subdivision=${location.subdivision}`);
const data = await response.json();
setLocationData(location);
setIsSupported(data.supported);
} catch (error) {
console.error('Location check failed:', error);
setIsSupported(false);
}
}
checkLocation();
}, []);
if (isSupported === null) return <div>Checking availability...</div>;
if (!isSupported) return <div>Onramp not available in your region</div>;
// Show onramp components only after location validation
return <OnrampButton walletAddress={walletAddress} location={locationData} />;
}
```
## **PRODUCTION ERROR HANDLING PATTERNS**
**CRITICAL:** Handle these failure scenarios (see relevant doc files):
```tsx
// Essential error handling for production
export async function handleOnrampErrors(error: any, context: string) {
// Rate limiting (see onramp-overview.md - 10 req/sec limit)
if (error.status === 429) {
throw new Error('Rate limited. Please try again in a few seconds.');
}
// Session token expired/reused (see secure-init.md - 5min expiry, single-use)
if (error.message?.includes('session_token')) {
throw new Error('Session expired. Please refresh and try again.');
}
// Country/payment restrictions (see countries-and-currencies.md)
if (error.message?.includes('not_supported')) {
throw new Error('This payment method is not available in your region.');
}
// Payment amount limits (see countries-and-currencies.md)
if (error.message?.includes('amount')) {
throw new Error('Amount outside allowed limits for your region.');
}
// Generic fallback
console.error(`Onramp error in ${context}:`, error);
throw new Error('Onramp service temporarily unavailable.');
}
```
## **AI MODEL VERIFICATION STEPS**
Before generating any CDP Onramp code, you **MUST** verify:
1. **Pre-Implementation Reading**: Did you read the required docs in PRE-IMPLEMENTATION order?
2. **Location Validation**: Are you checking user location BEFORE showing onramp?
3. **Session Token Properties**: Are you handling 5-minute expiry and single-use correctly?
4. **Payment Limits**: Are you validating amounts against country/payment method limits?
5. **Guest Checkout**: Are you handling users without Coinbase accounts ($500/week limit)?
6. **Error Handling**: Did you implement production error patterns above?
7. **Rate Limiting**: Are you handling 10 req/sec API limits?
8. **Documentation Reference**: Did you reference specific doc files for each feature?
9. **Client Directive**: Does EVERY client-side file start with `"use client";`?
10. **Server-Side Security**: Are Secret API Keys only used in server actions/API routes?
If ANY check **fails**, **STOP** and revise until compliance is achieved.
---
## **DEPLOYMENT CHECKLIST**
Before deploying to production:
- [ ] **Location validation implemented** (CRITICAL - will fail without this)
- [ ] **Session tokens implemented** (mandatory after 7/31/2025)
- [ ] **Portal secure init toggle enabled** (see secure-init.md Step 5)
- [ ] **Error handling for all failure scenarios** (rate limiting, expired tokens, etc.)
- [ ] **User location detection** (country + US state validation)
- [ ] **Payment limit validation** per region
- [ ] **Guest checkout flow tested** ($500/week limit messaging)
- [ ] Environment variables set in deployment platform
- [ ] Secret API keys secured (never exposed on client)
- [ ] HTTPS enabled
- [ ] Loading states added
- [ ] Wallet connection verified
- [ ] Domain whitelisted in CDP dashboard
- [ ] Rate limiting handling implemented (10 req/sec)
- [ ] Documentation reviewed for production considerations
---
## **SUPPORT AND DOCUMENTATION**
- **Local Documentation:** `doc/onramp/onramp/` (installed via setup command)
- **CDP Portal:** https://portal.cdp.coinbase.com
- **Official Docs:** https://docs.cdp.coinbase.com/onramp-offramp/docs
- **OnchainKit:** https://onchainkit.xyz/fund/fund-card
- **Discord Support:** https://discord.com/invite/cdp (join #onramp channel)
**Remember:** Always reference the local documentation first, as it contains the most relevant patterns and examples for your specific use case.
---
**FINAL NOTE:** This integration enables your users to seamlessly purchase cryptocurrency and have it sent directly to their wallet address. The onramp handles all payment processing, KYC compliance, and cryptocurrency delivery, while giving you control over the user experience and integration points.