在本教程中,我们将学习如何构建一个连接到你的加密钱包的 Web3 Dapp(去中心化应用程序),允许你转移资金和铸造 NFT。我们将使用 Next.js(一个流行的 React 框架)和 Wagmi.sh(一个 React Hooks 集合,可轻松将你的钱包集成到你的网站中)。
以下是本教程的大纲:
- 入门指南
- 第 1 部分:使用 React + Wagmi 转移加密货币
- 第 2 部分:使用 React + Wagmi 铸造 NFT
作者
本文由 RareSkills 的研究实习生 Aymeric Taylor(LinkedIn,Twitter)共同撰写。
入门指南
为了简单起见,我们将在此教程中使用“Polygon Mumbai”作为我们的测试网络,OpenSea 也支持该网络。
将你的钱包连接到 Polygon Mumbai - MetaMask
导航至 MetaMask → Settings → Advanced,并确保你启用了 Show test networks(显示测试网络)。

接下来,点击右上角的网络选择,然后选择 Add network(添加网络)

如果 Polygon Mumbai 尚未提供选择,你可以按照以下步骤操作。
选择 Add a network manually(手动添加网络)并输入以下内容:
Network Name: Matic Mumbai
New RPC URL : https://rpc-mumbai.maticvigil.com/
Chain ID : 80001
Currency Symbol : MATIC
Block explorer URL (optional) : https://mumbai.polygonscan.com/
现在只需切换到 Polygon 网络即可
Wagmi 示例第 1 部分:使用 React + Wagmi 转移 Ether
第 1 步:使用 node js 设置网站
你需要安装 nodejs 才能进行此操作。
首先,使用以下命令创建你的 Next.js 项目:
npx create-next-app@latest myapp
使用方向键和回车键勾选 Typescript 和 ESLint 选项。它应该看起来像这样:

在 vscode 中打开你的项目,并安装 wagmi.sh 和 use-debounce 包(我们稍后会详细介绍)。
npm i wagmi ethers@^5
npm i use-debounce --save
Wagmi 本质上是一组 React Hooks,它通过提供连接钱包和与合约交互等实用功能来简化 Ethereum 开发,我们将在本教程中学习这些功能。更多信息请访问 wagmi.sh。
第 2 步:使用 configureChains 选择要连接的网络
前往 pages/_app.tsx 并添加以下代码。每项功能都在注释部分进行了解释。
import "@/styles/globals.css"; // CSS doesnt really matter now
import type { AppProps } from "next/app";
import { WagmiConfig, configureChains, createClient, mainnet } from "wagmi";
import { publicProvider } from "wagmi/providers/public";
import { polygonMumbai } from "wagmi/chains";
import { CoinbaseWalletConnector } from 'wagmi/connectors/coinbaseWallet'
import { InjectedConnector } from 'wagmi/connectors/injected'
import { MetaMaskConnector } from 'wagmi/connectors/metaMask'
import { WalletConnectConnector } from 'wagmi/connectors/walletConnect'
// configure the chains and provider that you want to use for your app,
// keep in mind that you're allowed to pass any EVM-compatible chain.
// It is also encouraged that you pass both alchemyProvider and infuraProvider.
const { chains, provider, webSocketProvider } = configureChains(
[mainnet, polygonMumbai],
[publicProvider()]
);
// This creates a wagmi client instance of createClient
// and passes in the provider and webSocketProvider.
const client = createClient({
autoConnect: false,
provider,
webSocketProvider,
connectors: [ // connectors is to connect your wallet, defaults to InjectedConnector();
new MetaMaskConnector({ chains }),
new CoinbaseWalletConnector({
chains,
options: {
appName: 'wagmi',
},
}),
new WalletConnectConnector({
chains,
options: {
projectId: '...',
},
}),
new InjectedConnector({
chains,
options: {
name: 'Injected',
shimDisconnect: true,
},
}),
],
});
export default function App({ Component, pageProps }: AppProps) {
return (
// Wrap your application with the WagmiConfig component
// and pass the client instance as a prop to it.
<WagmiConfig client={client}>
<Component {...pageProps} />
</WagmiConfig>
);
}
现在我们已经配置了网络,下一步是允许用户选择要连接的钱包。正如你上面看到的,我们设置了 4 个钱包连接器。MetaMask、WalletConnect、Coinbase 和 Injected(再次强调,它基本上就是你的默认钱包)。
第 3 步:使用 useConnect 启用选择浏览器钱包功能
在 pages/index.tsx 中复制并粘贴以下代码。请确保添加 CSS,这样它看起来才会美观!
import { useAccount, useConnect } from "wagmi";
import { useEffect } from "react";
import styles from "@/styles/Home.module.css";
export default function Home() {
const { connect, connectors } = useConnect();
const { isConnected } = useAccount();
useEffect(() => {
console.log(
`Current connection status: ${isConnected ? "connected" : "disconnected"}`
);
}, [isConnected]);
return (
<>
<p
className={styles.status}
style={{
color: isConnected ? "green" : "red",
}}
>
{" "}
{isConnected !== undefined
? isConnected
? "Connected"
: "Disconnected"
: "loading..."}
</p>
<div className={styles.maincontainer}>
<div className={styles.container}>
<div className={styles.buttonswrapper}>
<div className={styles.buttonswrapperGrid}>
{connectors.map((connector) => (
<button
suppressHydrationWarning
key={connector.id}
onClick={() => connect({ connector })}
className={styles.button28}
>
{connector.name}
</button>
))}
</div>
</div>
</div>
{/* send funds */}
{/* mint nft */}
</div>
</>
);
}
第 4 步:添加 CSS 使其看起来更美观
删除 styles/globals.css 和 styles/Home.module.css 中的所有内容。
将下面的 CSS 代码复制粘贴到 styles/globals.css 中。
body {
height: 100vh;
background: rgb(11,3,48); /* For browsers that do not support gradients */
background: linear-gradient(to bottom right,#0b0330, #5904a4);
font-family: 'Inter Medium', sans-serif;
}
将下面的 CSS 代码复制粘贴到 styles/Home.module.css 中。
.status {
text-align: left;
margin: 0px;
font-family: "Inter Medium", sans-serif;
}
.maincontainer {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
height: 60%;
width: 60%;
}
.buttonswrapperGrid {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(2, 1fr);
grid-gap: 1.5rem;
justify-items: center;
align-items: center;
justify-content: center;
margin-bottom: 40px;
padding: 20px;
}
/* CSS */
.button28 {
align-items: center;
background-color: #da5fff;
border: 2px solid #1d0321;
border-radius: 8px;
box-sizing: border-box;
color: #111;
cursor: pointer;
display: flex;
font-family: Inter, sans-serif;
font-size: 16px;
height: 55px;
justify-content: center;
line-height: 24px;
max-width: 75%;
padding: 0 25px;
position: relative;
text-align: center;
text-decoration: none;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
width: 100%;
}
.button28:after {
background-color: #210c20;
border-radius: 8px;
content: "";
display: block;
height: 48px;
left: 0;
width: 100%;
position: absolute;
top: -2px;
transform: translate(8px, 14px);
transition: transform 0.2s ease-out;
z-index: -1;
}
.button28:hover:after {
transform: translate(0, 0);
}
.button28:active {
background-color: #ffdeda;
outline: 0;
}
.button28:hover {
outline: 0;
}
@media (min-width: 768px) {
.button28 {
padding: 0 40px;
}
}
.myinput {
background-color: rgb(253, 232, 255);
border-radius: 15px;
border: 2px solid #1d0321;
padding: 20px;
width: 44%;
height: 15px;
}
.inputcontainer {
display: flex;
justify-content: space-between; /* Space the input fields evenly */
align-items: center; /* Align the input fields vertically in the container */
}
.buttoncontainer {
display: flex;
justify-content: center; /* Center the button horizontally */
align-items: center; /* Center the button vertically */
margin-top: 16px;
margin-bottom: 16px;
}
.mintcontainer {
display: flex;
justify-content: center; /* Center the button horizontally */
align-items: center; /* Center the button vertically */
margin-top: 50px;
margin-bottom: 16px;
}
/* CSS */
.button64 {
align-items: center;
background-image: linear-gradient(144deg, #af40ff, #280b36 50%, #e30eff);
border: 0;
border-radius: 8px;
box-shadow: rgba(151, 65, 252, 0.2) 0 15px 30px -5px;
box-sizing: border-box;
color: #ffffff;
display: flex;
font-family: Phantomsans, sans-serif;
font-size: 20px;
justify-content: center;
line-height: 2em;
max-width: 100%;
min-width: 140px;
padding: 3px;
text-decoration: none;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
white-space: nowrap;
cursor: pointer;
}
.button64:active,
.button64:hover {
outline: 0;
}
.button64 span {
background-color: rgb(5, 6, 45);
padding: 16px 24px;
border-radius: 6px;
width: 100%;
height: 100%;
transition: 300ms;
}
.button64:hover span {
background: none;
}
@media (min-width: 768px) {
.button64 {
font-size: 24px;
min-width: 196px;
}
}
/* CSS */
.button49,
.button49:after {
width: 150px;
height: 76px;
line-height: 78px;
font-size: 20px;
font-family: 'Bebas Neue', sans-serif;
background: linear-gradient(45deg, transparent 5%, #ff01ee 5%);
border: 0;
color: #fff;
letter-spacing: 3px;
box-shadow: 6px 0px 0px #00E6F6;
outline: transparent;
position: relative;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
}
.button49:after {
--slice-0: inset(50% 50% 50% 50%);
--slice-1: inset(80% -6px 0 0);
--slice-2: inset(50% -6px 30% 0);
--slice-3: inset(10% -6px 85% 0);
--slice-4: inset(40% -6px 43% 0);
--slice-5: inset(80% -6px 5% 0);
content: 'GET YOUR NFT';
display: block;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, transparent 3%, #00E6F6 3%, #00E6F6 5%, #c401ff 5%);
text-shadow: -3px -3px 0px #F8F005, 3px 3px 0px #00E6F6;
clip-path: var(--slice-0);
}
.button49:hover:after {
animation: 1s glitch;
animation-timing-function: steps(2, end);
}
@keyframes glitch {
0% {
clip-path: var(--slice-1);
transform: translate(-20px, -10px);
}
10% {
clip-path: var(--slice-3);
transform: translate(10px, 10px);
}
20% {
clip-path: var(--slice-1);
transform: translate(-10px, 10px);
}
30% {
clip-path: var(--slice-3);
transform: translate(0px, 5px);
}
40% {
clip-path: var(--slice-2);
transform: translate(-5px, 0px);
}
50% {
clip-path: var(--slice-3);
transform: translate(5px, 0px);
}
60% {
clip-path: var(--slice-4);
transform: translate(5px, 10px);
}
70% {
clip-path: var(--slice-2);
transform: translate(-10px, 10px);
}
80% {
clip-path: var(--slice-5);
transform: translate(20px, -10px);
}
90% {
clip-path: var(--slice-1);
transform: translate(-10px, 0px);
}
100% {
clip-path: var(--slice-1);
transform: translate(0);
}
}
@media (min-width: 768px) {
.button49,
.button49:after {
width: 200px;
height: 86px;
line-height: 88px;
}
}
第 5 步:运行网站并进行测试
当你运行它时,这应该会启动与你钱包的连接。一旦你批准了连接,它应该看起来像这样:
npm run dev

第 6 步:添加转移加密货币的功能
现在我们已经连接了钱包,我们可以将钱包中的资金转移给其他人。确保你的钱包中有一些 Mumbai MATIC。你可以在这里获取一些:https://faucet.polygon.technology
我们现在将为交易创建输入字段和发送按钮。在 pages/ 目录下创建一个新文件并命名为 RareSend.tsx。复制粘贴下面的代码。相关解释都在代码注释中。
import { parseEther } from "ethers/lib/utils.js";
import React, { useState } from "react";
import { useDebounce } from "use-debounce";
import { usePrepareSendTransaction, useSendTransaction } from "wagmi";
import styles from "@/styles/Home.module.css";
// what this does is simply disable the SendFunds function if the value passed is false
interface SendFundsProps {
disabled?: boolean;
}
export default function SendFunds(props: SendFundsProps) {
// declare two state variables for the recipient and the amount
const [to, setTo] = useState("");
const [debouncedTo] = useDebounce(to, 500); // useDebounce() hook to debounce the recipient input
const [amount, setAmount] = useState("");
const [debouncedAmount] = useDebounce(amount, 500); // useDebounce() hook to debounce the amount input
// use the usePrepareSendTransaction() hook to prepare a transaction request
const { config } = usePrepareSendTransaction({
request: {
to: debouncedTo, // recipient from debounced input
value: debouncedAmount ? parseEther(debouncedAmount) : undefined, // amount from debounced input
},
});
// use the useSendTransaction() hook to create a transaction and send it
const { sendTransaction } = useSendTransaction(config);
return (
<>
<form
onSubmit={(e) => {
e.preventDefault();
sendTransaction?.(); // if sendTransaction is defined, execute it
}}
>
<div className={styles.inputcontainer}>
<input
aria-label="Recipient"
onChange={(e) => setTo(e.target.value)} // update the recipient state on input change
placeholder="Address destination"
value={to}
className={styles.myinput}
/>
<input
aria-label="Amount (ether)"
onChange={(e) => setAmount(e.target.value)} // update the amount state on input change
placeholder="enter amount"
value={amount}
className={styles.myinput}
/>
</div>
<div className={styles.buttoncontainer}>
<button
disabled={!sendTransaction || !to || !amount}
className={styles.button64}
>
Send
</button>{" "}
{/* disable the button if required fields are empty */}
</div>
</form>
</>
);
}
useDebounce() 解释
为了避免 RPC 过载和受到速率限制,我们将限制 usePrepareContractWrite Hook 的使用,该 Hook 会在组件挂载和参数修改时请求 Gas 估算。我们在组件中使用 useDebounce Hook,如果在 500 毫秒内没有进行任何更改,则延迟更新 Token ID。
第 7 步:插入 sendFunds 组件
接下来,只需像这样在 pages/index.tsx 中添加
import { useAccount, useConnect } from "wagmi";
import SendFunds from "./RareSend";
import { useEffect } from "react";
import styles from "@/styles/Home.module.css";
export default function Home() {
{
/* some code... */
}
return (
<>
<div>
{/* some code... */}
{/* send funds */}
<SendFunds disabled={!isConnected} />
{/* mint nft */}
</div>
</>
);
}
只要你的地址中有 Matic,你就可以将其发送到另一个账户!
你的网站现在应该看起来像这样:

恭喜你进行到这一步!你已成功创建了自己的网站,能够将钱包中的资金发送到另一个账户。你现在可以轻松地在账户之间转移加密货币,而无需依赖第三方服务或手动输入交易详情。做得好!就是这么简单!
Wagmi 示例第 2 部分:使用 React + Wagmi 铸造 NFT
我们假设你已经将 NFT 智能合约部署到了区块链上。你可以按照这个视频教程进行操作:https://youtu.be/LIoFbudNVZs。

由 AI 生成的猫的 NFT
mint(铸造)功能可以按照以下方式创建。创建一个 mint.tsx 文件并添加下面的代码。
// Initialize ethers.js and wagmi dependencies
import { ethers } from "ethers";
import * as React from "react";
import {
usePrepareContractWrite,
useContractWrite,
useWaitForTransaction,
} from "wagmi";
import styles from "@/styles/Home.module.css";
// Define a React function component to mint an NFT
export function MintNFT() {
// Prepare the contract write configuration by providing the contract's address, ABI, function name, and overrides
const { config } = usePrepareContractWrite({
address: "Your Contract Address",
abi: [
{
name: "mint",
type: "function",
stateMutability: "payable",
inputs: [],
outputs: [],
},
],
functionName: "mint",
overrides: {
from: "Your Walllet Address",
value: ethers.utils.parseEther("0.000000001"), //the integer value should match your nft minting requirements
},
});
// Use the useContractWrite hook to write to the contract's mint function and obtain the transaction data and write function
const { data, write } = useContractWrite(config);
// Use the useWaitForTransaction hook to wait for the transaction to be mined and return loading and success states
const { isLoading, isSuccess } = useWaitForTransaction({
hash: data?.hash,
});
// Render the component with a button that triggers the mint transaction when clicked, a loading message while the transaction is in progress, and a success message when the transaction is successful
return (
<div className={styles.mintcontainer}>
<button
disabled={!write || isLoading}
onClick={() => write?.()}
className={styles.button49}
>
{isLoading ? "Minting..." : "Mint"}
</button>
{isSuccess && (
<div>
Successfully minted your NFT!
<div>
<a href={`https://etherscan.io/tx/${data?.hash}`}>Etherscan</a>
</div>
</div>
)}
</div>
);
}
现在你可以像这样简单地将它插入到 index.tsx 文件中:
import { useAccount, useConnect } from "wagmi";
import SendFunds from "./RareSend";
import { useEffect } from "react";
import styles from "@/styles/Home.module.css";
import { MintNFT } from "./mint";
export default function Home() {
const { connect, connectors } = useConnect();
const { isConnected } = useAccount();
useEffect(() => {
console.log(
`Current connection status: ${isConnected ? "connected" : "disconnected"}`
);
}, [isConnected]);
return (
<>
<p
className={styles.status}
style={{
color: isConnected ? "green" : "red",
}}
>
{" "}
{isConnected !== undefined
? isConnected
? "Connected"
: "Disconnected"
: "loading..."}
</p>
<div className={styles.maincontainer}>
<div className={styles.container}>
<div className={styles.buttonswrapper}>
<div className={styles.buttonswrapperGrid}>
{connectors.map((connector) => (
<button
suppressHydrationWarning
key={connector.id}
onClick={() => connect({ connector })}
className={styles.button28}
>
{connector.name}
</button>
))}
</div>
</div>
</div>
{/* send funds */}
<SendFunds disabled={!isConnected} />
{/* mint nft */}
<MintNFT />
</div>
</>
);
}
你最终的网站应该看起来像这样:

恭喜你!你刚刚制作了自己的去中心化应用程序,它能够发送交易和铸造 NFT。
最初发布于 2023 年 4 月 24 日