@@ -7,7 +7,186 @@ order: 3
77
88# 调用合约
99
10- <!-- prettier-ignore -->
11- ::: warning
12- 课程编写中,欢迎给我们提建议或者直接提 PR。
13- :::
10+ ## 简述
11+
12+ 在这一篇课程中,将指导你在 DApp 中调用一个真实的合约。我们将以以太坊为例,调用一个 NFT 的合约 [ 0xEcd0D12E21805803f70de03B72B1C162dB0898d9] ( https://etherscan.io/address/0xEcd0D12E21805803f70de03B72B1C162dB0898d9 ) 。
13+
14+ 合约 [ 0xEcd0D12E21805803f70de03B72B1C162dB0898d9] ( https://etherscan.io/address/0xEcd0D12E21805803f70de03B72B1C162dB0898d9 ) 是一个 NFT 项目 [ OurMetaverse] ( https://our-metaverse.xyz/ ) 的合约。它是一个符合 [ ERC721] ( https://ethereum.org/zh/developers/docs/standards/tokens/erc-721 ) 标准的合约。我们尝试调用它的 ` totalSupply ` 方法来获取当前这个合约一共发行了多少个 NFT。
15+
16+ ## 配置节点服务
17+
18+ 因为我们只需要读取合约数据,并不需要修改合约数据。所以我们不需要向区块链发起交易,也就是说不需要消耗代币作为 [ GAS] ( https://ethereum.org/zh/developers/docs/gas ) 。但是我们需要一个节点来帮助我们读取合约数据,在 Web3 生态中有很多提供节点服务的厂商,你可以选择自己喜欢的节点服务。这里以 [ ZAN 的节点服务] ( https://zan.top/home/node-service ) 为例,指引你如何配置节点服务。
19+
20+ 首先注册并登录 [ https://zan.top ] ( https://zan.top ) 之后进入到节点服务的控制台 [ https://zan.top/service/apikeys ] ( https://zan.top/service/apikeys ) 创建一个 Key,每个 Key 都有默认的免费额度,对于微型项目来说够用了,但是对于生产环境的项目来说,请结合实际情况购买节点服务。
21+
22+ 创建成功后你会看到如下的页面:
23+
24+ ![ ] ( ./img/zan-service.png )
25+
26+ 选择以太坊主网的节点服务地址复制,复制后的地址添加到 wagmi 的 ` http() ` 方法中,如下:
27+
28+ ``` diff
29+ const config = createConfig({
30+ chains: [mainnet],
31+ transports: {
32+ - [mainnet.id]: http(),
33+ + [mainnet.id]: http('https://api.zan.top/node/v1/eth/mainnet/{YourZANApiKey}'),
34+ },
35+ connectors: [
36+ injected({
37+ target: "metaMask",
38+ }),
39+ ],
40+ });
41+ ```
42+
43+ 上面代码中的 ` YourZANApiKey ` 需要替换成你自己的 Key。另外在实际的项目中,为了避免你的 Key 被滥用,建议你将 Key 放到后端服务中,然后通过后端服务来调用节点服务,或者在 ZAN 的控制台中设置域名白名单来降低被滥用的风险。当然,在教程中你也可以继续直接使用 ` http() ` ,使用 wagmi 内置的默认的实验性的节点服务。
44+
45+ ### 读的方式调用合约方法
46+
47+ 配置好节点服务后,我们就可以开始调用合约了。我们使用 wagmi 提供的 [ useReadContract] ( https://wagmi.sh/react/api/hooks/useReadContract ) Hook 来读取合约数据。示例代码如下:
48+
49+ ``` diff
50+ - import { createConfig, http } from "wagmi";
51+ + import { createConfig, http, useReadContract } from "wagmi";
52+ import { mainnet } from "wagmi/chains";
53+ import { WagmiWeb3ConfigProvider, MetaMask } from "@ant-design/web3-wagmi";
54+ import { Address, NFTCard, Connector, ConnectButton } from "@ant-design/web3";
55+ import { injected } from "wagmi/connectors";
56+
57+ const config = createConfig({
58+ chains: [mainnet],
59+ transports: {
60+ [mainnet.id]: http(),
61+ },
62+ connectors: [
63+ injected({
64+ target: "metaMask",
65+ }),
66+ ],
67+ });
68+
69+ + const CallTest = () => {
70+ + const result = useReadContract({
71+ + abi: [{
72+ + type: 'function',
73+ + name: 'totalApply',
74+ + stateMutability: 'view',
75+ + inputs: [],
76+ + outputs: [{ name: 'supply', type: 'uint256' }],
77+ + }],
78+ + address: '0xEcd0D12E21805803f70de03B72B1C162dB0898d9',
79+ + functionName: 'totalSupply',
80+ + });
81+ + return (
82+ + <div>{result.data?.toString()}</div>
83+ + );
84+ + }
85+
86+ export default () => {
87+ return (
88+ <WagmiWeb3ConfigProvider config={config} wallets={[MetaMask()]}>
89+ <Address format address="0xEcd0D12E21805803f70de03B72B1C162dB0898d9" />
90+ <NFTCard
91+ address="0x79fcdef22feed20eddacbb2587640e45491b757f"
92+ tokenId={8540}
93+ />
94+ <Connector>
95+ <ConnectButton />
96+ </Connector>
97+ + <CallTest />
98+ </WagmiWeb3ConfigProvider>
99+ );
100+ };
101+
102+ ```
103+
104+ 参考以上的代码添加调用合约的 ` totalSupply ` 方法,我们新建了一个 ` CallTest ` 组件,然后在 ` WagmiWeb3ConfigProvider ` 内添加了这个组件。因为 ` useReadContract ` 必须在 ` WagmiWeb3ConfigProvider ` 内部才能正常工作,所以我们不能在 ` export default () => { ` 这一行代码下面直接使用 ` useReadContract ` 。在实际的项目中 ` WagmiWeb3ConfigProvider ` 通常应该在你项目组件的最外层,这样确保你的项目所有组件中都可以正常使用相关的 Hooks。
105+
106+ 代码中的 abi 字段定义了方法的类型,这样 wagmi 才能知道如何处理方法的入参和返回,把 JavaScript 中的对象转换的区块链的交易信息。通常 abi 都是通过合约代码自动生成的,我们会在下一章讲到这一部分。
107+
108+ ### 写的方式调用合约方法
109+
110+ 仅仅是读取合约还不够,一个真正的 DApp,肯定会涉及到向智能合约写入数据。向智能合约写入数据通常都是通过在区块链上执行智能合约的方法,方法执行过程中会改写合约中的数据。
111+
112+ 接下来我们尝试调用下一本课程使用合约的 [ mint] ( https://etherscan.io/address/0xEcd0D12E21805803f70de03B72B1C162dB0898d9#writeContract#F6 ) 方法,` mint ` 方法并不是 ERC721 规范中的方法,它是该合约自行定义的。在本合约中,调用 ` mint ` 方法需要消耗 GAS 以及至少 ` 0.01ETH ` 的费用来获取 NFT。
113+
114+ 需要改动的代码如下:
115+
116+ ``` diff
117+ - import { createConfig, http, useReadContract } from "wagmi";
118+ + import { createConfig, http, useReadContract, useWriteContract } from "wagmi";
119+ + import { Button, message } from "antd";
120+ + import { parseEther } from "viem";
121+
122+ // ...
123+
124+ const CallTest = () => {
125+
126+ // ...
127+
128+ return (
129+ <div>
130+ {result.data?.toString()}
131+ + <Button
132+ + onClick={() => {
133+ + writeContract(
134+ + {
135+ + abi: [
136+ + {
137+ + type: "function",
138+ + name: "mint",
139+ + stateMutability: "payable",
140+ + inputs: [
141+ + {
142+ + internalType: "uint256",
143+ + name: "quantity",
144+ + type: "uint256",
145+ + },
146+ + ],
147+ + outputs: [],
148+ + },
149+ + ],
150+ + address: "0xEcd0D12E21805803f70de03B72B1C162dB0898d9",
151+ + functionName: "mint",
152+ + args: [1],
153+ + value: parseEther("0.01"),
154+ + },
155+ + {
156+ + onSuccess: () => {
157+ + message.success("Mint Success");
158+ + },
159+ + onError: (err) => {
160+ + message.error(err.message);
161+ + },
162+ + }
163+ + );
164+ + }}
165+ + >
166+ + mint
167+ + </Button>
168+ </div>
169+ );
170+ };
171+
172+ // ...
173+
174+ ```
175+
176+ 在上面的代码中,我们用到了 ` viem ` 这个库,它是 ` wagmi ` 底层依赖的一个库,你需要在项目中安装它:
177+
178+ ``` bash
179+ npm i viem --save
180+ ```
181+
182+ 这段代码中,我们实现了点击 ` mint ` 按钮后调用合约的 ` mint ` 方法,传入参数 ` 1 ` ,在该合约的实现逻辑中,这代表要铸造一个 NFT。由于每个 NFT 铸造的价格是 ` 0.01 ETH ` ,所以我们还需要在交易中发送 ` 0.01 ETH ` 的费用,这样才能成功铸造 NFT。所以上面会在调用合约中配置 ` value: parseEther("0.01") ` 。在以太坊的合约方法执行中,合约并不能直接提取调用者的 ETH,所以我们需要在调用合约的时候主动发送 ETH 给合约,这是合约安全设计上的考虑。
183+
184+ 合约调用成功和失败会有对应的提示,如果还未连接账号会抛出未连接账号的错误。所以你需要先点击我们在上一节课程中实现的连接按钮连接你的账户地址,如果你的账户有足够的 ETH,那么点击后会出现类似如下的授权弹窗:
185+
186+ ![ ] ( ./img/call-contract.png )
187+
188+ 点击 ** 拒绝** 后,不会执行合约调用,不会消耗你的任何 ETH。在下一章中,我们会指引你部署一个测试合约,在测试环境中体验完整的流程。当然如果你很富有,你也可以点击确认,这样就会执行合约调用,消耗你的 ETH,得到一个 NFT。
189+
190+ 最终完整的代码如下:
191+
192+ <code src =" ./demos/call-contract.tsx " ></code >
0 commit comments