@coinbase/cdp-hooks

中文文档
0.0.130发布于 2026/11/10TypeScriptES Module零依赖近 30 天下载 2.9万

这个软件包为方便地访问嵌入式钱包功能提供了回放钩. 建于顶上 @coinbase/cdp-core,它为终端用户认证和嵌入式钱包操作提供了一个反应方便的界面.

中文使用文档
文档目录17 节
  1. 快速启动
  2. 安装
  3. 收集您的 CDP 项目信息
  4. 允许列出本地应用程序
  5. 设置提供者
  6. 签名 在用户中
  7. 处理多个账户
  8. 链接其他认证方法
  9. 签名 使用自定义认证
  10. 多功能认证( MFA)
  11. 查看用户信息
  12. 手动创建账户
  13. 与索拉纳合作
  14. 发送交易
  15. 签名信件和输入的数据
  16. 导出私有密钥
  17. 智能账户操作

这个软件包为方便地访问嵌入式钱包功能提供了回放钩. 建于顶上 @coinbase/cdp-core,它为终端用户认证提供了方便反应的界面 和嵌入式钱包操作.

快速启动

此指南将帮助您从 @ coinbase/ cdp- hooks 开始 。 你会学习如何安装软件包,设置提供者,在网络和React Industrial应用中都使用钩子.

安装

网络应用程序

对于网络应用程序,请使用您首选的软件包管理器将软件包添加到您的项目中 :

# 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

反应本地应用程序

对于反应原生应用程序,您需要额外的加密多填充和依赖性:

# 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

使用不同商店的 Apps 可以省略 AsyncStorage 并跟随核心 README 的 反应原生存储指南。 。 。 .

反应原生设置代码

在导入您的应用程序之前, 您需要初始化密码多填充 。 创建或更新您的出入口文件( 通常情况下) index.js 或者说 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);

为什么这些依赖性?

  • react-native-quick-crypto: 为 JWT 签名和加密所需的不对称密钥生成( ECDSA, RSA) 提供 Web Crypto API相容性
  • react-native-get-random-values:通过 crypto.getRandomValues()
  • @ungap/structured-clone: 多元填充器 structuredClone 用于复制对象的兼容性
  • @react-native-async-storage/async-storage: 为本地 SDK 状态提供默认的持续存储

收集您的 CDP 项目信息

  1. 在此处签名或创建账户 CDP 门户
  2. 在您的仪表板上,从最上方的下拉位置选择一个项目,并复制项目ID

允许列出本地应用程序

  1. 导航到 嵌入式钱包配置 在 CDP 门户网站中,单击添加来源以包含本地应用程序
  2. 输入本地运行的应用程序的出处 - 例如 , http://localhost:3000
  3. 点击 重新添加来源以保存您的更改

设置提供者

接下来,您需要用 CDPHooks Provider 来包装您的应用程序,该软件为 钩以正行.

网络应用程序

更新您的主要应用程序文件( 如 主. tsx) 以包含提供者 :

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

反应本地应用程序

对于回放原生,设置相同.

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

分析

默认SDK会释放出使用分析器来帮助我们改进SDK. 如果您想选择退出,您可以通过设置 disableAnalytics 配置选项到 true。 。 。 .

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

智能账户配置

您可以配置提供者为新用户自动创建智能账户 :

function App() {
  return (
    <CDPHooksProvider
      config={{
        projectId: "your-project-id",
        ethereum: {
          createOnLogin: "smart", // Creates Smart Accounts instead of EOAs
        },
      }}
    >
      <App />
    </CDPHooksProvider>
  );
}
  • 何时 ethereum.createOnLogin 设置为 "smart",新用户将自动获得EOA和智能账户.

索拉纳配置

您可以配置提供者为新用户创建索拉纳账户 :

function App() {
  return (
    <CDPHooksProvider
      config={{
        projectId: "your-project-id",
        solana: {
          createOnLogin: true, // Creates Solana accounts
        },
      }}
    >
      <App />
    </CDPHooksProvider>
  );
}
  • 何时 solana.createOnLogin 设置为 true,新用户会自动获得一个索拉纳账户而不是EVM账户.

递延账户的创建

可以省略 createOnLogin 完全防止自动创建账户,而是在需要时手工创建账户:

function App() {
  return (
    <CDPHooksProvider
      config={{
        projectId: "your-project-id",
        // No ethereum or solana createOnLogin configuration
      }}
    >
      <App />
    </CDPHooksProvider>
  );
}

何时 createOnLogin 省略, SDK 将:

  1. 用户登录时不自动创建任何账户
  2. 需要使用账户创建钩头进行手动账户创建(见下文)
  3. 完全控制创建时间和类型的账户

签名 在用户中

终端用户认证分两个步骤进行:

  1. 用户输入其电子邮件地址以启动认证流程,该流程将发送用户一个时间密码并返回 a flowId
  2. 用户提交6位数的检察官办公室和 flowId,然后用户将被认证,返回用户对象.

网络应用程序

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

      // Using new account objects (recommended)
      if (user.evmAccountObjects?.length > 0) {
        user.evmAccountObjects.forEach((account, index) => {
          console.log(`EVM Account ${index + 1}:`, account.address);
          console.log(`Created:`, new Date(account.createdAt).toLocaleDateString());
        });
      }
      if (user.evmSmartAccountObjects?.length > 0) {
        user.evmSmartAccountObjects.forEach((account, index) => {
          console.log(`Smart Account ${index + 1}:`, account.address);
          console.log(`Owners:`, account.ownerAddresses.join(', '));
        });
      }
      if (user.solanaAccountObjects?.length > 0) {
        user.solanaAccountObjects.forEach((account, index) => {
          console.log(`Solana Account ${index + 1}:`, account.address);
        });
      }

      // Legacy deprecated arrays (for backward compatibility)
      // ⚠️ Use *AccountObjects instead
      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("[email protected]")}>Sign In</button>;
}

处理多个账户

用户每个区块链类型最多可有10个账户(EVM,索拉纳). SDK同时提供单账户和多账户钩来支持不同的使用案例.

单账户钩

这些钩子还给我 第一账户(索引 0)。它们对于大多数用户有一个账户的简单应用程序来说是理想的:

  • useEvmAddress() - 首先返回智能账户,然后先返回EOA
  • useSolanaAddress() - 首先返回索拉纳账户
function SimpleWallet() {
  const { evmAddress } = useEvmAddress(); // Returns first EVM address
  const { solanaAddress } = useSolanaAddress(); // Returns first Solana address

  return (
    <div>
      <p>EVM: {evmAddress ?? 'No account'}</p>
      <p>Solana: {solanaAddress ?? 'No account'}</p>
    </div>
  );
}

多账户钩 (所有有元数据账户)

这些钩子回来了 所有账户 附加元数据(创建时间戳、所有者地址)。 当:

  • 用户可能有多个账户
  • 您需要账户元数据( 创建日期等)
  • 您正在构建一个账户选择器 UI

可用的钩子 :

  • useEvmAccounts() - EVM EOA所有有元数据的账户
  • useSolanaAccounts() - 所有索拉纳账户都有元数据
  • useEvmSmartAccounts() - 所有有元数据的EVM智能账户
function MultiAccountWallet() {
  const { evmAccounts } = useEvmAccounts();
  const { solanaAccounts } = useSolanaAccounts();

  return (
    <div>
      <h3>EVM Accounts</h3>
      {evmAccounts?.map((account, idx) => (
        <div key={account.address}>
          <p>Account {idx + 1}: {account.address}</p>
          <p>Created: {new Date(account.createdAt).toLocaleDateString()}</p>
        </div>
      ))}

      <h3>Solana Accounts</h3>
      {solanaAccounts?.map((account, idx) => (
        <div key={account.address}>
          <p>Account {idx + 1}: {account.address}</p>
          <p>Created: {new Date(account.createdAt).toLocaleDateString()}</p>
        </div>
      ))}
    </div>
  );
}

什么时候用哪?

使用大小写要使用的钩
简单的钱包显示useEvmAddress() 编号 : useSolanaAddress()
账户选择器下拉useEvmAccounts() 编号 : useSolanaAccounts()
显示创建日期useEvmAccounts() 编号 : useSolanaAccounts()
显示所有者地址( 智能账户)useEvmSmartAccounts()
大多数用户有一个账户useEvmAddress() 编号 : useSolanaAddress()
高级多账户功能useEvmAccounts() 编号 : useSolanaAccounts()

反应本地应用程序

对于React Industrial,您会使用本地的UI组件,并同样处理登录流:

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

一旦一个用户被认证,您就可以将额外的认证方法链接到他们的账户. 这允许用户在使用多种方法(电子邮件,短消息,OAuth提供商)时签名,使用相同的嵌入式钱包.

使用 useLinkEmail 将电子邮件地址链接到当前已认证的用户 :

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

function LinkEmail() {
  const { linkEmail } = useLinkEmail();
  const { verifyEmailOTP } = useVerifyEmailOTP();
  const { currentUser } = useCurrentUser();
  const [flowId, setFlowId] = useState("");

  const handleLinkEmail = async (email: string) => {
    if (!currentUser) {
      console.error("User must be signed in first");
      return;
    }

    try {
      // Initiate email linking
      const result = await linkEmail(email);
      setFlowId(result.flowId);

      // In a real application, you would prompt the user for the OTP
      const otp = "123456";

      // Verify the OTP to complete linking
      await verifyEmailOTP({
        flowId: result.flowId,
        otp
      });

      console.log("Email linked successfully!");
    } catch (error) {
      console.error("Failed to link email:", error);
    }
  };

  return (
    <button
      onClick={() => handleLinkEmail("[email protected]")}
      disabled={!currentUser}
    >
      Link Email
    </button>
  );
}

使用 useLinkSms 连接电话号码到当前认证的用户的钩子 :

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

function LinkPhoneNumber() {
  const { linkSms } = useLinkSms();
  const { verifySmsOTP } = useVerifySmsOTP();
  const { currentUser } = useCurrentUser();
  const [flowId, setFlowId] = useState("");

  const handleLinkSms = async (phoneNumber: string) => {
    if (!currentUser) {
      console.error("User must be signed in first");
      return;
    }

    try {
      // Initiate SMS linking
      const result = await linkSms(phoneNumber);
      setFlowId(result.flowId);

      // In a real application, you would prompt the user for the OTP
      const otp = "123456";

      // Verify the OTP to complete linking
      await verifySmsOTP({
        flowId: result.flowId,
        otp
      });

      console.log("Phone number linked successfully!");
    } catch (error) {
      console.error("Failed to link phone number:", error);
    }
  };

  return (
    <button
      onClick={() => handleLinkSms("+14155552671")}
      disabled={!currentUser}
    >
      Link Phone Number
    </button>
  );
}

使用 useLinkGoogle 将 Google 账户链接到当前认证的用户的钩子 :

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

function LinkGoogleAccount() {
  const { linkGoogle } = useLinkGoogle();
  const { currentUser } = useCurrentUser();

  const handleLinkGoogle = async () => {
    if (!currentUser) {
      console.error("User must be signed in first");
      return;
    }

    try {
      // This initiates the OAuth flow to link a Google account
      await linkGoogle();
      // The user will be redirected to Google for authentication
      // After successful authentication, the Google account will be linked
    } catch (error) {
      console.error("Failed to link Google account:", error);
    }
  };

  return (
    <button onClick={handleLinkGoogle} disabled={!currentUser}>
      Link Google Account
    </button>
  );
}

使用 useLinkApple 将 Apple 账户链接到当前认证用户的钩子 :

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

function LinkAppleAccount() {
  const { linkApple } = useLinkApple();
  const { currentUser } = useCurrentUser();

  const handleLinkApple = async () => {
    if (!currentUser) {
      console.error("User must be signed in first");
      return;
    }

    try {
      // Link an Apple account
      await linkApple();
    } catch (error) {
      console.error("Failed to link Apple account:", error);
    }
  };

  return (
    <button onClick={handleLinkApple} disabled={!currentUser}>  
      Link Apple Account
    </button>
  );
}

使用 useLinkOAuth 将任何支持的 OAuth 提供者( Google, Apple) 链接到当前认证的用户 :

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

function LinkOAuthProvider() {
  const { linkOAuth } = useLinkOAuth();
  const { currentUser } = useCurrentUser();

  const handleLinkGoogle = async () => {
    if (!currentUser) {
      console.error("User must be signed in first");
      return;
    }

    try {
      // Link a Google account
      await linkOAuth("google");
    } catch (error) {
      console.error("Failed to link Google account:", error);
    }
  };

  const handleLinkApple = async () => {
    if (!currentUser) return;

    try {
      // Link an Apple account
      await linkOAuth("apple");
    } catch (error) {
      console.error("Failed to link Apple account:", error);
    }
  };

  return (
    <div>
      <button onClick={handleLinkGoogle} disabled={!currentUser}>
        Link Google
      </button>
      <button onClick={handleLinkApple} disabled={!currentUser}>
        Link Apple
      </button>
    </div>
  );
}

签名 使用自定义认证

如果您使用第三方身份提供商(Auth0, Firebase, AWS Cognitto, 或任何符合OIDC的提供商), 您可以使用您的提供商提供的JWT认证用户 useAuthenticateWithJWT 钩.

先决条件

在使用自定义认证前 :

  1. 在 CDP 门户网站中配置身份提供者数字 :

    • 导航到 嵌入式钱包配置
    • 点击自定义认证标签
    • 添加您的 JWKS 端点 URL( 例如 ), https://your-domain.auth0.com/.well-known/jwks.json(中文(简体) )
    • 配置您的 JWT 发行者和观众
  2. 提供a customAuth.getJwt 回调 在提供者配置中:

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

function App() {
  return (
    <CDPHooksProvider
      config={{
        projectId: "your-project-id",
        customAuth: {
          // This callback should return a fresh JWT from your identity provider
          getJwt: async () => {
            // Return a JWT from your IDP (Auth0, Firebase, Cognito, etc.)
            // This will be called automatically when the SDK needs a fresh token
            const token = await yourAuthProvider.getAccessToken();
            return token;
          }
        },
        ethereum: {
          createOnLogin: "eoa" // Optional: configure wallet creation
        }
      }}
    >
      <YourApp />
    </CDPHooksProvider>
  );
}

认证用户

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

function CustomAuthSignIn() {
  const { authenticateWithJWT } = useAuthenticateWithJWT();
  const { currentUser } = useCurrentUser();

  const handleSignIn = async () => {
    try {
      // After your user has signed in to your IDP (Auth0, Firebase, etc.)
      const result = await authenticateWithJWT();

      console.log("User authenticated:", result.user);
      console.log("Is new user:", result.isNewUser);

      // The user is now signed in and wallets are created based on your config
      if (result.user.evmAccountObjects?.[0]?.address) {
        console.log("EVM Address:", result.user.evmAccounts[0]);
      }
    } catch (error) {
      console.error("Authentication failed:", error);
    }
  };

  return (
    <div>
      <button onClick={handleSignIn}>Sign In with Custom Auth</button>
      {currentUser && <p>Signed in as: {currentUser.userId}</p>}
    </div>
  );
}

多功能认证( MFA)

CDP SDK提供钩子,用于采用两种方法实施多功能认证:

  • TOTP( 基于时间的一次性密码): 用户使用Google认证或Authy等认证程序注册
  • 简讯: 用户通过短信到其电话号码接收验证码

外交部为用户账户和敏感业务增加了一层安全.

重要:用户必须经过认证(签名),才能报名参加外交部或进行外交部核查.

在 TOTP MFA 中输入用户

使用 useInitiateMfaEnrollment 财务报告和审定财务报表 useSubmitMfaEnrollment 在TOTP MFA中注册用户:

import { 
  useInitiateMfaEnrollment, 
  useSubmitMfaEnrollment,
  useCurrentUser 
} from "@coinbase/cdp-hooks";
import { useState } from "react";
import QRCode from "react-qr-code";

function MfaEnrollment() {
  const { initiateMfaEnrollment } = useInitiateMfaEnrollment();
  const { submitMfaEnrollment } = useSubmitMfaEnrollment();
  const { currentUser } = useCurrentUser();
  const [enrollmentData, setEnrollmentData] = useState<{
    authUrl: string;
    secret: string;
  } | null>(null);
  const [mfaCode, setMfaCode] = useState("");

  const handleInitiateEnrollment = async () => {
    try {
      // Step 1: Initiate MFA enrollment
      const result = await initiateMfaEnrollment({ mfaMethod: "totp" });
      
      // Store the authUrl and secret for QR code display
      setEnrollmentData(result);
      
      console.log("Scan QR code or enter secret:", result.secret);
    } catch (error) {
      console.error("Failed to initiate MFA enrollment:", error);
    }
  };

  const handleSubmitEnrollment = async () => {
    try {
      // Step 2: Submit the 6-digit code from authenticator app
      const result = await submitMfaEnrollment({
        mfaMethod: "totp",
        mfaCode: mfaCode,
      });
      
      console.log("MFA enrolled successfully for user:", result.user.userId);
      setEnrollmentData(null);
      setMfaCode("");
    } catch (error) {
      console.error("Failed to submit MFA enrollment:", error);
    }
  };

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

  return (
    <div>
      <h2>Enable MFA</h2>
      
      {!enrollmentData ? (
        <button onClick={handleInitiateEnrollment}>
          Start MFA Enrollment
        </button>
      ) : (
        <div>
          <p>Scan this QR code with your authenticator app:</p>
          <QRCode value={enrollmentData.authUrl} />
          
          <p>Or manually enter this secret: {enrollmentData.secret}</p>
          
          <input
            type="text"
            placeholder="Enter 6-digit code"
            value={mfaCode}
            onChange={(e) => setMfaCode(e.target.value)}
            maxLength={6}
          />
          
          <button 
            onClick={handleSubmitEnrollment}
            disabled={mfaCode.length !== 6}
          >
            Verify and Enable MFA
          </button>
        </div>
      )}
    </div>
  );
}

在 MFA 中输入短消息用户

使用 useInitiateMfaEnrollment 财务报告和审定财务报表 useSubmitMfaEnrollment 以 SMS MFA 为用户注册 :

import {
  useInitiateMfaEnrollment,
  useSubmitMfaEnrollment,
  useCurrentUser
} from "@coinbase/cdp-hooks";
import { useState } from "react";

function SmsMfaEnrollment() {
  const { initiateMfaEnrollment } = useInitiateMfaEnrollment();
  const { submitMfaEnrollment } = useSubmitMfaEnrollment();
  const { currentUser } = useCurrentUser();
  const [phoneNumber, setPhoneNumber] = useState("");
  const [mfaCode, setMfaCode] = useState("");
  const [enrollmentInitiated, setEnrollmentInitiated] = useState(false);

  const handleInitiateEnrollment = async () => {
    try {
      // Step 1: Initiate SMS MFA enrollment (sends OTP to phone)
      const result = await initiateMfaEnrollment({
        mfaMethod: "sms",
        phoneNumber: phoneNumber // E.164 format (e.g., +14155552671)
      });

      console.log("SMS sent:", result.success);
      setEnrollmentInitiated(true);
    } catch (error) {
      console.error("Failed to initiate SMS MFA enrollment:", error);
    }
  };

  const handleSubmitEnrollment = async () => {
    try {
      // Step 2: Submit the 6-digit code received via SMS
      const result = await submitMfaEnrollment({
        mfaMethod: "sms",
        mfaCode: mfaCode,
      });

      console.log("SMS MFA enrolled successfully for user:", result.user.userId);
      console.log("SMS enrollment info:", result.user.mfaMethods?.sms);
      setEnrollmentInitiated(false);
      setPhoneNumber("");
      setMfaCode("");
    } catch (error) {
      console.error("Failed to submit SMS MFA enrollment:", error);
    }
  };

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

  return (
    <div>
      <h2>Enable SMS MFA</h2>

      {!enrollmentInitiated ? (
        <div>
          <input
            type="tel"
            placeholder="Phone number (e.g., +14155552671)"
            value={phoneNumber}
            onChange={(e) => setPhoneNumber(e.target.value)}
          />
          <button
            onClick={handleInitiateEnrollment}
            disabled={!phoneNumber}
          >
            Send SMS Code
          </button>
        </div>
      ) : (
        <div>
          <p>A 6-digit code has been sent to {phoneNumber}</p>

          <input
            type="text"
            placeholder="Enter 6-digit code"
            value={mfaCode}
            onChange={(e) => setMfaCode(e.target.value)}
            maxLength={6}
          />

          <button
            onClick={handleSubmitEnrollment}
            disabled={mfaCode.length !== 6}
          >
            Verify and Enable SMS MFA
          </button>
        </div>
      )}
    </div>
  );
}

为敏感操作验证 TOTP MFA

使用 useInitiateMfaVerification 财务报告和审定财务报表 useSubmitMfaVerification 为敏感行动核查TOTP MFA:

import {
  useInitiateMfaVerification,
  useSubmitMfaVerification,
  useCurrentUser
} from "@coinbase/cdp-hooks";
import { useState } from "react";

function MfaVerification() {
  const { initiateMfaVerification } = useInitiateMfaVerification();
  const { submitMfaVerification } = useSubmitMfaVerification();
  const { currentUser } = useCurrentUser();
  const [mfaCode, setMfaCode] = useState("");
  const [verificationInitiated, setVerificationInitiated] = useState(false);

  const handleSensitiveOperation = async () => {
    try {
      // Step 1: Initiate MFA verification
      await initiateMfaVerification({ mfaMethod: "totp" });
      setVerificationInitiated(true);
      
      console.log("MFA verification initiated. Enter your code.");
    } catch (error) {
      console.error("Failed to initiate MFA verification:", error);
    }
  };

  const handleVerifyMfa = async () => {
    try {
      // Step 2: Submit the 6-digit code from authenticator app
      await submitMfaVerification({
        mfaMethod: "totp",
        mfaCode: mfaCode,
      });
      
      console.log("MFA verified successfully");
      setVerificationInitiated(false);
      setMfaCode("");
      
      // Now proceed with your sensitive operation
      // e.g., sign a transaction, export private key, etc.
    } catch (error) {
      console.error("MFA verification failed:", error);
    }
  };

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

  return (
    <div>
      <h2>Sensitive Operation</h2>
      
      {!verificationInitiated ? (
        <button onClick={handleSensitiveOperation}>
          Perform Sensitive Operation
        </button>
      ) : (
        <div>
          <p>Enter your MFA code to continue:</p>
          
          <input
            type="text"
            placeholder="Enter 6-digit code"
            value={mfaCode}
            onChange={(e) => setMfaCode(e.target.value)}
            maxLength={6}
          />
          
          <button 
            onClick={handleVerifyMfa}
            disabled={mfaCode.length !== 6}
          >
            Verify MFA
          </button>
        </div>
      )}
    </div>
  );
}

校验敏感操作的短消息

使用 useInitiateMfaVerification 财务报告和审定财务报表 useSubmitMfaVerification 核查敏感业务的短信:

import {
  useInitiateMfaVerification,
  useSubmitMfaVerification,
  useCurrentUser
} from "@coinbase/cdp-hooks";
import { useState } from "react";

function SmsMfaVerification() {
  const { initiateMfaVerification } = useInitiateMfaVerification();
  const { submitMfaVerification } = useSubmitMfaVerification();
  const { currentUser } = useCurrentUser();
  const [mfaCode, setMfaCode] = useState("");
  const [verificationInitiated, setVerificationInitiated] = useState(false);

  const handleSensitiveOperation = async () => {
    try {
      // Step 1: Initiate SMS MFA verification (sends code to enrolled phone)
      await initiateMfaVerification({ mfaMethod: "sms" });
      setVerificationInitiated(true);

      console.log("SMS sent to enrolled phone number. Enter your code.");
    } catch (error) {
      console.error("Failed to initiate SMS MFA verification:", error);
    }
  };

  const handleVerifyMfa = async () => {
    try {
      // Step 2: Submit the 6-digit code from SMS
      await submitMfaVerification({
        mfaMethod: "sms",
        mfaCode: mfaCode,
      });

      console.log("SMS MFA verified successfully");
      setVerificationInitiated(false);
      setMfaCode("");

      // Now proceed with your sensitive operation
      // e.g., sign a transaction, export private key, etc.
    } catch (error) {
      console.error("SMS MFA verification failed:", error);
    }
  };

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

  return (
    <div>
      <h2>Sensitive Operation (SMS MFA)</h2>

      {!verificationInitiated ? (
        <button onClick={handleSensitiveOperation}>
          Perform Sensitive Operation
        </button>
      ) : (
        <div>
          <p>A 6-digit code has been sent to your phone. Enter it to continue:</p>

          <input
            type="text"
            placeholder="Enter 6-digit code"
            value={mfaCode}
            onChange={(e) => setMfaCode(e.target.value)}
            maxLength={6}
          />

          <button
            onClick={handleVerifyMfa}
            disabled={mfaCode.length !== 6}
          >
            Verify SMS MFA
          </button>
        </div>
      )}
    </div>
  );
}

检查 MFA 配置

使用 useGetMfaConfig 检查您项目是否启用了外交部:

import { useGetMfaConfig } from "@coinbase/cdp-hooks";
import { useState, useEffect } from "react";

function MfaStatus() {
  const { getMfaConfig } = useGetMfaConfig();
  const [config, setConfig] = useState<{
    enabled: boolean;
    totpConfig: { enabled: boolean };
    createdAt: string;
    updatedAt: string;
  } | null>(null);

  useEffect(() => {
    const fetchMfaConfig = async () => {
      try {
        const result = await getMfaConfig();
        setConfig(result);
        console.log("MFA enabled:", result.enabled);
        console.log("TOTP enabled:", result.totpConfig.enabled);
      } catch (error) {
        console.error("Failed to fetch MFA config:", error);
      }
    };

    fetchMfaConfig();
  }, []);

  if (!config) {
    return <div>Loading MFA configuration...</div>;
  }

  return (
    <div>
      <h3>MFA Configuration</h3>
      <p>MFA Enabled: {config.enabled ? "Yes" : "No"}</p>
      <p>TOTP Enabled: {config.totpConfig.enabled ? "Yes" : "No"}</p>
      <p>Last Updated: {new Date(config.updatedAt).toLocaleDateString()}</p>
    </div>
  );
}

音轨 MFA 录入提示

使用 useRecordMfaEnrollmentPrompted 以跟踪显示用户时的 MFA 入学提示 :

import { 
  useRecordMfaEnrollmentPrompted, 
  useCurrentUser 
} from "@coinbase/cdp-hooks";
import { useState } from "react";

function MfaEnrollmentPrompt() {
  const { recordMfaEnrollmentPrompted } = useRecordMfaEnrollmentPrompted();
  const { currentUser } = useCurrentUser();
  const [showPrompt, setShowPrompt] = useState(true);

  const handleSkip = async () => {
    try {
      // Record that the user was prompted and chose to skip
      const result = await recordMfaEnrollmentPrompted();
      
      console.log("Enrollment prompt recorded at:", result.enrollmentPromptedAt);
      
      // User's state is now updated with the timestamp
      console.log("Last prompted:", currentUser?.mfaMethods?.enrollmentPromptedAt);
      
      setShowPrompt(false);
    } catch (error) {
      console.error("Failed to record MFA prompt:", error);
    }
  };

  const handleEnroll = async () => {
    // Record the prompt before starting enrollment
    await recordMfaEnrollmentPrompted();
    
    // Navigate to enrollment flow
    console.log("Starting MFA enrollment...");
  };

  // Check if user was recently prompted (within last 30 days)
  const wasRecentlyPrompted = () => {
    if (!currentUser?.mfaMethods?.enrollmentPromptedAt) {
      return false;
    }
    
    const promptedDate = new Date(currentUser.mfaMethods.enrollmentPromptedAt);
    const thirtyDaysAgo = new Date();
    thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
    
    return promptedDate > thirtyDaysAgo;
  };

  // Don't show prompt if user was recently prompted or already enrolled
  if (!showPrompt || wasRecentlyPrompted() || currentUser?.mfaMethods?.totp) {
    return null;
  }

  return (
    <div style={{ 
      padding: "20px", 
      backgroundColor: "#f0f9ff", 
      border: "1px solid #bae6fd",
      borderRadius: "8px"
    }}>
      <h3>🔒 Secure Your Account with MFA</h3>
      <p>
        Add an extra layer of security by enabling Multi-Factor Authentication.
      </p>
      
      <div style={{ marginTop: "15px", display: "flex", gap: "10px" }}>
        <button onClick={handleEnroll}>
          Enable MFA
        </button>
        
        <button onClick={handleSkip} style={{ backgroundColor: "#e5e7eb" }}>
          Skip for Now
        </button>
      </div>
      
      <p style={{ fontSize: "12px", marginTop: "10px", color: "#6b7280" }}>
        You won't be prompted again for 30 days
      </p>
    </div>
  );
}

使用案例跟踪录入提示 :

  • 跟踪用户跳过 MFA 注册以避免超速
  • 根据时间间隔执行智能再提示逻辑
  • 分析整个用户群的MFA采纳率
  • 确定需要更多教育的用户,了解外交部福利

查看用户信息

一旦最终用户签名,您可以在您的应用程序中显示其信息:

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.evmSmartAccountObjects?.[0]?.address && (
          <p>Smart Account: {user.evmSmartAccounts[0]}</p>
        )}
      </>

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

手动创建账户

如果您没有配置您的提供者 createOnLogin,您可以在需要使用这些钩子时手动为认证用户创建账户.

创建 EOA 账户

使用 useCreateEoaAccount 勾选为当前用户创建 EOA( 外部拥有账户 ) :

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

function CreateEoaAccount() {
  const { createEoaAccount } = useCreateEoaAccount();
  const { currentUser } = useCurrentUser();

  const handleCreateAccount = async () => {
    try {
      const evmAddress = await createEoaAccount();
      console.log("Created EOA:", evmAddress);
      // The user object is automatically updated
    } catch (error) {
      console.error("Failed to create EOA:", error);
    }
  };

  return (
    <button
      onClick={handleCreateAccount}
      disabled={!currentUser || currentUser.evmAccounts?.length > 0}
    >
      Create EOA Account
    </button>
  );
}

说明: 如果用户已经有一个 EOA 账户, 这将产生错误 .

创建智能账户

使用 useCreateSmartAccount 为当前用户创建智能账户的钩子 :

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

function CreateSmartAccount() {
  const { createSmartAccount } = useCreateSmartAccount();
  const { currentUser } = useCurrentUser();

  const handleCreateAccount = async () => {
    try {
      // Create Smart Account (will automatically create EOA first if needed)
      const smartAccountAddress = await createSmartAccount();
      console.log("Created Smart Account:", smartAccountAddress);
      // The user object is automatically updated
    } catch (error) {
      console.error("Failed to create Smart Account:", error);
    }
  };

  const handleCreateWithSpendPermissions = async () => {
    try {
      // Create Smart Account with spend permissions enabled
      const smartAccountAddress = await createSmartAccount({
        enableSpendPermissions: true
      });
      console.log("Created Smart Account with spend permissions:", smartAccountAddress);
    } catch (error) {
      console.error("Failed to create Smart Account:", error);
    }
  };

  return (
    <div>
      <button
        onClick={handleCreateAccount}
        disabled={!currentUser || currentUser.evmSmartAccounts?.length > 0}
      >
        Create Smart Account
      </button>
      <button
        onClick={handleCreateWithSpendPermissions}
        disabled={!currentUser || currentUser.evmSmartAccounts?.length > 0}
      >
        Create Smart Account with Spend Permissions
      </button>
    </div>
  );
}

说明: 如果用户已有智能账户, 则会出错 。 如果用户没有EOA,将首先自动创建一个,作为智能账户所有者.

创建一个索拉纳账户

使用 useCreateSolanaAccount 为当前用户创建 Solana 账户的钩选 :

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

function CreateSolanaAccount() {
  const { createSolanaAccount } = useCreateSolanaAccount();
  const { currentUser } = useCurrentUser();

  const handleCreateAccount = async () => {
    try {
      const solanaAddress = await createSolanaAccount();
      console.log("Created Solana account:", solanaAddress);
      // The user object is automatically updated
    } catch (error) {
      console.error("Failed to create Solana account:", error);
    }
  };

  return (
    <button
      onClick={handleCreateAccount}
      disabled={!currentUser || currentUser.solanaAccounts?.length > 0}
    >
      Create Solana Account
    </button>
  );
}

说明: 如果用户已经有一个索拉纳账户, 则会出现错误 .

与索拉纳合作

当您的应用程序配置为 solana: { createOnLogin: true },可以使用索拉纳特定的钩子与索拉纳账户进行交互.

访问索拉纳地址

使用 useSolanaAddress 获取用户第一个索拉纳地址的钩子 :

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

签署索拉纳交易

使用 useSignSolanaTransaction 签下索拉纳交易的钩子 :

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

签署索拉纳信件

使用 useSignSolanaMessage 在 Solana 账户上签名任意消息的钩钩 :

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

发送索拉纳交易

使用 useSendSolanaTransaction 钩住在单一动作中签名和发送索拉纳交易. 这一点得到以下支持:

  • 索拉纳主网
  • 索拉纳·德夫内
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
        useCdpSponsor: true, // Optional: CDP sponsors the transaction fee
      });
      
      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>
  );
}

发送交易

我们支持在以下网络上以单一行动签署和发送一个区块链交易:

  • 基础
  • 塞波利亚基地
  • 以利亚
  • 伊特鲁姆·塞波利亚
  • 雪崩
  • 坐标
  • 乐观主义
  • 多边形
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>;
}

对于CDP API所支持的网络以外的网络,您的终端用户必须签名交易,然后 您必须亲自广播交易。 此示例使用来自 viem 广播交易.

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

签名信件和输入的数据

终端用户可以签名EVM消息,散列和打出的数据来生成各种链上应用程序的签名.

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

导出私有密钥

终端用户可以从嵌入式钱包中导出私人密钥,让他们能将密钥导入自己选择的相容钱包.

安全 Iframe 导出

私人密钥导出只通过安全的iframe钩支持. 他们创建了一个安全的iframe,将私钥直接复制到用户的剪贴板上,而不会暴露在您的应用程序的JavaScript上下文中.

通过 Iframe 导出 EVM 私钥
import { useEvmKeyExportIframe, useEvmAddress } from "@coinbase/cdp-hooks";
import { useRef, useState } from "react";

function ExportEvmKey() {
  const { evmAddress } = useEvmAddress();
  const containerRef = useRef<HTMLDivElement>(null);
  const [isActive, setIsActive] = useState(false);

  const { status, cleanup } = useEvmKeyExportIframe({
    address: isActive && evmAddress ? evmAddress : "",
    containerRef: isActive ? containerRef : { current: null },
    label: "Copy Private Key",
    copiedLabel: "Copied!",
  });

  return (
    <div>
      {!isActive ? (
        <button onClick={() => setIsActive(true)} disabled={!evmAddress}>
          Show Export Button
        </button>
      ) : (
        <button onClick={() => { cleanup(); setIsActive(false); }}>
          Hide Export Button
        </button>
      )}
      {isActive && (
        <div>
          <div ref={containerRef} />
          <p>Status: {status ?? "initializing"}</p>
        </div>
      )}
    </div>
  );
}
通过 Iframe 导出 Solana 私钥
import { useSolanaKeyExportIframe, useSolanaAddress } from "@coinbase/cdp-hooks";
import { useRef, useState } from "react";

function ExportSolanaKey() {
  const { solanaAddress } = useSolanaAddress();
  const containerRef = useRef<HTMLDivElement>(null);
  const [isActive, setIsActive] = useState(false);

  const { status, cleanup } = useSolanaKeyExportIframe({
    address: isActive && solanaAddress ? solanaAddress : "",
    containerRef: isActive ? containerRef : { current: null },
    label: "Copy Private Key",
    copiedLabel: "Copied!",
  });

  return (
    <div>
      {!isActive ? (
        <button onClick={() => setIsActive(true)} disabled={!solanaAddress}>
          Show Export Button
        </button>
      ) : (
        <button onClick={() => { cleanup(); setIsActive(false); }}>
          Hide Export Button
        </button>
      )}
      {isActive && (
        <div>
          <div ref={containerRef} />
          <p>Status: {status ?? "initializing"}</p>
        </div>
      )}
    </div>
  );
}
通过 Iframe 导出回收词

设定 exportFormat: "mnemonic" 以导出 EVM 或 Solana 账户的 24 字恢复短语 。 检查 availableExportFormats 在提供选项之前包括它。 清单中不包括的格式 error 无需出口请求。 如果列表缺失,则由服务器决定. 选择 exportFormat 输出私钥.

import { useCurrentUser, useEvmKeyExportIframe } from "@coinbase/cdp-hooks";
import { useRef } from "react";

function ExportEvmRecoveryPhrase() {
  const { currentUser } = useCurrentUser();
  const account = currentUser?.evmAccountObjects?.[0];
  const canExportMnemonic = account?.availableExportFormats?.includes("mnemonic") ?? false;
  const containerRef = useRef<HTMLDivElement>(null);

  const { status, message } = useEvmKeyExportIframe({
    address: account && canExportMnemonic ? account.address : "",
    containerRef: canExportMnemonic ? containerRef : { current: null },
    exportFormat: "mnemonic",
    label: "Copy recovery phrase",
    copiedLabel: "Copied!",
  });

  if (!canExportMnemonic) return <p>This account only supports private key export.</p>;

  return (
    <div>
      <div ref={containerRef} />
      <p>Status: {status ?? "initializing"}</p>
      {status === "error" && <p>{message}</p>}
    </div>
  );
}

智能账户操作

Smart counts提供高级账户抽象功能,并带有React hooks.

创建支出权限

支出许可允许Smart Accounts在规定的期限和期限内将支出授权给其他账户. 这个 useCreateSpendPermission 钩子提供了使用自动用户操作跟踪来创建花费权限的简单方式.

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?.evmSmartAccountObjects?.[0]?.address}
      >
        {status === "pending" ? "Creating..." : "Create Spend Permission"}
      </button>
    </div>
  );
}

钩子自动 :

  • 检测用户的智能账户(或允许您指定) evmSmartAccount(中文(简体) )
  • 转换 periodInDays 至秒时
  • 确定像“eth”和“usdc”这样的符号,以订立地址
  • 跟踪用户运行状态并提供实时更新

列表支出权限

使用 useListSpendPermissions 抓取智能账户的全部使用权限 。 此勾钩遵循查询样式模式, 启用时会自动获取权限 .

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

function SpendPermissionsList() {
  const { currentUser } = useCurrentUser();
  const { data, error, status, refetch } = useListSpendPermissions({
    evmSmartAccount: currentUser?.evmSmartAccountObjects?.[0]?.address,
    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>
  );
}

撤销支出权限

使用 useRevokeSpendPermission 取消支出许可。 钩子会自动跟踪用户操作,并提供实时状态更新.

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

发送用户操作

从Smart counts发送用户操作,支持多通电话和付费赞助. 钩子返回执行用户操作的方法,并且 status, (中文(简体) ), data,以及 error 要读取用户操作结果的属性 :

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

function SendUserOperation() {
  const { sendUserOperation, status, data, error } = useSendUserOperation();
  const { currentUser } = useCurrentUser();

  const handleSendUserOperation = async () => {
    const smartAccount = currentUser?.evmSmartAccountObjects?.[0]?.address;
    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>
  );
}

与EIP-8021的交易归属:

您可以在用户操作中添加归属数据, 用于跟踪应用使用情况和收入共享 :

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

function SendUserOperationWithAttribution() {
  const { sendUserOperation, status } = useSendUserOperation();
  const { currentUser } = useCurrentUser();
  
  // EIP-8021 data suffix for "baseapp" attribution
  const dataSuffix = "0xdddddddd62617365617070070080218021802180218021802180218021";

  const handleSendWithAttribution = async () => {
    const smartAccount = currentUser?.evmSmartAccountObjects?.[0]?.address;
    if (!smartAccount) return;

    await sendUserOperation({
      evmSmartAccount: smartAccount,
      network: "base-sepolia",
      calls: [{ to: "0xTargetContract", value: 0n, data: "0x" }],
      dataSuffix, // Attribution data appended to callData
      useCdpPaymaster: true,
    });
  };

  return (
    <button onClick={handleSendWithAttribution} disabled={status === "pending"}>
      Send with Attribution
    </button>
  );
}

这个 dataSuffix 参数接受后面的六进制编码字符串 EIP-8021标准这有助于跟踪和分享收入的链上归属.

跟踪用户运行状态

使用 useWaitForUserOperation 勾通用户运行状态的民意调查并提供实时更新. 此钩子立即点出一个查询以获取用户操作的结果 :

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

有条件投票

你可以控制当 useWaitForUserOperation 钩子应该使用 enabled 参数 :

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 Op

版本与发布记录

@coinbase/cdp-hooks 共发布 129 个版本,首次发布于 2025/07/12;过去 12 个月发布了 71 个版本。

版本发布日期距今
0.0.130latest2026/11/10今天
0.0.1292026/11/055 天前
0.0.1282026/10/2219 天前
0.0.1272026/10/071 个月前
0.0.1262026/09/191 个月前
0.0.1252026/09/191 个月前

其他发布通道:placeholder → 0.0.0。

关于 @coinbase/cdp-hooks 的常见问题

@coinbase/cdp-hooks 是什么?

这个软件包为方便地访问嵌入式钱包功能提供了回放钩. 建于顶上 @coinbase/cdp-core,它为终端用户认证和嵌入式钱包操作提供了一个反应方便的界面.

如何安装 @coinbase/cdp-hooks?

在项目目录运行 npm install @coinbase/cdp-hooks,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(>=18.2.0)、@coinbase/cdp-core(^0.0.130)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@coinbase/cdp-hooks 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@coinbase/cdp-hooks 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@coinbase/cdp-hooks 可以商用吗?

@coinbase/cdp-hooks 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@coinbase/cdp-hooks 的下载量有多少?

根据 npm 官方下载统计,@coinbase/cdp-hooks 在 2026/10/11 – 2026/11/09 这 30 天内被下载 29,475 次,日均约 983 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@coinbase/cdp-hooks 还在维护吗?

最新版本 0.0.130 发布于 2026/11/10(今天);至今共发布 129 个版本,其中过去 12 个月发布了 71 个;首次发布于 2025/07/12。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。