这个软件包为方便地访问嵌入式钱包功能提供了回放钩.
建于顶上 @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 项目信息
- 在此处签名或创建账户 CDP 门户
- 在您的仪表板上,从最上方的下拉位置选择一个项目,并复制项目ID
允许列出本地应用程序
- 导航到 嵌入式钱包配置 在 CDP 门户网站中,单击添加来源以包含本地应用程序
- 输入本地运行的应用程序的出处 - 例如 ,
http://localhost:3000 - 点击 重新添加来源以保存您的更改
设置提供者
接下来,您需要用 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 将:
- 用户登录时不自动创建任何账户
- 需要使用账户创建钩头进行手动账户创建(见下文)
- 完全控制创建时间和类型的账户
签名 在用户中
终端用户认证分两个步骤进行:
- 用户输入其电子邮件地址以启动认证流程,该流程将发送用户一个时间密码并返回 a
flowId - 用户提交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()- 首先返回智能账户,然后先返回EOAuseSolanaAddress()- 首先返回索拉纳账户
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>
);
}
链接任何 OAuth 提供者
使用 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 钩.
先决条件
在使用自定义认证前 :
-
在 CDP 门户网站中配置身份提供者数字 :
- 导航到 嵌入式钱包配置
- 点击自定义认证标签
- 添加您的 JWKS 端点 URL( 例如 ),
https://your-domain.auth0.com/.well-known/jwks.json(中文(简体) ) - 配置您的 JWT 发行者和观众
-
提供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