CDP Frontend SDK
    Preparing search index...

    Module @coinbase/cdp-hooks - v0.0.47

    This package provides React hooks for conveniently accessing embedded wallet functionality. Built on top of @coinbase/cdp-core, it offers a React-friendly interface for end user authentication and embedded wallet operations.

    This guide will help you get started with @coinbase/cdp-hooks. You'll learn how to install the package, set up the provider, and use the hooks in both web and React Native applications.

    For web applications, add the package to your project using your preferred package manager:

    # With npm
    npm install @coinbase/cdp-core @coinbase/cdp-hooks

    # With pnpm
    pnpm add @coinbase/cdp-core @coinbase/cdp-hooks

    # With yarn
    yarn add @coinbase/cdp-core @coinbase/cdp-hooks

    For React Native applications, you'll need additional crypto polyfills and dependencies:

    # Core packages
    npm install @coinbase/cdp-core @coinbase/cdp-hooks

    # Install this polyfill with expo for better compatibility
    npx expo install react-native-quick-crypto

    # Required crypto polyfills for React Native
    npm install react-native-get-random-values @ungap/structured-clone

    # AsyncStorage for React Native storage
    npm install @react-native-async-storage/async-storage

    React Native Setup Code

    You'll need to initialize the crypto polyfills before importing your app. Create or update your entry point file (typically index.js or index.ts):

    import structuredClone from "@ungap/structured-clone";
    import { install } from "react-native-quick-crypto";
    import "react-native-get-random-values";

    // Install crypto polyfills
    if (!("structuredClone" in globalThis)) {
    globalThis.structuredClone = structuredClone as any;
    }

    install(); // Install react-native-quick-crypto

    // Import your app after polyfills are installed
    import App from "./App";

    // Register your app component
    import { registerRootComponent } from "expo"; // For Expo apps
    registerRootComponent(App);

    Why these dependencies?

    • react-native-quick-crypto: Provides Web Crypto API compatibility for asymmetric key generation (ECDSA, RSA) required for JWT signing and encryption
    • react-native-get-random-values: Provides secure random number generation via crypto.getRandomValues()
    • @ungap/structured-clone: Polyfills structuredClone for object cloning compatibility
    • @react-native-async-storage/async-storage: Provides persistent storage for auth tokens and secrets
    1. Sign in or create an account on the CDP Portal
    2. On your dashboard, select a project from the dropdown at the at the top, and copy the Project ID
    1. Navigate to the Embedded Wallet Configuration in CDP Portal, and click Add origin to include your local app
    2. Enter the origin of your locally running app - e.g., http://localhost:3000
    3. Click Add origin again to save your changes

    Next, you need to wrap your application with the CDPHooksProvider, which provides the necessary context for hooks to work correctly.

    Update your main application file (e.g., main.tsx) to include the provider:

    import React from "react";
    import { CDPHooksProvider } from "@coinbase/cdp-hooks";
    import { App } from './App'; // Your main App component

    function App() {
    return (
    <CDPHooksProvider
    config={{
    // Copy and paste your project ID here.
    projectId: "your-project-id",
    }}
    >
    <App />
    </CDPHooksProvider>
    );
    }

    For React Native, the setup is identical.

    import React from "react";
    import { CDPHooksProvider } from "@coinbase/cdp-hooks";
    import { App } from "./App";

    export default function App() {
    return (
    <CDPHooksProvider config={{
    projectId: "your-project-id",
    }}>
    <App />
    </CDPHooksProvider>
    );
    }

    You can configure the provider to automatically create Smart Accounts for new users:

    function App() {
    return (
    <CDPHooksProvider
    config={{
    projectId: "your-project-id",
    ethereum: {
    createOnLogin: "smart", // Creates Smart Accounts instead of EOAs
    },
    }}
    >
    <App />
    </CDPHooksProvider>
    );
    }
    • When ethereum.createOnLogin is set to "smart", new users will automatically get both an EOA and a Smart Account.

    You can configure the provider to create Solana accounts for new users:

    function App() {
    return (
    <CDPHooksProvider
    config={{
    projectId: "your-project-id",
    solana: {
    createOnLogin: true, // Creates Solana accounts
    },
    }}
    >
    <App />
    </CDPHooksProvider>
    );
    }
    • When solana.createOnLogin is set to true, new users will automatically get a Solana account instead of EVM accounts.

    End user authentication proceeds in two steps:

    1. The user inputs their email address to initiate the authentication flow, which will send the user a One Time Password (OTP) and return a flowId
    2. The user submits 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 });

    // In a real application, you would prompt the user for the OTP they received
    // in their email. Here, we hardcode it for convenience.
    const otp = "123456";

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

    console.log("Signed in user:", user);

    // Access different account types based on configuration
    if (user.evmAccounts?.length > 0) {
    console.log("User EVM address (EOA):", user.evmAccounts[0]);
    }
    if (user.evmSmartAccounts?.length > 0) {
    console.log("User Smart Account:", user.evmSmartAccounts[0]);
    }
    if (user.solanaAccounts?.length > 0) {
    console.log("User Solana address:", user.solanaAccounts[0]);
    }
    } catch (error) {
    console.error("Sign in failed:", error);
    }
    };

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

    For React Native, you'll use native UI components and handle the sign-in flow similarly:

    import React, { useState } from "react";
    import { View, Text, TextInput, TouchableOpacity, Alert } from "react-native";
    import { useSignInWithEmail, useVerifyEmailOTP } from "@coinbase/cdp-hooks";

    function SignInScreen() {
    const { signInWithEmail } = useSignInWithEmail();
    const { verifyEmailOTP } = useVerifyEmailOTP();
    const [email, setEmail] = useState("");
    const [otp, setOtp] = useState("");
    const [flowId, setFlowId] = useState("");
    const [isLoading, setIsLoading] = useState(false);

    const handleSignIn = async () => {
    if (!email) {
    Alert.alert("Error", "Please enter an email address");
    return;
    }

    setIsLoading(true);
    try {
    const result = await signInWithEmail({ email });
    setFlowId(result.flowId);
    Alert.alert("Success", "OTP sent to your email!");
    } catch (error) {
    Alert.alert("Error", error instanceof Error ? error.message : "Failed to sign in");
    } finally {
    setIsLoading(false);
    }
    };

    const handleVerifyOTP = async () => {
    if (!otp || !flowId) {
    Alert.alert("Error", "Please enter the OTP");
    return;
    }

    setIsLoading(true);
    try {
    const { user } = await verifyEmailOTP({ flowId, otp });
    Alert.alert("Success", "Successfully signed in!");
    console.log("Signed in user:", user);
    } catch (error) {
    Alert.alert("Error", error instanceof Error ? error.message : "Failed to verify OTP");
    } finally {
    setIsLoading(false);
    }
    };

    return (
    <View style={{ padding: 20 }}>
    <Text>Email:</Text>
    <TextInput
    value={email}
    onChangeText={setEmail}
    placeholder="Enter your email"
    keyboardType="email-address"
    autoCapitalize="none"
    editable={!isLoading}
    style={{ borderWidth: 1, borderColor: "#ddd", padding: 12, marginBottom: 16 }}
    />

    <TouchableOpacity
    onPress={handleSignIn}
    disabled={isLoading}
    style={{
    backgroundColor: "#007AFF",
    padding: 15,
    borderRadius: 8,
    alignItems: "center",
    marginBottom: 12,
    opacity: isLoading ? 0.6 : 1,
    }}
    >
    <Text style={{ color: "white", fontSize: 16, fontWeight: "600" }}>
    {isLoading ? "Sending..." : "Sign In with Email"}
    </Text>
    </TouchableOpacity>

    {flowId && (
    <>
    <Text>Enter OTP from email:</Text>
    <TextInput
    value={otp}
    onChangeText={setOtp}
    placeholder="Enter 6-digit OTP"
    keyboardType="number-pad"
    maxLength={6}
    editable={!isLoading}
    style={{ borderWidth: 1, borderColor: "#ddd", padding: 12, marginBottom: 16 }}
    />

    <TouchableOpacity
    onPress={handleVerifyOTP}
    disabled={isLoading}
    style={{
    backgroundColor: "#007AFF",
    padding: 15,
    borderRadius: 8,
    alignItems: "center",
    opacity: isLoading ? 0.6 : 1,
    }}
    >
    <Text style={{ color: "white", fontSize: 16, fontWeight: "600" }}>
    {isLoading ? "Verifying..." : "Verify OTP"}
    </Text>
    </TouchableOpacity>
    </>
    )}
    </View>
    );
    }

    Once the end user has signed in, you can display their information in your application:

    import { useCurrentUser, useEvmAddress } from "@coinbase/cdp-hooks";

    function UserInformation() {
    const { currentUser: user } = useCurrentUser();
    const { evmAddress } = useEvmAddress();

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

    const emailAddress = user.authenticationMethods.email?.email;

    return (
    <div>
    <h2>User Information</h2>
    <p>User ID: {user.userId}</p>

    <>
    <p>EVM Address (EOA): {evmAddress}</p>
    {user.evmSmartAccounts?.[0] && (
    <p>Smart Account: {user.evmSmartAccounts[0]}</p>
    )}
    </>

    {emailAddress && <p>Email Address: {emailAddress}</p>}
    </div>
    );
    }

    When your application is configured with solana: { createOnLogin: true }, you can use Solana-specific hooks to interact with Solana accounts.

    Use the useSolanaAddress hook to get the user's primary Solana address:

    import { useSolanaAddress } from "@coinbase/cdp-hooks";

    function SolanaWallet() {
    const { solanaAddress } = useSolanaAddress();

    if (!solanaAddress) {
    return <div>No Solana wallet connected</div>;
    }

    return (
    <div>
    <h3>Your Solana Wallet</h3>
    <p>Address: {solanaAddress}</p>
    </div>
    );
    }

    Use the useSignSolanaTransaction hook to sign Solana transactions:

    import { useSignSolanaTransaction, useSolanaAddress } from "@coinbase/cdp-hooks";

    function SolanaTransactionSigner() {
    const { signSolanaTransaction } = useSignSolanaTransaction();
    const { solanaAddress } = useSolanaAddress();

    const handleSignTransaction = async () => {
    if (!solanaAddress) return;

    try {
    const result = await signSolanaTransaction({
    solanaAccount: solanaAddress,
    transaction: "base64-encoded-solana-transaction" // Your Solana transaction here
    });

    console.log("Signed Transaction:", result.signedTransaction);
    // The signedTransaction can now be broadcast to the Solana network
    } catch (error) {
    console.error("Failed to sign transaction:", error);
    }
    };

    if (!solanaAddress) {
    return <div>Please connect your Solana wallet first</div>;
    }

    return (
    <div>
    <h3>Sign Solana Transaction</h3>
    <button onClick={handleSignTransaction}>
    Sign Transaction
    </button>
    </div>
    );
    }

    Use the useSignSolanaMessage hook to sign arbitrary messages with Solana accounts:

    import { useSignSolanaMessage, useSolanaAddress } from "@coinbase/cdp-hooks";

    function SolanaMessageSigner() {
    const { signSolanaMessage } = useSignSolanaMessage();
    const { solanaAddress } = useSolanaAddress();

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

    try {
    const message = Buffer.from("Hello, Solana!", "utf8").toString("base64");
    const result = await signSolanaMessage({
    solanaAccount: solanaAddress,
    message // Base64 encoded message to sign
    });

    console.log("Message Signature:", result.signature);
    // The signature can be used for authentication or verification purposes
    } catch (error) {
    console.error("Failed to sign message:", error);
    }
    };

    if (!solanaAddress) {
    return <div>Please connect your Solana wallet first</div>;
    }

    return (
    <div>
    <h3>Sign Solana Message</h3>
    <button onClick={handleSignMessage}>
    Sign Message
    </button>
    </div>
    );
    }

    Use the useSendSolanaTransaction hook to sign and send Solana transactions in a single action. This is supported on:

    • Solana Mainnet
    • Solana Devnet
    import { useSendSolanaTransaction, useSolanaAddress } from "@coinbase/cdp-hooks";

    function SolanaTransactionSender() {
    const { sendSolanaTransaction } = useSendSolanaTransaction();
    const { solanaAddress } = useSolanaAddress();

    const handleSendTransaction = async () => {
    if (!solanaAddress) return;

    try {
    const result = await sendSolanaTransaction({
    solanaAccount: solanaAddress,
    network: "solana-devnet", // or "solana" for mainnet
    transaction: "base64-encoded-solana-transaction" // Your Solana transaction here
    });

    console.log("Transaction Signature:", result.transactionSignature);
    // The transaction has been broadcast to the Solana network
    } catch (error) {
    console.error("Failed to send transaction:", error);
    }
    };

    if (!solanaAddress) {
    return <div>Please connect your Solana wallet first</div>;
    }

    return (
    <div>
    <h3>Send Solana Transaction</h3>
    <button onClick={handleSendTransaction}>
    Send Transaction
    </button>
    </div>
    );
    }

    We support signing and sending a Blockchain transaction in a single action on the following networks:

    • Base
    • Base Sepolia
    • Ethereum
    • Ethereum Sepolia
    • Avalanche
    • Arbitrum
    • Optimism
    • Polygon
    import { useSendEvmTransaction, useEvmAddress } from "@coinbase/cdp-hooks";

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

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

    try {
    const result = await sendTransaction({
    evmAccount: evmAddress,
    transaction: {
    to: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    value: 100000000000000n, // 0.0001 ETH in wei
    nonce: 0,
    gas: 21000n,
    maxFeePerGas: 30000000000n,
    maxPriorityFeePerGas: 1000000000n,
    chainId: 84532, // Base Sepolia
    type: "eip1559",
    }
    });

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

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

    For networks other than those supported by the CDP APIs, your end user must sign the transaction, and then you must broadcast the transaction 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 CrossChainTransaction() {
    const { signEvmTransaction: signTransaction } = useSignEvmTransaction();
    const { evmAddress } = useEvmAddress();

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

    try {
    // Sign the transaction
    const { signedTransaction } = await signTransaction({
    evmAccount: evmAddress,
    transaction: {
    to: "0x...",
    value: 100000000000000n,
    nonce: 0,
    gas: 21000n,
    maxFeePerGas: 30000000000n,
    maxPriorityFeePerGas: 1000000000n,
    chainId: 728126428, // Tron
    type: "eip1559",
    }
    });

    // 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>;
    }

    End users can sign EVM messages, hashes, and typed data to generate signatures for various onchain applications.

    import { useSignEvmMessage, useSignEvmTypedData, useEvmAddress } from "@coinbase/cdp-hooks";

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

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

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

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

    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);
    };

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

    End users can export their private keys from their embedded wallet, allowing them to import them into compatible wallets of their choice.

    import { useExportEvmAccount, useEvmAddress } from "@coinbase/cdp-hooks";

    function ExportEvmKey() {
    const { exportEvmAccount: exportAccount } = useExportEvmAccount();
    const { evmAddress } = useEvmAddress();

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

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

    console.log("EVM Private Key:", privateKey);
    // Warning: Handle private keys with extreme care!
    } catch (error) {
    console.error("Export failed:", error);
    }
    };

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

    When your application is configured with solana: { createOnLogin: true }, you can export Solana private keys:

    import { useExportSolanaAccount, useSolanaAddress } from "@coinbase/cdp-hooks";

    function ExportSolanaKey() {
    const { exportSolanaAccount: exportAccount } = useExportSolanaAccount();
    const { solanaAddress } = useSolanaAddress();

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

    try {
    const { privateKey } = await exportAccount({
    solanaAccount: solanaAddress
    });

    console.log("Solana Private Key:", privateKey);
    // Warning: Handle private keys with extreme care!
    } catch (error) {
    console.error("Export failed:", error);
    }
    };

    if (!solanaAddress) {
    return <div>Please connect your Solana wallet first</div>;
    }

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

    Smart Accounts provide advanced account abstraction features with React hooks.

    Spend permissions allow Smart Accounts to delegate spending authority to other accounts within specified limits and time periods. The useCreateSpendPermission hook provides an easy way to create spend permissions with automatic user operation tracking.

    import { useCreateSpendPermission, useCurrentUser } from "@coinbase/cdp-hooks";

    function CreateSpendPermission() {
    const { createSpendPermission, status, data, error } = useCreateSpendPermission();
    const { currentUser } = useCurrentUser();

    const handleCreateSpendPermission = async () => {
    try {
    const result = await createSpendPermission({
    network: "base-sepolia",
    spender: "0x742D35Cc6634C0532925a3b8D6Ec6F1C2b9c1E46", // Address that can spend tokens
    token: "usdc", // Token symbol ("eth", "usdc") or contract address
    allowance: "10000000", // 10 USDC (6 decimals)
    periodInDays: 7, // Weekly recurring allowance
    useCdpPaymaster: true, // Use CDP paymaster for gas sponsorship
    });

    console.log("User Operation Hash:", result.userOperationHash);
    } catch (error) {
    console.error("Failed to create spend permission:", error);
    }
    };

    return (
    <div>
    {status === "idle" && <p>Ready to create spend permission</p>}

    {status === "pending" && (
    <div>
    <p>Creating spend permission...</p>
    {data && <p>User Op Hash: {data.userOpHash}</p>}
    </div>
    )}

    {status === "success" && data && (
    <div>
    <p>Spend permission created successfully!</p>
    <p>Transaction Hash: {data.transactionHash}</p>
    <p>Status: {data.status}</p>
    </div>
    )}

    {status === "error" && (
    <div>
    <p>Failed to create spend permission</p>
    <p>Error: {error?.message}</p>
    </div>
    )}

    <button
    onClick={handleCreateSpendPermission}
    disabled={status === "pending" || !currentUser?.evmSmartAccounts?.[0]}
    >
    {status === "pending" ? "Creating..." : "Create Spend Permission"}
    </button>
    </div>
    );
    }

    The hook automatically:

    • Detects the user's Smart Account (or allows you to specify evmSmartAccount)
    • Converts periodInDays to seconds
    • Resolves token symbols like "eth" and "usdc" to contract addresses
    • Tracks the user operation status and provides real-time updates

    Use the useListSpendPermissions hook to retrieve all spend permissions for a Smart Account. This hook follows a query-style pattern and automatically fetches permissions when enabled.

    import { useListSpendPermissions, useCurrentUser } from "@coinbase/cdp-hooks";

    function SpendPermissionsList() {
    const { currentUser } = useCurrentUser();
    const { data, error, status, refetch } = useListSpendPermissions({
    evmSmartAccount: currentUser?.evmSmartAccounts?.[0],
    network: "base-sepolia",
    pageSize: 10,
    enabled: true
    });

    if (status === "pending") {
    return <div>Loading spend permissions...</div>;
    }

    if (status === "error") {
    return <div>Error: {error?.message}</div>;
    }

    return (
    <div>
    <button onClick={refetch}>Refresh</button>
    <h3>Spend Permissions ({data?.spendPermissions.length})</h3>
    {data?.spendPermissions.map((permission) => (
    <div key={permission.permissionHash}>
    <p>Hash: {permission.permissionHash}</p>
    <p>Spender: {permission.permission.spender}</p>
    <p>Token: {permission.permission.token}</p>
    <p>Allowance: {permission.permission.allowance}</p>
    <p>Revoked: {permission.revoked ? "Yes" : "No"}</p>
    </div>
    ))}
    {data?.hasNextPage && (
    <p>More permissions available. Use pageToken to load next page.</p>
    )}
    </div>
    );
    }

    Use the useRevokeSpendPermission hook to revoke a spend permission. The hook automatically tracks the user operation and provides real-time status updates.

    import { useRevokeSpendPermission } from "@coinbase/cdp-hooks";

    function RevokeSpendPermission({ permissionHash }) {
    const { revokeSpendPermission, data, error, status } = useRevokeSpendPermission();

    const handleRevokeSpendPermission = async () => {
    try {
    const result = await revokeSpendPermission({
    network: "base-sepolia",
    permissionHash: permissionHash,
    useCdpPaymaster: true
    });
    console.log("User Operation Hash:", result.userOperationHash);
    } catch (error) {
    console.error("Failed to revoke spend permission:", error);
    }
    };

    return (
    <div>
    {status === "pending" && <p>Revoking permission...</p>}

    {status === "success" && data && (
    <div>
    <p>Permission revoked successfully!</p>
    <p>Transaction Hash: {data.transactionHash}</p>
    </div>
    )}

    {status === "error" && <p>Error: {error?.message}</p>}

    <button
    onClick={handleRevokeSpendPermission}
    disabled={status === "pending"}
    >
    {status === "pending" ? "Revoking..." : "Revoke Permission"}
    </button>
    </div>
    );
    }

    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 status, data, and error properties to read the result of the user operation:

    import { useSendUserOperation, useCurrentUser } from "@coinbase/cdp-hooks";

    function SendUserOperation() {
    const { sendUserOperation, status, data, error } = 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 (
    <div>
    {status === "idle" && <p>Ready to send user operation</p>}

    {status === "pending" && (
    <div>
    <p>User operation pending...</p>
    {data && <p>User Op Hash: {data.userOpHash}</p>}
    </div>
    )}

    {status === "success" && data && (
    <div>
    <p>User operation successful!</p>
    <p>Transaction Hash: {data.transactionHash}</p>
    <p>Status: {data.status}</p>
    </div>
    )}

    {status === "error" && (
    <div>
    <p>User operation failed</p>
    <p>Error: {error?.message}</p>
    </div>
    )}

    <button onClick={handleSendUserOperation} disabled={status === "pending"}>
    {status === "pending" ? "Sending..." : "Send User Operation"}
    </button>
    </div>
    );
    }

    Use the useWaitForUserOperation hook to poll for user operation status and provide real-time updates. This hook immediately fires off a query to get the result of the user operation:

    import { useWaitForUserOperation, useState } from "react";

    function WaitForUserOperation() {
    const { status, data, error } = useWaitForUserOperation({
    userOperationHash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
    evmSmartAccount: "0x1234567890123456789012345678901234567890",
    network: "base-sepolia"
    });

    return (
    <div>
    {status === "idle" && <p>No user operation being tracked</p>}

    {status === "pending" && (
    <div>
    <p>User operation pending...</p>
    {data && <p>User Op Hash: {data.userOpHash}</p>}
    </div>
    )}

    {status === "success" && data && (
    <div>
    <p>User operation successful!</p>
    <p>Transaction Hash: {data.transactionHash}</p>
    <p>Status: {data.status}</p>
    </div>
    )}

    {status === "error" && (
    <div>
    <p>User operation failed</p>
    <p>Error: {error?.message}</p>
    </div>
    )}
    </div>
    );
    }

    You can control when the useWaitForUserOperation hook should start polling using the enabled parameter:

    function ConditionalWaitForUserOperation() {
    const [shouldPoll, setShouldPoll] = useState(false);

    const { status, data, error } = useWaitForUserOperation({
    userOperationHash: "0x1234...",
    evmSmartAccount: "0x5678...",
    network: "base-sepolia",
    enabled: shouldPoll // Only poll when this is true
    });

    return (
    <div>
    <button onClick={() => setShouldPoll(true)}>
    Start Polling
    </button>
    <button onClick={() => setShouldPoll(false)}>
    Stop Polling
    </button>

    <p>Status: {status}</p>
    {data && <p>User Operation Status: {data.status}</p>}
    {error && <p>Error: {error.message}</p>}
    </div>
    );
    }

    Functions

    useConfig
    useIsInitialized
    useSignInWithEmail
    useSignInWithSms
    useVerifyEmailOTP
    useVerifySmsOTP
    useSignInWithOAuth
    useOAuthState
    useIsSignedIn
    useCurrentUser
    useSignOut
    useGetAccessToken
    useEvmAddress
    useSolanaAddress
    useSignEvmHash
    useSignEvmTransaction
    useSendEvmTransaction
    useSignEvmMessage
    useSignSolanaMessage
    useSignEvmTypedData
    useExportEvmAccount
    useExportSolanaAccount
    useSignSolanaTransaction
    useSendSolanaTransaction
    useEnforceAuthenticated
    useEnforceUnauthenticated
    useSendUserOperation
    useWaitForUserOperation
    CDPHooksProvider
    useCreateSpendPermission
    useListSpendPermissions
    useRevokeSpendPermission

    Classes

    APIError

    Interfaces

    CDPHooksProviderProps
    CDPContextValue
    EIP712TypedData
    OAuthFlowState

    Type Aliases

    Config
    TransactionState
    Status
    UseSendUserOperationReturnType
    UseWaitForUserOperationParameters
    UseWaitForUserOperationReturnType
    UseCreateSpendPermissionReturnType
    CreateSpendPermissionOptions
    UseListSpendPermissionsReturnType
    UseListSpendPermissionsOptions
    SpendPermissionPage
    UseRevokeSpendPermissionReturnType
    RevokeSpendPermissionOptions
    AllowedEvmTransactionType
    APIErrorType
    EvmAddress
    ExportEvmAccountOptions
    ExportEvmAccountResult
    ExportSolanaAccountOptions
    ExportSolanaAccountResult
    GetUserOperationResult
    Hex
    SendEvmTransactionOptions
    SendEvmTransactionResult
    SendSolanaTransactionOptions
    SendSolanaTransactionResult
    SignEvmHashOptions
    SignEvmHashResult
    SignEvmMessageOptions
    SignEvmMessageResult
    SignEvmTransactionOptions
    SignEvmTransactionResult
    SignEvmTypedDataOptions
    SignEvmTypedDataResult
    SignInWithEmailOptions
    SignInWithEmailResult
    User
    VerifyEmailOTPOptions
    VerifyEmailOTPResult
    OAuth2ProviderType
    SpendPermission

    Variables

    CDPContext
    OAuth2ProviderType