{"content":{"title":"一文解决 web3 合约 ethers 交互基础","body":"文章参考：[https://wtf.academy/ether-start/ReadContract/](https://wtf.academy/ether-start/ReadContract/)\r\n# 一、查询以太坊主网某地址的余额\r\n\r\n安装好 erthers 后，通过 import 对其引入：\r\n\r\n```typescript\r\nimport { ethers } from \"ethers\";\r\n```\r\n\r\n引入完毕后，可以 get 一个 Provider 对以太坊网络进行只读访问；通过 ethers 获取 Provider 对象：\r\n\r\n```typescript\r\n const provider = ethers.getDefaultProvider();\r\n```\r\n\r\n获得了对应的 provider 之后，接下来可以使用 async await 语法糖创建一个异步函数：\r\n\r\n```typescript\r\nconst main=async()=>{\r\n    \r\n}\r\n```\r\n\r\n接着可以在函数中通过 provider 调用 getBalance 方法去获取某个地址的余额：\r\n\r\n```typescript\r\n//查询某个地址的余额\r\nconst balance = await provider.getBalance(`0xeB869f9835006A829120cC62d995570997e6322F`);\r\n```\r\n\t\r\n由于这个 balance 是一个 bigNumber 对象，我们需要通过 ethers 提供的 formatEther 方法将其转化为 eth 单位才可以获得准确的 eth 单位的数值，此时若你直接输出将会显示如下：\r\n\r\n ![在这里插入图片描述](https://img-blog.csdnimg.cn/1e91e4dcc9c64b5c86ba9ebf49da8a2c.png)\r\n此时代码应为：\r\n\r\n```typescript\r\nconsole.log(`0xcEb1C2fB4198E865B33d9cEaDc7d3bbEcD759aB3: ` + ethers.utils.formatEther(balance));\r\n```\r\n\r\n完整代码如下：\r\n\r\n```typescript\r\nimport { ethers } from \"ethers\";\r\nconst provider = ethers.getDefaultProvider();\r\nconst main = async () => {\r\n    const balance = await provider.getBalance(\"0xcEb1C2fB4198E865B33d9cEaDc7d3bbEcD759aB3\");\r\n    console.log(`0xcEb1C2fB4198E865B33d9cEaDc7d3bbEcD759aB3: ` + ethers.utils.formatEther(balance));\r\n}\r\nmain()\r\n```\r\n输出结果为：\r\n\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/d1471faaf35c48b7a9dd830948f9f13a.png)\r\n\r\n# 二、指定测试网络的查询余额\r\n\r\n在正式发布主网合约前我们一般是在测试网络上部署对应的合约，有时候需要查询测试网络的数据，那么就需要使用 providers 下的 JsonRpcProvider 指定网络，代码如下：\r\n\r\n```typescript\r\nconst providerLocal = new ethers.providers.JsonRpcProvider(`http://127.0.0.1:7545`)\r\n```\r\n这个地址是我本地的测试网络，我是使用 Ganache 搭建的本地网络：\r\n\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/56e630e35c9e48dbbd67340471ed4b1e.png)\r\n\r\n此时的地址是 `HTTP://127.0.0.1:7545`，我们指定后即可链接到本地测试网。\r\n\r\n此时我们复制其中一个地址对其进行查询余额：\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/23a13fb1b2304fceb4732ce77eb7b42f.png)\r\n修改第一点中的代码中查询的地址以及 provider 对象：\r\n\r\n```typescript\r\nconst balance = await providerLocal.getBalance(\"0x4c63Fd8500Dc73dd72cB999c5Cf305F2c846b2f8\");\r\n```\r\n\r\n此时完整代码如下：\r\n\r\n```typescript\r\nimport { ethers } from \"ethers\";\r\nconst providerLocal = new ethers.providers.JsonRpcProvider(`http://127.0.0.1:7545`)\r\nconst main = async () => {\r\n    const balance = await providerLocal.getBalance(\"0x4c63Fd8500Dc73dd72cB999c5Cf305F2c846b2f8\");\r\n    console.log(`0x4c63Fd8500Dc73dd72cB999c5Cf305F2c846b2f8: ` + ethers.utils.formatEther(balance));\r\n}\r\nmain()\r\n```\r\n\r\n结果如下：\r\n\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/f3b1f9d1edb448ca89942a541a990571.png)\r\n# 三、查询链接到的网络地址\r\n\r\n我们可以通过 provider 提供的 getNetwork 方法查询对应的provider 所链接的网络，在此我们在一个示例中同时链接本地网络以及主网，直接通过对应的 provider 调用 getNetwork 方法可以得到网络标识：\r\n\r\n```typescript\r\nimport { ethers } from \"ethers\";\r\nconst providerLocal = new ethers.providers.JsonRpcProvider(`http://127.0.0.1:7545`)\r\nconst provider = ethers.getDefaultProvider();\r\nconst main = async () => {\r\n    const balance = await providerLocal.getBalance(\"0x4c63Fd8500Dc73dd72cB999c5Cf305F2c846b2f8\");\r\n    console.log(`0x4c63Fd8500Dc73dd72cB999c5Cf305F2c846b2f8: ` + ethers.utils.formatEther(balance));\r\n    \r\n    const networkLocal = await providerLocal.getNetwork();//getNetwork\r\n    const networkMain = await provider.getNetwork();//getNetwork\r\n    console.log(\"network:\", networkLocal);\r\n    console.log(\"networkMain:\", networkMain);\r\n}\r\nmain()\r\n```\r\n\r\n此时结果如下：\r\n\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/a697d0056add4e20ae8edd5040193ce3.png)\r\n\r\n\r\n此时可以看到对应的网络。\r\n\r\n# 四、查询区块高度、gas、建议gas、区块、字节码\r\n这些查询都是通过 provider 查询的，provider 可查询在区块网络中的只读信息，查询标题所述的内容只需要调用以下方法：\r\n\r\n - getBlockNumber 区块高度 \r\n - getGasPrice gas价 \r\n - getFeeData 建议gas等信息\r\n -  getBlock 某个区块信息 \r\n - getCode 某个合约的字节码\r\n\r\n具体使用如下：\r\n\r\n```typescript\r\nconst blockNumber = await provider.getBlockNumber();\r\nconst gasPrice = await provider.getGasPrice();\r\nconst feeData = await provider.getFeeData();\r\nconst block = await provider.getBlock([某个区块号]);\r\nconst code = await provider.getCode([合约地址]]);\r\n```\r\n\r\n在这里需要注意 gasPrice 是一个 bigNumber 需要使用 erthers 中的 format 对其格式化：\r\n\r\n```typescript\r\nconsole.log(\"gas 价:\", ethers.utils.formatUnits(gasPrice));\r\n```\r\n\r\n而 feeData 是一个对象数组：\r\n\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/7dd17671d35f4828b722408ccfa820b0.png)\r\n可以使用不同的值，例如：\r\n\r\n```typescript\r\nconsole.log(\"建议 gas 价:\", ethers.utils.formatUnits(feeData.gasPrice));\r\n```\r\n\r\n# 五、合约操作\r\n在使用合约中的方法时，有不消耗 gas 的只读方法，也有修改状态变量需要 gas 的方法，那么在 ethers 中由于两者的特性不同，分为了只读合约和可读写合约，只读合约不需要钱包对其进行操作，可读写合约需要钱包进行操作。\r\n## 5.1 只读合约 \r\n只读合约是创建一个对合约操作的对象后，只能对合约的只读方法进行调用。\r\n\r\n首先指定链接的节点：\r\n\r\n```typescript\r\nimport { ethers } from \"ethers\";\r\nconst providerLocal = new ethers.providers.JsonRpcProvider(`http://127.0.0.1:7545`);\r\n```\r\n\r\n接着需要我们获取合约的 abi：\r\n\r\n```typescript\r\nconst ContractAbi = '[{\"inputs\": [{\"internalType\": \"address\",\"name\": \"_owner\",\"type\": \"address\"}],\"name\": \"setOwner\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"constructor\"},{\"inputs\": [],\"name\": \"getOwner\",\"outputs\": [{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"}],\"stateMutability\": \"view\",\"type\": \"function\"}]';\r\n```\r\n\r\n合约的 abi 若你是 remix 进行部署的，可以在这里找到：\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/a0d91f69b99845c491da01e1b4342c63.png)\r\n有了 abi 后我们还需要你部署后的合约的地址：\r\n\r\n```typescript\r\nconst ContractAddress = '0x4562645a0d04a536000d5E10a494C6d07Be1e314';\r\n```\r\n\r\n最后我们使用 ethers 的 Contract 创建合约对象，需要传入合约地址、abi 以及 provider：\r\n\r\n```typescript\r\nconst _Contract = new ethers.Contract(ContractAddress, ContractAbi, providerLocal);\r\n```\r\n此时我们就拥有了一个 Contract 对象，可以使用这个 Contract 对象对里面的方法进行调用了：\r\n\r\n```typescript\r\nconst ownerVal = await _Contract.getOwner();\r\nconsole.log(\"ownerVal:\", ownerVal);\r\n```\r\n\r\n完整代码如下：\r\n\r\n```typescript\r\nimport { ethers } from \"ethers\";\r\nconst providerLocal = new ethers.providers.JsonRpcProvider(`http://127.0.0.1:7545`);\r\n//创建合约对象\r\nconst ContractAbi = '[{\"inputs\": [{\"internalType\": \"address\",\"name\": \"_owner\",\"type\": \"address\"}],\"name\": \"setOwner\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"constructor\"},{\"inputs\": [],\"name\": \"getOwner\",\"outputs\": [{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"}],\"stateMutability\": \"view\",\"type\": \"function\"}]';\r\nconst ContractAddress = '0x4562645a0d04a536000d5E10a494C6d07Be1e314';\r\nconst _Contract = new ethers.Contract(ContractAddress, ContractAbi, providerLocal);\r\n\r\nconst main = async () => {\r\n    const ownerVal = await _Contract.getOwner();\r\n    console.log(\"ownerVal:\", ownerVal);\r\n}\r\nmain()\r\n```\r\n\r\n## 5.2 友好的 abi\r\n在上一点中，我们使用的 abi 并不是非常友好，咱们编写 abi 还有一种比较简单的 函数签名的方式 编写abi：\r\n\r\n```typescript\r\nconst ContractAbi = [\r\n    \"function getOwner()view public returns(address)\",\r\n];\r\n```\r\n其实简单点来说你就把函数签名进行复制过来就ok了，由于我们只是一个只读合约，复制一个只读方法即可，这样也可以完成上一点的内容。\r\n\r\n此时运行代码后：\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/f7adbfa6499c41208164d4f47dac6d59.png)\r\n\r\n\r\n## 5.3 可写合约\r\n进行可写合约时，我们需要某一个账户去支付消耗的 gas，那么此时需要创建一个账户。\r\n\r\n创建一个账户可以使用私钥进行创建，此时我们使用 ganache 打开后随便选择一个账户的私钥信息复制：\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/3da0065f1bb5458cb90d985ad6f365e9.png)\r\n随后在接着上一点只读合约代码中进行更改，添加以下代码记录私钥内容：\r\n\r\n```typescript\r\n//私钥\r\nconst privateKey = 'bde959799bd8bd8e3d27686087a53b121276f2ea8edff5f213ef799adff9ffee'\r\n```\r\n\r\n随后使用 eth 的 Wallet 方法创建钱包对象，需要传入一个私钥以及一个对应的 provider：\r\n\r\n```typescript\r\nconst wallet = new ethers.Wallet(privateKey, providerLocal)\r\n```\r\n\r\n接着我们把原来创建只读合约时指定的 provider 替换成 钱包对象：\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/0996a1de679d4fa793a66556815b1ad6.png)\r\n\r\n此时即可创建一个读写合约，不过此时我们还需要更改 ABI 内容，毕竟现在的 ABI 只有一个只读方法：\r\n\r\n```typescript\r\nconst ContractAbi = [\r\n    \"function getOwner()view public returns(address)\",\r\n    \"function setOwner(address _owner) public\"\r\n];\r\n```\r\n接着我们可以调用 setOwner 设置 owner 的值：\r\n\r\n```typescript\r\nawait _Contract.setOwner(\"0x503063dD8f114059B09FD5bC953E71fc14a1d672\");\r\n```\r\n\r\n完整代码如下：\r\n\r\n```typescript\r\nimport { ethers } from \"ethers\";\r\nconst providerLocal = new ethers.providers.JsonRpcProvider(`http://127.0.0.1:7545`);\r\n//私钥\r\nconst privateKey = 'bde959799bd8bd8e3d27686087a53b121276f2ea8edff5f213ef799adff9ffee'\r\nconst wallet = new ethers.Wallet(privateKey, providerLocal)\r\n//读写合约\r\nconst ContractAddress = '0x4562645a0d04a536000d5E10a494C6d07Be1e314';\r\nconst ContractAbi = [\r\n    \"function getOwner()view public returns(address)\",\r\n    \"function setOwner(address _owner) public\"\r\n];\r\nconst _Contract = new ethers.Contract(ContractAddress, ContractAbi, wallet);\r\n\r\nconst main = async () => {\r\n    //显示原本的 owner\r\n    let ownerVal = await _Contract.getOwner();\r\n    console.log(\"原本的 owner:\", ownerVal);\r\n    //调用 setOwner 更改 owner\r\n    await _Contract.setOwner(\"0x503063dD8f114059B09FD5bC953E71fc14a1d672\");\r\n    //更改后的 owner\r\n    ownerVal = await _Contract.getOwner();\r\n    console.log(\"更改后的 owner:\", ownerVal);\r\n}\r\nmain()\r\n```\r\n\r\n最后结果如下：\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/6154ddf56ec642d38bc2594fd4e5e2b4.png)\r\n\r\n## 5.4 合约部署\r\n合约部署主要是通过合约工厂方法 ContractFactory 对合约进行部署，需要传入部署合约的 ABI、字节码、以及一个钱包对象（毕竟需要花钱）。\r\n\r\n既然已经知道了合约部署的主要方法，那么接下来操作就简单了，首先指定 provider：\r\n\r\n```typescript\r\nimport { ethers } from \"ethers\";\r\nconst providerLocal = new ethers.providers.JsonRpcProvider(`http://127.0.0.1:7545`);\r\n```\r\n\r\n接着创建钱包：\r\n\r\n```typescript\r\n//创建钱包\r\nconst privateKey = 'bde959799bd8bd8e3d27686087a53b121276f2ea8edff5f213ef799adff9ffee';\r\nconst wallet = new ethers.Wallet(privateKey, providerLocal);\r\n```\r\n\r\n得到 ABI 和字节码：\r\n\r\n```typescript\r\n//若构造函数有参数那么需要在 abi 中加上构造函数\r\nconst abi = [\r\n    \"function getOwner()view public returns(address)\",\r\n    \"function setOwner(address _owner) public\"\r\n];\r\n//字节码\r\nconst bytecode = '608060405234801561001057600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506101d1806100606000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806313af40351461003b578063893d20e814610057575b600080fd5b61005560048036038101906100509190610144565b610075565b005b61005f6100b8565b60405161006c9190610180565b60405180910390f35b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610111826100e6565b9050919050565b61012181610106565b811461012c57600080fd5b50565b60008135905061013e81610118565b92915050565b60006020828403121561015a576101596100e1565b5b60006101688482850161012f565b91505092915050565b61017a81610106565b82525050565b60006020820190506101956000830184610171565b9291505056fea2646970667358221220486a69de337d6a3ede1bd3652b7f28e79df06d6e07dfc307e656a54ff44c42f464736f6c63430008110033';\r\n```\r\n\r\n字节码在这里找：\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/eff9c3f76b5245518c5ffe360744f5e8.png)\r\n接着直接调用工厂合约：\r\n\r\n```typescript\r\n//工厂合约部署\r\nconst _Contract = await new ethers.ContractFactory(abi, bytecode, wallet).deploy();\r\n```\r\ndeploy() 方法表示部署，之后就等链上确认，部署完毕：\r\n```typescript\r\nawait _Contract.deployed();//等待合约部署完毕\r\n```\r\n可以查看当前合约的地址和一些部署详情：\r\n\r\n```typescript\r\nconsole.log(\"合约地址：\", _Contract.address);\r\nconsole.log(\"部署合约后的交易详情：\", _Contract.deployTransaction);\r\n```\r\n\r\n最后还可以调用以下方法，完整代码如下：\r\n\r\n```typescript\r\nimport { ethers } from \"ethers\";\r\nconst providerLocal = new ethers.providers.JsonRpcProvider(`http://127.0.0.1:7545`);\r\n\r\n//创建钱包\r\nconst privateKey = 'bde959799bd8bd8e3d27686087a53b121276f2ea8edff5f213ef799adff9ffee';\r\nconst wallet = new ethers.Wallet(privateKey, providerLocal);\r\n//若构造函数有参数那么需要在 abi 中加上构造函数\r\nconst abi = [\r\n    \"function getOwner()view public returns(address)\",\r\n    \"function setOwner(address _owner) public\"\r\n];\r\n//字节码\r\nconst bytecode = '608060405234801561001057600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506101d1806100606000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806313af40351461003b578063893d20e814610057575b600080fd5b61005560048036038101906100509190610144565b610075565b005b61005f6100b8565b60405161006c9190610180565b60405180910390f35b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610111826100e6565b9050919050565b61012181610106565b811461012c57600080fd5b50565b60008135905061013e81610118565b92915050565b60006020828403121561015a576101596100e1565b5b60006101688482850161012f565b91505092915050565b61017a81610106565b82525050565b60006020820190506101956000830184610171565b9291505056fea2646970667358221220486a69de337d6a3ede1bd3652b7f28e79df06d6e07dfc307e656a54ff44c42f464736f6c63430008110033';\r\n\r\nconst main = async () => {\r\n    //工厂合约部署\r\n    const _Contract = await new ethers.ContractFactory(abi, bytecode, wallet).deploy();\r\n    await _Contract.deployed();//等待合约部署完毕\r\n\r\n    console.log(\"合约地址：\", _Contract.address);\r\n    console.log(\"部署合约后的交易详情：\", _Contract.deployTransaction);\r\n    //调用方法\r\n    //显示原本的 owner\r\n    let ownerVal = await _Contract.getOwner();\r\n    console.log(\"原本的 owner:\", ownerVal);\r\n    //调用 setOwner 更改 owner\r\n    await _Contract.setOwner(\"0x7A85F346BbC42769cE13910fCF878211A767FF1F\");\r\n    //更改后的 owner\r\n    ownerVal = await _Contract.getOwner();\r\n    console.log(\"更改后的 owner:\", ownerVal);\r\n}\r\nmain()\r\n```\r\n结果如下：\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/be1a7892c2be42219e871495518f2f14.png)\r\n\r\n\r\n# 六、账户操作\r\n## 6.1 转账\r\n上一点我们已经知道如何创建钱包，那么接下来就通过钱包对某个地址发起转账。\r\n\r\n创建一个转账的交易只需要在对应的一个钱包基础上创建一个 tx 对象：\r\n\r\n```typescript\r\nconst tx = {\r\n    to: \"0xB464C27f8d481D3a733f91B6059A2AF26CC9e5AB\",\r\n    value: ethers.utils.parseEther(\"30\");//此时是 eth 作为单位\r\n}\r\n```\r\n这个 tx 对象中的 to 表示向某个地址转账，value 表示转账的金额，在此是用 utils 中的 parseEther 将转账的金额单位设置为 eth，转账 30 个eth。\r\n\r\n完整代码如下：\r\n\r\n```typescript\r\nimport { ethers } from \"ethers\";\r\nconst providerLocal = new ethers.providers.JsonRpcProvider(`http://127.0.0.1:7545`);\r\n\r\n//私钥\r\nconst privateKey = 'bde959799bd8bd8e3d27686087a53b121276f2ea8edff5f213ef799adff9ffee'\r\nconst wallet = new ethers.Wallet(privateKey, providerLocal)\r\nconst tx = {\r\n    to: \"0xB464C27f8d481D3a733f91B6059A2AF26CC9e5AB\",\r\n    value: ethers.utils.parseEther(\"30\")//此时是 eth 作为单位\r\n}\r\n\r\nconst main = async () => {\r\n    //发送交易 返回一个交易详情\r\n    const receipt = await wallet.sendTransaction(tx);\r\n    //等待确认\r\n    await receipt.wait();\r\n    console.log(receipt);\r\n}\r\nmain()\r\n```\r\n\r\n此时我设置的账户是第一个：\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/90605a7ad8b24f3284c4291c350c2472.png)\r\n运行后交易完毕：\r\n\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/bf12bf18255242eea2a4b571fb0ac6af.png)\r\n\r\n在 ganache 中查看对应的 eth 余额，发现另一个账户新增了 30，我选择的账户减少了 30：\r\n\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/a878c690997541bc919206a8fa8d17e8.png)\r\n你也可以打印对应的交易详情：\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/91dc0730b4fe45b6acd09ed871e59279.png)\r\n## 6.2 账户信息\r\n账户还可以查看对应的信息：\r\n\r\n```typescript\r\n> import { ethers } from \"ethers\"; const providerLocal = new\r\n> ethers.providers.JsonRpcProvider(`http://127.0.0.1:7545`);\r\n> \r\n> //私钥 const privateKey =\r\n> 'bde959799bd8bd8e3d27686087a53b121276f2ea8edff5f213ef799adff9ffee'\r\n> const wallet = new ethers.Wallet(privateKey, providerLocal) const tx =\r\n> {\r\n>     to: \"0xB464C27f8d481D3a733f91B6059A2AF26CC9e5AB\",\r\n>     value: ethers.utils.parseEther(\"30\")//此时是 eth 作为单位 }\r\n> \r\n> console.log(\"私钥:\" + wallet.privateKey); console.log(\"交易次数:\" + (await\r\n> wallet.getTransactionCount()));\r\n```\r\n\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/c73e1bad39ee4c5788486f3b7d7300c8.png)\r\n\r\n# 七、alchemy\r\n\r\n在平常的开发测试中，使用 alchemy 可以使我们在连接到各个节点，例如 goerli 测试网。\r\n\r\nalchemy 的 官网：[alchemy.com](https://alchemy.com/?r=15285cf5c04819e2)\r\n\r\n注册之后，打开 Dashboard 点击 create App 创建应用：\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/717a5bc37dea4fe49829d1f0cb989fd3.png)\r\n输入对应的项目名信息，选择对应的网络即可创建：\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/edacd7e0bce44febb51e6eeb1af841b3.png)\r\n在你创建的内容中，点击 view key 可以查看链接的 PRC：\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/4a8d539f576d403b838bed4f8f1890e4.png)\r\n等下我们就在 ethers 中使用 https 的链接方式：\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/fe18e5777eb640a1ba9234d06db3a489.png)\r\n\r\n# 八、事件\r\n首先我们需要将一个合约部署在测试网：\r\n\r\n```typescript\r\n// SPDX-License-Identifier:MIT\r\npragma solidity ^0.8.17;\r\ncontract test{\r\n    address owner;\r\n    constructor(){\r\n        owner=msg.sender;\r\n    }\r\n    event Transfer(address indexed msg, address indexed to, uint256 indexed number);\r\n\r\n    function getOwner()view public returns(address){\r\n        return owner;\r\n    }\r\n\r\n    function setOwner(address _owner) public{\r\n        owner=_owner;\r\n        emit Transfer(msg.sender, _owner, 10);\r\n    }\r\n}\r\n```\r\n部署完毕后开始测试。\r\n\r\n## 8.1 检索得到某一合约的事件\r\n刚刚部署的合约中有一个事件：\r\n\r\n```typescript\r\nevent Transfer(address indexed msg, address indexed to, uint256 indexed number);\r\n```\r\n\r\n我们可以调用 setOwner 方法对其触发，触发完毕后，开始编写 ethers 代码。\r\n\r\n首先引入 eth 并且指定对应的网络，这个时候你就需要拿你在 alchemy 中得到的 https 链接过来了：\r\n\r\n```typescript\r\nimport { ethers } from \"ethers\";\r\nconst provider = new ethers.providers.JsonRpcProvider('这里是你的alchemy 链接');\r\n```\r\n指定完毕后，需要你的这个事件的 abi：\r\n\r\n```typescript\r\nconst abi = [\r\n    \"event Transfer(address indexed msg, address indexed to, uint256 indexed number)\"\r\n];\r\n```\r\n接着是你这个合约部署的地址：\r\n\r\n```typescript\r\nconst address = '0x37F64f179e4549580a15e20c2Ee89B776003Ca30';\r\n```\r\n\r\n创建一个合约的操作对象：\r\n\r\n```typescript\r\nconst contract = new ethers.Contract(address, abi, provider);\r\n```\r\n\r\n此时我们就需要准备使用  contract 的 queryFilter 获取当前合约的事件了。queryFilter 接收 3 个参数，分别是 事件名、起始区块和结束区块。此时我们就需要得到对应的起始区块和结束区块，我们可以对这个范围内的事件信息做检索，找到我们当前合约所释放的时间。（注意，这个区块范围是你查找的这个事件的区间）\r\n\r\n接着通过 getBlockNumber 获取当前区块高度：\r\n\r\n```typescript\r\nconst height= await provider.getBlockNumber();\r\n```\r\n\r\n随后我们可以通过 queryFilter  检索这个区块区间内的时间事件了：\r\n\r\n```typescript\r\nconst transferEvents = await contract.queryFilter('Transfer', height - 5000, height);\r\n```\r\n\r\n随后可以打印出第一个，当然你也可以遍历输出：\r\n\r\n```typescript\r\nconsole.log(transferEvents[0]);\r\n```\r\n由于我是昨天晚上释放的事件，所以我就减去了 5000 的区块范围，否则就找不到了。\r\n\r\n完整代码如下：\r\n\r\n```typescript\r\nimport { ethers } from \"ethers\";\r\nconst provider = new ethers.providers.JsonRpcProvider(`https://eth-goerli.g.alchemy.com/v2/1YDyu2dfaCyIDHFsVfI_0qzBDYC19qAA`)\r\n\r\nconst abi = [\r\n    \"event Transfer(address indexed msg, address indexed to, uint256 indexed number)\"\r\n];\r\nconst address = '0x37F64f179e4549580a15e20c2Ee89B776003Ca30';\r\nconst contract = new ethers.Contract(address, abi, provider);\r\n\r\nconst height = await provider.getBlockNumber();\r\nconst transferEvents = await contract.queryFilter('Transfer', height - 5000, height);\r\nconsole.log('事件:');\r\nconsole.log(transferEvents[0]);\r\n```\r\n\r\n## 8.2 监听合约释放的事件\r\n监听合约事件我们使用 contract.on 或者 contract.once，一个是监听一个是只监听一次。\r\n\r\n我们可以通过这个监听事件监听某一个交易所的充值提现，例如监听币安的提现充值。\r\n\r\n咱们可以在币安的帮助文档可以找到合约地址：[https://www.binance.com/zh-CN/support/faq/360040487711](https://www.binance.com/zh-CN/support/faq/360040487711)\r\n\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/bdc47e4117ad4d6e8dc83141c728a553.png)\r\n\r\n进去查看后可以找到事件：\r\n\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/50d7b4592a98441e8c3dfab5ab9b0271.png)\r\n\r\n我们可以查看币安的合约事件，从而得到 abi：\r\n\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/0e7d8f39b41c4a0db24b8ed0c922addc.png)\r\n那么此时已经得到了地址和abi，那么我们开始编写对应监听币安的 transfer 事件。首先我们可以在 [alchemy.com](https://alchemy.com/?r=15285cf5c04819e2) 中创建对应的网络 RPC 地址供我们链接到以太坊网络，复制后编写代码：\r\n\r\n```typescript\r\nimport { ethers } from \"ethers\";\r\nconst provider = new ethers.providers.JsonRpcProvider('更改为你的 RPC 也就是那个 HTTPS（这里是主网）');\r\n```\r\n接着弄一个变量存那个合约地址：\r\n\r\n```typescript\r\nconst address = '0xdac17f958d2ee523a2206206994597c13d831ec7';\r\n```\r\n创建 abi：\r\n\r\n```typescript\r\nconst abi = [\r\n    \"event Transfer(address indexed from, address indexed to, uint value)\"\r\n];\r\n```\r\n接着创建一个对应的合约对象：\r\n\r\n```typescript\r\nconst contract = new ethers.Contract(address, abi, provider);\r\n```\r\n\r\n接着开始使用 contract.on 对合约进行监听，contract.on 第一个参数是你的事件名，接着是一个function，这个function接收的参数是对应的事件参数，可以得到该事件参数的值：\r\n\r\n```typescript\r\nconsole.log(\"币安的合约 transfer 监听：\");\r\ncontract.on('Transfer', (from, to, value) => {\r\n    console.log(\r\n        '从 ' + from + ' 转账到' + to + ' ' + ethers.utils.formatUnits(ethers.BigNumber.from(value))\r\n    );\r\n});\r\n```\r\n\r\n完整代码如下：\r\n\r\n```typescript\r\nimport { ethers } from \"ethers\";\r\nconst provider = new ethers.providers.JsonRpcProvider('https://eth-mainnet.g.alchemy.com/v2/OcBb0EJSh2QoQQoaAO30ndviX4r6GRd0');\r\nconst address = '0xdac17f958d2ee523a2206206994597c13d831ec7';\r\nconst abi = [\r\n    \"event Transfer(address indexed from, address indexed to, uint value)\"\r\n];\r\n\r\nconst contract = new ethers.Contract(address, abi, provider);\r\nconsole.log(\"币安的合约 transfer 监听：\");\r\ncontract.on('Transfer', (from, to, value) => {\r\n    console.log(\r\n        '从 ' + from + ' 转账到' + to + ' ' + ethers.utils.formatUnits(ethers.BigNumber.from(value),6)\r\n        //引：6为token有效小数位。查到的余额除以有效小数位才是实际余额\r\n    );\r\n});\r\n```\r\n\r\n结果如下：\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/90547e123e5d4d1e8c32a70256935d8f.png)\r\n\r\n## 8.3 合约过滤\r\n\r\n# 九、ethers 中的 eth 单位转化\r\n在以太坊中 1 个 eth 等于 10^18 wei，在 ethers 中提供了相关的函数给我们互相之间进行转化。\r\n\r\n# 十、查看合约是否是 ERC721 标准\r\n查看合约是不是 ERC721 其实简单，直接通过provider 指定了 RPC 后，创建合约对象去调用 supportsInterface 接口即可。\r\n\r\n首先指定 provider：\r\n\r\n```typescript\r\nimport { ethers } from \"ethers\";\r\nconst provider = new ethers.providers.JsonRpcProvider([这里改成你指定的RPC]);\r\n```\r\n\r\n接着是对应的 abi 和 address ：\r\n\r\n```typescript\r\nconst abi = [\r\n    \"function supportsInterface(bytes4) public view returns(bool)\",//改成自己的\r\n];\r\nconst address = \"合约地址\";\r\n```\r\n接着创建合约以及对应的传入接口id：\r\n\r\n```typescript\r\nconst contract = new ethers.Contract(address, abi, provider);\r\nconst isERC721 = await contract.supportsInterface('0x80ac58cd');\r\n```\r\n最后打印即可：\r\n\r\n```typescript\r\nconsole.log('isERC721?', isERC721)\r\n```\r\n\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/f5ec63fbc9104d45b81dfe71a6c143ec.png)\r\n接口 id 我们可以在以太坊改进建议 EIP 中得到，例如你要得到 721 的就去看 721的：\r\n[https://eips.ethereum.org/EIPS/eip-721](https://eips.ethereum.org/EIPS/eip-721)：\r\n![在这里插入图片描述](https://img-blog.csdnimg.cn/40aa0a2b42dd432db6d1961397a01ef0.png)"},"author":{"user":"https://learnblockchain.cn/people/11898","address":null},"history":null,"timestamp":1671601442,"version":1}