从零开始构建你的第一个 Web3 DApp —— 3. DApp 调用智能合约

第三节:DApp调用智能合约前言在上一节中,我们成功实现了DApp与钱包的连接。这意味着用户已经可以在我们的应用里点击“ConnectWallet”,并授权DApp获取他们的地址信息。但是,光有钱包还不够。DApp真正的核心在于——调用智能合约。智能合约是区块链上的“后端逻辑”

第三节:DApp 调用智能合约

前言

在上一节中,我们成功实现了 DApp 与钱包的连接。这意味着用户已经可以在我们的应用里点击“Connect Wallet”,并授权 DApp 获取他们的地址信息。
但是,光有钱包还不够。DApp 真正的核心在于——调用智能合约

智能合约是区块链上的“后端逻辑”。当我们要转账、铸造 NFT、参加投票、质押代币时,实际上就是在调用智能合约的方法。
因此,学习如何调用合约,是让 DApp 具备实际业务功能的关键一步。

在本节中,我们将学习:

  1. 调用合约的基本原理(ABI、合约地址、交易流程)
  2. 调用合约的两种方式(通过钱包、通过节点 RPC)
  3. 实战 1:调用只读方法 balanceOf
  4. 实战 2:调用写方法 mint
  5. 最佳实践与常见错误

一、调用智能合约的基本原理

1. 合约 ABI

ABI,全称 Application Binary Interface(应用二进制接口)。
它定义了智能合约的方法签名、参数类型、返回类型。
举个例子,ERC721 标准里的 balanceOf 方法的 ABI 描述如下:

{
  "type": "function",
  "name": "balanceOf",
  "stateMutability": "view",
  "inputs": [{ "name": "owner", "type": "address" }],
  "outputs": [{ "type": "uint256" }]
}

这就告诉我们:

  • 方法名:balanceOf
  • 参数:一个 address
  • 返回值:一个 uint256
  • 方法属性:view(只读,不会改写链上状态)

2. 合约地址

当合约被部署到区块链上时,会获得一个唯一的地址,比如:

0xEcd0D12E21805803f70de03B72B1C162dB0898d9

这个地址和 ABI 一起,就可以让 DApp 确定如何与合约交互。

3. 交易流程

调用智能合约方法,本质上就是发起一个以太坊交易(Transaction):

  1. 构造交易数据:确定方法、参数、Gas、转账金额等。
  2. 钱包签名:用户用私钥确认交易,保证不可否认性。
  3. 广播交易:交易发送到网络,由节点打包进区块。

特别要注意:

  • 只读方法(view/pure):不需要签名,不消耗 Gas,可以直接调用节点接口读取。
  • 写方法(state-changing):必须签名,会消耗 Gas,执行后改变链上状态。

二、DApp 如何调用合约

DApp 与合约交互有两种方式:

1. 通过钱包插件连接

  • 钱包(如 MetaMask)会在浏览器中注入 window.ethereum 对象。
  • DApp 可以直接调用 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];
}

2. 通过节点 RPC 直接调用

  • 区块链是分布式网络,我们可以访问任意节点。
  • 节点提供 JSON-RPC 接口,例如 Infura、Alchemy、ZAN.top。
  • 这种方式适合服务端调用,或读取公共数据。
// 不需要用户钱包
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 足够

  • 如果要转账 / 调用合约写方法 → 必须通过钱包,因为只有钱包有用户的私钥能签名。

三、实战一:调用只读方法 balanceOf

我们来实现一个简单的需求:读取当前钱包地址下有多少个 NFT

1. 基本思路

  • 使用 wagmi 提供的 useReadContract Hook 读取数据。
  • 使用 useAccount 获取当前连接的钱包地址。
  • 将地址传给合约的 balanceOf 方法。

2. 示例代码

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

3. 说明

  • useReadContract:读取合约方法的 Hook。
  • abi:定义方法的 ABI。
  • args:传入参数,这里是用户钱包地址。
  • result.data:返回值(用户 NFT 数量)。

4. 常见问题

  • 未连接钱包account 为空,需要先点击“Connect Wallet”。
  • 调用失败:检查合约地址和 ABI 是否匹配。
  • 返回 undefined:确保钱包在正确的网络(例如 Sepolia 测试网)。

四、实战二:调用写方法 mint

仅仅能读取数据还不够,一个真正的 DApp 必须能 写入链上数据。\ 我们来实现调用合约的 mint 方法,铸造一个 NFT。

1. 思路

  • 使用 useWriteContract Hook 调用写方法。
  • 构造交易参数:abi、合约地址、方法名、参数、value(ETH)。
  • 让钱包签名并发送交易。
  • 根据结果给用户提示。

2. 示例代码

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

3. 关键点

  • payable 方法:mint 需要支付 ETH,所以要设置 value
  • parseEther("0.01"):将字符串形式的 ETH 转换为正确的 wei 单位。
  • 回调函数onSuccessonError 用于用户提示。

4. 常见错误

  • 账户 ETH 不足:交易失败,提示 Gas 不够。
  • 用户拒绝交易:钱包弹窗点击“拒绝”。
  • 网络错误:钱包与 DApp 不在同一网络。

五、最佳实践与注意事项

  1. 全局配置 WagmiWeb3ConfigProvider\ 建议放在应用的最外层,避免在多个组件中重复配置。

  2. ABI 文件管理

    • 在实际项目中 ABI 文件通常由合约编译器生成。
    • 建议将 ABI JSON 文件存放在 src/abis/ 目录下统一管理。
  3. 安全性考虑

    • 不要在前端硬编码私钥。
    • 写方法必须经过用户钱包签名,DApp 不应直接持有私钥。
    • 交易金额、Gas 费用应在 UI 中清楚展示给用户。
  4. 错误处理

    • 监听用户拒绝请求。
    • 捕获网络错误,提示用户切换正确的链。
    • 给出友好的错误信息,而不是原始报错。

六、总结

在本节中,我们完成了 DApp 的一次跨越式提升:

  1. 学习了调用合约的基本原理(ABI、合约地址、交易流程)。
  2. 了解了两种调用方式(钱包注入接口、RPC 节点)。
  3. 实现了调用合约的 只读方法 balanceOf
  4. 实现了调用合约的 写方法 mint,完成了 NFT 铸造。
  5. 掌握了最佳实践与常见错误处理。

到这里,我们的 DApp 已经具备了“读 + 写”的链上交互能力。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
不会喷火的小火龙
不会喷火的小火龙
0xa2ae...f650
211密码学专硕在读,正在研究区块链技术领域。