{"content":{"title":"Viem极简教程：与链上合约交互","body":"Viem是一个相当新的web3库，它专注于EVM，提供了更好的开发体验，更小的包体积等等。在本文中，将使用foundry部署一个简单的合约，并在node环境下使用viem与部署的链上合约执行读写交互。\r\n\r\n- Viem docs — [https://viem.sh/docs/getting-started.html](https://viem.sh/docs/getting-started.html)\r\n- GitHub repository for the code used in this article — [https://github.com/CarryWang/viem-playground](https://github.com/CarryWang/viem-playground)\r\n## 使用foundry创建合约\r\n首先使用foundry来部署一个简单的合约。首先新建一个foundry项目，使用[forge init](https://book.getfoundry.sh/reference/forge/forge-init.html)命令。\r\n```shell\r\nforge init viem-foundry\r\n```\r\n创建好后的项目结构会像下面这样。\r\n```shell\r\n➜  viem-foundry git:(main) ✗ tree . -L 1\r\n.\r\n├── README.md\r\n├── foundry.toml\r\n├── lib\r\n├── script\r\n├── src\r\n└── test\r\n```\r\nfoundry会在`src`中创建一个叫`Counter.sol`的示例合约。对这个合约稍作修改，增加一个`decrement`方法。\r\n```solidity\r\n// SPDX-License-Identifier: UNLICENSED\r\npragma solidity ^0.8.13;\r\n\r\ncontract Counter {\r\n  uint256 public number;\r\n\r\n  function setNumber(uint256 newNumber) public {\r\n    number = newNumber;\r\n  }\r\n\r\n  function increment() public {\r\n    number++;\r\n  }\r\n\r\n  function decrement() public {\r\n    number--;\r\n  }\r\n}\r\n\r\n```\r\n同时在`test/Counter.t.sol`中增加一个`test_Decrement`的测试用例。\r\n```solidity\r\n// SPDX-License-Identifier: UNLICENSED\r\npragma solidity ^0.8.13;\r\n\r\nimport {Test, console} from \"forge-std/Test.sol\";\r\nimport {Counter} from \"../src/Counter.sol\";\r\n\r\ncontract CounterTest is Test {\r\n    Counter public counter;\r\n\r\n    function setUp() public {\r\n        counter = new Counter();\r\n        counter.setNumber(0);\r\n    }\r\n\r\n    function test_Increment() public {\r\n        counter.increment();\r\n        assertEq(counter.number(), 1);\r\n    }\r\n\r\n    function test_Decrement() public {\r\n        counter.setNumber(1);\r\n        counter.decrement();\r\n        assertEq(counter.number(), 0);\r\n    }\r\n\r\n    function testFuzz_SetNumber(uint256 x) public {\r\n        counter.setNumber(x);\r\n        assertEq(counter.number(), x);\r\n    }\r\n}\r\n\r\n```\r\n接下来执行[forge build](https://book.getfoundry.sh/reference/forge/forge-build.html)命令。\r\n```solidity\r\n$ forge build\r\nCompiling 27 files with Solc 0.8.19\r\nSolc 0.8.19 finished in 1.27s\r\nCompiler run successful!\r\n```\r\n接着执行[forge test](https://book.getfoundry.sh/reference/forge/forge-test.html)命令。终端会显示下面的结果，所有的用例都PASS都没有问题，就可以部署合约了。\r\n```shell\r\nviem-foundry git:(main) forge test\r\n[⠢] Compiling...\r\nNo files changed, compilation skipped\r\n\r\nRan 3 tests for test/Counter.t.sol:CounterTest\r\n[PASS] testFuzz_SetNumber(uint256) (runs: 256, μ: 31098, ~: 31332)\r\n[PASS] test_Decrement() (gas: 21546)\r\n[PASS] test_Increment() (gas: 31359)\r\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 12.49ms (8.09ms CPU time)\r\n\r\nRan 1 test suite in 250.79ms (12.49ms CPU time): 3 tests passed, 0 failed, 0 skipped (3 total tests)\r\n\r\n```\r\n这里使用sepolia作为测试部署。使用[forge create](https://book.getfoundry.sh/reference/forge/forge-create.html)命令。这里的`--rpc-url`填入sepolia的rpc，可以使用公共的[rpc节点](https://chainlist.org/chain/11155111)，`--private-key`就是你的钱包私钥，`--etherscan-api-key`用于验证合约用，这个api-key可以去[https://etherscan.io/login](https://etherscan.io/login)注册账号获得。\r\n```shell\r\nforge create --rpc-url <your_rpc_url> \\\r\n    --private-key <your_private_key> \\\r\n    --etherscan-api-key <your_etherscan_api_key> \\\r\n    --verify \\\r\n    src/Counter.sol:Counter\r\n```\r\n部署完成后，就可以在sepolia的区块链浏览器中查看到部署好的合约，这里已经部署好的合约可以在这里[查看](https://sepolia.etherscan.io/address/0x6b565dE192A1Be17a4F077B5Fda6b3A100498790)。\r\n\r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/06/cUFOGkHl6679506ba98db.png)\r\n经过验证后的合约，在`Contract`标签栏上会有一个绿色的小勾，并且可以查看合约的源码。\r\n\r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/06/KTAX3kyN6679505403817.png)\r\n在区块链浏览器中可以直接与合约进行交互，在`Write Contract`中可以看到合约中的函数，可以通过链接钱包，进行对合约的写入操作。当然，执行每一个操作都需要支付gas费。\r\n\r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/06/aYFvVf2Y6679503236743.png)\r\n在`Read Contract`中可以查看所有的public变量。\r\n\r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/06/qkWoine566795021bc8f3.png)\r\n至此，合约的部署工作已经全部完成了。\r\n\r\n---\r\n\r\n## 使用Viem与合约交互\r\n接下来是viem的部分，这一部分主要是关于如何使用viem与合约进行交互。\r\n首先，创建一个`viem-scripts`文件夹，首先使用pnpm初始化项目，使用`pnpm init`，终端会出现以下信息。\r\n```solidity\r\n{\r\n  \"name\": \"viem-scripts\",\r\n  \"version\": \"1.0.0\",\r\n  \"description\": \"\",\r\n  \"main\": \"index.js\",\r\n  \"scripts\": {\r\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\r\n  },\r\n  \"keywords\": [],\r\n  \"author\": \"\",\r\n  \"license\": \"ISC\"\r\n}\r\n\r\n```\r\n接下来需要安装一些必要的依赖，使用pnpm安装。\r\n```shell\r\npnpm install dotenv viem\r\npnpm install -D typescript ts-node @types/node\r\n```\r\n安装依赖后，需要在项目中初始化typescript的配置文件，输入以下命令。\r\n```shell\r\nnpx tsc --init\r\n```\r\n项目根目录下会自动创建一个`tsconfig.json`文件，终端显示如下。\r\n```solidity\r\nCreated a new tsconfig.json with:                                               \r\n                                                                             TS \r\n  target: es2016\r\n  module: commonjs\r\n  strict: true\r\n  esModuleInterop: true\r\n  skipLibCheck: true\r\n  forceConsistentCasingInFileNames: true\r\n\r\n\r\nYou can learn more at https://aka.ms/tsconfig\r\n```\r\n新建一个`index.ts`文件，创建一个`client`，调用viem提供的`createPublicClient`函数，该函数需要传入一个对象，对象包含两个重要的字段`chain`和`transport`。\r\n```javascript\r\nimport { createPublicClient, http } from \"viem\";\r\nimport { sepolia } from \"viem/chains\";\r\n\r\nconst client = createPublicClient({\r\n  chain: sepolia,\r\n  transport: http(),\r\n});\r\n```\r\n本文的合约部署在sepolia上，所以这里的`chain`使用`sepolia`，viem/chains中提供了很多公链，更多支持的链可以这个[列表](https://github.com/wevm/viem/blob/main/src/chains/index.ts)。`transport`指的是前端与合约的通信方式，viem中的`transport`支持三种模式，分别是 [HTTP Transport](https://viem.sh/docs/clients/transports/http)，[WebSocket Transport](https://viem.sh/docs/clients/transports/websocket) 和 [Custom Transport](https://viem.sh/docs/clients/transports/custom) ，这里使用最常用的`http`方式。\r\n\r\n有了`client`，就可以与链做交互了，先做一个最简单的交互，查询当前链的区块高度。输入下面的代码。\r\n```javascript\r\nasync function main() {\r\n  const blockNumber = await client.getBlockNumber();\r\n  console.log(blockNumber);\r\n}\r\n\r\nmain();\r\n```\r\n打开根目录中的`package.json`，在`scripts`中添加`\"start\": \"ts-node index.ts\"`。\r\n```json\r\n\"scripts\": {\r\n  \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\r\n  \"start\": \"ts-node index.ts\"\r\n},\r\n```\r\n运行`pnpm start`，终端会显示目前的区块高度。注意，你运行的时候，这个值会和我的不一样，因为区块数一直在增加。\r\n```json\r\n> viem-scripts@1.0.0 start /viem-playground/viem-scripts\r\n> ts-node index.ts\r\n\r\n6153864n\r\n```\r\n为了和合约交互，需要知道合约的abi。在foundry项目中，当我们执行`forge build`后，会把所有项目中涉及到的所有合约进行编译，并在根目录下会生成一个`out`文件夹，文件夹内会对应生成每个合约的json文件。\r\n```shell\r\n.\r\n├── cache\r\n├── lib\r\n├── out\r\n│   ├── Base.sol\r\n│   │   ├── CommonBase.json\r\n│   │   ├── ScriptBase.json\r\n│   │   └── TestBase.json\r\n│   ├── Counter.s.sol\r\n│   │   └── CounterScript.json\r\n│   ├── Counter.sol\r\n│   │   └── Counter.json\r\n│   ├── Counter.t.sol\r\n│   │   └── CounterTest.json\r\n│   ├── IERC165.sol\r\n│   │   └── IERC165.json\r\n│   ├── IERC20.sol\r\n│   │   └── IERC20.json\r\n│   ├── IERC721.sol\r\n│   │   ├── IERC721.json\r\n│   │   ├── IERC721Enumerable.json\r\n│   │   ├── IERC721Metadata.json\r\n│   │   └── IERC721TokenReceiver.json\r\n│   ├── IMulticall3.sol\r\n│   │   └── IMulticall3.json\r\n│   ├── MockERC20.sol\r\n│   │   └── MockERC20.json\r\n│   ├── MockERC721.sol\r\n│   │   ├── IERC721TokenReceiver.json\r\n│   │   └── MockERC721.json\r\n│   ├── Script.sol\r\n│   │   └── Script.json\r\n│   ├── StdAssertions.sol\r\n│   │   └── StdAssertions.json\r\n│   ├── StdChains.sol\r\n│   │   └── StdChains.json\r\n│   ├── StdCheats.sol\r\n│   │   ├── StdCheats.json\r\n│   │   └── StdCheatsSafe.json\r\n│   ├── StdError.sol\r\n│   │   └── stdError.json\r\n│   ├── StdInvariant.sol\r\n│   │   └── StdInvariant.json\r\n│   ├── StdJson.sol\r\n│   │   └── stdJson.json\r\n│   ├── StdMath.sol\r\n│   │   └── stdMath.json\r\n│   ├── StdStorage.sol\r\n│   │   ├── stdStorage.json\r\n│   │   └── stdStorageSafe.json\r\n│   ├── StdStyle.sol\r\n│   │   └── StdStyle.json\r\n│   ├── StdToml.sol\r\n│   │   └── stdToml.json\r\n│   ├── StdUtils.sol\r\n│   │   └── StdUtils.json\r\n│   ├── Test.sol\r\n│   │   └── Test.json\r\n│   ├── Vm.sol\r\n│   │   ├── Vm.json\r\n│   │   └── VmSafe.json\r\n│   ├── console.sol\r\n│   │   └── console.json\r\n│   ├── console2.sol\r\n│   │   └── console2.json\r\n│   └── safeconsole.sol\r\n│       └── safeconsole.json\r\n├── script\r\n├── src\r\n└── test\r\n```\r\n找到`Counter.json`文件，可以看到里面包含了几个主要字段，`abi`，`bytecode`，`deployedBytecode`，`methodIdentifiers`，`rawMetadata`，`metadata`以及`id`。这些字段构成了描述一个合约的完整信息。\r\n```json\r\n{\r\n  \"abi\": [\r\n    {\r\n      \"type\": \"function\",\r\n      \"name\": \"decrement\",\r\n      \"inputs\": [],\r\n      \"outputs\": [],\r\n      \"stateMutability\": \"nonpayable\"\r\n    },\r\n    {\r\n      \"type\": \"function\",\r\n      \"name\": \"increment\",\r\n      \"inputs\": [],\r\n      \"outputs\": [],\r\n      \"stateMutability\": \"nonpayable\"\r\n    },\r\n    {\r\n      \"type\": \"function\",\r\n      \"name\": \"number\",\r\n      \"inputs\": [],\r\n      \"outputs\": [{ \"name\": \"\", \"type\": \"uint256\", \"internalType\": \"uint256\" }],\r\n      \"stateMutability\": \"view\"\r\n    },\r\n    {\r\n      \"type\": \"function\",\r\n      \"name\": \"setNumber\",\r\n      \"inputs\": [\r\n        { \"name\": \"newNumber\", \"type\": \"uint256\", \"internalType\": \"uint256\" }\r\n      ],\r\n      \"outputs\": [],\r\n      \"stateMutability\": \"nonpayable\"\r\n    }\r\n  ],\r\n  \"bytecode\": {\r\n    \"object\": \"0x6080604052348015600f57600080fd5b506101328061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060465760003560e01c80632baeceb714604b5780633fb5c1cb1460535780638381f58a146063578063d09de08a14607d575b600080fd5b60516083565b005b6051605e36600460a4565b600055565b606b60005481565b60405190815260200160405180910390f35b60516097565b60008054908060908360d2565b9190505550565b60008054908060908360e6565b60006020828403121560b557600080fd5b5035919050565b634e487b7160e01b600052601160045260246000fd5b60008160de5760de60bc565b506000190190565b60006001820160f55760f560bc565b506001019056fea2646970667358221220aa623ffc9dd48bbbf1da02199cadc16e0e718c5d6cc383341f77e57e2cb4412564736f6c63430008190033\",\r\n    \"sourceMap\": \"65:251:25:-:0;;;;;;;;;;;;;;;;;;;\",\r\n    \"linkReferences\": {}\r\n  },\r\n  \"deployedBytecode\": {\r\n    \"object\": \"0x6080604052348015600f57600080fd5b506004361060465760003560e01c80632baeceb714604b5780633fb5c1cb1460535780638381f58a146063578063d09de08a14607d575b600080fd5b60516083565b005b6051605e36600460a4565b600055565b606b60005481565b60405190815260200160405180910390f35b60516097565b60008054908060908360d2565b9190505550565b60008054908060908360e6565b60006020828403121560b557600080fd5b5035919050565b634e487b7160e01b600052601160045260246000fd5b60008160de5760de60bc565b506000190190565b60006001820160f55760f560bc565b506001019056fea2646970667358221220aa623ffc9dd48bbbf1da02199cadc16e0e718c5d6cc383341f77e57e2cb4412564736f6c63430008190033\",\r\n    \"sourceMap\": \"65:251:25:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;261:53;;;:::i;:::-;;116:80;;;;;;:::i;:::-;171:6;:18;116:80;88:21;;;;;;;;;345:25:27;;;333:2;318:18;88:21:25;;;;;;;202:53;;;:::i;261:::-;299:6;:8;;;:6;:8;;;:::i;:::-;;;;;;261:53::o;202:::-;240:6;:8;;;:6;:8;;;:::i;14:180:27:-;73:6;126:2;114:9;105:7;101:23;97:32;94:52;;;142:1;139;132:12;94:52;-1:-1:-1;165:23:27;;14:180;-1:-1:-1;14:180:27:o;381:127::-;442:10;437:3;433:20;430:1;423:31;473:4;470:1;463:15;497:4;494:1;487:15;513:136;552:3;580:5;570:39;;589:18;;:::i;:::-;-1:-1:-1;;;625:18:27;;513:136::o;654:135::-;693:3;714:17;;;711:43;;734:18;;:::i;:::-;-1:-1:-1;781:1:27;770:13;;654:135::o\",\r\n    \"linkReferences\": {}\r\n  },\r\n  \"methodIdentifiers\": {\r\n    \"decrement()\": \"2baeceb7\",\r\n    \"increment()\": \"d09de08a\",\r\n    \"number()\": \"8381f58a\",\r\n    \"setNumber(uint256)\": \"3fb5c1cb\"\r\n  },\r\n  \"rawMetadata\": \"{\\\"compiler\\\":{\\\"version\\\":\\\"0.8.25+commit.b61c2a91\\\"},\\\"language\\\":\\\"Solidity\\\",\\\"output\\\":{\\\"abi\\\":[{\\\"inputs\\\":[],\\\"name\\\":\\\"decrement\\\",\\\"outputs\\\":[],\\\"stateMutability\\\":\\\"nonpayable\\\",\\\"type\\\":\\\"function\\\"},{\\\"inputs\\\":[],\\\"name\\\":\\\"increment\\\",\\\"outputs\\\":[],\\\"stateMutability\\\":\\\"nonpayable\\\",\\\"type\\\":\\\"function\\\"},{\\\"inputs\\\":[],\\\"name\\\":\\\"number\\\",\\\"outputs\\\":[{\\\"internalType\\\":\\\"uint256\\\",\\\"name\\\":\\\"\\\",\\\"type\\\":\\\"uint256\\\"}],\\\"stateMutability\\\":\\\"view\\\",\\\"type\\\":\\\"function\\\"},{\\\"inputs\\\":[{\\\"internalType\\\":\\\"uint256\\\",\\\"name\\\":\\\"newNumber\\\",\\\"type\\\":\\\"uint256\\\"}],\\\"name\\\":\\\"setNumber\\\",\\\"outputs\\\":[],\\\"stateMutability\\\":\\\"nonpayable\\\",\\\"type\\\":\\\"function\\\"}],\\\"devdoc\\\":{\\\"kind\\\":\\\"dev\\\",\\\"methods\\\":{},\\\"version\\\":1},\\\"userdoc\\\":{\\\"kind\\\":\\\"user\\\",\\\"methods\\\":{},\\\"version\\\":1}},\\\"settings\\\":{\\\"compilationTarget\\\":{\\\"src/Counter.sol\\\":\\\"Counter\\\"},\\\"evmVersion\\\":\\\"paris\\\",\\\"libraries\\\":{},\\\"metadata\\\":{\\\"bytecodeHash\\\":\\\"ipfs\\\"},\\\"optimizer\\\":{\\\"enabled\\\":true,\\\"runs\\\":200},\\\"remappings\\\":[\\\":forge-std/=lib/forge-std/src/\\\"]},\\\"sources\\\":{\\\"src/Counter.sol\\\":{\\\"keccak256\\\":\\\"0xf45df1ccab4c7ce7c7c8b529079d4e3c914cf09350e26d1b2a168e89792c9124\\\",\\\"license\\\":\\\"UNLICENSED\\\",\\\"urls\\\":[\\\"bzz-raw://7f911aefa13cda2bf5eadfb5cc672af7dceee749563afb27e237f225a221cb4a\\\",\\\"dweb:/ipfs/QmdUkxZJkFgz8oC9ARaKhRJNVRGvyGnC8TFNCvh7ejc5k2\\\"]}},\\\"version\\\":1}\",\r\n  \"metadata\": {\r\n    \"compiler\": { \"version\": \"0.8.25+commit.b61c2a91\" },\r\n    \"language\": \"Solidity\",\r\n    \"output\": {\r\n      \"abi\": [\r\n        {\r\n          \"inputs\": [],\r\n          \"stateMutability\": \"nonpayable\",\r\n          \"type\": \"function\",\r\n          \"name\": \"decrement\"\r\n        },\r\n        {\r\n          \"inputs\": [],\r\n          \"stateMutability\": \"nonpayable\",\r\n          \"type\": \"function\",\r\n          \"name\": \"increment\"\r\n        },\r\n        {\r\n          \"inputs\": [],\r\n          \"stateMutability\": \"view\",\r\n          \"type\": \"function\",\r\n          \"name\": \"number\",\r\n          \"outputs\": [\r\n            { \"internalType\": \"uint256\", \"name\": \"\", \"type\": \"uint256\" }\r\n          ]\r\n        },\r\n        {\r\n          \"inputs\": [\r\n            {\r\n              \"internalType\": \"uint256\",\r\n              \"name\": \"newNumber\",\r\n              \"type\": \"uint256\"\r\n            }\r\n          ],\r\n          \"stateMutability\": \"nonpayable\",\r\n          \"type\": \"function\",\r\n          \"name\": \"setNumber\"\r\n        }\r\n      ],\r\n      \"devdoc\": { \"kind\": \"dev\", \"methods\": {}, \"version\": 1 },\r\n      \"userdoc\": { \"kind\": \"user\", \"methods\": {}, \"version\": 1 }\r\n    },\r\n    \"settings\": {\r\n      \"remappings\": [\"forge-std/=lib/forge-std/src/\"],\r\n      \"optimizer\": { \"enabled\": true, \"runs\": 200 },\r\n      \"metadata\": { \"bytecodeHash\": \"ipfs\" },\r\n      \"compilationTarget\": { \"src/Counter.sol\": \"Counter\" },\r\n      \"evmVersion\": \"paris\",\r\n      \"libraries\": {}\r\n    },\r\n    \"sources\": {\r\n      \"src/Counter.sol\": {\r\n        \"keccak256\": \"0xf45df1ccab4c7ce7c7c8b529079d4e3c914cf09350e26d1b2a168e89792c9124\",\r\n        \"urls\": [\r\n          \"bzz-raw://7f911aefa13cda2bf5eadfb5cc672af7dceee749563afb27e237f225a221cb4a\",\r\n          \"dweb:/ipfs/QmdUkxZJkFgz8oC9ARaKhRJNVRGvyGnC8TFNCvh7ejc5k2\"\r\n        ],\r\n        \"license\": \"UNLICENSED\"\r\n      }\r\n    },\r\n    \"version\": 1\r\n  },\r\n  \"id\": 25\r\n}\r\n\r\n```\r\n使用viem与合约交互需要用到abi以及bytecode。\r\n\r\n在`viem-scripts`项目文件夹中，新建一个`abi.ts`文件，声明并暴露两个变量`abi`和`address`。`abi`这个变量的内容来自`Counter.json`,`address`就是sepolia上部署的`Counter`合约地址。注意，下面代码中第30行，需要添加`as const`关键词，这样viem可以智能的读取里面的function信息。\r\n```javascript\r\nexport const abi = [\r\n  {\r\n    type: \"function\",\r\n    name: \"decrement\",\r\n    inputs: [],\r\n    outputs: [],\r\n    stateMutability: \"nonpayable\",\r\n  },\r\n  {\r\n    type: \"function\",\r\n    name: \"increment\",\r\n    inputs: [],\r\n    outputs: [],\r\n    stateMutability: \"nonpayable\",\r\n  },\r\n  {\r\n    type: \"function\",\r\n    name: \"number\",\r\n    inputs: [],\r\n    outputs: [{ name: \"\", type: \"uint256\", internalType: \"uint256\" }],\r\n    stateMutability: \"view\",\r\n  },\r\n  {\r\n    type: \"function\",\r\n    name: \"setNumber\",\r\n    inputs: [{ name: \"newNumber\", type: \"uint256\", internalType: \"uint256\" }],\r\n    outputs: [],\r\n    stateMutability: \"nonpayable\",\r\n  },\r\n] as const;\r\n\r\nexport const address = \"0x6b565dE192A1Be17a4F077B5Fda6b3A100498790\" as const;\r\n\r\n```\r\n接下来需要导入私钥，在项目根文件夹下新建`.env`文件，添加私钥。下面的私钥是示例，假数据。注意，私钥很重要，不要轻易外泄。\r\n```shell\r\nPRIVATE_KEY=45210d79205254d4505912eb32371f7f2f0b059ed771898554f0d0f169c87e45\r\n```\r\n同时，记得将`.env`文件添加到`.gitignore`文件中，确保不要将此文件上传至github或其他代码托管平台上。\r\n```shell\r\n.env\r\nnode_modules/\r\n```\r\n在`index.ts`中配置`dotenv`，导入`.env`中的私钥变量。调用`privateKeyToAccount`函数，传入私钥。\r\n```javascript\r\nimport { createPublicClient, http } from \"viem\";\r\nimport { sepolia } from \"viem/chains\";\r\nimport { privateKeyToAccount } from \"viem/accounts\";\r\nimport dotenv from \"dotenv\";\r\n\r\ndotenv.config();\r\n\r\nconst account = privateKeyToAccount(`0x${process.env.PRIVATE_KEY}`);\r\n\r\n```\r\n使用本地私钥的方式与合约做交互需要使用viem的Wallet Client。导入`createWalletClient`方法，并创建一个`walletClient`。注意，`http`方法接受自定义rpc节点链接，如果你有自己的rpc可以传入其中，否则viem将会使用公共节点，有时候公共节点会比较不稳定，同时也有速度限制。\r\n```javascript\r\nimport { createPublicClient, createWalletClient, http } from \"viem\";\r\nimport { sepolia } from \"viem/chains\";\r\nimport { privateKeyToAccount } from \"viem/accounts\";\r\nimport dotenv from \"dotenv\";\r\n\r\ndotenv.config();\r\n\r\nconst account = privateKeyToAccount(`0x${process.env.PRIVATE_KEY}`);\r\n\r\nconst rpc = process.env.ETH_RPC_URL;\r\n\r\nconst walletClient = createWalletClient({\r\n  account,\r\n  chain: sepolia,\r\n  transport: http(rpc),\r\n});\r\n```\r\n接下来实现与合约交互，首先实现读合约的操作。导入`abi`和`address`，使用`readContract`方法，传入`address`，`abi`和`functionName`。注意，读取操作使用的client是Public Client而不是Wallet Client。\r\n```javascript\r\nimport { abi, address } from \"./abi\";\r\n\r\nasync function main() {\r\n  // const blockNumber = await client.getBlockNumber();\r\n  // console.log(blockNumber);\r\n\r\n  const number = await client.readContract({\r\n    address,\r\n    abi,\r\n    functionName: \"number\",\r\n  });\r\n\r\n  console.log(number);\r\n}\r\n\r\nmain();\r\n```\r\n完整的代码如下。\r\n```javascript\r\nimport { createPublicClient, createWalletClient, http } from \"viem\";\r\nimport { sepolia } from \"viem/chains\";\r\nimport { privateKeyToAccount } from \"viem/accounts\";\r\nimport { abi, address } from \"./abi\";\r\nimport dotenv from \"dotenv\";\r\n\r\ndotenv.config();\r\n\r\nconst account = privateKeyToAccount(`0x${process.env.PRIVATE_KEY}`);\r\n\r\nconst rpc = process.env.ETH_RPC_URL;\r\n\r\nconst walletClient = createWalletClient({\r\n  account,\r\n  chain: sepolia,\r\n  transport: http(rpc),\r\n});\r\n\r\nconst client = createPublicClient({\r\n  chain: sepolia,\r\n  transport: http(rpc),\r\n});\r\n\r\nasync function main() {\r\n  // const blockNumber = await client.getBlockNumber();\r\n  // console.log(blockNumber);\r\n\r\n  const number = await client.readContract({\r\n    address,\r\n    abi,\r\n    functionName: \"number\",\r\n  });\r\n\r\n  console.log(number);\r\n}\r\n\r\nmain();\r\n\r\n```\r\n运行`main`函数。会得到下面的结果。可以看到输出了101。证明已经成功的从链上读取了函数的返回值。\r\n```shell\r\n> viem-scripts@1.0.0 start /viem-playground/viem-scripts\r\n> ts-node index.ts\r\n\r\n101n\r\n```\r\n执行写操作就需要使用Wallet Client。改造一下`main`函数。首先，把获取`number`的函数单独抽取出来。调用`walletClient`的`writeContract`方法，注意`args`，这个字段接收一个数组，里面就是调用合约函数时需要传入的参数，传入number型变量时需要先转化为BigInt类型。\r\n调用`writeContract`函数后会返回一个哈希值，这个哈希值可以作为`waitForTransactionReceipt`的参数。当对合约进行写操作时，可以看作是进行了一笔Transaction，对于以太坊来说，会在单位时间内打包多笔交易并生成一个新的区块。执行`waitForTransactionReceipt`会返回一个`receipt`对象，可以获取这次交易的信息。\r\n```javascript\r\nasync function main() {\r\n  // const blockNumber = await client.getBlockNumber();\r\n  // console.log(blockNumber);\r\n getNumber();\r\n\r\n  const hash = await walletClient.writeContract({\r\n    address,\r\n    abi,\r\n    functionName: \"setNumber\",\r\n    args: [BigInt(100)],\r\n  });\r\n\r\n  console.log(\"The hash is:\", hash);\r\n\r\n  const receipt = await client.waitForTransactionReceipt({ hash });\r\n\r\n  console.log(\"receipt info:\", receipt);\r\n\r\n  receipt && getNumber();\r\n}\r\n\r\nasync function getNumber() {\r\n  const number = await client.readContract({\r\n    address,\r\n    abi,\r\n    functionName: \"number\",\r\n  });\r\n\r\n  console.log(\"The number is:\", number);\r\n}\r\n\r\nmain();\r\n```\r\n执行`main`函数，可以依次看到如下信息。可以看到`number`从101变成了100。\r\n```powershell\r\n> viem-scripts@1.0.0 start /web3/viem-playground/viem-scripts\r\n> ts-node index.ts\r\n\r\nThe number is: 101n\r\nThe hash is: 0x96c55da3ef7b9b0ef209d7329cd74a4fb1b1c493d8efad55814a156d867f96a6\r\nreceipt info: {\r\n  type: 'eip1559',\r\n  from: '0xa0466a82b961e85077d4a8debc35fbf6cf18d464',\r\n  to: '0x6b565de192a1be17a4f077b5fda6b3a100498790',\r\n  status: 'success',\r\n  cumulativeGasUsed: 20730581n,\r\n  logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',\r\n  logs: [],\r\n  transactionHash: '0x96c55da3ef7b9b0ef209d7329cd74a4fb1b1c493d8efad55814a156d867f96a6',\r\n  contractAddress: null,\r\n  gasUsed: 26416n,\r\n  blockHash: '0xce27361d3088232abd4f63fc3f5aaf9c63c7bd38f3f54f3df08dbe26f8fe6147',\r\n  blockNumber: 6167597n,\r\n  transactionIndex: 136,\r\n  effectiveGasPrice: 1964520099n\r\n}\r\nThe number is: 100n\r\n```\r\n去sepolia的区块链浏览器中也能看到每一次调用成功后的Transaction Hash。\r\n\r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/06/0uElx3GU66794fdf37d17.png)\r\n同样，在区块链浏览器中查询也能看到`number`更新了。\r\n\r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/06/dvbHp7rz66794fca15c00.png)\r\n至此，使用viem与合约的交互工作已经完成。viem的更多功能请参考[官方文档](https://viem.sh/docs/clients/intro)。"},"author":{"user":"https://learnblockchain.cn/people/19477","address":"0xa0466a82b961e85077d4a8debc35fbf6cf18d464"},"history":"bafkreiaprc5oywnlx7xpfgnynlclyon6pgvqnnjcsal6zg6tcojvvh75sy","timestamp":1719226518,"version":1}