Skip to content

Commit 73888cc

Browse files
docs: Add call contract course docs (#508)
* docs: Add call contract course docs * Update docs/course/dev-connect.zh-CN.md * Update docs/course/dev-call-contract.zh-CN.md Co-authored-by: thinkasany <[email protected]> --------- Co-authored-by: thinkasany <[email protected]>
1 parent fe1ec95 commit 73888cc

File tree

7 files changed

+281
-6
lines changed

7 files changed

+281
-6
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { Address, ConnectButton, Connector, NFTCard } from '@ant-design/web3';
2+
import { MetaMask, WagmiWeb3ConfigProvider } from '@ant-design/web3-wagmi';
3+
import { Button, message } from 'antd';
4+
import { parseEther } from 'viem';
5+
import { createConfig, http, useReadContract, useWriteContract } from 'wagmi';
6+
import { mainnet } from 'wagmi/chains';
7+
import { injected } from 'wagmi/connectors';
8+
9+
const config = createConfig({
10+
chains: [mainnet],
11+
transports: {
12+
[mainnet.id]: http(),
13+
},
14+
connectors: [
15+
injected({
16+
target: 'metaMask',
17+
}),
18+
],
19+
});
20+
21+
const CallTest = () => {
22+
const result = useReadContract({
23+
abi: [
24+
{
25+
type: 'function',
26+
name: 'totalSupply',
27+
stateMutability: 'view',
28+
inputs: [],
29+
outputs: [{ name: 'supply', type: 'uint256' }],
30+
},
31+
],
32+
address: '0xEcd0D12E21805803f70de03B72B1C162dB0898d9',
33+
functionName: 'totalSupply',
34+
});
35+
const { writeContract } = useWriteContract();
36+
37+
return (
38+
<div>
39+
{result.data?.toString()}
40+
<Button
41+
onClick={() => {
42+
writeContract(
43+
{
44+
abi: [
45+
{
46+
type: 'function',
47+
name: 'mint',
48+
stateMutability: 'payable',
49+
inputs: [
50+
{
51+
internalType: 'uint256',
52+
name: 'quantity',
53+
type: 'uint256',
54+
},
55+
],
56+
outputs: [],
57+
},
58+
],
59+
address: '0xEcd0D12E21805803f70de03B72B1C162dB0898d9',
60+
functionName: 'mint',
61+
args: [1],
62+
value: parseEther('0.01'),
63+
},
64+
{
65+
onSuccess: () => {
66+
message.success('Mint Success');
67+
},
68+
onError: (err) => {
69+
message.error(err.message);
70+
},
71+
},
72+
);
73+
}}
74+
>
75+
mint
76+
</Button>
77+
</div>
78+
);
79+
};
80+
81+
export default () => {
82+
return (
83+
<WagmiWeb3ConfigProvider config={config} wallets={[MetaMask()]}>
84+
<Address format address="0xEcd0D12E21805803f70de03B72B1C162dB0898d9" />
85+
<NFTCard address="0x79fcdef22feed20eddacbb2587640e45491b757f" tokenId={8540} />
86+
<Connector>
87+
<ConnectButton />
88+
</Connector>
89+
<CallTest />
90+
</WagmiWeb3ConfigProvider>
91+
);
92+
};

docs/course/dev-call-contract.zh-CN.md

Lines changed: 183 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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>

docs/course/dev-connect.zh-CN.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export default () => {
128128

129129
## 配置钱包
130130

131-
上面步骤中的 NFT 智能合约中的信息是开放的,我们不需要连接用户钱包就可以直接通过节点服务获取到。但是,如果我们想要获取用户的钱包地址,那么我们就需要连接用户的钱包
131+
在上述步骤中,NFT 智能合约中的信息是公开的,无需连接用户钱包,即可通过节点服务(我们将在下一节详细介绍节点服务)直接获取。但是,如果我们希望获取用户的钱包地址,则需要与用户的钱包建立连接
132132

133133
我们以 [MetaMask](https://metamask.io/) 为例,看一下如何配置钱包。
134134

@@ -173,7 +173,7 @@ export default () => {
173173

174174
```
175175

176-
最终完整的代码如下(去掉了部分 Demo 中不需要的 div 样式)
176+
最终完整的代码如下:
177177

178178
<code src="./demos/connect.tsx"></code>
179179

docs/course/img/call-contract.png

551 KB
Loading

docs/course/img/zan-service.png

1.96 MB
Loading

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"vite-plugin-svgr": "^4.2.0",
8484
"vitest": "~1.1.0",
8585
"wagmi": "^2.0.0",
86+
"viem": "^2.0.0",
8687
"@tanstack/react-query": "^5.17.0"
8788
},
8889
"ci": {

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)