# @coinbase/cdp-hooks 中文文档

规范地址：https://npmcn.com/package/%40coinbase/cdp-hooks/

版本：0\.0\.130
npm 元数据更新时间：2026-11-09T23:36:29.332Z
中文译文更新时间：2026-11-10T10:29:06.814Z
软件包许可：未声明

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40coinbase/cdp-hooks
原文对照：https://npmcn.com/package/%40coinbase/cdp-hooks/?lang=original

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：746 kB，398 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：0 个
- peer 依赖：react \>=18\.2\.0，@coinbase/cdp-core ^0\.0\.130
- 首次发布：2025-07-11T19:29:18.843Z
- 最新版本发布：2026-11-09T23:36:29.140Z
- 近 30 天下载量：29475（2026-10-11 至 2026-11-09，npm 官方下载统计）
- 已发布版本数：129（近 12 个月 71 个）

## 中文简介

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

## 安装

```sh
npm install @coinbase/cdp-hooks
```

也可以使用：`pnpm add @coinbase/cdp-hooks`、`yarn add @coinbase/cdp-hooks`、`bun add @coinbase/cdp-hooks`

## README

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

## 快速启动

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

### 安装

#### 网络应用程序

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

```bash
# 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
```

#### 反应本地应用程序

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

```bash
# 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 的 [反应原生存储指南](https://github.com/coinbase/cdp-web/blob/master/packages/core/README.md#react-native-storage)。 。 。 .

**反应原生设置代码**

在导入您的应用程序之前, 您需要初始化密码多填充 。 创建或更新您的出入口文件( 通常情况下) `index.js` 或者说 `index.ts`:

```typescript
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 门户](https://portal.cdp.coinbase.com/)
2.  在您的仪表板上,从最上方的下拉位置选择一个项目,并复制项目ID

### 允许列出本地应用程序

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

### 设置提供者

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

#### 网络应用程序

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

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

#### 反应本地应用程序

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

```tsx
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`。 。 。 .

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

#### 智能账户配置

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

```tsx
function App() {
  return (
    <CDPHooksProvider
      config={{
        projectId: "your-project-id",
        ethereum: {
          createOnLogin: "smart", // Creates Smart Accounts instead of EOAs
        },
      }}
    >
      <App />
    </CDPHooksProvider>
  );
}
```

-   何时 `ethereum.createOnLogin` 设置为 `"smart"`,新用户将自动获得EOA和智能账户.

#### 索拉纳配置

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

```tsx
function App() {
  return (
    <CDPHooksProvider
      config={{
        projectId: "your-project-id",
        solana: {
          createOnLogin: true, // Creates Solana accounts
        },
      }}
    >
      <App />
    </CDPHooksProvider>
  );
}
```

-   何时 `solana.createOnLogin` 设置为 `true`,新用户会自动获得一个索拉纳账户而不是EVM账户.

#### 递延账户的创建

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

```tsx
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`,然后用户将被认证,返回用户对象.

#### 网络应用程序

```tsx
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("user@example.com")}>Sign In</button>;
}
```

### 处理多个账户

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

#### 单账户钩

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

-   `useEvmAddress()` - 首先返回智能账户,然后先返回EOA
-   `useSolanaAddress()` - 首先返回索拉纳账户

```tsx
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智能账户

```tsx
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组件,并同样处理登录流:

```tsx
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` 将电子邮件地址链接到当前已认证的用户 :

```tsx
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("additional-email@example.com")}
      disabled={!currentUser}
    >
      Link Email
    </button>
  );
}
```

#### 链接电话号码

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

```tsx
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 账户链接到当前认证的用户的钩子 :

```tsx
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 账户链接到当前认证用户的钩子 :

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

#### 链接任何 OAuth 提供者

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

```tsx
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 门户网站中配置身份提供者**数字 :
    
    -   导航到 [嵌入式钱包配置](https://portal.cdp.coinbase.com/products/embedded-wallets)
    -   点击自定义认证标签
    -   添加您的 JWKS 端点 URL( 例如 ), `https://your-domain.auth0.com/.well-known/jwks.json`(中文(简体) )
    -   配置您的 JWT 发行者和观众
2.  **提供a `customAuth.getJwt` 回调** 在提供者配置中:
    

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

#### 认证用户

```tsx
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中注册用户:

```tsx
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 为用户注册 :

```tsx
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:

```tsx
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` 核查敏感业务的短信:

```tsx
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` 检查您项目是否启用了外交部:

```tsx
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 入学提示 :

```tsx
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采纳率
-   确定需要更多教育的用户,了解外交部福利

### 查看用户信息

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

```tsx
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( 外部拥有账户 ) :

```tsx
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` 为当前用户创建智能账户的钩子 :

```tsx
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 账户的钩选 :

```tsx
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` 获取用户第一个索拉纳地址的钩子 :

```tsx
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` 签下索拉纳交易的钩子 :

```tsx
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 账户上签名任意消息的钩钩 :

```tsx
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` 钩住在单一动作中签名和发送索拉纳交易. 这一点得到以下支持:

-   索拉纳主网
-   索拉纳·德夫内

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

### 发送交易

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

-   基础
-   塞波利亚基地
-   以利亚
-   伊特鲁姆·塞波利亚
-   雪崩
-   坐标
-   乐观主义
-   多边形

```tsx
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` 广播交易.

```tsx
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消息,散列和打出的数据来生成各种链上应用程序的签名.

```tsx
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 私钥

```tsx
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 私钥

```tsx
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` 输出私钥.

```tsx
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` 钩子提供了使用自动用户操作跟踪来创建花费权限的简单方式.

```tsx
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` 抓取智能账户的全部使用权限 。 此勾钩遵循查询样式模式, 启用时会自动获取权限 .

```tsx
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` 取消支出许可。 钩子会自动跟踪用户操作,并提供实时状态更新.

```tsx
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` 要读取用户操作结果的属性 :

```tsx
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的交易归属:**

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

```tsx
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标准](https://eip.tools/eip/8021)这有助于跟踪和分享收入的链上归属.

#### 跟踪用户运行状态

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

```tsx
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` 参数 :

```tsx
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 是什么？

这个软件包为方便地访问嵌入式钱包功能提供了回放钩\. 建于顶上 @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 和提交记录判断。

