第三节:DApp调用智能合约前言在上一节中,我们成功实现了DApp与钱包的连接。这意味着用户已经可以在我们的应用里点击“ConnectWallet”,并授权DApp获取他们的地址信息。但是,光有钱包还不够。DApp真正的核心在于——调用智能合约。智能合约是区块链上的“后端逻辑”
在上一节中,我们成功实现了 DApp 与钱包的连接。这意味着用户已经可以在我们的应用里点击“Connect Wallet”,并授权 DApp 获取他们的地址信息。
但是,光有钱包还不够。DApp 真正的核心在于——调用智能合约。
智能合约是区块链上的“后端逻辑”。当我们要转账、铸造 NFT、参加投票、质押代币时,实际上就是在调用智能合约的方法。
因此,学习如何调用合约,是让 DApp 具备实际业务功能的关键一步。
在本节中,我们将学习:
ABI,全称 Application Binary Interface(应用二进制接口)。
它定义了智能合约的方法签名、参数类型、返回类型。
举个例子,ERC721 标准里的 balanceOf
方法的 ABI 描述如下:
{
"type": "function",
"name": "balanceOf",
"stateMutability": "view",
"inputs": [{ "name": "owner", "type": "address" }],
"outputs": [{ "type": "uint256" }]
}
这就告诉我们:
address
uint256
view
(只读,不会改写链上状态)当合约被部署到区块链上时,会获得一个唯一的地址,比如:
0xEcd0D12E21805803f70de03B72B1C162dB0898d9
这个地址和 ABI 一起,就可以让 DApp 确定如何与合约交互。
调用智能合约方法,本质上就是发起一个以太坊交易(Transaction):
特别要注意:
DApp 与合约交互有两种方式:
window.ethereum
对象。ethereum.request({ method, params })
来与区块链交互。例如,获取当前网络 ID:
await window.ethereum.request({ method: "eth_chainId" });
// 0x1 表示以太坊主网
获取用户的账户:
async function getAccount() {
const accounts = await window.ethereum
.request({ method: "eth_requestAccounts" })
.catch((err) => {
if (err.code === 4001) {
console.log("用户拒绝了连接请求");
} else {
console.error(err);
}
});
return accounts[0];
}
// 不需要用户钱包
const account = "0xEcd0D12E21805803f70de03B72B1C162dB0898d9";
const response = await fetch("https://mainnet.infura.io/v3/<你的API_KEY>", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "eth_getBalance",
params: [account, "latest"]
}),
});
const data = await response.json();
console.log("用户余额:", parseInt(data.result, 16) / 1e18, "ETH");
如果只是查余额 / 读合约数据 → 节点 RPC 足够。
如果要转账 / 调用合约写方法 → 必须通过钱包,因为只有钱包有用户的私钥能签名。
我们来实现一个简单的需求:读取当前钱包地址下有多少个 NFT。
useReadContract
Hook 读取数据。useAccount
获取当前连接的钱包地址。balanceOf
方法。import { http, useReadContract } from "wagmi";
import { Mainnet, WagmiWeb3ConfigProvider, MetaMask } from '@ant-design/web3-wagmi';
import { Address, NFTCard, ConnectButton, Connector, useAccount } from "@ant-design/web3";
const CallTest = () => {
const { account } = useAccount();
const result = useReadContract({
abi: [
{
type: 'function',
name: 'balanceOf',
stateMutability: 'view',
inputs: [{ name: 'account', type: 'address' }],
outputs: [{ type: 'uint256' }],
},
],
address: '0xEcd0D12E21805803f70de03B72B1C162dB0898d9',
functionName: 'balanceOf',
args: [account?.address as `0x${string}`],
});
return (
<div>
你的 NFT 数量:{result.data?.toString()}
</div>
);
};
export default function Web3() {
return (
<WagmiWeb3ConfigProvider
chains={[Mainnet]}
transports={{ [Mainnet.id]: http() }}
wallets={[MetaMask()]}
>
<Connector>
<ConnectButton />
</Connector>
<CallTest />
</WagmiWeb3ConfigProvider>
);
}
useReadContract
:读取合约方法的 Hook。abi
:定义方法的 ABI。args
:传入参数,这里是用户钱包地址。result.data
:返回值(用户 NFT 数量)。account
为空,需要先点击“Connect Wallet”。仅仅能读取数据还不够,一个真正的 DApp 必须能 写入链上数据。\
我们来实现调用合约的 mint
方法,铸造一个 NFT。
useWriteContract
Hook 调用写方法。import { parseEther } from "viem";
import { Button, message } from "antd";
import { http, useReadContract, useWriteContract } from "wagmi";
import { Mainnet, WagmiWeb3ConfigProvider, MetaMask } from '@ant-design/web3-wagmi';
import { Address, NFTCard, ConnectButton, Connector, useAccount } from "@ant-design/web3";
const CallTest = () => {
const { account } = useAccount();
const result = useReadContract({
abi: [
{
type: 'function',
name: 'balanceOf',
stateMutability: 'view',
inputs: [{ name: 'account', type: 'address' }],
outputs: [{ type: 'uint256' }],
},
],
address: '0xEcd0D12E21805803f70de03B72B1C162dB0898d9',
functionName: 'balanceOf',
args: [account?.address as `0x${string}`],
});
const { writeContract } = useWriteContract();
return (
<div>
你的 NFT 数量:{result.data?.toString()}
<Button
onClick={() => {
writeContract(
{
abi: [
{
type: "function",
name: "mint",
stateMutability: "payable",
inputs: [{ name: "quantity", type: "uint256" }],
outputs: [],
},
],
address: "0xEcd0D12E21805803f70de03B72B1C162dB0898d9",
functionName: "mint",
args: [BigInt(1)],
value: parseEther("0.01"),
},
{
onSuccess: () => {
message.success("Mint 成功!");
},
onError: (err) => {
message.error(err.message);
},
}
);
}}
>
Mint NFT
</Button>
</div>
);
};
value
。onSuccess
与 onError
用于用户提示。全局配置 WagmiWeb3ConfigProvider\ 建议放在应用的最外层,避免在多个组件中重复配置。
ABI 文件管理
src/abis/
目录下统一管理。安全性考虑
错误处理
在本节中,我们完成了 DApp 的一次跨越式提升:
到这里,我们的 DApp 已经具备了“读 + 写”的链上交互能力。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!