Overview

CDP provides React hooks for conveniently accessing the CDP Embedded Wallet SDK functionality. Built on top of @coinbase/cdp-core, these hooks offer a React-friendly interface for authentication and embedded wallet operations.
Check out the CDP Web SDK reference for comprehensive method signatures, types, and examples.

Prerequisites

The fastest way to get started is to complete the Quickstart. If you already have your own app, you should complete the prerequisites below before proceeding. You will need:
  1. A CDP Portal account and CDP project
  2. Node.js 22+ installed
  3. Your local app domain configured in CDP Portal
  4. A package manager of your choice, with cdp-hooks installed:
# With npm
npm install @coinbase/cdp-core @coinbase/cdp-hooks

1. Setup hooks provider

If you’re not using the demo app from the Quickstart, you’ll need to manually set up the CDPHooksProvider in your application:
Using Next.js? Check out our Next.js integration guide for "use client" requirements and common gotchas.
import { CDPHooksProvider } from "@coinbase/cdp-hooks";

function App() {
  return (
    <CDPHooksProvider 
      config={{
        projectId: "your-project-id",
        basePath: "https://api.cdp.coinbase.com", // CDP API url
        useMock: false, // Use live APIs or use mock data for testing
        debugging: false, // Log API requests and responses
      }}
    >
      <YourApp />
    </CDPHooksProvider>
  );
}

2. Ensure SDK initialization

Always ensure the SDK is initialized before authenticating a user or performing wallet operations:
import { useIsInitialized } from "@coinbase/cdp-hooks";

function App() {
  const isInitialized = useIsInitialized();

  if (!isInitialized) {
    return <div>Loading...</div>;
  }

  return <Page />;
}

Hook examples

Now let’s explore how to use CDP hooks to build wallet functionality into your React application.

User sign-in

Our authentication uses a two-step flow:
  1. Submit user email to initiate the authentication flow, which will send the user a One-Time-Password (OTP) and return a flowId
  2. Submit the six-digit OTP and flowId, after which the user will be authenticated, returning a User object
import { useSignInWithEmail, useVerifyEmailOTP } from "@coinbase/cdp-hooks";

function SignIn() {
  const signInWithEmail = useSignInWithEmail();
  const verifyEmailOTP = useVerifyEmailOTP();

  const handleSignIn = async (email: string) => {
    try {
      // Start sign in flow
      const { flowId } = await signInWithEmail({ email });

      // Get OTP from user input...
      const otp = "123456";

      // Complete sign in
      const { user, isNewUser } = await verifyEmailOTP({
        flowId,
        otp
      });

      console.log("Signed in user:", user);
      console.log("User EVM address", user.evmAccounts[0]);
    } catch (error) {
      console.error("Sign in failed:", error);
    }
  };

  return <button onClick={() => handleSignIn("user@example.com")}>Sign In</button>;
}

View user profile

Display user information and wallet addresses using CDP hooks:
import { useCurrentUser, useEvmAddress } from "@coinbase/cdp-hooks";

function Profile() {
  const user = useCurrentUser();
  const primaryAddress = useEvmAddress();

  if (!user) {
    return <div>Please sign in</div>;
  }

  return (
    <div>
      <h2>Profile</h2>
      <p>User ID: {user.userId}</p>
      <p>Primary Address: {primaryAddress}</p>
      <p>All Accounts: {user.evmAccounts.join(", ")}</p>
    </div>
  );
}

Send a transaction

We support signing and sending a blockchain transaction in a single action on Base or Base Sepolia. For other networks, see the next section.
import { useSendEvmTransaction, useEvmAddress } from "@coinbase/cdp-hooks";

function SendTransaction() {
  const sendTransaction = useSendEvmTransaction();
  const evmAddress = useEvmAddress();

  const handleSend = async () => {
    if (!evmAddress) return;

    try {
      const result = await sendTransaction({
        transaction: {
          to: evmAddress,              // Send to yourself for testing
          value: 1000000000000n,       // 0.000001 ETH in wei
          gas: 21000n,                 // Standard ETH transfer gas limit
          chainId: 84532,              // Base Sepolia
          type: "eip1559",             // Modern gas fee model
        },
        evmAccount: evmAddress,        // Your CDP wallet address
        network: "base-sepolia",       // Target network
      });

      console.log("Transaction hash:", result.transactionHash);
    } catch (error) {
      console.error("Transaction failed:", error);
    }
  };

  return <button onClick={handleSend}>Send Transaction</button>;
}

Sign and broadcast (non-Base networks)

For networks other than Base or Base Sepolia, you can sign a transaction with the wallet and broadcast it yourself. This example uses the public client from viem to broadcast the transaction:
import { useSignEvmTransaction, useEvmAddress } from "@coinbase/cdp-hooks";
import { http, createPublicClient } from "viem";
import { sepolia } from "viem/chains";

function NonBaseTransaction() {
  const signTransaction = useSignEvmTransaction();
  const evmAddress = useEvmAddress();

  const handleSend = async () => {
    if (!evmAddress) return;

    try {
      // Sign the transaction
      const { signedTransaction } = await signTransaction({
        evmAccount: evmAddress,
        transaction: {
          to: evmAddress,              // Send to yourself for testing
          value: 1000000000000n,       // 0.000001 ETH in wei
          gas: 21000n,                 // Standard ETH transfer gas limit
          chainId: 11155111,           // Sepolia
          type: "eip1559",             // Modern gas fee model
        }
      });

      // Broadcast using a different client
      const client = createPublicClient({
        chain: sepolia,
        transport: http()
      });

      const hash = await client.sendRawTransaction({
        serializedTransaction: signedTransaction
      });

      console.log("Transaction hash:", hash);
    } catch (error) {
      console.error("Transaction failed:", error);
    }
  };

  return <button onClick={handleSend}>Send Transaction</button>;
}

Sign messages and typed data

You can sign EVM messages, hashes, and typed data to generate signatures for various on-chain applications:
import { useSignEvmMessage, useSignEvmTypedData, useSignEvmHash, useEvmAddress } from "@coinbase/cdp-hooks";

function SignData() {
  const signMessage = useSignEvmMessage();
  const signTypedData = useSignEvmTypedData();
  const signHash = useSignEvmHash();
  const evmAddress = useEvmAddress();

  const handleSignMessage = async () => {
    if (!evmAddress) return;

    const result = await signMessage({
      evmAccount: evmAddress,
      message: "Hello World"
    });

    console.log("Message signature:", result.signature);
  };

  const handleSignTypedData = async () => {
    if (!evmAddress) return;

    const result = await signTypedData({
      evmAccount: evmAddress,
      typedData: {
        domain: {
          name: "Example DApp",
          version: "1",
          chainId: 84532,
        },
        types: {
          Person: [
            { name: "name", type: "string" },
            { name: "wallet", type: "address" }
          ]
        },
        primaryType: "Person",
        message: {
          name: "Bob",
          wallet: evmAddress
        }
      }
    });

    console.log("Typed data signature:", result.signature);
  };

  const handleSignHash = async () => {
    if (!evmAddress) return;

    const result = await signHash({
      evmAccount: evmAddress,
      hash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
    });

    console.log("Hash signature:", result.signature);
  };

  return (
    <div>
      <button onClick={handleSignMessage}>Sign Message</button>
      <button onClick={handleSignTypedData}>Sign Typed Data</button>
      <button onClick={handleSignHash}>Sign Hash</button>
    </div>
  );
}

Export private keys

Allow users to export their private key to import it into an EVM-compatible wallet of their choice:
Handle private keys with extreme care! Never log them to console in production or expose them in your UI without proper security measures.
import { useExportEvmAccount, useEvmAddress } from "@coinbase/cdp-hooks";

function ExportKey() {
  const exportAccount = useExportEvmAccount();
  const evmAddress = useEvmAddress();

  const handleExport = async () => {
    if (!evmAddress) return;

    try {
      const { privateKey } = await exportAccount({
        evmAccount: evmAddress
      });

      // Handle the private key securely
      // Never log or display in production!
      navigator.clipboard.writeText(privateKey);
      alert("Private key copied to clipboard");
    } catch (error) {
      console.error("Export failed:", error);
    }
  };

  return <button onClick={handleExport}>Export Private Key</button>;
}