Skip to main content
EVMSolana

Overview

Coinbase Developer Platform (CDP) provides React hooks for conveniently accessing the CDP Embedded Wallet SDK functionality on both EVM and Solana networks. Built on top of @coinbase/cdp-core, these hooks offer a React-friendly interface for authentication and embedded wallet operations.
For a complete list of available hooks with comprehensive method signatures, types, and examples, see the CDP Web SDK reference.

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:
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>
  );
}
Config options:
  • projectId (required)
  • ethereum.createOnLogin = "eoa" | "smart" (optional)
  • solana.createOnLogin = boolean (optional)
  • basePath (optional API base URL)
  • useMock (optional mock mode for local testing)
  • debugging (optional verbose API logging)
Check out the Smart Accounts guide for more information about EVM smart accounts.

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("EVM address:", user.evmAccounts[0]);
      console.log("Solana address:", user.solanaAccounts[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:
  • evmProfile.tsx
  • solanaProfile.tsx
import { useCurrentUser, useEvmAddress } from "@coinbase/cdp-hooks";

function Profile() {
  const { currentUser } = useCurrentUser();
  const { evmAddress } = useEvmAddress();

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

  return (
    <div>
      <h2>Profile</h2>
      <p>User ID: {currentUser.userId}</p>
      <p>EVM Address: {evmAddress}</p>
      <p>All EVM Accounts: {currentUser.evmAccounts.join(", ")}</p>
      {currentUser.evmSmartAccounts?.[0] && (
        <p>Smart Account: {currentUser.evmSmartAccounts[0]}</p>
      )}
    </div>
  );
}

Send a transaction

We support signing and sending a blockchain transaction in a single action on the networks listed below.
EVM networks:
  • Base
  • Base Sepolia
  • Ethereum
  • Ethereum Sepolia
  • Arbitrum
  • Avalanche
  • Optimism
  • Polygon
Working with Solana? See Solana Sending Transactions for Solana transaction examples.
evmTransaction.tsx
import { useSendEvmTransaction, useEvmAddress } from "@coinbase/cdp-hooks";

function SendTransaction() {
  const { sendEvmTransaction } = useSendEvmTransaction();
  const { evmAddress } = useEvmAddress();

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

    try {
      const result = await sendEvmTransaction({
        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>;
}
The useSendEvmTransaction hook also returns a data state with statuses idle | pending | success | error that reflects the most recent transaction.

Sign and broadcast (non-supported EVM networks)

For networks other than those supported by the Send Transaction API, 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 { tron } from "viem/chains";

function NonBaseTransaction() {
  const { signEvmTransaction } = useSignEvmTransaction();
  const { evmAddress } = useEvmAddress();

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

    try {
      // Sign the transaction
      const { signedTransaction } = await signEvmTransaction({
        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: 728126428,          // Tron
          type: "eip1559",             // Modern gas fee model
        }
      });

      // Broadcast using a different client
      const client = createPublicClient({
        chain: tron,
        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:
Working with Solana? See Solana Signing for Solana message and transaction signing examples.
evmSigning.tsx
import { useSignEvmMessage, useSignEvmTypedData, useSignEvmHash, useEvmAddress } from "@coinbase/cdp-hooks";

function SignData() {
  const { signEvmMessage } = useSignEvmMessage();
  const { signEvmTypedData } = useSignEvmTypedData();
  const { signEvmHash } = useSignEvmHash();
  const { evmAddress } = useEvmAddress();

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

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

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

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

    const result = await signEvmTypedData({
      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 signEvmHash({
      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

Private key export is a high-risk security operation. See our comprehensive Security & Export guide for proper implementation, security considerations, and best practices.
The useExportEvmAccount and useExportSolanaAccount hooks allows users to export their respective private keys for wallet migration or other purposes. For detailed implementation examples and security guidance, see the Security & Export documentation.

Send a user operation (Smart Accounts)

Send user operations from Smart Accounts with support for multiple calls and paymaster sponsorship. The hook returns a method to execute the user operation and forwards status, data, and error from its internal tracking.
import { useSendUserOperation, useCurrentUser } from "@coinbase/cdp-hooks";

function SendUserOperation() {
  const { sendUserOperation } = useSendUserOperation();
  const { currentUser } = useCurrentUser();

  const handleSendUserOperation = async () => {
    const smartAccount = currentUser?.evmSmartAccounts?.[0];
    if (!smartAccount) return;

    try {
      // This will automatically start tracking the user operation status
      const result = await sendUserOperation({
        evmSmartAccount: smartAccount,
        network: "base-sepolia",
        calls: [{
          to: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
          value: 1000000000000000000n,
          data: "0x",
        }],
      });

      console.log("User Operation Hash:", result.userOperationHash);
    } catch (error) {
      console.error("Failed to send user operation:", error);
    }
  };

  return <button onClick={handleSendUserOperation}>Send User Operation</button>;
}
The hook returns the sendUserOperation method, which you call with the transaction parameters that you want to send. The hook also returns status, data, and error values which you can use to track the status of the user operation. For more information, see the Smart Accounts guide.
I