31 | ```
32 |
33 | 上面代码中的 `YourZANApiKey` 需要替换成你自己的 Key。另外在实际的项目中,为了避免你的 Key 被滥用,建议你将 Key 放到后端服务中,然后通过后端服务来调用节点服务,或者在 ZAN 的控制台中设置域名白名单来降低被滥用的风险。当然,在教程中你也可以继续直接使用 `http()`,使用 wagmi 内置的默认的实验性的节点服务。
34 |
35 | 同样,如果你使用的是 Infura 或者 Alchemy 的节点服务,你也可以将它们的节点服务地址添加到 `WagmiWeb3ConfigProvider` 的 `http()` 方法中。
36 |
37 | ## 从水龙头获取测试网 ETH
38 |
39 | 除了节点服务,用于测试的 ETH 也是开发中必不可少的部分。通常,我们可以通过水龙头服务来获取。水龙头(Faucet)是一种在线服务,用于提供免费的测试网加密货币(通常是小额的代币),用于在开发环境中进行测试。这些服务通常由测试网官方、开发者社区、节点服务技术供应商等提供。
40 |
41 | 比如你可以通过 [ZAN 的水龙头服务](https://zan.top/faucet?chInfo=wtf) 来获取一定量的 Sepolia 测试网 ETH 用于测试。
42 |
43 | 
44 |
45 | 请在上图示意的水龙头网页中填入钱包地址,领取适量的测试网 SepoliaETH,我们在后面的课程中可能会用到。
46 |
47 | 本次领取 0.01 SepoliaETH
48 |
49 | 
50 |
51 |
--------------------------------------------------------------------------------
/03_NodeService/web3.tsx:
--------------------------------------------------------------------------------
1 | import { http } from "wagmi";
2 | import { Mainnet, WagmiWeb3ConfigProvider } from '@ant-design/web3-wagmi';
3 | import { Address, NFTCard} from "@ant-design/web3";
4 |
5 | export default function Web3() {
6 | return (
7 |
13 |
14 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/04_CallContract/img/call-contract.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/04_CallContract/img/call-contract.png
--------------------------------------------------------------------------------
/04_CallContract/img/call-read-contract.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/04_CallContract/img/call-read-contract.png
--------------------------------------------------------------------------------
/04_CallContract/img/metamask.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/04_CallContract/img/metamask.png
--------------------------------------------------------------------------------
/04_CallContract/img/metamask3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/04_CallContract/img/metamask3.png
--------------------------------------------------------------------------------
/04_CallContract/img/no-gas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/04_CallContract/img/no-gas.png
--------------------------------------------------------------------------------
/04_CallContract/img/zan-api-doc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/04_CallContract/img/zan-api-doc.png
--------------------------------------------------------------------------------
/04_CallContract/web3.tsx:
--------------------------------------------------------------------------------
1 | import { parseEther } from "viem";
2 | import { Button, message } from "antd";
3 | import { http, useReadContract, useWriteContract } from "wagmi";
4 | import { Mainnet, WagmiWeb3ConfigProvider, MetaMask } from '@ant-design/web3-wagmi';
5 | import { Address, NFTCard, ConnectButton, Connector, useAccount } from "@ant-design/web3";
6 |
7 |
8 | const CallTest = () => {
9 | const { account } = useAccount();
10 | const result = useReadContract({
11 | abi: [
12 | {
13 | type: 'function',
14 | name: 'balanceOf',
15 | stateMutability: 'view',
16 | inputs: [{ name: 'account', type: 'address' }],
17 | outputs: [{ type: 'uint256' }],
18 | },
19 | ],
20 | address: '0xEcd0D12E21805803f70de03B72B1C162dB0898d9',
21 | functionName: 'balanceOf',
22 | args: [account?.address as `0x${string}`],
23 | });
24 |
25 |
26 | const { writeContract } = useWriteContract();
27 | return (
28 |
29 | {result.data?.toString()}
30 |
67 |
68 | );
69 | };
70 |
71 | export default function Web3() {
72 | return (
73 |
80 |
81 |
85 |
86 |
87 |
88 |
89 |
90 | );
91 | }
92 |
--------------------------------------------------------------------------------
/05_Events/readme.md:
--------------------------------------------------------------------------------
1 | 本节作者:[@愚指导](https://x.com/yudao1024)
2 |
3 | 这一讲将会介绍如何在 DApp 中监听合约事件,实时更新 DApp 界面。
4 |
5 | ## 简介
6 |
7 | 区块链智能合约的事件和我们传统的应用开发理解的事件概念是有些不同的,区块链本身并没有一个消息机制来向应用发送事件。它本质上是 EVM 上日志的抽象。
8 |
9 | 这一类日志相比智能合约的状态变化,更加便宜,是一种比较经济数据存储方式,每个事件大概消耗 2000 gas,而链上存储一个新变量至少需要 20000 gas。所以在智能合约中,我们通常会使用事件来记录一些重要的状态变化。另外基于节点服务提供的 RPC 接口可以实现 DApp 前端页面监听合约事件,实现相对实时的更新。
10 |
11 | ## 如何在智能合约中添加事件
12 |
13 | 在智能合约中事件由 `event` 声明,以 `emit` 触发,下面是一个示例,这里只做简单展示,具体我们会在后面的合约开发课程中具体讲解:
14 |
15 | ```diff
16 | // SPDX-License-Identifier: MIT
17 | pragma solidity ^0.8.20;
18 |
19 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
20 | import "@openzeppelin/contracts/access/Ownable.sol";
21 |
22 | contract MyToken is ERC721, Ownable {
23 | uint256 private _nextTokenId = 0;
24 | + event Minted(address minter, uint256 amount);
25 | constructor()
26 | ERC721("MyToken", "MTK")
27 | Ownable(msg.sender)
28 | {}
29 | function mint(uint256 quantity) public payable {
30 | require(quantity == 1, "quantity must be 1");
31 | require(msg.value == 0.01 ether, "must pay 0.01 ether");
32 | uint256 tokenId = _nextTokenId++;
33 | _mint(msg.sender, tokenId);
34 | + emit Minted(msg.sender, quantity);
35 | }
36 | }
37 | ```
38 |
39 | ## 如何在 DApp 中监听事件
40 |
41 | 在 DApp 前端页面,我们可以通过调用节点服务提供的 RPC 接口来监听合约事件,这里我们继续使用 `wagmi` 来开发。
42 |
43 | 首先引入 [useWatchContractEvent](https://wagmi.sh/react/api/hooks/useWatchContractEvent#abi)。
44 |
45 | ```diff
46 | import {
47 | createConfig,
48 | http,
49 | useReadContract,
50 | useWriteContract,
51 | + useWatchContractEvent,
52 | } from "wagmi";
53 | ```
54 |
55 | 然后使用 `useWatchContractEvent` 监听合约事件。
56 |
57 | ```ts
58 | useWatchContractEvent({
59 | address: "0xEcd0D12E21805803f70de03B72B1C162dB0898d9",
60 | abi: [
61 | {
62 | anonymous: false,
63 | inputs: [
64 | {
65 | indexed: false,
66 | internalType: "address",
67 | name: "minter",
68 | type: "address",
69 | },
70 | {
71 | indexed: false,
72 | internalType: "uint256",
73 | name: "amount",
74 | type: "uint256",
75 | },
76 | ],
77 | name: "Minted",
78 | type: "event",
79 | },
80 | ],
81 | eventName: "Minted",
82 | onLogs() {
83 | message.success("new minted!");
84 | },
85 | });
86 | ```
87 |
88 | 具体的使用方法其实在类似 wagmi 这样的 SDK 的封装下和传统的前端开发并没有太大的区别,你传入合约地址和要监听的事件后就可以在 `onLogs` 回调中处理事件。
89 |
90 | 当然,上面代码只是为了演示。实际编码的时候你可能需要在 `onLogs` 中判断事件的具体内容,然后更新页面。
91 |
92 | 此外,`abi` 参数通常来说都是合约编译时候自动生成的,整体在项目中维护一份完整的内容传入即可,不需要像示例这样单独在每个调用的地方手动编写。
93 |
94 | 因为事件只有在合约被调用后才会触发,所以我们目前还无法调试,你可以继续课程,等到我们在测试环境可以部署一个测试合约后再来测试。
95 |
96 | 完整的代码可以参考 [web3.tsx](./web3.tsx)。
97 |
--------------------------------------------------------------------------------
/05_Events/web3.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Address,
3 | ConnectButton,
4 | Connector,
5 | NFTCard,
6 | useAccount,
7 | } from "@ant-design/web3";
8 | import { MetaMask, WagmiWeb3ConfigProvider } from "@ant-design/web3-wagmi";
9 | import { Button, message } from "antd";
10 | import { parseEther } from "viem";
11 | import {
12 | createConfig,
13 | http,
14 | useReadContract,
15 | useWriteContract,
16 | useWatchContractEvent,
17 | } from "wagmi";
18 | import { mainnet } from "wagmi/chains";
19 | import { injected } from "wagmi/connectors";
20 |
21 | const config = createConfig({
22 | chains: [mainnet],
23 | transports: {
24 | [mainnet.id]: http(),
25 | },
26 | connectors: [
27 | injected({
28 | target: "metaMask",
29 | }),
30 | ],
31 | });
32 |
33 | const CallTest = () => {
34 | const { account } = useAccount();
35 | const result = useReadContract({
36 | abi: [
37 | {
38 | type: "function",
39 | name: "balanceOf",
40 | stateMutability: "view",
41 | inputs: [{ name: "account", type: "address" }],
42 | outputs: [{ type: "uint256" }],
43 | },
44 | ],
45 | // Sepolia test contract 0x418325c3979b7f8a17678ec2463a74355bdbe72c
46 | address: "0xEcd0D12E21805803f70de03B72B1C162dB0898d9",
47 | functionName: "balanceOf",
48 | args: [account?.address as `0x${string}`],
49 | });
50 | const { writeContract } = useWriteContract();
51 |
52 | useWatchContractEvent({
53 | address: "0xEcd0D12E21805803f70de03B72B1C162dB0898d9",
54 | abi: [
55 | {
56 | anonymous: false,
57 | inputs: [
58 | {
59 | indexed: false,
60 | internalType: "address",
61 | name: "minter",
62 | type: "address",
63 | },
64 | {
65 | indexed: false,
66 | internalType: "uint256",
67 | name: "amount",
68 | type: "uint256",
69 | },
70 | ],
71 | name: "Minted",
72 | type: "event",
73 | },
74 | ],
75 | eventName: "Minted",
76 | onLogs() {
77 | message.success("new minted!");
78 | },
79 | });
80 |
81 | return (
82 |
83 | {result.data?.toString()}
84 |
121 |
122 | );
123 | };
124 |
125 | export default function Web3() {
126 | return (
127 |
128 |
129 |
133 |
134 |
135 |
136 |
137 |
138 | );
139 | }
140 |
--------------------------------------------------------------------------------
/06_NextJS/img/createnew.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/06_NextJS/img/createnew.png
--------------------------------------------------------------------------------
/06_NextJS/img/deploy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/06_NextJS/img/deploy.png
--------------------------------------------------------------------------------
/06_NextJS/img/domain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/06_NextJS/img/domain.png
--------------------------------------------------------------------------------
/06_NextJS/img/editroot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/06_NextJS/img/editroot.png
--------------------------------------------------------------------------------
/06_NextJS/img/import.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/06_NextJS/img/import.png
--------------------------------------------------------------------------------
/06_NextJS/img/nextbuild.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/06_NextJS/img/nextbuild.png
--------------------------------------------------------------------------------
/06_NextJS/readme.md:
--------------------------------------------------------------------------------
1 | 本节作者:[@愚指导](https://x.com/yudao1024)
2 |
3 | 这一讲将会介绍如何把我们的应用部署到服务器上,完成前端部分功能从开发到最终上线。
4 |
5 | ## 简介
6 |
7 | 这里会简单介绍一下 Next.js 以及前端部署的一些基本概念。如果你对前端已经很熟悉,可以跳过这部分。
8 |
9 | [Next.js](https://nextjs.org/) 是一个前端框架,它可以帮助我们快速搭建 React 应用。我们基于 React 和 Next.js 开发完成我们的应用之后,需要将其构建并部署到服务器上,让用户可以访问我们的应用。
10 |
11 | 通过在项目中执行 `npm run build`(对应就是 `package.json` 中定义的 `next build`),它会将我们的应用构建为 JavaScript、CSS 等文件,这些文件或是在浏览器中运行的文件,浏览器本质上只支持执行 JavaScript,并不会原生支持 React,所以需要我们将其编译为浏览器可以执行的文件。又或者是在服务器的 Node.JS 环境中执行的 JavaScript,这部分让我们有能力处理一些服务端的逻辑,我们前面的课程中并未涉及到这部分,但是在后续的课程中,类似签名验证这样的逻辑就依赖于服务端的实现了。
12 |
13 | 执行成功可以看到类似下面这样的效果:
14 |
15 | 
16 |
17 | 构建完成后你可以继续执行 `npm run start`,这样你的应用就成功在你本地部署了。对应的,如果你需要部署在你自己的服务器,那么流程是相同的,你需要在你的服务器中安装依赖、构建并通过 `npm run start` 或者 `next start` 启动你的应用。
18 |
19 | 当然,如果你使用的是 [umi](https://umijs.org/) 等其它前端框架,也是类似的逻辑,通常都需要构建和启动服务。如果构建产物是只有浏览器环境运行的静态资源,那么你只需要类似 Nginx 或者 express 这样的框架来部署一个 Web 容器存放即可,或者也可以使用 [Github Pages](https://pages.github.com/) 来部署。
20 |
21 | ## 基于 Vercel 部署
22 |
23 | 这一部分我们引导你基于 [Vercel](https://vercel.com/) 部署你的应用。 Next.js 就是 Vercel 官方在做支持的框架,它提供了一个非常简单的部署方式,你只需要将你的代码上传到 Github 或者 Gitlab 等代码托管平台,然后在 Vercel 上选择你的仓库,它会自动帮你构建并部署你的应用。
24 |
25 | 首先,请将你之前的代码推送到你自己的 Github 上,或者你也可以 Fork 本仓库 [https://github.com/WTFAcademy/WTF-Dapp](https://github.com/WTFAcademy/WTF-Dapp),我们在 [demo](../demo/) 文件夹中提供了完整的代码。
26 |
27 | 登录 Vercel 后在其控制台中创建一个新的项目:
28 |
29 | 
30 |
31 | 接下来选择并导入你的 Github 项目,这个过程中可能需要你授权 Vercel 访问你的 Github 仓库:
32 |
33 | 
34 |
35 | 你需要选择 Root Directory 为 Next.js 项目的根目录,然后点击 Deploy:
36 |
37 | 
38 |
39 | 接下来一切都会自动完成:
40 |
41 | 
42 |
43 | 最后本教程的 Demo 就呈现在 [https://wtf-dapp.vercel.app/web3](https://wtf-dapp.vercel.app/web3) 中了。
44 |
45 | ## 自定义域名
46 |
47 | Vercel 会提供一个默认的域名,但是通常我们需要一个自定义的域名,这样更加专业。你可以在 Vercel 的控制台中找到你的项目,然后在 Domains 中添加你的域名:
48 |
49 | 
50 |
51 | 添加后你需要按照提示在你的域名服务商中添加一条 CNAME 记录,这样你的域名就可以指向 Vercel 的服务器了。
52 |
--------------------------------------------------------------------------------
/07_ContractDev/MyToken.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.20;
3 |
4 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
5 | import "@openzeppelin/contracts/access/Ownable.sol";
6 |
7 | contract MyToken is ERC721, Ownable {
8 | uint256 private _nextTokenId = 0;
9 |
10 | constructor()
11 | ERC721("MyToken", "MTK")
12 | Ownable(msg.sender)
13 | {}
14 |
15 | function mint(uint256 quantity) public payable {
16 | require(quantity == 1, "quantity must be 1");
17 | require(msg.value == 0.01 ether, "must pay 0.01 ether");
18 | uint256 tokenId = _nextTokenId++;
19 | _mint(msg.sender, tokenId);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/07_ContractDev/img/chai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/07_ContractDev/img/chai.png
--------------------------------------------------------------------------------
/07_ContractDev/img/create.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/07_ContractDev/img/create.png
--------------------------------------------------------------------------------
/07_ContractDev/img/createBtn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/07_ContractDev/img/createBtn.png
--------------------------------------------------------------------------------
/07_ContractDev/img/generate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/07_ContractDev/img/generate.png
--------------------------------------------------------------------------------
/07_ContractDev/img/initCode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/07_ContractDev/img/initCode.png
--------------------------------------------------------------------------------
/07_ContractDev/img/mintable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/07_ContractDev/img/mintable.png
--------------------------------------------------------------------------------
/07_ContractDev/img/more.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/07_ContractDev/img/more.png
--------------------------------------------------------------------------------
/07_ContractDev/img/remix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/07_ContractDev/img/remix.png
--------------------------------------------------------------------------------
/07_ContractDev/img/run.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/07_ContractDev/img/run.png
--------------------------------------------------------------------------------
/07_ContractDev/img/slide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/07_ContractDev/img/slide.png
--------------------------------------------------------------------------------
/07_ContractDev/img/unitTest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/07_ContractDev/img/unitTest.png
--------------------------------------------------------------------------------
/07_ContractDev/img/unitTest1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/07_ContractDev/img/unitTest1.png
--------------------------------------------------------------------------------
/08_ContractDeploy/demo/dapp.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Address,
3 | ConnectButton,
4 | Connector,
5 | NFTCard,
6 | useAccount,
7 | } from "@ant-design/web3";
8 | import {
9 | Sepolia,
10 | MetaMask,
11 | WagmiWeb3ConfigProvider,
12 | } from "@ant-design/web3-wagmi";
13 | import { Button, message } from "antd";
14 | import { parseEther } from "viem";
15 | import { createConfig, http, useReadContract, useWriteContract } from "wagmi";
16 | import { sepolia, mainnet } from "wagmi/chains";
17 | import { injected } from "wagmi/connectors";
18 |
19 | const config = createConfig({
20 | chains: [mainnet, sepolia],
21 | transports: {
22 | [mainnet.id]: http(),
23 | [sepolia.id]: http(),
24 | },
25 | connectors: [
26 | injected({
27 | target: "metaMask",
28 | }),
29 | ],
30 | });
31 |
32 | const CallTest = () => {
33 | const { account } = useAccount();
34 | const result = useReadContract({
35 | abi: [
36 | {
37 | type: "function",
38 | name: "balanceOf",
39 | stateMutability: "view",
40 | inputs: [{ name: "account", type: "address" }],
41 | outputs: [{ type: "uint256" }],
42 | },
43 | ],
44 | // Sepolia test contract 0x418325c3979b7f8a17678ec2463a74355bdbe72c
45 | address: "0xEcd0D12E21805803f70de03B72B1C162dB0898d9",
46 | functionName: "balanceOf",
47 | args: [account?.address as `0x${string}`],
48 | });
49 | const { writeContract } = useWriteContract();
50 |
51 | return (
52 |
53 | {result.data?.toString()}
54 |
91 |
92 | );
93 | };
94 |
95 | export default function Web3() {
96 | return (
97 |
102 |
103 |
107 |
108 |
109 |
110 |
111 |
112 | );
113 | }
114 |
--------------------------------------------------------------------------------
/08_ContractDeploy/img/call-in-ide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/08_ContractDeploy/img/call-in-ide.png
--------------------------------------------------------------------------------
/08_ContractDeploy/img/changeNode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/08_ContractDeploy/img/changeNode.png
--------------------------------------------------------------------------------
/08_ContractDeploy/img/compile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/08_ContractDeploy/img/compile.png
--------------------------------------------------------------------------------
/08_ContractDeploy/img/connect1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/08_ContractDeploy/img/connect1.png
--------------------------------------------------------------------------------
/08_ContractDeploy/img/connect2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/08_ContractDeploy/img/connect2.png
--------------------------------------------------------------------------------
/08_ContractDeploy/img/copyABI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/08_ContractDeploy/img/copyABI.png
--------------------------------------------------------------------------------
/08_ContractDeploy/img/json.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/08_ContractDeploy/img/json.png
--------------------------------------------------------------------------------
/08_ContractDeploy/img/mint-test-net.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/08_ContractDeploy/img/mint-test-net.png
--------------------------------------------------------------------------------
/08_ContractDeploy/img/sendTrans.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/08_ContractDeploy/img/sendTrans.png
--------------------------------------------------------------------------------
/08_ContractDeploy/img/transInfo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/08_ContractDeploy/img/transInfo.png
--------------------------------------------------------------------------------
/09_EIP1193/img/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/09_EIP1193/img/demo.png
--------------------------------------------------------------------------------
/09_EIP1193/readme.md:
--------------------------------------------------------------------------------
1 | 本节作者:[@愚指导](https://x.com/yudao1024)
2 |
3 | 我们在第三讲[连接钱包](./03_ConnectWallet/readme.md)中介绍了如何连接钱包,但是没有深入讲解连接钱包的原理。EIP1193 和 EIP6963 是两个重要的协议,它们定义了 DApp 连接钱包的标准。EIP1193 是最早期的标准,但是有一些缺陷,EIP6963 则是对 EIP1193 的改进,解决了那些缺陷。
4 |
5 | 本文将介绍这两个协议的基本概念,帮助你理解链接钱包的逻辑。
6 |
7 | ## EIP1193
8 |
9 | EIP1193 的规范地址在[https://eips.ethereum.org/EIPS/eip-1193](https://eips.ethereum.org/EIPS/eip-1193),它定义了在浏览器中如何通过 JavaScript 与钱包进行交互,有了这个规范钱包才能按照他提供相关接口,DApp 才能按照它调用钱包提供的接口。
10 |
11 | 这个定义很简单,其实就是约定了浏览器运行时的全局对象 `window` 上的 `ethereum` 对象的格式,定义了一些方法和事件。
12 |
13 | 对于 DApp 来说,它需要检查 `window.ethereum` 是否存在,如果存在,那么它就可以调用 `window.ethereum` 上的方法来和钱包进行交互。就同调用浏览器其他 API 类似,比如 `window.localStorage`。
14 |
15 | 下面是一个简单的例子可以获取链的 ID:
16 |
17 | ```javascript
18 | const ethereum = window.ethereum;
19 |
20 | ethereum
21 | .request({ method: "eth_chainId" })
22 | .then((chainId) => {
23 | console.log(`hexadecimal string: ${chainId}`);
24 | console.log(`decimal number: ${parseInt(chainId, 16)}`);
25 | })
26 | .catch((error) => {
27 | console.error(`Error fetching chainId: ${error.code}: ${error.message}`);
28 | });
29 | ```
30 |
31 | 你可以尝试在浏览器的控制台运行查看效果:
32 |
33 | 
34 |
35 | 对于更多方法,也就对应修改 `request` 调用时的参数即可,支持的方法可以参考 [JSON-RPC API](https://ethereum.org/developers/docs/apis/json-rpc)。当然,对于一些钱包可能不支持的方法,你需要做好异常处理。也有一些钱包特有的一些方法或者一些已经约定俗成的方法,你需要查看钱包的文档。
36 |
37 | 通常来说,在 DApp 中你应该使用类似 `web3.js`、`ethers`、`viem` 这样的 SDK 来和钱包进行交互,这些 SDK 会帮你封装好一些方法,让你更方便的和钱包进行交互。
38 |
39 | 以上就是 EIP1193 的基本概念,但是 EIP1193 有一个主要的缺陷。就是 `window.ethereum` 对象只有一个,所以当用户安装了多个钱包时,用户只能选择一个钱包来使用。这样会导致钱包之间会争抢 `window.ethereum` 对象,一方面损害了用户体验,另外也不利于钱包之间的良性竞争。
40 |
41 | 在之前很长一段时间,针对这个问题钱包的做法一般是会注入自己独特的对象,比如 TokenPocket 会注入 `window.tokenPocket`。但是这样的做法并不是标准的,也不是一个好的解决方案。另外,这样的做法也会导致 DApp 需要适配很多钱包,增加了 DApp 的开发成本。
42 |
43 | 所以就有了 EIP6963,接下来我们会介绍 EIP6963。
44 |
45 | ## EIP6963
46 |
47 | EIP6963 的规范地址在[https://eips.ethereum.org/EIPS/eip-6963](https://eips.ethereum.org/EIPS/eip-6963)。
48 |
49 | EIP6963 不再通过 `window.ethereum` 对象来和钱包进行交互,而是通过往 `window` 发送事件的方式来和钱包进行交互。这样就解决了 EIP1193 的问题,多个钱包可以和 DApp 进行交互,而不会争抢 `window.ethereum` 对象。
50 |
51 | 另外钱包也可以通过发送事件的方式主动告知 DApp 它的存在,这样 DApp 就可以知道用户安装了哪些钱包,然后根据用户的选择来和钱包进行交互。
52 |
53 | 技术上来讲其实就是通过浏览器的 `window.addEventListener` 来监听消息,通过 `window.dispatchEvent` 来发送消息。所有消息的 `type` 都有 `eip6963:` 前缀,具体的消息内容定义可以参考规范文档。
54 |
55 | 对于开发者来说,和 EIP1193 一样,你使用一些社区的库即可,这样可以免去对细节的关注。比如你如果使用 wagmi,那么通过配置 [multiInjectedProviderDiscovery](https://wagmi.sh/core/api/createConfig#multiinjectedproviderdiscovery) 即可接入 EIP6963。
56 |
57 | 如果你使用了 [Ant Design Web3](https://web3.ant.design/zh-CN/components/wagmi#eip6963),通过配置 `WagmiWeb3ConfigProvider` 的 `eip6963` 即可在 DApp 中使用 EIP6963。它的连接钱包的弹窗会自动添加检测到的钱包。
58 |
59 | 下面是我们基于之前的课程例子的修改示例:
60 |
61 | ```diff
62 | export default function Web3() {
63 | return (
64 |
71 |
72 |
76 |
77 |
78 |
79 |
80 |
81 | );
82 | }
83 | ```
84 |
85 | 其中配置了 `eip6963` 使得使用通过 EIP6963 协议连接钱包,避免了多个钱包之间可能出现的冲突。另外添加了 `autoAddInjectedWallets` 配置使得自动添加检测到的钱包到 Ant Design Web3 的 UI 中,提升用户体验,让用户可以自由选择他已经安装的钱包。
86 |
87 | ## 总结
88 |
89 | 不管是 EIP1193 还是 EIP6963,它们都是通过浏览器的 JavaScript API 来和钱包进行交互的。它要求钱包可以向 DApp 的运行时注入对象或者发送事件,比如通过 Chrome 浏览器插件,或者你在钱包内置的浏览器中访问 DApp。
90 |
91 | 但是对于有的场景,用户没有安装插件,或者是在移动端浏览器访问 DApp,无法使用插件。又或者用户需要用其他手机安装的钱包客户端来连接 DApp。不管是 EIP1193 还是 EIP6963,都无法满足这些场景。所以,我们还需要其他的方式来连接钱包,比如 WalletConnect。我们会在下一讲介绍如何使用 WalletConnect 来连接钱包。
92 |
--------------------------------------------------------------------------------
/09_EIP1193/web3.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Address,
3 | ConnectButton,
4 | Connector,
5 | NFTCard,
6 | useAccount,
7 | } from "@ant-design/web3";
8 | import { MetaMask, WagmiWeb3ConfigProvider } from "@ant-design/web3-wagmi";
9 | import { Button, message } from "antd";
10 | import { parseEther } from "viem";
11 | import {
12 | createConfig,
13 | http,
14 | useReadContract,
15 | useWriteContract,
16 | useWatchContractEvent,
17 | } from "wagmi";
18 | import { mainnet } from "wagmi/chains";
19 | import { injected } from "wagmi/connectors";
20 |
21 | const config = createConfig({
22 | chains: [mainnet],
23 | transports: {
24 | [mainnet.id]: http(),
25 | },
26 | connectors: [
27 | injected({
28 | target: "metaMask",
29 | }),
30 | ],
31 | });
32 |
33 | const CallTest = () => {
34 | const { account } = useAccount();
35 | const result = useReadContract({
36 | abi: [
37 | {
38 | type: "function",
39 | name: "balanceOf",
40 | stateMutability: "view",
41 | inputs: [{ name: "account", type: "address" }],
42 | outputs: [{ type: "uint256" }],
43 | },
44 | ],
45 | // Sepolia test contract 0x418325c3979b7f8a17678ec2463a74355bdbe72c
46 | address: "0xEcd0D12E21805803f70de03B72B1C162dB0898d9",
47 | functionName: "balanceOf",
48 | args: [account?.address as `0x${string}`],
49 | });
50 | const { writeContract } = useWriteContract();
51 |
52 | useWatchContractEvent({
53 | address: "0xEcd0D12E21805803f70de03B72B1C162dB0898d9",
54 | abi: [
55 | {
56 | anonymous: false,
57 | inputs: [
58 | {
59 | indexed: false,
60 | internalType: "address",
61 | name: "minter",
62 | type: "address",
63 | },
64 | {
65 | indexed: false,
66 | internalType: "uint256",
67 | name: "amount",
68 | type: "uint256",
69 | },
70 | ],
71 | name: "Minted",
72 | type: "event",
73 | },
74 | ],
75 | eventName: "Minted",
76 | onLogs() {
77 | message.success("new minted!");
78 | },
79 | });
80 |
81 | return (
82 |
83 | {result.data?.toString()}
84 |
121 |
122 | );
123 | };
124 |
125 | export default function Web3() {
126 | return (
127 |
134 |
135 |
139 |
140 |
141 |
142 |
143 |
144 | );
145 | }
146 |
--------------------------------------------------------------------------------
/10_WalletConnect/img/walletconnect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/10_WalletConnect/img/walletconnect.png
--------------------------------------------------------------------------------
/10_WalletConnect/img/walletnetwork.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/10_WalletConnect/img/walletnetwork.png
--------------------------------------------------------------------------------
/10_WalletConnect/img/walletqrcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/10_WalletConnect/img/walletqrcode.png
--------------------------------------------------------------------------------
/10_WalletConnect/web3.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Address,
3 | ConnectButton,
4 | Connector,
5 | NFTCard,
6 | useAccount,
7 | } from "@ant-design/web3";
8 | import {
9 | MetaMask,
10 | WagmiWeb3ConfigProvider,
11 | WalletConnect,
12 | } from "@ant-design/web3-wagmi";
13 | import { Button, message } from "antd";
14 | import { parseEther } from "viem";
15 | import {
16 | createConfig,
17 | http,
18 | useReadContract,
19 | useWriteContract,
20 | useWatchContractEvent,
21 | } from "wagmi";
22 | import { mainnet } from "wagmi/chains";
23 | import { injected, walletConnect } from "wagmi/connectors";
24 |
25 | const config = createConfig({
26 | chains: [mainnet],
27 | transports: {
28 | [mainnet.id]: http(),
29 | },
30 | connectors: [
31 | injected({
32 | target: "metaMask",
33 | }),
34 | walletConnect({
35 | projectId: "c07c0051c2055890eade3556618e38a6",
36 | showQrModal: false,
37 | }),
38 | ],
39 | });
40 |
41 | const CallTest = () => {
42 | const { account } = useAccount();
43 | const result = useReadContract({
44 | abi: [
45 | {
46 | type: "function",
47 | name: "balanceOf",
48 | stateMutability: "view",
49 | inputs: [{ name: "account", type: "address" }],
50 | outputs: [{ type: "uint256" }],
51 | },
52 | ],
53 | // Sepolia test contract 0x418325c3979b7f8a17678ec2463a74355bdbe72c
54 | address: "0xEcd0D12E21805803f70de03B72B1C162dB0898d9",
55 | functionName: "balanceOf",
56 | args: [account?.address as `0x${string}`],
57 | });
58 | const { writeContract } = useWriteContract();
59 |
60 | useWatchContractEvent({
61 | address: "0xEcd0D12E21805803f70de03B72B1C162dB0898d9",
62 | abi: [
63 | {
64 | anonymous: false,
65 | inputs: [
66 | {
67 | indexed: false,
68 | internalType: "address",
69 | name: "minter",
70 | type: "address",
71 | },
72 | {
73 | indexed: false,
74 | internalType: "uint256",
75 | name: "amount",
76 | type: "uint256",
77 | },
78 | ],
79 | name: "Minted",
80 | type: "event",
81 | },
82 | ],
83 | eventName: "Minted",
84 | onLogs() {
85 | message.success("new minted!");
86 | },
87 | });
88 |
89 | return (
90 |
91 | {result.data?.toString()}
92 |
129 |
130 | );
131 | };
132 |
133 | export default function Web3() {
134 | return (
135 |
142 |
143 |
147 |
148 |
149 |
150 |
151 |
152 | );
153 | }
154 |
--------------------------------------------------------------------------------
/11_MultipleChain/img/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/11_MultipleChain/img/image.png
--------------------------------------------------------------------------------
/11_MultipleChain/web3.tsx:
--------------------------------------------------------------------------------
1 | import { createConfig, http, useReadContract, useWriteContract } from "wagmi";
2 | import { mainnet, sepolia, polygon } from "wagmi/chains";
3 | import {
4 | WagmiWeb3ConfigProvider,
5 | MetaMask,
6 | Sepolia,
7 | Polygon,
8 | } from "@ant-design/web3-wagmi";
9 | import {
10 | Address,
11 | NFTCard,
12 | Connector,
13 | ConnectButton,
14 | useAccount,
15 | useProvider,
16 | } from "@ant-design/web3";
17 | import { injected } from "wagmi/connectors";
18 | import { Button, message } from "antd";
19 | import { parseEther } from "viem";
20 |
21 | const config = createConfig({
22 | chains: [mainnet, sepolia, polygon],
23 | transports: {
24 | [mainnet.id]: http(),
25 | [sepolia.id]: http(),
26 | [polygon.id]: http(),
27 | },
28 | connectors: [
29 | injected({
30 | target: "metaMask",
31 | }),
32 | ],
33 | });
34 |
35 | const contractInfo = [
36 | {
37 | id: 1,
38 | name: "Ethereum",
39 | contractAddress: "0xEcd0D12E21805803f70de03B72B1C162dB0898d9",
40 | },
41 | {
42 | id: 5,
43 | name: "Sepolia",
44 | contractAddress: "0x418325c3979b7f8a17678ec2463a74355bdbe72c",
45 | },
46 | {
47 | id: 137,
48 | name: "Polygon",
49 | contractAddress: "0x418325c3979b7f8a17678ec2463a74355bdbe72c",
50 | },
51 | ];
52 |
53 | const CallTest = () => {
54 | const { account } = useAccount();
55 | const { chain } = useProvider();
56 | const result = useReadContract({
57 | abi: [
58 | {
59 | type: "function",
60 | name: "balanceOf",
61 | stateMutability: "view",
62 | inputs: [{ name: "account", type: "address" }],
63 | outputs: [{ type: "uint256" }],
64 | },
65 | ],
66 | address: contractInfo.find((item) => item.id === chain?.id)
67 | ?.contractAddress as `0x${string}`,
68 | functionName: "balanceOf",
69 | args: [account?.address as `0x${string}`],
70 | });
71 | const { writeContract } = useWriteContract();
72 |
73 | return (
74 |
75 | {result.data?.toString()}
76 |
114 |
115 | );
116 | };
117 |
118 | export default function Web3() {
119 | return (
120 |
125 |
126 |
130 |
131 |
132 |
133 |
134 |
135 | );
136 | }
137 |
--------------------------------------------------------------------------------
/12_Signature/img/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/12_Signature/img/demo.png
--------------------------------------------------------------------------------
/13_Payment/img/send.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/13_Payment/img/send.png
--------------------------------------------------------------------------------
/14_LocalDev/img/addnetwork.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/14_LocalDev/img/addnetwork.png
--------------------------------------------------------------------------------
/14_LocalDev/img/hardhat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/14_LocalDev/img/hardhat.png
--------------------------------------------------------------------------------
/14_LocalDev/img/initfiles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/14_LocalDev/img/initfiles.png
--------------------------------------------------------------------------------
/14_LocalDev/img/localnode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/14_LocalDev/img/localnode.png
--------------------------------------------------------------------------------
/14_LocalDev/img/minttest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WTFAcademy/WTF-Dapp/9703ad24deeb7b83afa7ff2b5232a1070dcd31e9/14_LocalDev/img/minttest.png
--------------------------------------------------------------------------------
/15_WagmiCli/readme.md:
--------------------------------------------------------------------------------
1 | 本节作者:[@愚指导](https://x.com/yudao1024)
2 |
3 | 在上一讲我们基于 Hardhat 在本地创建了一个合约项目,在未来的 DEX 实战课程中我们会继续基于该项目创建更复杂的合约。伴随着合约越来越复杂,ABI 也会越来越庞大,这时候我们就需要一个工具来帮助我们更好的调试合约。在这一讲中我们将会引导大家给予 Wagmi CLI 自动化的创建用于合约调试的代码,并在项目中使用它,为后续的 DEX 实战课程做准备。
4 |
5 | ---
6 |
7 | ## 初始化 Wagmi CLI
8 |
9 | 按照下面步骤初始化,你也可以参考 [Wagmi CLI 的官方文档](https://wagmi.sh/cli/getting-started)操作。
10 |
11 | 安装依赖:
12 |
13 | ```bash
14 | npm install --save-dev @wagmi/cli
15 | ```
16 |
17 | 修改配置 `demo/wagmi.config.ts`,添加 [hardhat](https://wagmi.sh/cli/api/plugins/hardhat) 和 [react](https://wagmi.sh/cli/api/plugins/react) 插件。
18 |
19 | 完整配置如下:
20 |
21 | ```ts
22 | import { defineConfig } from "@wagmi/cli";
23 | import { hardhat } from "@wagmi/cli/plugins";
24 | import { react } from "@wagmi/cli/plugins";
25 |
26 | export default defineConfig({
27 | out: "utils/contracts.ts",
28 | plugins: [
29 | hardhat({
30 | project: "../demo-contract",
31 | }),
32 | react(),
33 | ],
34 | });
35 | ```
36 |
37 | 执行下面命令生成代码:
38 |
39 | ```bash
40 | npx wagmi generate
41 | ```
42 |
43 | 执行完成后你会看到生成的代码在 `utils/contracts.ts` 中,接下来你就可以更方便的在项目中使用 `utils/contracts.ts` 导出的 React Hooks 来调用合约了。
44 |
45 | ## 使用 Wagmi CLI 生成的代码
46 |
47 | 我们继续修改 `demo/pages/web3.tsx` 的代码,将之前使用的 `useReadContract` 和 `useWriteContract` 替换为 Wagmi CLI 生成的代码。
48 |
49 | ```diff
50 | // ...
51 | -import {
52 | - createConfig,
53 | - http,
54 | - useReadContract,
55 | - useWriteContract,
56 | - useWatchContractEvent,
57 | -} from "wagmi";
58 | +import { createConfig, http, useWatchContractEvent } from "wagmi";
59 | import { injected, walletConnect } from "wagmi/connectors";
60 | +import {
61 | + useReadMyTokenBalanceOf,
62 | + useWriteMyTokenMint,
63 | +} from "@/utils/contracts";
64 |
65 | // ...
66 | ```
67 |
68 | 修改 `balanceOf` 方法的调用逻辑:
69 |
70 | ```diff
71 |
72 | // ...
73 |
74 | - const result = useReadContract({
75 | - abi: [
76 | - {
77 | - type: "function",
78 | - name: "balanceOf",
79 | - stateMutability: "view",
80 | - inputs: [{ name: "account", type: "address" }],
81 | - outputs: [{ type: "uint256" }],
82 | - },
83 | - ],
84 | + const result = useReadMyTokenBalanceOf({
85 | address: contractInfo.find((item) => item.id === chain?.id)
86 | ?.contractAddress as `0x${string}`,
87 | - functionName: "balanceOf",
88 | args: [account?.address as `0x${string}`],
89 | });
90 |
91 | // ...
92 |
93 | ```
94 |
95 | 修改 `mint` 方法的调用逻辑:
96 |
97 | ```diff
98 | - const { writeContract } = useWriteContract();
99 | + const { writeContract: mintNFT } = useWriteMyTokenMint();
100 |
101 | // ...
102 |
103 | const CallTest = () => {
104 | {result.data?.toString()}
105 |