{"content":{"title":"Viem React 教程：如何转账、铸币和查看链状态","body":"> * 原文链接：https://www.rareskills.io/post/viem-ethereum\r\n> * 译文出自：[登链翻译计划](https://github.com/lbc-team/Pioneer)\r\n> * 译者：[翻译小组](https://learnblockchain.cn/people/412)  校对：[Tiny 熊](https://learnblockchain.cn/people/15)\r\n> * 本文永久链接：[learnblockchain.cn/article…](https://learnblockchain.cn/article/7291)\r\n\r\n\r\n\r\n在本教程中，我们将使用 Viem TypeScript库和 React (Next.js) 构建一个功能齐全的 Dapp。我们将介绍连接钱包、转账加密货币、与智能合约交互（如铸造 NFT）和查询区块链的必要步骤。\r\n\r\nViem 是 Typescript 的实现，可替代现有的 web3.js 和 ethers.js 等低级以太坊接口。它支持浏览器原生支持的[BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)，并能自动从 ABI 和 EIP-712 中[推断类型](https://viem.sh/docs/typescript.html)。它的打包大小为 35kb，采用tree-shakable 设计，以尽量减少最终包大小，测试覆盖率为 99.8%。请查看其[基准和完整文档](https://viem.sh/docs/introduction.html)。\r\n\r\n我们通过行内代码注释提供了简明的解释。只需复制粘贴代码并阅读注释即可。\r\n\r\n下面是本教程的大纲：\r\n\r\n- Viem 术语：Client | Transport | Chain\r\n- 入门：使用 React + Viem 设置 Client & Transport\r\n- 第 1 部分：使用 React + Viem 连接到 Web3 钱包\r\n- 第 2 部分：使用 React + Viem 转账加密货币\r\n- 第 3 部分：使用 React + Viem 铸造 NFT\r\n\r\n先展示构建的内容：\r\n\r\n![Viem TypeScript演示 dapp](https://img.learnblockchain.cn/pics/20240120095959.png)\r\n\r\n> [Git 仓库中的完整代码](https://github.com/AymericRT/viemtutorial.git)\r\n\r\n## Viem 术语：Client | Transport | Chain\r\n\r\nViem 有三个基本概念：**Client | Transport | Chain**\r\n\r\n- viem 中的 **Client** 类似于 Ether.js Provider。它提供在以太坊上执行常见操作的TypeScript函数。根据操作的不同，它将属于三种类型的Client 之一。\r\n- **Public Client**是 \"公共 \"JSON RPC API 方法的接口，例如，检索区块编号、查询账户余额、访问智能合约上的 [\"视图\" 函数](https://decert.me/tutorial/solidity/solidity-basic/solidity_layout#%E8%A7%86%E5%9B%BE%E5%87%BD%E6%95%B0)，以及其他只读、不改变状态的操作。这些功能被称为公共操作。\r\n- **Wallet Client**是与以太坊账户交互的接口，例如发送交易、签署信息、请求地址、切换链以及需要用户许可的操作。例如，铸币 NFT 是改变状态的操作，因此需要在 Wallet Client 下完成。这些功能被称为钱包操作。\r\n- **Test Client**用于创建模拟交易进行测试。这通常用于单元测试。\r\n\r\n- **（Transport）** 与**Client**一起实例化，是执行请求的中间层。**Transport**有三种类型：\r\n  - **HTTP** Transport，利用 HTTP JSON-RPC；\r\n  - **WebSocket** Transport，通过 WebSocket JSON-RPC 进行实时连接；\r\n  - **自定义 Transport**，通过 EIP-1193 请求方法处理请求；\r\n  - **Fallback** 允许你在列表中指定多个 Transport。如果其中一个 Transport 失败，它就会向下移动列表，以找到一个能正常运行的 Transport。稍后将提供一个示例。\r\n\r\n- **Chain**指用于建立连接的 EVM 兼容链，它们通过链对象（以链 id 标识）来识别。一个Client 只能实例化一个链。我们可以使用提供的 viem chain 库（如 polygon、eth mainnet），也可以手动创建自己的链。\r\n\r\n### Public Client\r\n\r\n这就是如何声明一个 **Public Client**:\r\n\r\n```js\r\nimport { createPublicClient, http } from 'viem'\r\nimport { mainnet } from 'viem/chains'\r\n\r\nconst publicClient = createPublicClient({ \r\n  chain: mainnet,\r\n  transport: http()\r\n})\r\n```\r\n\r\n这就是如何使用Public Client 进行操作:\r\n\r\n```typescript\r\nconst balance = await publicClient.getBalance({ \r\n  address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',\r\n})\r\n\r\nconst block = await publicClient.getBlockNumber()\r\n```\r\n\r\n这些是可用的公共操作：\r\n\r\n- getChainId : 获取Chain ID\r\n- getGasPrice : 获取Gas Price\r\n- signMessage ： 签署消息\r\n- verifyMessage ： 验证消息\r\n- getTransactionReceipt ：获取交易收据\r\n- 更多关于[公共操作的文档](https://viem.sh/docs/actions/public/introduction.html)\r\n\r\nViem 有一项名为 \"优化 Public Client \"的功能，它支持[eth_call Aggregation](https://viem.sh/docs/clients/public.html)，可通过发送批量请求提高性能。这一点有非常详细的记录。\r\n\r\n### Wallet Client\r\n\r\n这就是建立Wallet Client的方法：\r\n\r\n```javascript\r\nimport { createWalletClient, custom } from 'viem'\r\nimport { mainnet } from 'viem/chains'\r\n\r\nconst walletClient = createWalletClient({\r\n    chain: mainnet,transport: custom(window.ethereum)\r\n})\r\n```\r\n\r\n如何使用钱包Wallet Client的操作：\r\n\r\n```javascript\r\n// Get's the user address\r\nconst [address] = await walletClient.getAddresses()\r\n\r\n// Sends a transaction\r\nconst hash = await walletClient.sendTransaction({\r\n    account: address,\r\n    to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC',\r\n    value: parseEther('0.001')\r\n})\r\n```\r\n\r\n可用的Wallet Client：\r\n\r\n- requestAddresses ( 像 Metamask 这样的钱包可能需要用户先 requestAddresses )\r\n- switchChain： 切换链\r\n- signMessage ： 签署消息\r\n- getPermissions： 获取权限\r\n- sendTransaction ：发送交易\r\n- 更多内容请参见[钱包操作文档](https://viem.sh/docs/actions/wallet/introduction.html)\r\n\r\n我们将在本教程中演示如何使用**sendTransaction**和**getAddresses**。\r\n\r\n**可选方式**\r\n\r\n你可以使用[公共操作](https://viem.sh/docs/actions/public/introduction.html)**扩展Wallet Client。**这有助于你避免处理多个Client 。以下代码片段使用公共操作扩展了Wallet Client。\r\n\r\n```\r\nimport { createWalletClient, http, publicActions } from 'viem'\r\nimport { mainnet } from 'viem/chains'\r\n\r\nconst extendedClient = createWalletClient({\r\n  chain: mainnet,\r\n  transport: http()\r\n}).extend(publicActions)\r\n\r\n// Public Action\r\nconst block = await extendedClient.getBlockNumber() \r\n// Wallet Action\r\nconst [address] = await extendedClient.getAddresses(); \r\n```\r\n\r\n### Test Client\r\n\r\nTest Client提供了一个接口，可通过本地测试节点（如 Anvil 或 Hardhat）进行覆盖账户、挖矿区块和冒充交易。我们不会对此进行详细讨论，但你可以在 [Test Client文档](https://viem.sh/docs/clients/test.html) 上阅读更多内容。\r\n\r\n### Transport\r\n\r\n我们一次只能通过一种 Transport（传输）方式，我们将用它来连接到区块链的协议），以下是如何使用每种传输方式的示例。\r\n\r\n**HTTP**\r\n\r\n```javascript\r\nimport { createPublicClient, http } from 'viem'\r\nimport { mainnet } from 'viem/chains'\r\nconst transport = http('https://eth-mainnet.g.alchemy.com/v2/...')\r\n\r\nconst client = createPublicClient({\r\n  chain: mainnet,\r\n  transport,\r\n})\r\n```\r\n\r\n如果未提供 **url**，Transport 系统将退回到公共 RPC URL。建议传递经过验证的 RPC URL，以尽量减少速率限制问题。\r\n\r\n**WebSocket**\r\n\r\n```javascript\r\nimport { createPublicClient, webSocket } from 'viem'\r\nimport { mainnet } from 'viem/chains'\r\n\r\nconst transport = webSocket('wss://eth-mainnet.g.alchemy.com/v2/...')\r\nconst client = createPublicClient({\r\n  chain: mainnet, \r\n  transport,\r\n})\r\n```\r\n\r\n由于上述同样原因，Transport 将退回到公共 RPC URL。\r\n\r\n**注意** 在上述两个示例中，Transport 是由通过指定 URL 类型定义的。第一个 URL 是 HTTPS URL，第二个是 WSS URL。\r\n\r\n**自定义（EIP-1193）（我们使用的）**\r\n\r\n此Transport方式用于集成提供 EIP-1193 提供者的注入钱包，如 WalletConnect、Coinbase SDK 和 Metamask。\r\n\r\n```javascript\r\nimport { createWalletClient, custom } from 'viem'\r\nimport { mainnet } from 'viem/chains'\r\n\r\nconst client = createWalletClient({\r\n  chain: mainnet,\r\n  transport: custom(window.ethereum)\r\n})\r\n```\r\n\r\n**Fallback**\r\n\r\n 此transport 会接收多个transport。如果一个传输方式失败，它将采用下一个方式。在下面的示例中，如果 Alchemy 失败，它将退到 Infura 。\r\n\r\n```js\r\nimport { createPublicClient, fallback, http } from 'viem'\r\nimport { mainnet } from 'viem/chains'\r\n\r\nconst alchemy = http('https://eth-mainnet.g.alchemy.com/v2/...')\r\n\r\nconst infura = http('https://mainnet.infura.io/v3/...')\r\n\r\nconst client = createPublicClient({\r\n  chain: mainnet,\r\n  transport: fallback([alchemy, infura]),\r\n})\r\n```\r\n\r\n### Chains\r\n\r\nViem 通过 viem/chains 库提供了流行的 EVM 兼容链，如 Polygon、Optimism、Avalanche 等：Polygon、Optimism、Avalanche 等，请参见 [Viem Chains Documentation](https://viem.sh/docs/clients/chains.html)。\r\n\r\n你可以通过作为参数传递来切换链，例如 polygonMumbai。\r\n\r\n```js\r\nimport { createPublicClient, http } from 'viem'\r\nimport { polygonMumbai } from 'viem/chains'\r\n\r\nconst client = createPublicClient({\r\n    chain: polygonMumbai,\r\n    transport: http(),\r\n})\r\n```\r\n\r\n你还可以创建自己的链对象，该对象继承了链类型（参考资料：[Viem.sh]()）：[Viem.sh](https://viem.sh/docs/clients/chains.html))\r\n\r\n```js\r\nimport { Chain } from 'viem'\r\n\r\nexport const avalanche = {\r\n    id: 43_114,\r\n    name: 'Avalanche',\r\n    network: 'avalanche',\r\n    nativeCurrency: {\r\n        decimals: 18,\r\n        name: 'Avalanche',\r\n        symbol: 'AVAX',\r\n    },\r\n    rpcUrls: {\r\n        public: { \r\n            http: ['https://api.avax.network/ext/bc/C/rpc']        \r\n        },\r\n        default: { \r\n            http: ['https://api.avax.network/ext/bc/C/rpc'] \r\n        },\r\n    },\r\n    blockExplorers: {\r\n        etherscan: { \r\n            name: 'SnowTrace', \r\n            url: 'https://snowtrace.io' \r\n        },\r\n            default: { \r\n                name: 'SnowTrace', \r\n                url: 'https://snowtrace.io' \r\n            },\r\n        },\r\n        contracts: {multicall3: {\r\n            address: '0xca11bde05977b3631167028862be2a173976ca11',\r\n            blockCreated: 11_907_934,\r\n        },\r\n    },\r\n} as const satisfies Chain\r\n```\r\n\r\n请记住，一个 Client 一次只能分配一个链。\r\n\r\n## 入门：使用 React + Viem 设置 Client 和 Transport\r\n\r\n为简单起见，我们建议使用 **polygonMumbai** 或 **Sepolia** 作为测试网络。\r\n\r\n### 第 1 步：创建 Next.js 项目并安装 Viem\r\n\r\n首先用以下命令创建**Next.js**项目\r\n\r\n```\r\nnpx create-next-app@latest myapp\r\n```\r\n\r\n选中以下选项 [是]：\r\n\r\n- **Typescript**\r\n- **ESLint**\r\n- **Tailwind**\r\n- **App Router**（最好是）\r\n\r\n在 **vscode** 中打开你的项目。\r\n\r\n使用以下命令安装 viem：\r\n\r\n```\r\nnpm i viem \r\npnpm i viem\r\nyarn add viem\r\n```\r\n\r\n### 第 2 步：设置Client 和 Transport\r\n\r\n在应用程序目录下创建两个新文件：\r\n\r\n- **client.ts**\r\n- **walletButton.tsx**\r\n\r\n你的应用程序目录应该是这样的\r\n\r\n```\r\napp\r\n├── client.ts\r\n├── globals.css\r\n├── layout.tsx\r\n├── page.tsx \r\n└── walletButton.tsx\r\n```\r\n\r\n### Client.ts\r\n\r\n我们将在一个单独的 typescript 文件中初始化 Client 和 Transport 。将以下代码复制粘贴到 **client.ts** 中。\r\n\r\n```typescript\r\n// client.ts\r\nimport { createWalletClient, createPublicClient, custom, http } from \"viem\";\r\nimport { polygonMumbai, mainnet } from \"viem/chains\";\r\nimport \"viem/window\";\r\n\r\n// Instantiate Public Client\r\nconst publicClient = createPublicClient({\r\n  chain: mainnet,\r\n  transport: http(),\r\n});\r\n  \r\n// Instantiate Wallet Client\r\nconst walletClient = createWalletClient({\r\n    chain: polygonMumbai,\r\n    transport: custom(window.ethereum),\r\n});\r\n```\r\n\r\n这将不可避免地产生一个类型错误，**window.ethereum** 可能是**未定义的**，因为某些浏览器（如 safari）不支持 window.ethereum 对象。\r\n\r\n我们可以通过检查 window.ethereum 是否存在或未定义来处理该错误。\r\n\r\n```typescript\r\n// client.ts\r\n\r\nimport { createWalletClient, createPublicClient, custom, http } from \"viem\";\r\nimport { polygonMumbai } from \"viem/chains\";\r\nimport \"viem/window\";\r\n\r\nexport function ConnectWalletClient() {\r\n    // Check for window.ethereum\r\n    let transport;\r\n    if (window.ethereum) {\r\n        transport = custom(window.ethereum);\r\n    } else {\r\n        const errorMessage =\"MetaMask or another web3 wallet is not installed. Please install one to proceed.\";\r\n        throw new Error(errorMessage);\r\n    }\r\n    \r\n    // Delcalre a Wallet Client\r\n    const walletClient = createWalletClient({\r\n        chain: polygonMumbai,\r\n        transport: transport,\r\n    });\r\n    \r\n    return walletClient;\r\n}\r\n\r\nexport function ConnectPublicClient() {\r\n    // Check for window.ethereum\r\n    let transport;\r\n    if (window.ethereum) {\r\n        transport = custom(window.ethereum);\r\n    } else {\r\n        const errorMessage =\"MetaMask or another web3 wallet is not installed. Please install one to proceed.\";\r\n        throw new Error(errorMessage);\r\n    }\r\n    \r\n    // Delcare a Public Client\r\n    const publicClient = createPublicClient({\r\n        chain: polygonMumbai,\r\n        transport: transport,\r\n    });\r\n    \r\n    return publicClient;\r\n}\r\n```\r\n\r\n这样，你仍然可以在不支持 window.ethereum 的浏览器中打开网站。\r\n\r\n建议保持 **walletClient** 和 **publicClient** 的链一致，否则可能会出现链不兼容的错误。\r\n\r\n\r\n\r\n## Part 1：使用 React + Viem 连接到 Web3 钱包\r\n\r\n本部分演示 viem Client 如何连接 Web3 钱包。\r\n\r\n### 第 2 步：创建一个连接 Web3 钱包的按钮\r\n\r\n### walletButton.tsx\r\n\r\n现在我们将创建一个Client 组件，处理与 web3 钱包的连接逻辑。\r\n\r\n该按钮将实例化一个 walletClient 并请求用户的钱包地址，如果钱包尚未连接，它将提示用户连接，最后输出地址。\r\n\r\n这里有很多代码，但重点是 handleClick() 函数。\r\n\r\n```typescript\r\n// walletButton.tsx\r\n\"use client\";\r\nimport { useState } from \"react\";\r\nimport { ConnectWalletClient, ConnectPublicClient } from \"./client\";\r\n\r\nexport default function WalletButton() {\r\n    //State variables for address & balance\r\n    const [address, setAddress] = useState<string | null>(null);\r\n    const [balance, setBalance] = useState<BigInt>(BigInt(0));\r\n    // Function requests connection and retrieves the address of wallet\r\n    // Then it retrievies the balance of the address \r\n    // Finally it updates the value for address & balance variable\r\n    async function handleClick() {\r\n        try {\r\n            // Instantiate a Wallet & Public Client\r\n            const walletClient = ConnectWalletClient();\r\n            const publicClient = ConnectPublicClient();\r\n        \r\n            // Performs Wallet Action to retrieve wallet address\r\n            const [address] = await walletClient.getAddresses();\r\n            \r\n            // Performs Public Action to retrieve address balance\r\n            const balance = await publicClient.getBalance({ address });\r\n            // Update values for address & balance state variable\r\n            setAddress(address);\r\n            setBalance(balance);\r\n        } catch (error) {\r\n            // Error handling\r\n            alert(`Transaction failed: ${error}`);\r\n        }\r\n    }\r\n// Unimportant Section Below / Nice to Have UI\r\n    return (\r\n        <>\r\n            <Status address={address} balance={balance} />\r\n            <button className=\"px-8 py-2 rounded-md bg-[#1e2124] flex flex-row items-center justify-center border border-[#1e2124] hover:border hover:border-indigo-600 shadow-md shadow-indigo-500/10\"\r\n             onClick={handleClick}\r\n            >\r\n            <img     src=\"https://upload.wikimedia.org/wikipedia/commons/3/36/MetaMask_Fox.svg\" alt=\"MetaMask Fox\" style={{ width: \"25px\", height: \"25px\" }} />\r\n            <h1 className=\"mx-auto\">Connect Wallet</h1>\r\n            </button></>);}\r\n            \r\n// Displays the wallet address once it’s successfuly connected\r\n// You do not have to read it, it's just frontend stuff\r\n\r\nfunction Status({\r\n  address,\r\n  balance,}: {\r\n  address: string | null;\r\n  balance: BigInt;\r\n}) {\r\n    if (!address) {\r\n        return (\r\n            <div className=\"flex items-center\">\r\n                <div className=\"border bg-red-600 border-red-600 rounded-full w-1.5 h-1.5 mr-2\">\r\n                </div>\r\n                <div>Disconnected</div>\r\n            </div>);\r\n    }\r\n    return (\r\n        <div className=\"flex items-center w-full\">\r\n            <div className=\"border bg-green-500 border-green-500 rounded-full w-1.5 h-1.5 mr-2\"></div>\r\n            <div className=\"text-xs md:text-xs\">{address} <br /> Balance: {balance.toString()}</div>\r\n            </div>\r\n    );\r\n}\r\n```\r\n\r\n### 第 3 步：插入 walletButton 组件\r\n\r\n#### page.tsx\r\n\r\n剩下的就是设计主页面和导入 WalletButton 组件了。我们已经注释了一些你稍后要添加的代码。\r\n\r\n```typescript\r\nimport WalletButton from \"./walletButton\";\r\n// import MintButton from \"./mintButton\";\r\n// import SendButton from \"./sendButton\";\r\n\r\nexport default function Home() {\r\n      return (\r\n            <main className=\"min-h-screen\">\r\n                  <div className=\"flex flex-col items-center justify-center h-screen \">\r\n                        <a href=\"https://rareskills.io\" target=\"_blank\" className=\"text-white font-bold text-3xl hover:text-[#0044CC]\" > Viem.sh </a>\r\n                        <div className=\"h-[300px] min-w-[150px] flex flex-col justify-between  backdrop-blur-2xl bg-[#290330]/30 rounded-lg mx-auto p-7 text-white border border-purple-950\">                                                                     \r\n                              <WalletButton />\r\n                              {/* <SendButton />\r\n                              <MintButton /> */}\r\n                        </div>\r\n                        <a href=\"https://rareskills.io\" target=\"_blank\" className=\"text-white font-bold text-3xl hover:text-[#0044CC]\" > Rareskills.io </a>\r\n                 </div>\r\n            </main>\r\n      );\r\n}\r\n```\r\n\r\n#### globals.css\r\n\r\n一些漂亮的用户界面背景，用下面的代码替换 **globals.css** 。\r\n\r\n```\r\n@tailwind base;@tailwind components;@tailwind utilities;\r\n\r\nbody {\r\n  background-color: #0c002e;\r\n  background-image: radial-gradient(\r\n      at 100% 100%,rgb(84, 2, 103) 0px,\r\n      transparent 50%),\r\n    radial-gradient(at 0% 0%, rgb(97, 0, 118) 0px, transparent 50%);}\r\n```\r\n\r\n### 第 4 步：运行网站并进行测试\r\n\r\n点击按钮后，它将启动与你钱包的连接。一旦你授权，它就会变成这样：\r\n\r\n```\r\nnpm run dev\r\n```\r\n\r\n![viem connect wallet](https://img.learnblockchain.cn/pics/20240120113510.png)\r\n\r\n点击按钮后，将显示以下内容：\r\n\r\n![viem显示连接地址](https://img.learnblockchain.cn/pics/20240120113517.png)\r\n\r\n## 第二部分：使用 React + Viem 转账加密信息\r\n\r\n现在我们的钱包已经连接好了，可以开始转账加密货币了。我们将使用 **sendTransaction** 钱包操作。\r\n\r\n- 从 [Matic Faucet](https://faucet.polygon.technology/) 获取一些 Matic。\r\n\r\n### 第 5 步：添加加密货币转账功能\r\n\r\n在应用程序目录中创建一个新的 tsx 文件 **sendButton.tsx**\r\n\r\n```\r\napp\r\n├── client.ts\r\n├── globals.css\r\n├── layout.tsx\r\n├── page.tsx \r\n├── sendButton.tsx \r\n└── walletButton.tsx\r\n```\r\n\r\n#### sendButton.tsx\r\n\r\n我们将创建一个启动 **sendTransaction** 动作的按钮。Viem 可以让我们非常简单地做到这一点。逻辑流程应类似于 **walletButton.tsx**，实例化 walletClient 并执行 Wallet Client 操作。\r\n\r\n```typescript\r\n\"use client\";\r\nimport { parseEther } from \"viem\";\r\nimport { ConnectWalletClient} from \"./client\";\r\n\r\nexport default function SendButton() {\r\n     //Send Transaction Function\r\n     async function handleClick() {\r\n        try {\r\n           // Declare wallet client\r\n           const walletClient = ConnectWalletClient();\r\n           // Get the main wallet address\r\n           const [address] = await walletClient.getAddresses();\r\n           // sendTransaction is a Wallet action. \r\n           // It returns the transaction hash \r\n           // requires 3 parameters  to transfer cryptocurrency, \r\n           // account, to and value\r\n           const hash = await walletClient.sendTransaction({\r\n              account: address,\r\n              to: \"Account_Address\",\r\n              value: parseEther(\"0.001\"), // send 0.001 matic\r\n            });\r\n            // Display the transaction hash in an alert\r\n            alert(`Transaction successful. Transaction Hash: ${hash}`);\r\n         } catch (error) {\r\n              // Handle Error\r\n              alert(`Transaction failed: ${error}`);\r\n         }\r\n     }\r\n     \r\n     return (\r\n        <button\r\n            className=\"py-2.5 px-2 rounded-md bg-[#1e2124] flex flex-row items-center justify-center border border-[#1e2124] hover:border hover:border-indigo-600 shadow-md shadow-indigo-500/10\"\r\n            onClick={handleClick}>\r\n            Send Transaction\r\n       </button>\r\n    );\r\n}\r\n```\r\n\r\n### 第 6 步：加入 sendButton 组件\r\n\r\n#### page.tsx\r\n\r\n取消与 sendButton 组件有关的注释。\r\n\r\n```typescript\r\nimport WalletButton from \"./walletButton\";\r\nimport SendButton from \"./sendButton\";\r\n// import MintButton from \"./mintButton\";\r\n\r\nexport default function Home() {\r\n\r\n    return (\r\n        <main className=\"min-h-screen\">\r\n            <div className=\"flex flex-col items-center justify-center h-screen \">\r\n                <a href=\"https://rareskills.io\" target=\"_blank\" className=\"text-white font-bold text-3xl hover:text-[#0044CC]\" > Viem.sh </a>\r\n                <div className=\"h-[300px] min-w-[150px] flex flex-col justify-between  backdrop-blur-2xl bg-[#290330]/30 rounded-lg mx-auto p-7 text-white border border-purple-950\">\r\n                    <WalletButton />\r\n                    <SendButton />\r\n                    {/* <MintButton /> */}\r\n                </div>\r\n                <a href=\"https://rareskills.io\" target=\"_blank\" className=\"text-white font-bold text-3xl hover:text-[#0044CC]\" > Rareskills.io </a>\r\n            </div>\r\n        </main>\r\n    );\r\n}\r\n```\r\n\r\n你的浏览器现在应该是这样的\r\n\r\n![viem发送交易](https://img.learnblockchain.cn/pics/20240120113525.png)\r\n\r\n\r\n\r\n## 第三部分：使用 React + Viem 创建一个 NFT\r\n\r\n本部分将讨论如何与智能合约交互，并通过一个 NFT 铸币实例进行说明。\r\n\r\n要与智能合约交互，我们需要两样东西：\r\n\r\n- 合约地址\r\n- 合约 ABI\r\n\r\n在这个例子中，我们将用 Rareskill 的合约来演示，该合约有一个mint功能，除了跟踪你的mint次数外，它什么也不做。\r\n\r\n- Rareskill 的合约地址：**0x7E6Ddd9dC419ee2F10eeAa8cBB72C215B9Eb5E23**\r\n- [Rareskill的合约ABI](https://mumbai.polygonscan.com/address/0x7e6ddd9dc419ee2f10eeaa8cbb72c215b9eb5e23#code)\r\n\r\n请使用你自己的合约。\r\n\r\n### 第 7 步：添加与智能合约交互的功能\r\n\r\n创建两个新文件 **abi.ts** 和 **mintButton.tsx**\r\n\r\n```\r\napp\r\n├── abi.ts\r\n├── client.ts\r\n├── globals.css\r\n├── layout.tsx\r\n├── mintButton.tsx\r\n├── page.tsx \r\n├── sendButton.tsx \r\n└── walletButton.tsx\r\n```\r\n\r\n#### abi.ts\r\n\r\n复制粘贴 Rareskill 的合约 ABI 或你自己的 ABI。\r\n\r\n```\r\n// abi.ts\r\nexport const wagmiAbi = [...contract abi...] as const;\r\n```\r\n\r\n请务必严格遵守此格式，不要忘记结尾处的 \"**as const; \"**。\r\n\r\n#### 合约实例和合约操作方法\r\n\r\n**合约实例方法**\r\n\r\n```\r\n//Contract Instance\r\nconst contract = getContract({\r\n  address: \"0x7E6Ddd9dC419ee2F10eeAa8cBB72C215B9Eb5E23\",\r\n  abi: wagmiAbi,\r\n  publicClient,\r\n  walletClient,\r\n});\r\n```\r\n\r\n**getContract** 函数创建我们的合约实例 **contract**。创建后，我们就可以调用合约方法、监听事件等。这是一种更简单的方法，因为我们不必重复传递 **address** 和 **abi** 属性来执行合约操作。\r\n\r\n**参数：**\r\n\r\n- 地址\r\n- abi\r\n- Public Client（可选）\r\n- Wallet Client（可选）\r\n\r\n我们必须传递地址和 abi 参数。传递 publicClient 和 walletClient 是可选的，但它允许我们根据Client 类型访问一组合约方法。\r\n\r\n**Public Client**的可用合约方法：\r\n\r\n- [createEventFilter](https://viem.sh/docs/contract/createContractEventFilter.html)\r\n- [estimateGas](https://viem.sh/docs/contract/estimateContractGas.html)\r\n- [read](https://viem.sh/docs/contract/readContract.html)\r\n- [simulate](https://viem.sh/docs/contract/simulateContract.html)\r\n- [watchEvent](https://viem.sh/docs/contract/watchContractEvent.html)\r\n\r\n**walletClient** 可用的合约方法：\r\n\r\n- [estimateGas](https://viem.sh/docs/contract/estimateContractGas.html)\r\n- [write](https://viem.sh/docs/contract/writeContract.html)\r\n\r\n一般来说，调用合约实例方法的格式如下：\r\n\r\n```js\r\n// function\r\ncontract.(estimateGas|read|simulate|write).(functionName)(args, options)\r\n\r\n// event\r\ncontract.(createEventFilter|watchEvent).(eventName)(args, options)\r\n```\r\n\r\n使用合约实例调用合约方法\r\n\r\n```js\r\n// Read Contract symbol\r\nconst symbol = await contract.read.symbol();\r\n\r\n// Read Contract name\r\nconst name = await contract.read.name();\r\n\r\n// Call mint method\r\nconst result = await contract.write.mint({account: address});\r\n```\r\n\r\n上面的示例通过 **contract** 实例调用了读写合约方法。如果使用 Type-script，它会自动补全可用合约方法的建议。\r\n\r\n**read.symbol()**和**read.name()**很简单。另一方面，写入函数\r\n\r\n```typescript\r\nconst result = await contract.write.mint({account: address});\r\n```\r\n\r\n将 {account: address} 作为必选参数，其他参数均为可选参数。如果你不知道该添加哪些参数，请将鼠标悬停在 \"mint() \"关键字上，VS 代码会提示你。\r\n\r\n**繁琐的合约操作方法**\r\n\r\n上节代码是下文的语法糖。我们包含这一部分是为了向你展示在底层发生了什么。\r\n\r\n这段代码将获取合约的总发行量（totalSupply）：\r\n\r\n```typescript\r\nconst totalSupply = await publicClient.readContract({\r\n    address: '0x7E6Ddd9dC419ee2F10eeAa8cBB72C215B9Eb5E23',\r\n    abi: wagmiAbi,\r\n    functionName: 'totalSupply',\r\n})\r\n```\r\n\r\n很麻烦吧？你必须反复传递地址和 abi。\r\n\r\n这相当于在上面的示例中使用 Contract Action 方法调用 **mint** 函数。成功后，它将返回交易哈希值。\r\n\r\n```typescript\r\nconst hash = await walletClient.writeContract({\r\n\taddress: \"0x7E6Ddd9dC419ee2F10eeAa8cBB72C215B9Eb5E23\",\r\n\tabi: wagmiAbi,\r\n\tfunctionName: \"mint\",\r\n\taccount,\r\n});\r\n```\r\n\r\n要保持最小的包大小，请使用 Contract Action；虽然 Contract instance 提供了更多的功能，但它会增加内存使用量。\r\n\r\n#### mintButton.tsx\r\n\r\n为了演示状态更改交易，我们将创建一个按钮，调用智能合约的**mint**函数，并查询它的**名称、符号**和**总发行量**。\r\n\r\n我们利用合约实例和合约操作方法来展示它是如何实现的。\r\n\r\n```typescript\r\n\"use client\";\r\nimport { formatEther, getContract } from \"viem\";\r\nimport { wagmiAbi } from \"./abi\";\r\nimport { ConnectWalletClient, ConnectPublicClient } from \"./client\";\r\n\r\nexport default function MintButton() {\r\n\r\n  // Function to Interact With Smart Contract\r\n  async function handleClick() {\r\n  \r\n    // Declare Client\r\n    const walletClient = ConnectWalletClient();\r\n    const publicClient = ConnectPublicClient();\r\n    \r\n    // Create a Contract Instance\r\n    // Pass publicClient to perform Public Client Contract Methods\r\n    // Pass walletClient to perform Wallet Client Contract Methods\r\n    const contract = getContract({\r\n      address: \"0x7E6Ddd9dC419ee2F10eeAa8cBB72C215B9Eb5E23\",\r\n      abi: wagmiAbi,\r\n      publicClient,\r\n      walletClient,\r\n    });\r\n    \r\n    // 通过合约实例读取状态\r\n    const symbol = await contract.read.symbol();\r\n    \r\n    // 通过合约实例读取状态\r\n    const name = await contract.read.name();\r\n    \r\n    // 通过合约Action 读取状态\r\n    const totalSupply = await publicClient.readContract({\r\n      address: '0x7E6Ddd9dC419ee2F10eeAa8cBB72C215B9Eb5E23',\r\n      abi: wagmiAbi,\r\n      functionName: 'totalSupply',\r\n    })\r\n    \r\n    // Format ether converts BigInt(Wei) to String(Ether)\r\n    const totalSupplyInEther = formatEther(totalSupply);\r\n    \r\n    alert(`Symbol: ${symbol}\\nName: ${name}\\ntotalSupply: ${totalSupplyInEther}`);\r\n    \r\n    try {\r\n      // Declare Wallet Client and Retrieve wallet address\r\n      const client = walletClient;\r\n      const [address] = await client.getAddresses();\r\n      \r\n      // Writes the state-changin function mint via Contract Instance method.\r\n      const result = await contract.write.mint({\r\n        account: address\r\n      });\r\n      \r\n      alert(`${result} ${name}`);\r\n    } catch (error) {\r\n      // Handle any errors that occur during the transaction\r\n      alert(`Transaction failed: ${error}`);\r\n    }}\r\n    \r\n    return (\r\n      <>\r\n        <button\r\n          className=\"py-2.5 px-2 rounded-md bg-[#1e2124] flex flex-row items-center justify-center border border-[#1e2124] hover:border hover:border-indigo-600 shadow-md shadow-indigo-500/10\"\r\n          onClick={handleClick}>\r\n          <svg\r\n            className=\"w-4 h-4 mr-2 -ml-1 text-[#626890]\"\r\n            aria-hidden=\"true\"\r\n            focusable=\"false\"\r\n            data-prefix=\"fab\"\r\n            data-icon=\"ethereum\"\r\n            role=\"img\"\r\n            xmlns=\"http://www.w3.org/2000/svg\"\r\n            viewBox=\"0 0 320 512\">\r\n            <path\r\n              fill=\"currentColor\"\r\n              d=\"M311.9 260.8L160 353.6 8 260.8 160 0l151.9 260.8zM160 383.4L8 290.6 160 512l152-221.4-152 92.8z\">\r\n            </path>\r\n          </svg>\r\n          <h1 className=\"text-center\">Mint</h1>\r\n        </button>\r\n      </>\r\n    );\r\n}\r\n```\r\n\r\n恭喜你完成了本教程！你的最终产品应该是这样的\r\n\r\n![viem mint NFT](https://img.learnblockchain.cn/pics/20240120113802.png)\r\n\r\n----\r\n\r\n本翻译由 [DeCert.me](https://decert.me/) 协助支持， 来DeCert码一个未来， 支持每一位开发者构建自己的可信履历。"},"author":{"user":"https://learnblockchain.cn/people/412","address":"0x9e64a306aB319811C5a1270F2CA9f6E1e4857c84"},"history":"bafkreicf6hopuap5x4eeuolsq47udyz46flxcjslrs6uw4vpfom6nh672e","timestamp":1705847563,"version":1}