Overview

Embedded Wallets provides components that work with Coinbase’s Onramp API to enable developers to move money from fiat to onchain economies. A user can fund their wallet with their Coinbase account or through guest checkout with a debit card. This guide shows how to get started with the FundModal component.
Coinbase Onramp is enabled by default in trial mode for every CDP project. In trial mode, there are limitations to how much you can purchase.
The Fund and FundModal components will cost real money unless you enable mock buys and sends.

Quickstart

Get started in under 5 minutes with Embedded Wallet’s create-cdp-app package!

Prerequisites

  • A free CDP Portal account and project
  • Node.js 22+
  • A node package manager installed (i.e., npm, pnpm, or yarn)
  • Basic familiarity with Next.js and React
  • A Coinbase Retail account, if you wish to fund your wallet with Coinbase

1. Add your domain

To begin, add your domain to the list of allowed domains in CDP Portal.
1

Access CDP Portal

Navigate to the Domains Configuration in CDP Portal, and click Add domain to include your local app.
Add domain dialog in CDP Portal
2

Add your domain

Use http://localhost:3000 (the port your demo app will run locally).
Domain configuration with localhost
Do not do this in your CDP project intended for production use. Malicious apps running locally could impersonate your frontend and abuse your project credentials.
3

Save your changes

Click Add domain again to save your changes.
Domain configuration saved in CDP Portal
You should see your local app URL listed in the CDP Portal dashboard. The allowlist will take effect immediately upon saving.

2. Create the demo app

1

Create a Secret API Key

Navigate to the API Keys tab of the CDP Portal. Create and download your API key. Enter an API key nickname (restrictions are optional).
Create API Key button in CDP dashboard
Secure your private/public key pair in a safe location. You will use these in step 3 when configuring your demo app.
2

Copy your Project ID

Navigate to CDP Portal and select your project from the top-left dropdown. Clicking the gear icon will take you to your project details:
CDP Project ID in project settings
Copy the Project ID value. You will use this in the next step when configuring your demo app.
3

Create a new demo app

Use the latest version of create-cdp-app to create a new demo app using your package manager:
npm create @coinbase/cdp-app@latest
4

Configure your app

Follow the prompts to configure your app. Name your project, select the Next.js Full Stack App template, and paste your project ID from CDP Portal.
The Next.js Full Stack App template must be selected because Onramp requires server-side code!
You can choose to disable or enable Smart Accounts, and you should enable Onramp.To complete configuration, enter the API Key ID and API Key Secret key pair you created in the previous step and confirm that you have added your domain.
✔ Project name: … cdp-app-nextjs
✔ Template: › Next.js Full Stack App
✔ CDP Project ID: … 8c21e60b-c8af-4286-a0d3-111111111111
✔ Enable Smart Accounts?: … no
✔ Enable Coinbase Onramp?: … yes
✔ CDP API Key ID: … 9b12d52e-d2be-5516-bd90-111111111111
✔ CDP API Key Secret: … *****************************************
✔ Confirm you have whitelisted 'http://localhost:3000' … yes
5

Run your app

Navigate to your project and start the development server:
cd cdp-app-nextjs
npm install
npm run dev
Your app will be available at http://localhost:3000.

3. Demo your new wallet

Now that your embedded wallet is configured and your app is running, let’s try it out.
1

Sign in

Head to http://localhost:3000 and click the Sign In button.
The 'Sign in' button that begins the embedded wallet sign-in flow, and a welcome message.
2

Enter your email

The first step in the embedded wallet sign-in flow, where the user can sign in using their email address or phone number.
3

Verify

Enter the verification code sent to your e-mail.
Step 2 of the embedded wallet sign-in flow, where the user must enter a 6-digit verification code sent to their email to complete authentication.
4

View your new wallet

Congrats! Your new embedded wallet has been created, authenticated, and is ready to use on the Base network.
From the demo app, you can copy-and-paste your wallet address from the top-right corner. You can also fund your wallet and monitor your balance. You should see similar to the following:
The demo app home page after a successful sign-in, displaying the user's embedded wallet balance and an option to fund it on Base by depositing ETH.
Click the Deposit ETH button to start funding your new wallet.
5

Enter deposit details

This opens the funding modal where you can specify how much you want to deposit. Choose a preset amount or enter your own, select your preferred payment method, and click Deposit to proceed to the Coinbase Onramp widget.
The funding modal, where the user can deposit ETH into their embedded wallet. The modal shows various deposit amounts and has 'Coinbase' selected as the payment method, with the ability to choose other options.
6

Complete your purchase

The Coinbase Onramp widget opens for you to review the transaction details. Here, you can verify the payment method, destination address, and total cost before finalizing the purchase.
The Coinbase Onramp widget, where the user reviews transaction details like payment method, destination address, and total cost before confirming their purchase.
Click Confirm & Purchase to complete the transaction.
7

View your confirmation

Once the transaction is successful, you’ll see a confirmation message. The funds are now being sent to your wallet onchain, and your balance will update shortly.
A success modal with a green checkmark, indicating that the ETH deposit was successful. The modal is set to close automatically.
You can also find record of your wallet and its transaction on Base explorer using the URL: https://basescan.org/address/YOUR-WALLET-ADDRESS.

Manual setup

If you’d prefer to set integrate Onramp manually, this guide will show you how to do so.

Prerequisites

  • A free CDP Portal account and project
  • Node.js 22+
  • A node package manager installed (i.e., npm, pnpm, or yarn)
  • Basic familiarity with Next.js and React
  • A CDP project with Embedded Wallets enabled
  • @coinbase/cdp-core and @coinbase/cdp-hooks installed
  • A Coinbase Retail account, if you wish to fund your wallet with Coinbase

1. Create a Secret API Key

1

Create key

Navigate to the API Keys tab of the CDP Portal. Create and download your API key. Enter an API key nickname (restrictions are optional).
Create API Key button in CDP dashboard
Secure your private/public key pair in a safe location.
2

Update .env

Update your app’s .env file with the API Key ID and API Key Secret.
# CDP API Key
CDP_API_KEY_ID=[paste your API Key ID here]
CDP_API_KEY_SECRET=[paste your API Key Secret here]

2. Install @coinbase/cdp-sdk

The Onramp API requires authentication with a JWT. You can use @coinbase/cdp-sdk to generate one.
# With npm
npm install @coinbase/cdp-sdk

3. Create lib/cdp-auth.ts

Create a new file lib/cdp-auth.ts in your project root. This file exports helper functions to generate JWTs for authorizing Onramp API calls and provides the base URL for API requests.
lib/cdp-auth.ts
import { generateJwt } from "@coinbase/cdp-sdk/auth";

interface CDPAuthConfig {
  requestMethod: string;
  requestHost: string;
  requestPath: string;
  audience?: string[];
}

/**
 * Get CDP API credentials from environment variables
 *
 * @throws Error if credentials are not configured
 */
export function getCDPCredentials() {
  const apiKeyId = process.env.CDP_API_KEY_ID;
  const apiKeySecret = process.env.CDP_API_KEY_SECRET;

  if (!apiKeyId || !apiKeySecret) {
    throw new Error("CDP API credentials not configured");
  }

  return { apiKeyId, apiKeySecret };
}

/**
 * Generate JWT token for CDP API authentication
 *
 * @param config - Configuration for JWT generation
 * @returns JWT token string
 */
export async function generateCDPJWT(config: CDPAuthConfig): Promise<string> {
  const { apiKeyId, apiKeySecret } = getCDPCredentials();

  return generateJwt({
    apiKeyId,
    apiKeySecret,
    requestMethod: config.requestMethod,
    requestHost: config.requestHost,
    requestPath: config.requestPath,
  });
}

/**
 * Base URL for ONRAMP API
 * Can change to api.cdp.coinbase.com/platform once session token endpoints are supported in v2 API
 */
export const ONRAMP_API_BASE_URL = "https://api.developer.coinbase.com";
This utility file provides:
  • getCDPCredentials(): Reads your API credentials from environment variables
  • generateCDPJWT(): Creates authenticated JWT tokens for API calls
  • ONRAMP_API_BASE_URL: The base URL for all Onramp API requests
These functions will be imported and used in your API routes in the next step.

4. Set up server-side endpoints

You will need to create two server-side endpoints to interact with the Onramp API.
1

Transform response data helper

The FundModal component expects functions that return data in camel-case, so for now the data from the Onramp API needs to be transformed.
The v1 version of the Onramp API returns data with kebab-case keys. In v2, data will be returned with camel-case keys.
lib/to-camel-case.ts
type SnakeToCamelCase<S extends string> = S extends `${infer T}_${infer U}`
  ? `${T}${Capitalize<SnakeToCamelCase<U>>}`
  : S;

type CamelizeKeys<T> = T extends readonly unknown[]
  ? { [K in keyof T]: CamelizeKeys<T[K]> }
  : T extends object
    ? {
        [K in keyof T as SnakeToCamelCase<K & string>]: CamelizeKeys<T[K]>;
      }
    : T;

/**
 * Converts snake_case keys to camelCase in an object or array of objects.
 *
 * @param {T} obj - The object, array, or string to convert. (required)
 * @returns {T} The converted object, array, or string.
 */
export const convertSnakeToCamelCase = <T>(obj: T): CamelizeKeys<T> => {
  if (Array.isArray(obj)) {
    return obj.map(item => convertSnakeToCamelCase(item)) as CamelizeKeys<T>;
  }

  if (obj !== null && typeof obj === "object") {
    return Object.keys(obj).reduce((acc, key) => {
      const camelCaseKey = toCamelCase(key);
      (acc as Record<string, unknown>)[camelCaseKey] = convertSnakeToCamelCase(
        (obj as Record<string, unknown>)[key],
      );
      return acc;
    }, {} as CamelizeKeys<T>);
  }

  return obj as CamelizeKeys<T>;
};

const toCamelCase = (str: string) => {
  return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
};
2

Get buy options

The Buy Options API provides the available payment methods to the FundModal and Fund components.
app/api/onramp/buy-options/route.ts
import {
  type FetchBuyOptions,
  type OnrampBuyOptionsSnakeCaseResponse,
} from "@coinbase/cdp-react";
import { NextRequest, NextResponse } from "next/server";

import { generateCDPJWT, getCDPCredentials, ONRAMP_API_BASE_URL } from "@/lib/cdp-auth";
import { convertSnakeToCamelCase } from "@/lib/to-camel-case";

type OnrampBuyOptionsResponseRaw = OnrampBuyOptionsSnakeCaseResponse;
type OnrampBuyOptionsResponse = Awaited<ReturnType<FetchBuyOptions>>;

/**
 * Fetches available buy options (payment currencies and purchasable assets) for onramp
 *
 * @param request - NextRequest object
 * @returns NextResponse object
 */
export async function GET(request: NextRequest) {
  try {
    // Validate CDP credentials are configured
    try {
      getCDPCredentials();
    } catch (_error) {
      return NextResponse.json({ error: "CDP API credentials not configured" }, { status: 500 });
    }

    /**
     * Extract query parameters
     * Note: While the API documentation shows all parameters as optional,
     * the backend currently requires the 'country' parameter
     */
    const searchParams = request.nextUrl.searchParams;
    const country = searchParams.get("country");
    const subdivision = searchParams.get("subdivision");
    const networks = searchParams.get("networks");

    // Build query string
    const queryParams = new URLSearchParams();
    if (country) queryParams.append("country", country);
    if (subdivision) queryParams.append("subdivision", subdivision);
    if (networks) queryParams.append("networks", networks);

    const queryString = queryParams.toString();
    const apiPath = "/onramp/v1/buy/options";
    const fullPath = apiPath + (queryString ? `?${queryString}` : "");

    // Generate JWT for CDP API authentication
    const jwt = await generateCDPJWT({
      requestMethod: "GET",
      requestHost: new URL(ONRAMP_API_BASE_URL).hostname,
      requestPath: apiPath,
    });

    // Call CDP API to get buy options
    const response = await fetch(`${ONRAMP_API_BASE_URL}${fullPath}`, {
      method: "GET",
      headers: {
        Authorization: `Bearer ${jwt}`,
        "Content-Type": "application/json",
      },
    });

    if (!response.ok) {
      console.error("CDP API error:", response.statusText);
      const errorText = await response.text();
      console.error("Error details:", errorText);

      try {
        const errorData = JSON.parse(errorText);
        return NextResponse.json(
          { error: errorData.message || "Failed to fetch buy options" },
          { status: response.status },
        );
      } catch {
        return NextResponse.json(
          { error: "Failed to fetch buy options" },
          { status: response.status },
        );
      }
    }

    const data: OnrampBuyOptionsResponseRaw = await response.json();
    const dataCamelCase: OnrampBuyOptionsResponse = convertSnakeToCamelCase(data);
    return NextResponse.json(dataCamelCase);
  } catch (error) {
    console.error("Error fetching buy options:", error);
    return NextResponse.json({ error: "Internal server error" }, { status: 500 });
  }
}
3

Create buy quote

The Buy Quote API provides the exchange rate as well as the purchase URL to the Fund and FundModal components.
app/api/onramp/buy-quote/route.ts
import {
  type FetchBuyQuote,
  type OnrampBuyQuoteSnakeCaseResponse,
} from "@coinbase/cdp-react";
import { NextRequest, NextResponse } from "next/server";

import { generateCDPJWT, getCDPCredentials, ONRAMP_API_BASE_URL } from "@/lib/cdp-auth";
import { convertSnakeToCamelCase } from "@/lib/to-camel-case";

type OnrampBuyQuoteRequest = Parameters<FetchBuyQuote>[0];
type OnrampBuyQuoteResponseRaw = OnrampBuyQuoteSnakeCaseResponse;
type OnrampBuyQuoteResponse = Awaited<ReturnType<FetchBuyQuote>>;

/**
 * Creates a buy quote for onramp purchase
 *
 * @param request - Buy quote request parameters
 * @returns Buy quote with fees and onramp URL
 */
export async function POST(request: NextRequest) {
  try {
    const body: OnrampBuyQuoteRequest = await request.json();

    // Validate CDP credentials are configured
    try {
      getCDPCredentials();
    } catch (_error) {
      return NextResponse.json({ error: "CDP API credentials not configured" }, { status: 500 });
    }

    // Validate required fields

    // Note we don't require the wallet info because this endpoint is used to get an exchange rate. Only the onramp URL requires the wallet info.

    if (
      !body.purchaseCurrency ||
      !body.paymentAmount ||
      !body.paymentCurrency ||
      !body.paymentMethod ||
      !body.country
    ) {
      return NextResponse.json({ error: "Missing required parameters" }, { status: 400 });
    }

    // Validate US subdivision requirement
    if (body.country === "US" && !body.subdivision) {
      return NextResponse.json({ error: "State/subdivision is required for US" }, { status: 400 });
    }

    // Generate JWT for CDP API authentication
    const jwt = await generateCDPJWT({
      requestMethod: "POST",
      requestHost: new URL(ONRAMP_API_BASE_URL).hostname,
      requestPath: "/onramp/v1/buy/quote",
    });

    // Prepare request body for buy quote API
    const requestBody = {
      purchaseCurrency: body.purchaseCurrency,
      purchaseNetwork: body.purchaseNetwork, // Use the wallet's network
      paymentAmount: body.paymentAmount,
      paymentCurrency: body.paymentCurrency,
      paymentMethod: body.paymentMethod,
      country: body.country,
      subdivision: body.subdivision,
      destinationAddress: body.destinationAddress, // Include to get one-click-buy URL
    };

    // Call CDP Onramp API to get buy quote and URL
    const response = await fetch(`${ONRAMP_API_BASE_URL}/onramp/v1/buy/quote`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${jwt}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(requestBody),
    });

    if (!response.ok) {
      console.error("CDP API error:", response.statusText);
      const errorText = await response.text();
      console.error("Error details:", errorText);

      try {
        const errorData = JSON.parse(errorText);
        return NextResponse.json(
          { error: errorData.message || "Failed to create buy quote" },
          { status: response.status },
        );
      } catch {
        return NextResponse.json(
          { error: "Failed to create buy quote" },
          { status: response.status },
        );
      }
    }

    // convert response data to camelCase until migration to API v2 which will return camelCase data
    const data: OnrampBuyQuoteResponseRaw = await response.json();
    const dataCamelCase: OnrampBuyQuoteResponse = convertSnakeToCamelCase(data);
    return NextResponse.json(dataCamelCase);
  } catch (error) {
    console.error("Error creating buy quote:", error);
    return NextResponse.json({ error: "Internal server error" }, { status: 500 });
  }
}

5. FundModal component

Finally, you are ready to add the FundModal component to your app.
1

Create fetchBuyOptions and fetchBuyQuote

The FundModal component requires fetchBuyOptions and fetchBuyQuote props, which are functions that handle calling the Onramp API via your new server-side endpoints.
lib/onramp-api.ts
import {
  type FetchBuyOptions,
  type FetchBuyQuote,
} from "@coinbase/cdp-react/components/Fund";

/**
 * Fetches available buy options for onramp
 *
 * @param params - Query parameters for buy options
 * @returns Buy options including payment currencies and purchasable assets
 */
export const getBuyOptions: FetchBuyOptions = async params => {
  const queryParams = new URLSearchParams();
  queryParams.append("country", params.country);
  if (params?.subdivision) queryParams.append("subdivision", params.subdivision);

  const queryString = queryParams.toString();
  const url = `/api/onramp/buy-options${queryString ? `?${queryString}` : ""}`;

  const response = await fetch(url, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
  });

  if (!response.ok) {
    const errorData = await response.json();
    throw new Error(errorData.error || "Failed to fetch buy options");
  }

  return await response.json();
};

/**
 * Creates a buy quote for onramp purchase
 *
 * @param request - Buy quote request parameters
 * @returns Buy quote with fees and onramp URL
 */
export const createBuyQuote: FetchBuyQuote = async request => {
  const response = await fetch("/api/onramp/buy-quote", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(request),
  });

  if (!response.ok) {
    const errorData = await response.json();
    throw new Error(errorData.error || "Failed to create buy quote");
  }

  return await response.json();
};
2

Render FundModal

components/FundWallet.tsx
"use client";

import {
  FundModal,
  type FundModalProps,
} from "@coinbase/cdp-react";
import { useCallback } from "react";

import { getBuyOptions, createBuyQuote } from "@/lib/onramp-api";

/**
 * A component that wraps the FundModal component
 *
 * @param props - The props for the FundWallet component
 * @param props.onSuccess - The callback function to call when the onramp purchase is successful
 * @returns The FundWallet component
 */
export default function FundWallet({ onSuccess }: { onSuccess: () => void }) {
  // Get the user's location (i.e. from IP geolocation)
  const userCountry = "US";

  // If user is in the US, the state is also required
  const userSubdivision = userCountry === "US" ? "CA" : undefined;

  // Call your buy quote endpoint
  const fetchBuyQuote: FundModalProps["fetchBuyQuote"] = useCallback(async params => {
    return createBuyQuote(params);
  }, []);

  // Call your buy options endpoint
  const fetchBuyOptions: FundModalProps["fetchBuyOptions"] = useCallback(async params => {
    return getBuyOptions(params);
  }, []);

  return (
    <FundModal
      country={userCountry}
      subdivision={userSubdivision}
      cryptoCurrency="ETH"
      fiatCurrency="USD"
      fetchBuyQuote={fetchBuyQuote}
      fetchBuyOptions={fetchBuyOptions}
      network="base"
      presetAmountInputs={[10, 25, 50]}
      onSuccess={onSuccess}
    />
  );
}

Reference

ResourceDescription
Buy options APICoinbase Onramp Buy Options API reference
Buy quote APICoinbase Onramp Buy Quote API reference
Fund READMEComponent overview and usage
  • React Components: Explore all available Embedded Wallet React components, including authentication, wallet management, and transaction components to build complete wallet experiences
  • Onramp Overview: Learn about the complete Onramp API ecosystem, including advanced features like offramp, webhooks, and transaction monitoring for comprehensive fiat-to-crypto solutions