{"content":{"title":"ethers.js分析","body":"ethers.js使用TypeScript写的框架, 用来访问eth网络。\r\n\r\n# 连接provider\r\n支持多种方式连接eth node\r\nJSON-RPC, INFURA, Etherscan, Alchemy, Ankr or MetaMask\r\n```\r\n// JsonRpcProvider实现了Provider接口, Provider中包含一些read方法 e.g. getBalance, getBlockNumber, getBlock, 也包含call方法调用\r\n// Provider继承自ContractRunner、EventEmitterable、NameResolver\r\n// ContractRunner是合约运行的基础, 包含了estimateGas估算gas、call、sendTransaction, 注意provider未实现sendTransaction该方法需要私钥签名才能执行\r\nconst provider = new ethers.JsonRpcProvider(\"http://127.0.0.1:8545\");\r\n\r\n// 获取区块高度\r\nconst blockNum = await provider.getBlockNumber()\r\n```\r\n\r\n# 合约操作\r\n\r\n## 创建合约实例\r\n```\r\n    const abiERC20 = [\r\n        \"function name() view returns (string)\",\r\n        \"function symbol() view returns (string)\",\r\n        \"function totalSupply() view returns (uint256)\",\r\n        \"function balanceOf(address) view returns (uint)\",\r\n    ];\r\n    const provider = new ethers.JsonRpcProvider(\"https://goerli.infura.io/v3/52e90a56caa54252808c7d6c3add8a0f\");\r\n    const baseWallet = ethers.HDNodeWallet.createRandom();\r\n    // 绑定wallet和provider(可以发送交易)\r\n    const wallet = baseWallet.connect(provider);\r\n    // 合约地址\r\n    const addressDAI = '0x6B175474E89094C44Da98b954EedeAC495271d0F';\r\n    // 创建合约实例\r\n    const contractDAI = new ethers.Contract(addressDAI, abiERC20, wallet);\r\n    // 调用合约方法\r\n    const totalSupply = await contractDAI.totalSupply();\r\n```\r\n\r\n## interface编码\r\n可以使用Interface加载abi结构, 然后编码function, topics, eventlog等\r\n```\r\n    const abiERC20 = [\r\n        \"function name() view returns (string)\",\r\n        \"function symbol() view returns (string)\",\r\n        \"function totalSupply() view returns (uint256)\",\r\n        \"function balanceOf(address) view returns (uint)\",\r\n        \"event Transfer(address indexed from, address indexed to, uint256 value)\",\r\n    ];\r\n    const abi = new ethers.Interface(abiERC20);\r\n    const functionData = abi.encodeFunctionData('balanceOf',['0x6B175474E89094C44Da98b954EedeAC495271d0F']);\r\n    const filterTopics = abi.encodeFilterTopics('Transfer',['0x6B175474E89094C44Da98b954EedeAC495271d0F','0x6B175474E89094C44Da98b954EedeAC495271d0F'])\r\n    const eventLog = abi.encodeEventLog('Transfer',['0x6B175474E89094C44Da98b954EedeAC495271d0F','0x6B175474E89094C44Da98b954EedeAC495271d0F', 123])\r\n```\r\n\r\n## 发送tx\r\n```\r\n    const provider = new ethers.JsonRpcProvider(\"https://goerli.infura.io/v3/52e90a56caa54252808c7d6c3add8a0f\");\r\n    const baseWallet = ethers.HDNodeWallet.createRandom();\r\n    // 绑定wallet和provider(可以发送交易)\r\n    const wallet = baseWallet.connect(provider);\r\n    const tx = {\r\n        to: xxx,\r\n        data: functionData  // 上面编码出来的data\r\n    }\r\n    const response = await wallet.sendTransaction(tx);\r\n```\r\n\r\n## 发送call\r\n```\r\n    const provider = new ethers.JsonRpcProvider(\"https://goerli.infura.io/v3/52e90a56caa54252808c7d6c3add8a0f\");\r\n    const baseWallet = ethers.HDNodeWallet.createRandom();\r\n    // 绑定wallet和provider(可以发送交易)\r\n    const wallet = baseWallet.connect(provider);\r\n    const tx = {\r\n        to: xxx,\r\n        data: functionData  // 上面编码出来的data\r\n    }\r\n    // 如果不想真实发送交易到网络中可以使用call调用, 只在目标机器执行并返回结果\r\n    const response = await wallet.call(tx);\r\n```\r\n\r\n# wallet操作\r\n\r\n## SigningKey\r\n使用SigningKey进行私钥签名、验证签名、生成公钥、地址. 数据转换过程privateKey -> publicKey -> address.\r\n```\r\nconst { ethers,SigningKey } = require(\"ethers\");\r\n\r\n// 生成私钥\r\nconst privateKey = \"0x0123456789012345678901234567890123456789012345678901234567890123\";\r\n\r\n// 创建一个 ethers.js 的签名器对象\r\nconst signingKey = new ethers.SigningKey(privateKey);\r\n\r\n// 将消息进行哈希运算\r\nconst message = \"Hello, world!\";\r\nconst messageHash = ethers.keccak256(ethers.toUtf8Bytes(message));\r\n\r\n// 生成 r s v\r\nconst signature = signingKey.sign(messageHash);\r\nconst { r, s, v } = ethers.Signature.from(signature);\r\nconsole.log(\"r: \" + r);\r\nconsole.log(\"s: \" + s);\r\nconsole.log(\"v: \" + v);\r\n\r\n// public key验证\r\nconst recoverPublicKey = SigningKey.recoverPublicKey(messageHash, signature);\r\nconsole.log(`public key 验证:${recoverPublicKey === signingKey.publicKey}`);\r\n\r\n// address验证\r\nconst address = ethers.computeAddress(signingKey.publicKey);\r\nconst recoveredAddress = ethers.recoverAddress(messageHash, signature);\r\nconsole.log(`address 验证:${address === recoveredAddress}`);\r\n\r\n```\r\n上述代码中的SigningKey主要是管理私钥、公钥、以及数字签名的. Wallet包含SigningKey 除了上述功能还管理账户地址(通过publicKey生成), 查看余额, 发送交易等功能.  \r\n\r\n## 使用Mnemonic助记词生成HDWallet过程  \r\n1.生成Mnemonic  \r\n1).生成随机种子Entropy  \r\n2).根据种子+wordList生成助记词(种子相当于index wordList相当于array), 可选password 在用Mnemonic生成seed时会用到.  \r\n3).最后组装成Mnemonic  \r\n  \r\n2.生成HDNodeWallet  \r\n1).使用Mnemonic生成seed Mnemonic.computedSeed(使用PBKDF2函数和HMAC-SHA512算法,把助记词转换成一个种子seed)  \r\n2).使用种子生成HDWallet确定性钱包, 在这个钱包中可以根据路径派生出多个地址(每个地址都是独立的私钥)  \r\n使用ethers.js v6版本代码例子如下:  \r\n```\r\nconst { ethers,wordlists } = require(\"ethers\");\r\n\r\nconst mnemonic = ethers.Mnemonic.fromEntropy(\"0xd4e409e44611be1a7e93a0c2ade7a131\",\"123\", wordlists.zh_cn)\r\nconst seed = mnemonic.computeSeed();\r\n// path:'m'\r\nlet hdNodeWallet = ethers.HDNodeWallet.fromSeed(seed)\r\n\r\nlet basePath = \"m/44'/60'/0'/0\";\r\nlet wallet = hdNodeWallet.derivePath(basePath+'/0');\r\nconsole.log(wallet)\r\n\r\nwallet = hdNodeWallet.derivePath(basePath+'/1');\r\nconsole.log(wallet)\r\n\r\nwallet = hdNodeWallet.derivePath(basePath+'/2');\r\nconsole.log(wallet)\r\n```\r\n\r\n# 查询区块数据\r\n可以查询合约中的event事件, \r\n## 过滤事件\r\n使用provider过滤\r\n```\r\n    const logs = await provider.getLogs({\r\n        fromBlock: 8876717,\r\n        toBlock: 8876813,\r\n        address: '0x39c72916169b0d9eb22610a90f612d09302aa234',  // 合约地址\r\n        topics: ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef']  // topics编码信息\r\n    });\r\n```\r\n使用合约过滤查询\r\n```\r\n    const events = await erc20Contract.queryFilter('Transfer', fromBlock, toBlock);\r\n```\r\n## 监听器\r\n使用provider监听 block, pending, transaction, event, orphan等\r\n```\r\n     case \"block\": case \"pending\": case \"debug\": case \"network\": {return { type: _event, tag: _event }\r\n    { type: \"transaction\", tag: getTag(\"tx\", { hash }), hash }\r\n    { type: \"orphan\", tag: getTag(\"orphan\", event), filter: copy(event) }\r\n    { filter, tag: getTag(\"event\", filter), type: \"event\" }\r\n```\r\n代码\r\n```\r\n    provider.on('pending',async (txHash)=>{\r\n        \r\n    });\r\n```\r\n使用contract监听事件\r\n```\r\n    await erc20Contract.on('Transfer', (from, to, amount) => {\r\n        console.log(`from:${from} to:${to} amount:${amount}`);\r\n    });\r\n```"},"author":{"user":"https://learnblockchain.cn/people/11053","address":null},"history":"QmTsq4sHWcQtj7DCxATuLsB1N2W4EwEbPTHSQmPtZLRoZ4","timestamp":1685605532,"version":1}