├── 01_HelloVitalik ├── HelloVitalik.ipynb ├── img │ ├── 1-2.png │ ├── 1-3.png │ ├── 1-4.png │ └── 1-5.png └── readme.md ├── 02_Provider ├── Provider.ipynb ├── img │ ├── 2-1.png │ ├── 2-2.png │ ├── 2-3.png │ ├── 2-4.png │ ├── 2-5.png │ ├── 2-6.png │ ├── 2-7.png │ └── 2-8.png └── readme.md ├── 03_ReadContract ├── ReadContract.ipynb ├── img │ ├── 3-1.png │ ├── 3-2.png │ └── 3-3.png └── readme.md ├── 04_SendETH ├── SendETH.ipynb ├── img │ ├── 4-1.png │ ├── 4-2.png │ └── 4-3.png └── readme.md ├── 05 _WriteContract ├── README.md ├── img │ ├── 5-1.png │ ├── 5-2.png │ └── 5-3.png └── write_contract.py ├── 06 _DeployContract ├── ERC20.sol ├── IERC20.sol ├── deploy_contract.py ├── img │ ├── 6-1-2.png │ ├── 6-1.png │ ├── 6-2.png │ ├── 6-3.png │ └── 6-4.png └── readme.md ├── 11_StaticCall ├── StaticCall.ipynb ├── img │ ├── 11-1.png │ ├── 11-2.png │ └── 11-3.png └── readme.md ├── 12_ERC721Check ├── ERC721Check.ipynb ├── img │ ├── 12-1.jpg │ └── 12-2.jpg └── readme.md ├── 13_EncodeCalldata ├── EncodeCalldata.py ├── img │ ├── 13-1.png │ └── 13-2.png └── readme.md ├── 14_HDwallet ├── HDwallet.py ├── img │ ├── 14-1.png │ ├── 14-2.png │ ├── 14-3.png │ ├── 14-4.png │ └── 14-5.png └── readme.md ├── 15_MultiTransfer ├── img │ ├── 15-2.png │ ├── 15-3.png │ ├── 15-4.png │ ├── 15-5.png │ └── 15-6.png └── readme.md ├── 16_MultiCollect ├── MultiCollect.ipynb ├── img │ ├── 16-1.png │ ├── 16-2.png │ ├── 16-3.png │ ├── 16-4.png │ └── 16-6.png └── readme.md ├── 19_Mempool ├── Mempool.ipynb ├── img │ ├── 19-1.png │ ├── 19-2.png │ └── 19-3.png └── readme.md ├── 20_DecodeTx ├── DecodeTx.ipynb ├── img │ ├── 20-1.png │ ├── 20-2.png │ ├── 20-3.png │ └── 20-4.png └── readme.md ├── 7_Event ├── Event.ipynb ├── img │ ├── 7-1.png │ ├── 7-2.png │ ├── 7-3.png │ ├── 7-4.png │ └── 7-5.png └── readme.md ├── 8_ContractListener ├── ContractListener.ipynb ├── img │ └── 8-1.png └── readme.md ├── LICENSE ├── MultiTransfer.ipynb └── README.md /01_HelloVitalik/HelloVitalik.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "True\n" 13 | ] 14 | } 15 | ], 16 | "source": [ 17 | "from web3 import Web3\n", 18 | "w3 = Web3(Web3.HTTPProvider('https://rpc.ankr.com/eth'))\n", 19 | "print(w3.is_connected())" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 2, 25 | "metadata": {}, 26 | "outputs": [ 27 | { 28 | "name": "stdout", 29 | "output_type": "stream", 30 | "text": [ 31 | "753871743898804775906\n" 32 | ] 33 | } 34 | ], 35 | "source": [ 36 | "print(w3.eth.get_balance(\"vitalik.eth\"))" 37 | ] 38 | } 39 | ], 40 | "metadata": { 41 | "kernelspec": { 42 | "display_name": "base", 43 | "language": "python", 44 | "name": "python3" 45 | }, 46 | "language_info": { 47 | "codemirror_mode": { 48 | "name": "ipython", 49 | "version": 3 50 | }, 51 | "file_extension": ".py", 52 | "mimetype": "text/x-python", 53 | "name": "python", 54 | "nbconvert_exporter": "python", 55 | "pygments_lexer": "ipython3", 56 | "version": "3.9.13" 57 | }, 58 | "orig_nbformat": 4 59 | }, 60 | "nbformat": 4, 61 | "nbformat_minor": 2 62 | } 63 | -------------------------------------------------------------------------------- /01_HelloVitalik/img/1-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/01_HelloVitalik/img/1-2.png -------------------------------------------------------------------------------- /01_HelloVitalik/img/1-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/01_HelloVitalik/img/1-3.png -------------------------------------------------------------------------------- /01_HelloVitalik/img/1-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/01_HelloVitalik/img/1-4.png -------------------------------------------------------------------------------- /01_HelloVitalik/img/1-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/01_HelloVitalik/img/1-5.png -------------------------------------------------------------------------------- /01_HelloVitalik/readme.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 1. HelloVitalik (6行代码) 3 | tags: 4 | - web3py 5 | - python 6 | - ens 7 | - vitalik 8 | --- 9 | 10 | # web3.py极简入门: 1. HelloVitalik (6行代码) 11 | 12 | 我们最近在重新学`web3.py`,巩固一下细节,也写一个`WTF web3py极简入门`,供小白们使用。 13 | 14 | **推特**:[@0xAA_Science](https://twitter.com/0xAA_Science)[0xXQ](https://twitter.com/0xXQ1) 15 | 16 | 17 | **WTF Academy社群:** [官网 wtf.academy](https://wtf.academy) | [WTF Solidity教程](https://github.com/AmazingAng/WTFSolidity) | [discord](https://discord.gg/5akcruXrsk) | [微信群申请](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) 18 | 19 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-web3py](https://github.com/WTFAcademy/WTF-web3py) 20 | 21 | ----- 22 | 23 | 这一讲,我们会介绍`web3.py`库,python在线编辑器`colab`,并且我们会写第一个程序`HelloVitalik`:查询Vitalik的`ETH`余额,并输出在`console`中。 24 | 25 | > 教程使用 web3.py 最新的 v6.11.4 版本。 26 | 27 | ## web3.py简述 28 | 29 | `web3.py`适用于以太坊和 EVM 区块链的Python Web3 SDK,提供了许多方便实用的函数,支持开发者与以太坊主网进行便捷的交互。 30 | 31 | ## 开发工具 32 | 33 | ### 1. VScode + Jupyter Notebook 34 | 35 | 你可以使用本地`VScode`进行开发。你需要安装[Python](https://www.python.org/downloads/),以及[Jupyter Notebook]()然后利用包管理工具`pip`安装`web3.py`库: 36 | 37 | ```shell 38 | pip install web3 39 | ``` 40 | 41 | ### 2. Colab 42 | 43 | ![colab](./img/1-2.png) 44 | 45 | [colab](https://colab.research.google.com/)是一个在线`Python`编程的平台,你只需要有一个Google帐号,不需要配置`Python`环境就可以运行使用`Jupyter Notebook`运行`Python`代码。 46 | 47 | ![colab](./img/1-3.png) 48 | 49 | 这一讲,我们将用`colab`做演示。你需要有一个Google帐号并登录,然后点击`File > New Notebook`创建一个新项目,然后将代码写在自动生成的`Untitled1.ipynb`中即可。本地使用`VScode`的方法同理,相关配置方法请读者自行查阅资料。 50 | ![Colab](./img/1-4.png) 51 | `Jupyter Notebook`和`Colab`是一种交互式的编程环境,在这里代码被写在独立的Cell里并可以分步执行,适合对代码的逐步调试和修改。代码的运行结果也可以自动展示在Cell的输出部分中。运行独立的Cell可以通过选中Cell并点击运行按钮来运行,也可以使用快捷键`Shift+Enter`运行。 52 | 53 | 54 | ## HelloVitalik 55 | 56 | 现在,让我们用`web3.py`编写第一个程序`HelloVitalik`:查询Vitalik的`ETH`余额,并输出在NoteBook中。整个程序只需要4行,非常简单! 57 | 58 | **注意**:在`Colab`上第一次运行可能会提示`ModuleNotFoundError: No module named 'web3'`,这是因为`web3.py`库还没有安装,只需要在任意一个Cell,输入下列指令运行安装即可。 59 | ```shell 60 | pip install web3 61 | ``` 62 | 63 | 64 | ![Hello Vitalik](./img/1-4.png) 65 | 66 | ```python 67 | from web3 import Web3 68 | w3 = Web3(Web3.HTTPProvider('https://rpc.ankr.com/eth')) 69 | print(w3.is_connected()) 70 | print(w3.eth.get_balance("vitalik.eth")) 71 | ``` 72 | 73 | 我们逐行分析这个程序: 74 | 75 | ### 1. 导入`web3.py` 76 | 第一行的作用是导入已经安装好的`web3.py`库: 77 | ```python 78 | from web3 import Web3 79 | ``` 80 | 81 | ### 2. 连接以太坊 82 | 83 | 在`web3.py`中,`Provider`类是一个为以太坊网络连接提供抽象的类,它提供对区块链及其状态的只读访问。`Provider`包括`Test Provider, Local Provider, Remote Provider`三种,这里我们初始化一个`HTTPProvider`用于连接以太坊网络。 84 | `HTTPProvider`接收一个`String`类型的参数,用于指定`rpc`地址,这里我们使用的是以太坊主网的公共`rpc`。 85 | 完成`Provider`的创建后,我们使用`Provider`实例作为参数初始化一个`Web3`实例。`Web3`实例是与以太坊交互的入口,我们使用变量`w3`存储这个实例。 86 | 87 | 88 | ```python 89 | w3 = Web3(Web3.HTTPProvider('https://rpc.ankr.com/eth')) 90 | ``` 91 | 92 | ### 3. 测试链接状态 93 | 94 | 完成`Web3`实例的初始化后,我们需要测试实例是否能够成功连接到以太坊网络。`is_connected()`会返回一个布尔型变量,这里我们把他输出到控制台。返回为`True`代表连接成功。 95 | ```python 96 | print(w3.is_connected()) 97 | ``` 98 | 99 | ### 4. 获取Vitalik地址的`ETH`余额 100 | 101 | 我们可以利用`Web3.eth`实例的`get_balance()`函数来查询某个地址的`ETH`余额。由于`web3.py`支持`ENS`域名,我们不需要知道具体地址,用`ENS`域名`vitalik.eth`就可以查询到以太坊创始人豚林-vitalik的余额。 102 | 103 | ```python 104 | print(w3.eth.get_balance("vitalik.eth")) 105 | ``` 106 | 107 | 108 | ![在控制台打印Vitalik余额](./img/1-5.png) 109 | 110 | 111 | ## 总结 112 | 113 | 这是WTF web3.py极简教程的第一讲,我们介绍了`web3.py`,并完成了第一个使用`web3.py`的程序`HelloVitalik`,查询Vitalik钱包的`ETH`余额。 114 | 115 | **课后作业**:在第四步里我们获取了Vitalik地址的`ETH`余额,有没有合适的方法可以把输出的单位转化为`ETH`呢? 116 | 117 | web3.py官方文档:https://web3py.readthedocs.io/en/stable/index.html 118 | -------------------------------------------------------------------------------- /02_Provider/Provider.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 5, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "# 利用公共rpc节点连接以太坊网络\n", 10 | "# 可以在 https://chainlist.org 上找到\n", 11 | "from web3 import Web3\n", 12 | "ALCHEMY_MAINNET_URL = 'https://rpc.ankr.com/eth'\n", 13 | "ALCHEMY_SEPOLIA_URL = 'https://rpc.sepolia.org'\n", 14 | "# 连接以太坊主网\n", 15 | "provider_main = Web3.HTTPProvider(ALCHEMY_MAINNET_URL)\n", 16 | "# 连接Sepolia测试网\n", 17 | "provider_test = Web3.HTTPProvider(ALCHEMY_SEPOLIA_URL)\n", 18 | "w3_main = Web3(provider_main)\n", 19 | "w3_test = Web3(provider_test)" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 7, 25 | "metadata": {}, 26 | "outputs": [ 27 | { 28 | "name": "stdout", 29 | "output_type": "stream", 30 | "text": [ 31 | "1. 查询vitalik在主网和Sepolia测试网的ETH余额\n", 32 | "ETH Balance of vitalik 753.871743898804775906\n", 33 | "Sepolia ETH Balance of vitalik 4.211658876174074691\n" 34 | ] 35 | } 36 | ], 37 | "source": [ 38 | "# 1. 查询vitalik在主网和Sepolia测试网的ETH余额\n", 39 | "print(\"1. 查询vitalik在主网和Sepolia测试网的ETH余额\")\n", 40 | "balance_main = w3_main.eth.get_balance(\"vitalik.eth\")\n", 41 | "balance_test = w3_test.eth.get_balance(\"vitalik.eth\")\n", 42 | "# 将余额输出在console(主网)\n", 43 | "print(\"ETH Balance of vitalik\",w3_main.from_wei(balance_main,'ether'))\n", 44 | "# 输出Sepolia测试网ETH余额\n", 45 | "print(\"Sepolia ETH Balance of vitalik\",w3_test.from_wei(balance_test,'ether'))" 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": 8, 51 | "metadata": {}, 52 | "outputs": [ 53 | { 54 | "name": "stdout", 55 | "output_type": "stream", 56 | "text": [ 57 | "2. 查询provider连接到了哪条链\n", 58 | "Chain id: 1\n" 59 | ] 60 | } 61 | ], 62 | "source": [ 63 | "# 2. 查询provider连接到了哪条链\n", 64 | "print(\"2. 查询provider连接到了哪条链\")\n", 65 | "chain_id_main = w3_main.eth.chain_id\n", 66 | "print(f\"Chain id: {chain_id_main}\")" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": 11, 72 | "metadata": {}, 73 | "outputs": [ 74 | { 75 | "name": "stdout", 76 | "output_type": "stream", 77 | "text": [ 78 | "3. 查询区块高度\n", 79 | "Block number: 18768884\n" 80 | ] 81 | } 82 | ], 83 | "source": [ 84 | "# 3. 查询区块高度\n", 85 | "print(\"3. 查询区块高度\")\n", 86 | "block_number_main = w3_main.eth.block_number\n", 87 | "print(f\"Block number: {block_number_main}\")" 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": 12, 93 | "metadata": {}, 94 | "outputs": [ 95 | { 96 | "name": "stdout", 97 | "output_type": "stream", 98 | "text": [ 99 | "4. 查询 vitalik 钱包历史交易次数\n", 100 | "Tx Count: 1168\n" 101 | ] 102 | } 103 | ], 104 | "source": [ 105 | "# 4. 查询 vitalik 钱包历史交易次数\n", 106 | "print(\"4. 查询 vitalik 钱包历史交易次数\")\n", 107 | "tx_count_main = w3_main.eth.get_transaction_count(\"vitalik.eth\")\n", 108 | "print(f\"Tx Count: {tx_count_main}\")" 109 | ] 110 | }, 111 | { 112 | "cell_type": "code", 113 | "execution_count": 16, 114 | "metadata": {}, 115 | "outputs": [ 116 | { 117 | "name": "stdout", 118 | "output_type": "stream", 119 | "text": [ 120 | "5. 查询当前建议的gas设置\n", 121 | "Gas price: 41.128387669\n" 122 | ] 123 | } 124 | ], 125 | "source": [ 126 | "# 5. 查询当前建议的gas设置\n", 127 | "print(\"5. 查询当前建议的gas设置\")\n", 128 | "fee_data_main = w3_main.eth.gas_price\n", 129 | "print(f\"Gas price: {w3_main.from_wei(fee_data_main, 'gwei')}\")" 130 | ] 131 | }, 132 | { 133 | "cell_type": "code", 134 | "execution_count": 17, 135 | "metadata": {}, 136 | "outputs": [ 137 | { 138 | "name": "stdout", 139 | "output_type": "stream", 140 | "text": [ 141 | "6. 查询区块信息\n", 142 | "AttributeDict({'difficulty': 17179869184, 'extraData': HexBytes('0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa'), 'gasLimit': 5000, 'gasUsed': 0, 'hash': HexBytes('0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3'), 'logsBloom': HexBytes('0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), 'miner': '0x0000000000000000000000000000000000000000', 'mixHash': HexBytes('0x0000000000000000000000000000000000000000000000000000000000000000'), 'nonce': HexBytes('0x0000000000000042'), 'number': 0, 'parentHash': HexBytes('0x0000000000000000000000000000000000000000000000000000000000000000'), 'receiptsRoot': HexBytes('0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421'), 'sha3Uncles': HexBytes('0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347'), 'size': 540, 'stateRoot': HexBytes('0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544'), 'timestamp': 0, 'totalDifficulty': 17179869184, 'transactions': [], 'transactionsRoot': HexBytes('0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421'), 'uncles': []})\n" 143 | ] 144 | } 145 | ], 146 | "source": [ 147 | "# 6. 查询区块信息\n", 148 | "print(\"6. 查询区块信息\")\n", 149 | "block_main = w3_main.eth.get_block(0)\n", 150 | "print(block_main)" 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": 21, 156 | "metadata": {}, 157 | "outputs": [ 158 | { 159 | "name": "stdout", 160 | "output_type": "stream", 161 | "text": [ 162 | "7. 给定合约地址查询合约bytecode,例子用的WETH地址\n", 163 | "0x6060604052361561010f5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166306fdde03811461011157806307da68f5146101a1578063095ea7b3146101b357806313af4035146101e657806318160ddd1461020457806323b872dd14610226578063313ce5671461025f5780633452f51d1461028157806369d3e20e146102bd57806370a08231146102db57806375f12b21146103095780637a9e5e4b1461032d5780638402181f1461034b5780638da5cb5b1461038757806390bc1693146103b357806395d89b41146103d1578063a9059cbb14610461578063be9a655514610494578063bf7e214f146104a6578063dd62ed3e146104d2575bfe5b341561011957fe5b610121610506565b604080516020808252835181830152835191928392908301918501908083838215610167575b80518252602083111561016757601f199092019160209182019101610147565b505050905090810190601f1680156101935780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34156101a957fe5b6101b1610594565b005b34156101bb57fe5b6101d2600160a060020a0360043516602435610620565b604080519115158252519081900360200190f35b34156101ee57fe5b6101b1600160a060020a03600435166106a4565b005b341561020c57fe5b610214610721565b60408051918252519081900360200190f35b341561022e57fe5b6101d2600160a060020a0360043581169060243516604435610728565b604080519115158252519081900360200190f35b341561026757fe5b6102146107ae565b60408051918252519081900360200190f35b341561028957fe5b6101d2600160a060020a03600435166001608060020a03602435166107b4565b604080519115158252519081900360200190f35b34156102c557fe5b6101b16001608060020a03600435166107d2565b005b34156102e357fe5b610214600160a060020a03600435166108c2565b60408051918252519081900360200190f35b341561031157fe5b6101d26108e1565b604080519115158252519081900360200190f35b341561033557fe5b6101b1600160a060020a03600435166108ea565b005b341561035357fe5b6101d2600160a060020a03600435166001608060020a0360243516610967565b604080519115158252519081900360200190f35b341561038f57fe5b610397610986565b60408051600160a060020a039092168252519081900360200190f35b34156103bb57fe5b6101b16001608060020a0360043516610995565b005b34156103d957fe5b610121610a86565b604080516020808252835181830152835191928392908301918501908083838215610167575b80518252602083111561016757601f199092019160209182019101610147565b505050905090810190601f1680156101935780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561046957fe5b6101d2600160a060020a0360043516602435610b14565b604080519115158252519081900360200190f35b341561049c57fe5b6101b1610b98565b005b34156104ae57fe5b610397610c21565b60408051600160a060020a039092168252519081900360200190f35b34156104da57fe5b610214600160a060020a0360043581169060243516610c30565b60408051918252519081900360200190f35b6005805460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152929183018282801561058c5780601f106105615761010080835404028352916020019161058c565b820191906000526020600020905b81548152906001019060200180831161056f57829003601f168201915b505050505081565b6105b26105ad33600035600160e060020a031916610c5d565b610d65565b6040805134808252602082018381523693830184905260043593602435938493869333600160a060020a03169360008035600160e060020a031916949092606082018484808284376040519201829003965090945050505050a46008805460ff191660011790555b5b50505b565b6008546000906106339060ff1615610d65565b6040805134808252602082018381523693830184905260043593602435938493869333600160a060020a03169360008035600160e060020a031916949092606082018484808284376040519201829003965090945050505050a46106978585610d76565b92505b5b50505b92915050565b6106c26105ad33600035600160e060020a031916610c5d565b610d65565b6004805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0383811691909117918290556040519116907fce241d7ca1f669fee44b6fc00b8eba2df3bb514eed0f6f668f8f89096e81ed9490600090a25b5b50565b6000545b90565b60085460009061073b9060ff1615610d65565b6040805134808252602082018381523693830184905260043593602435938493869333600160a060020a03169360008035600160e060020a031916949092606082018484808284376040519201829003965090945050505050a46107a0868686610de1565b92505b5b50505b9392505050565b60075481565b60006107c983836001608060020a0316610b14565b90505b92915050565b6107f06105ad33600035600160e060020a031916610c5d565b610d65565b6008546108009060ff1615610d65565b6040805134808252602082018381523693830184905260043593602435938493869333600160a060020a03169360008035600160e060020a031916949092606082018484808284376040519201829003965090945050505050a4600160a060020a03331660009081526001602052604090205461088a906001608060020a03851681011015610d65565b600160a060020a033316600090815260016020526040812080546001608060020a03861690810190915581540190555b5b50505b5b50565b600160a060020a0381166000908152600160205260409020545b919050565b60085460ff1681565b6109086105ad33600035600160e060020a031916610c5d565b610d65565b6003805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0383811691909117918290556040519116907f1abebea81bfa2637f28358c371278fb15ede7ea8dd28d2e03b112ff6d936ada490600090a25b5b50565b60006107c98333846001608060020a0316610728565b90505b92915050565b600454600160a060020a031681565b6109b36105ad33600035600160e060020a031916610c5d565b610d65565b6008546109c39060ff1615610d65565b6040805134808252602082018381523693830184905260043593602435938493869333600160a060020a03169360008035600160e060020a031916949092606082018484808284376040519201829003965090945050505050a4600160a060020a033316600090815260016020526040902054610a4d906001608060020a03851681031115610d65565b600160a060020a033316600090815260016020526040812080546001608060020a0386169081900390915581540390555b5b50505b5b50565b6006805460408051602060026001851615610100026000190190941693909304601f8101849004840282018401909252818152929183018282801561058c5780601f106105615761010080835404028352916020019161058c565b820191906000526020600020905b81548152906001019060200180831161056f57829003601f168201915b505050505081565b600854600090610b279060ff1615610d65565b6040805134808252602082018381523693830184905260043593602435938493869333600160a060020a03169360008035600160e060020a031916949092606082018484808284376040519201829003965090945050505050a46106978585610ef4565b92505b5b50505b92915050565b610bb66105ad33600035600160e060020a031916610c5d565b610d65565b6040805134808252602082018381523693830184905260043593602435938493869333600160a060020a03169360008035600160e060020a031916949092606082018484808284376040519201829003965090945050505050a46008805460ff191690555b5b50505b565b600354600160a060020a031681565b600160a060020a038083166000908152600260209081526040808320938516835292905220545b92915050565b600030600160a060020a031683600160a060020a03161415610c815750600161069e565b600454600160a060020a0384811691161415610c9f5750600161069e565b600354600160a060020a03161515610cb95750600061069e565b600354604080516000602091820181905282517fb7009613000000000000000000000000000000000000000000000000000000008152600160a060020a0388811660048301523081166024830152600160e060020a0319881660448301529351939094169363b7009613936064808301949391928390030190829087803b1515610d3f57fe5b6102c65a03f11515610d4d57fe5b505060405151915061069e9050565b5b5b5b92915050565b80151561071d5760006000fd5b5b50565b600160a060020a03338116600081815260026020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b600160a060020a038316600090815260016020526040812054610e079083901015610d65565b600160a060020a0380851660009081526002602090815260408083203390941683529290522054610e3b9083901015610d65565b600160a060020a038316600090815260016020526040902054610e67906105ad9084610fb7565b610d65565b600160a060020a03808516600081815260026020908152604080832033861684528252808320805488900390558383526001825280832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35060015b9392505050565b600160a060020a033316600090815260016020526040812054610f1a9083901015610d65565b600160a060020a038316600090815260016020526040902054610f46906105ad9084610fb7565b610d65565b600160a060020a03338116600081815260016020908152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35060015b92915050565b808201829010155b929150505600a165627a7a723058202c338bb3d0c86b4d03a42df2ad24f83dbd41c3d87dbcd59296451a3a0c1683c70029\n" 164 | ] 165 | } 166 | ], 167 | "source": [ 168 | "# 7. 给定合约地址查询合约bytecode,例子用的WETH地址\n", 169 | "print(\"7. 给定合约地址查询合约bytecode,例子用的WETH地址\")\n", 170 | "weth_address = w3_main.to_checksum_address(\"0xc778417e063141139fce010982780140aa0cd5ab\")\n", 171 | "code_main = w3_main.eth.get_code(weth_address)\n", 172 | "print(code_main.hex())" 173 | ] 174 | }, 175 | { 176 | "cell_type": "code", 177 | "execution_count": null, 178 | "metadata": {}, 179 | "outputs": [], 180 | "source": [] 181 | } 182 | ], 183 | "metadata": { 184 | "kernelspec": { 185 | "display_name": "base", 186 | "language": "python", 187 | "name": "python3" 188 | }, 189 | "language_info": { 190 | "codemirror_mode": { 191 | "name": "ipython", 192 | "version": 3 193 | }, 194 | "file_extension": ".py", 195 | "mimetype": "text/x-python", 196 | "name": "python", 197 | "nbconvert_exporter": "python", 198 | "pygments_lexer": "ipython3", 199 | "version": "3.9.13" 200 | }, 201 | "orig_nbformat": 4 202 | }, 203 | "nbformat": 4, 204 | "nbformat_minor": 2 205 | } 206 | -------------------------------------------------------------------------------- /02_Provider/img/2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/02_Provider/img/2-1.png -------------------------------------------------------------------------------- /02_Provider/img/2-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/02_Provider/img/2-2.png -------------------------------------------------------------------------------- /02_Provider/img/2-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/02_Provider/img/2-3.png -------------------------------------------------------------------------------- /02_Provider/img/2-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/02_Provider/img/2-4.png -------------------------------------------------------------------------------- /02_Provider/img/2-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/02_Provider/img/2-5.png -------------------------------------------------------------------------------- /02_Provider/img/2-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/02_Provider/img/2-6.png -------------------------------------------------------------------------------- /02_Provider/img/2-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/02_Provider/img/2-7.png -------------------------------------------------------------------------------- /02_Provider/img/2-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/02_Provider/img/2-8.png -------------------------------------------------------------------------------- /02_Provider/readme.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 2. 提供器 Provider 3 | tags: 4 | tags: 5 | - web3py 6 | - python 7 | - ens 8 | - web 9 | --- 10 | 11 | # Ethers极简入门: 2. Provider 提供器 12 | 13 | 我们最近在重新学`web3.py`,巩固一下细节,也写一个`WTF web3py极简入门`,供小白们使用。 14 | 15 | **推特**:[@0xAA_Science](https://twitter.com/0xAA_Science)[0xXQ](https://twitter.com/0xXQ1) 16 | 17 | 18 | **WTF Academy社群:** [官网 wtf.academy](https://wtf.academy) | [WTF Solidity教程](https://github.com/AmazingAng/WTFSolidity) | [discord](https://discord.gg/5akcruXrsk) | [微信群申请](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) 19 | 20 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-web3py](https://github.com/WTFAcademy/WTF-web3py) 21 | 22 | ----- 23 | 24 | 这一讲,我们将介绍web3.py的`Provider`类,然后利用它连接上Infura节点,读取链上的信息。 25 | 26 | ## `Provider`类 27 | 28 | `Provider`类是对以太坊网络连接的抽象,为标准以太坊节点功能提供简洁、一致的接口。`Provider`是 `web3.py` 与区块链通信的方式。提供者接受 `JSON-RPC` 请求并返回响应。这通常是通过将请求提交到基于 `HTTP` 或 `IPC` 套接字的服务器来完成的。 29 | 30 | ## `HTTPProvider` 31 | 32 | ### 创建节点服务商的API Key 33 | 34 | 首先,你需要去节点服务商的网站注册并创建`API Key`。在`WTF Solidity极简教程`的工具篇,我们介绍了[Infura](https://github.com/AmazingAng/WTFSolidity/blob/main/Topics/Tools/TOOL02_Infura/readme.md)和[Alchemy](https://github.com/AmazingAng/WTFSolidity/blob/main/Topics/Tools/TOOL04_Alchemy/readme.md)两家公司`API Key`的创建方法,大家可以参考。 35 | 36 | ![Infura API Key](img/2-1.png) 37 | 38 | 你还可以在 [Chainlist](https://chainlist.org/) 网站找到各个链的公开节点。 39 | 40 | ### 连接公开节点 41 | 42 | 这里,我们用[Chainlist](https://chainlist.org/)上的公开节点作为例子。在找到合适的rpc之后,可以利用`Web3.HTTPProvider()`方法来创建`Provider`变量,该方法以节点服务的`url`链接作为参数。 43 | 44 | 在下面这个例子中,我们分别创建连接到`ETH`主网和`Sepolia`测试网的`provider`,并构建响应的`Web3`实例: 45 | 46 | ```python 47 | # 利用公共rpc节点连接以太坊网络 48 | # 可以在 https://chainlist.org 上找到 49 | from web3 import Web3 50 | ALCHEMY_MAINNET_URL = 'https://rpc.ankr.com/eth' 51 | ALCHEMY_SEPOLIA_URL = 'https://rpc.sepolia.org' 52 | # 连接以太坊主网 53 | provider_main = Web3.HTTPProvider(ALCHEMY_MAINNET_URL) 54 | # 连接Sepolia测试网 55 | provider_test = Web3.HTTPProvider(ALCHEMY_SEPOLIA_URL) 56 | w3_main = Web3(provider_main) 57 | w3_test = Web3(provider_test) 58 | ``` 59 | 60 | ### 利用`Web3.eth`实例读取链上数据 61 | 62 | `Web3.eth`实例封装了一些方法,可以便捷的读取链上数据: 63 | 64 | **1.** 利用`get_balance()`函数读取主网和测试网Vitalik的`ETH`余额,并且使用`Web3.from_wei()`函数将返回的结果转化为`ETH`单位: 65 | 66 | ```python 67 | # 1. 查询vitalik在主网和Sepolia测试网的ETH余额 68 | print("1. 查询vitalik在主网和Sepolia测试网的ETH余额") 69 | balance_main = w3_main.eth.get_balance("vitalik.eth") 70 | balance_test = w3_test.eth.get_balance("vitalik.eth") 71 | # 将余额输出在console(主网) 72 | print("ETH Balance of vitalik",w3_main.from_wei(balance_main,'ether')) 73 | # 输出Sepolia测试网ETH余额 74 | print("Sepolia ETH Balance of vitalik",w3_test.from_wei(balance_test,'ether')) 75 | ``` 76 | 77 | ![Vitalik余额](img/2-2.png) 78 | 79 | **2.** 利用`chain_id`属性查询`provider`连接到了哪条链: 80 | 81 | ```python 82 | # 2. 查询provider连接到了哪条链 83 | print("2. 查询provider连接到了哪条链") 84 | chain_id_main = w3_main.eth.chain_id 85 | print(f"Chain id: {chain_id_main}") 86 | ``` 87 | 88 | ![getNetwork](img/2-3.png) 89 | 90 | **3.** 利用`block_number`属性查询当前区块高度: 91 | 92 | ```python 93 | # 3. 查询区块高度 94 | print("3. 查询区块高度") 95 | block_number_main = w3_main.eth.block_number 96 | print(f"Block number: {block_number_main}") 97 | ``` 98 | 99 | ![getBlockNumber](img/2-4.png) 100 | 101 | **4.** 利用`get_transaction_count()`查询某个钱包的历史交易次数。 102 | 103 | ```python 104 | # 4. 查询 vitalik 钱包历史交易次数 105 | print("4. 查询 vitalik 钱包历史交易次数") 106 | tx_count_main = w3_main.eth.get_transaction_count("vitalik.eth") 107 | print(f"Tx Count: {tx_count_main}") 108 | ``` 109 | 110 | ![getGasPrice](img/2-5.png) 111 | 112 | 113 | **5.** 利用`gas_price`属性查询当前建议的`gas`设置,并以`gwei`为单位返回。 114 | 115 | ```python 116 | # 5. 查询当前建议的gas设置 117 | print("5. 查询当前建议的gas设置") 118 | fee_data_main = w3_main.eth.gas_price 119 | print(f"Gas price: {w3_main.from_wei(fee_data_main, 'gwei')}") 120 | ``` 121 | 122 | ![getFeeData](img/2-6.png) 123 | 124 | **6.** 利用`get_block()`查询区块信息,参数为要查询的区块高度: 125 | 126 | ```python 127 | # 6. 查询区块信息 128 | print("6. 查询区块信息") 129 | block_main = w3_main.eth.get_block(0) 130 | print(block_main) 131 | ``` 132 | 133 | ![getBlock](img/2-7.png) 134 | 135 | **7.** 利用`get_code()`查询某个地址的合约`bytecode`,参数为合约地址,下面例子中用的主网`WETH`的合约地。注意,这里的合约地址需要将地址使用`Web3.to_checksum_address()`转化为校验和地址: 136 | 137 | ```python 138 | # 7. 给定合约地址查询合约bytecode,例子用的WETH地址 139 | print("7. 给定合约地址查询合约bytecode,例子用的WETH地址") 140 | weth_address = w3_main.to_checksum_address("0xc778417e063141139fce010982780140aa0cd5ab") 141 | code_main = w3_main.eth.get_code(weth_address) 142 | print(code_main.hex()) 143 | ``` 144 | 145 | ![getCode](img/2-8.png) 146 | 147 | ## 总结 148 | 149 | 这一讲,我们介绍了`web3.py`的`Provider`类,并用Infura的节点API Key创建了`HTTPProvider`,读取了`ETH`主网和`Sepolia`测试网的链上信息。 -------------------------------------------------------------------------------- /03_ReadContract/img/3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/03_ReadContract/img/3-1.png -------------------------------------------------------------------------------- /03_ReadContract/img/3-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/03_ReadContract/img/3-2.png -------------------------------------------------------------------------------- /03_ReadContract/img/3-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/03_ReadContract/img/3-3.png -------------------------------------------------------------------------------- /03_ReadContract/readme.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3. 读取合约信息 3 | tags: 4 | - web3py 5 | - python 6 | - ens 7 | - vitalik 8 | --- 9 | 10 | # web3.py极简入门: 3.读取合约信息 11 | 12 | 我们最近在重新学`web3.py`,巩固一下细节,也写一个`WTF web3.py极简入门`,供小白们使用。 13 | 14 | **推特**:[@0xAA_Science](https://twitter.com/0xAA_Science)[@localcat15](https://twitter.com/localcat15) 15 | 16 | 17 | **WTF Academy社群:** [官网 wtf.academy](https://wtf.academy) | [WTF Solidity教程](https://github.com/AmazingAng/WTFSolidity) | [discord](https://discord.gg/5akcruXrsk) | [微信群申请](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) 18 | 19 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-web3py](https://github.com/WTFAcademy/WTF-web3py) 20 | 21 | ----- 22 | 23 | 这一讲,我们会介绍`Contract`合约对象,并利用它来读取链上的合约信息。 24 | 25 | ## `Contract`对象 26 | 27 | 在`web3.py`中,`Contract`对象是对区块链上运行的智能合约的抽象,将智能合约实例化为`Contract`对象后,我们就可以在`python`程序中方便地与之进行交互。 28 | 29 | ## 创建`Contract`变量 30 | 创建以太坊上的`Contract`对象非常简单,只需要借助`web3.eth.contract()`方法。想要调用这个方法,我们必须有一个预先创建好的`Provider`实例,并将合约地址和合约`ABI`作为参数传入。 31 | 32 | 合约的`ABI`指定了如何与合约进行交互,可以在我们编译合约文件时获得,也可以通过[以太坊浏览器](https://etherscan.io/)方便的读取。 33 | 34 | ## 读取合约信息 35 | 36 | 接下来,我们尝试借助`Contract`对象读取Vitalik的`USDT`持仓。 37 | 38 | ### 1. 创建Provider 39 | ```python 40 | from web3 import Web3 41 | #从alchemy申请rpc节点 42 | ALCHEMY_URL='https://eth-mainnet.g.alchemy.com/v2/hjZ-SwVhjRtBk-yUJ1SkWSTrz_dJl7of' 43 | w3=Web3(Web3.HTTPProvider(ALCHEMY_URL)) 44 | w3.is_connected() 45 | ``` 46 | 47 | ### 2. 创建`Contract`实例 48 | ```python 49 | #USDT主网地址 50 | addressUSDT=0xdAC17F958D2ee523a2206206994597C13D831ec7 51 | #从etherscan找到USDT的ABI 52 | ABIUSDT='[{"constant":true,"inputs":[],...}]' 53 | ``` 54 | ![从etherscan查找ABI](img/3-1.png) 55 | ### 3. 调用只读函数 56 | 可以使用`Contract.caller`方法简单地实现合约中只读函数的调用(涉及到签名和发送交易的调用将在后续章节讨论),以下两种调用方式都符合语法要求。 57 | ```python 58 | #第一种 59 | USDTcontract.caller.name() 60 | #第二种 61 | USDTcontract.caller().totalSupply() 62 | 63 | ``` 64 | ![调用只读函数](img/3-2.png) 65 | ### 4. 获取Vitalik地址的`USDT`余额 66 | 67 | 我们可以利用`USDTcontract`合约的`balanceOf`函数来查询Vitalik的`USDT`余额。 68 | 69 | ```python 70 | USDTcontract.caller.balanceOf('vitalik.eth') 71 | ``` 72 | 73 | 74 | ![查询Vitalik的USDT余额](./img/3-3.png) 75 | 76 | 77 | ## 总结 78 | 这一讲,我们介绍了如何在`web3.py`中创建合约实例,并调用合约中的只读函数读取了Vitalik的USDT余额。 -------------------------------------------------------------------------------- /04_SendETH/SendETH.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "id": "682b751a", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "from web3 import Web3, EthereumTesterProvider" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 2, 16 | "id": "3f281d38", 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "w3 = Web3(EthereumTesterProvider())" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 3, 26 | "id": "902b456d", 27 | "metadata": {}, 28 | "outputs": [ 29 | { 30 | "data": { 31 | "text/plain": [ 32 | "True" 33 | ] 34 | }, 35 | "execution_count": 3, 36 | "metadata": {}, 37 | "output_type": "execute_result" 38 | } 39 | ], 40 | "source": [ 41 | "w3.is_connected()" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 4, 47 | "id": "daca5f81", 48 | "metadata": {}, 49 | "outputs": [ 50 | { 51 | "data": { 52 | "text/plain": [ 53 | "AttributeDict({'number': 0,\n", 54 | " 'hash': HexBytes('0x42bcfcc168c8aeb1183071be32d5facbeba5f8b0dd701b088a042358c0e0ea60'),\n", 55 | " 'parentHash': HexBytes('0x0000000000000000000000000000000000000000000000000000000000000000'),\n", 56 | " 'nonce': HexBytes('0x0000000000000000'),\n", 57 | " 'sha3Uncles': HexBytes('0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347'),\n", 58 | " 'logsBloom': HexBytes('0x00'),\n", 59 | " 'transactionsRoot': HexBytes('0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421'),\n", 60 | " 'receiptsRoot': HexBytes('0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421'),\n", 61 | " 'stateRoot': HexBytes('0xf1588db9a9f1ed91effabdec31f93cb4212b008c8b8ba047fd55fabebf6fd727'),\n", 62 | " 'miner': '0x0000000000000000000000000000000000000000',\n", 63 | " 'difficulty': 0,\n", 64 | " 'totalDifficulty': 0,\n", 65 | " 'mixHash': HexBytes('0x0000000000000000000000000000000000000000000000000000000000000000'),\n", 66 | " 'size': 548,\n", 67 | " 'extraData': HexBytes('0x0000000000000000000000000000000000000000000000000000000000000000'),\n", 68 | " 'gasLimit': 30029122,\n", 69 | " 'gasUsed': 0,\n", 70 | " 'timestamp': 1702530112,\n", 71 | " 'transactions': [],\n", 72 | " 'uncles': [],\n", 73 | " 'baseFeePerGas': 1000000000,\n", 74 | " 'withdrawals': [],\n", 75 | " 'withdrawalsRoot': HexBytes('0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421')})" 76 | ] 77 | }, 78 | "execution_count": 4, 79 | "metadata": {}, 80 | "output_type": "execute_result" 81 | } 82 | ], 83 | "source": [ 84 | "w3.eth.get_block('latest')" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": 17, 90 | "id": "1525dbab", 91 | "metadata": {}, 92 | "outputs": [], 93 | "source": [ 94 | "# 创建随机的account对象\n", 95 | "account1 = w3.eth.account.create()" 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": 6, 101 | "id": "6a703b7a", 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [ 105 | "# 从已有密钥中生成\n", 106 | "account2 = w3.eth.account.from_key(\"0x227dbb8586117d55284e26620bc76534dfbd2394be34cf4a09cb775d593b6f2b\")" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": 7, 112 | "id": "f3f8b6f1", 113 | "metadata": {}, 114 | "outputs": [ 115 | { 116 | "name": "stdout", 117 | "output_type": "stream", 118 | "text": [ 119 | "0x1A3CE7d615867019a6c949D307e2802A72ca4b26\n", 120 | "0xe16C1623c1AA7D919cd2241d8b36d9E79C1Be2A2\n", 121 | "private key=0xfab317ba23e6396d76162070a8fa9a9c50e8a0bc32d57f4ab1fb0d42a0854270\n" 122 | ] 123 | } 124 | ], 125 | "source": [ 126 | "print(account1.address)\n", 127 | "print(account2.address)\n", 128 | "print(f'private key={w3.to_hex(account1.key)}')" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": 8, 134 | "id": "d98f3258", 135 | "metadata": { 136 | "scrolled": true 137 | }, 138 | "outputs": [ 139 | { 140 | "name": "stdout", 141 | "output_type": "stream", 142 | "text": [ 143 | "0\n" 144 | ] 145 | } 146 | ], 147 | "source": [ 148 | "print(w3.eth.get_transaction_count(account2.address))" 149 | ] 150 | }, 151 | { 152 | "cell_type": "code", 153 | "execution_count": 18, 154 | "id": "90696269", 155 | "metadata": {}, 156 | "outputs": [ 157 | { 158 | "name": "stdout", 159 | "output_type": "stream", 160 | "text": [ 161 | "0\n", 162 | "123123123123123\n" 163 | ] 164 | } 165 | ], 166 | "source": [ 167 | "print(w3.eth.get_balance(account1.address))\n", 168 | "# eth-tester populates accounts with test ether:\n", 169 | "account3 = w3.eth.accounts[0]\n", 170 | "tx_hash = w3.eth.send_transaction({\n", 171 | " \"from\": account3,\n", 172 | " \"to\": account1.address,\n", 173 | " \"value\": 123123123123123\n", 174 | "})\n", 175 | "print(w3.eth.get_balance(account1.address))" 176 | ] 177 | } 178 | ], 179 | "metadata": { 180 | "kernelspec": { 181 | "display_name": "Python 3 (ipykernel)", 182 | "language": "python", 183 | "name": "python3" 184 | }, 185 | "language_info": { 186 | "codemirror_mode": { 187 | "name": "ipython", 188 | "version": 3 189 | }, 190 | "file_extension": ".py", 191 | "mimetype": "text/x-python", 192 | "name": "python", 193 | "nbconvert_exporter": "python", 194 | "pygments_lexer": "ipython3", 195 | "version": "3.9.12" 196 | } 197 | }, 198 | "nbformat": 4, 199 | "nbformat_minor": 5 200 | } 201 | -------------------------------------------------------------------------------- /04_SendETH/img/4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/04_SendETH/img/4-1.png -------------------------------------------------------------------------------- /04_SendETH/img/4-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/04_SendETH/img/4-2.png -------------------------------------------------------------------------------- /04_SendETH/img/4-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/04_SendETH/img/4-3.png -------------------------------------------------------------------------------- /04_SendETH/readme.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 4、发送ETH 3 | tags: 4 | - web3py 5 | - python 6 | - ens 7 | - vitalik 8 | --- 9 | 10 | # web3.py极简入门: 4、发送ETH 11 | 12 | 我们最近在重新学`web3.py`,巩固一下细节,也写一个`WTF web3.py极简入门`,供小白们使用。 13 | 14 | **推特**:[@0xAA_Science](https://twitter.com/0xAA_Science)[@localcat15](https://twitter.com/localcat15) 15 | 16 | 17 | **WTF Academy社群:** [官网 wtf.academy](https://wtf.academy) | [WTF Solidity教程](https://github.com/AmazingAng/WTFSolidity) | [discord](https://discord.gg/5akcruXrsk) | [微信群申请](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) 18 | 19 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-web3py](https://github.com/WTFAcademy/WTF-web3py) 20 | 21 | ----- 22 | 23 | 这一讲,我们会介绍`Account`钱包对象,并学习如何签名并发送交易。 24 | 25 | ## 创建`Account`对象 26 | 27 | 在`web3.py`中,`Account`对象是对以太坊账户的抽象,可用于对交易进行签名,并将签名后的交易发送到以太坊。 28 | 29 | 创建以太坊上的`Account`对象有以下几种方法: 30 | ### 方法1:生成随机密钥 31 | ```python 32 | # 创建随机的account对象 33 | generated_account = w3.eth.account.create() 34 | ``` 35 | ### 方法2:导入已有私钥 36 | ```python 37 | # 从已有密钥中生成 38 | # 注意:不建议铭文编码私钥,更安全的方式是从系统环境变量中读取 39 | generated_account = w3.eth.account.from_key("0x227dbb8586117d55284e26620bc76534dfbd2394be34cf4a09cb775d593b6f2b") 40 | ``` 41 | ### 方法3:导入已有助记词 42 | ```python 43 | # 借助已有助记词生成 44 | generated_account = w3.eth.account.from_mnemonic(mnemonic) 45 | ``` 46 | ## 发送ETH 47 | 48 | 我们可以利用Account实例来发送ETH。首先我们要构造一个交易请求,需要指定发送方、接收方和发送数量三个参数: 49 | ```python 50 | tx_hash = w3.eth.send_transaction({ 51 | "from": acct1, 52 | "to": some_address, 53 | "value": 123123123123123 54 | }) 55 | ``` 56 | 注意:上述语法只对测试场景有效(即使用`EthereumTesterProvider`的场景),在测试账户中,签名过程会被自动完成。 57 | 58 | 如果想要在正式场景中发送交易,我们还需要补充签名的过程: 59 | ```python 60 | # 1. Build a new tx 61 | transaction = { 62 | 'from': acct2.address, 63 | 'to': some_address, 64 | 'value': 1000000000, 65 | 'nonce': w3.eth.get_transaction_count(acct2.address), 66 | 'gas': 200000, 67 | 'maxFeePerGas': 2000000000, 68 | 'maxPriorityFeePerGas': 1000000000, 69 | } 70 | # 2. Sign tx with a private key 71 | signed = w3.eth.account.sign_transaction(transaction, private_key) 72 | # 3. Send the signed transaction 73 | tx_hash = w3.eth.send_raw_transaction(signed.rawTransaction) 74 | tx = w3.eth.get_transaction(tx_hash) 75 | assert tx["from"] == acct2.address 76 | ``` 77 | 此外,对于一个经常使用的账户,我们也可以在正式场景中设置自动签名: 78 | ```python 79 | # Add acct2 as auto-signer: 80 | w3.middleware_onion.add(construct_sign_and_send_raw_middleware(acct2)) 81 | # pk also works: w3.middleware_onion.add(construct_sign_and_send_raw_middleware(pk)) 82 | 83 | # Transactions from `acct2` will then be signed, under the hood, in the middleware: 84 | tx_hash = w3.eth.send_transaction({ 85 | "from": acct2.address, 86 | "value": 3333333333, 87 | "to": some_address 88 | }) 89 | ``` 90 | ## 代码示例 91 | ### 1、创建`Provider`实例 92 | ```python 93 | from web3 import Web3,EthereumTesterProvider 94 | w3 = Web3(EthereumTesterProvider) 95 | w3.is_connected() 96 | ``` 97 | 98 | ### 2. 创建`Account`实例 99 | ```python 100 | account1 = w3.eth.account.create() 101 | account2 = w3.eth.account.from_key("0x227dbb8586117d55284e26620bc76534dfbd2394be34cf4a09cb775d593b6f2b") 102 | ``` 103 | ### 3. 获取钱包地址或私钥 104 | 105 | ```python 106 | print(account1.address) 107 | print(account2.address) 108 | print(f'private key={w3.to_hex(account1.key)}') 109 | ``` 110 | ![获取钱包地址或私钥](img/4-1.png) 111 | ### 4. 获取钱包在链上的交互次数 112 | ```python 113 | print(w3.eth.get_transaction_count(account2.address)) 114 | ``` 115 | ![获取钱包在链上的交互次数](img/4-2.png) 116 | 117 | ### 5、发送以太坊 118 | ```python 119 | print(w3.eth.get_balance(account1.address)) 120 | # account3是充值了测试ETH的测试账户: 121 | account3 = w3.eth.accounts[0] 122 | tx_hash = w3.eth.send_transaction({ 123 | "from": account3, 124 | "to": account1.address, 125 | "value": 123123123123123 126 | }) 127 | print(w3.eth.get_balance(account1.address)) 128 | ``` 129 | 130 | 131 | ![发送以太坊](./img/4-3.png) 132 | 133 | 134 | ## 总结 135 | 这一讲,我们介绍了如何在`web3.py`中创建钱包实例,并借助钱包签名并发送交易。 -------------------------------------------------------------------------------- /05 _WriteContract/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 5. 合约交互 3 | tags: 4 | - web3py 5 | - provider 6 | - wallet 7 | - contract 8 | - frontend 9 | - web 10 | --- 11 | 12 | # web3py极简入门: 5. 合约交互 13 | 14 | 15 | **WTF Academy社群:** [官网 wtf.academy](https://wtf.academy) | [WTF Solidity教程](https://github.com/AmazingAng/WTF-Solidity) | [discord](https://discord.gg/5akcruXrsk) | [微信群申请](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) 16 | 17 | ----- 18 | 19 | 这一讲,我们将介绍如何声明`Contract`合约变量,并利用它与测试网的`BBQ`合约交互。 20 | 21 | ## 创建`Contract`变量 22 | 23 | 声明`Contract`变量的规则: 24 | ```py 25 | contract= w3.eth.contract(address=contract_address, abi=contract_abi) 26 | ``` 27 | 28 | 其中`contract_address`为合约地址,`contract_abi`是合约的`abi`接口,`w3`为我们先前选取的`provider`。 29 | 30 | 31 | ## 合约交互 32 | 33 | 我们在第三讲介绍了读取合约信息。它不需要`gas`。这里我们介绍写入合约信息,你需要构建交易,并且支付`gas`。该交易将由整个网络上的每个节点以及矿工验证,并改变区块链状态。 34 | 35 | 你可以用下面的方法进行合约交互: 36 | 37 | ```py 38 | # 从账户私钥创建交易 39 | private_key = '0x你的私钥' 40 | account = Account.privateKeyToAccount(private_key) 41 | 42 | # 构建交易参数 43 | transaction = contract.functions.METHOD_NAME(args).buildTransaction({ 44 | 'gas': 2000000, # gas上限 45 | 'gasPrice': web3.toWei('50', 'gwei'), # gas价格 46 | 'nonce': web3.eth.getTransactionCount(account.address), # nonce 47 | 'value': web3.toWei(1, 'ether') # 传入的ether(单位是wei) 48 | }) 49 | 50 | # 签名交易 51 | signed_txn = web3.eth.account.sign_transaction(transaction, private_key) 52 | 53 | # 发送交易 54 | tx_hash = web3.eth.send_raw_transaction(signed_txn.rawTransaction) 55 | 56 | # 等待链上确认交易 57 | receipt = web3.eth.wait_for_transaction_receipt(tx_hash) 58 | print(receipt) 59 | ``` 60 | 61 | 其中`METHOD_NAME`为调用的函数名,`args`为函数参数,`buildTransaction` 是用于构建交易对象的方法。它接受一个包含交易参数的字典,并返回一个表示待发送交易的字典。这个字典包含了交易的各种参数,包括: 62 | - gasPrice:gas价格 63 | - gasLimit:gas上限 64 | - value:调用时传入的ether(单位是wei) 65 | - nonce:nonce 66 | 67 | ## 实例:与bsc测试网`BBQ`合约交互 68 | 69 | BBQ合约是我借鉴了uang合约重新在bsc测试网部署的一个土狗合约,它整体采用ERC20协议,并发行了自己的代币bbqCoin,拥有查询余额,转账等基本功能。 70 | 71 | 1. 创建`provider`,`wallet`变量。 72 | 73 | ```py 74 | from web3 import Web3 75 | from eth_account import Account 76 | #连接bsc测试网 77 | bsc_test=Web3(Web3.HTTPProvider('https://data-seed-prebsc-1-s1.binance.org:8545')) 78 | 79 | // 利用私钥创建wallet对象 80 | private_key = "e08ca922fedbc3d37fa677f1d8f7e8fcbe42d031186bcbbc763d20cbdac81f9d" 81 | 82 | # 创建钱包对象 83 | private_key = "e08ca922fedbc3d37fa677f1d8f7e8fcbe42d031186bcbbc763d20cbdac81f9d" 84 | # 创建钱包对象 85 | wallet = Account.from_key(private_key) 86 | # 定义钱包地址 87 | address =wallet.address 88 | ``` 89 | 2. 创建`BBQ`合约变量,我们在`ABI`中加入了4个我们要调用的函数: 90 | - `symbol`:是ERC20代币合约中的一个标准函数,用于返回代币的缩写符号。 91 | - `balanceOf(address)`:查询地址的`bbqCoin`余额。 92 | - `transfer(adress, uint256)`:转账。 93 | ```py 94 | # WETH的ABI 95 | contract_abi = [ 96 | { 97 | "inputs": [], 98 | "name": "symbol", 99 | "outputs": [ 100 | { 101 | "internalType": "string", 102 | "name": "", 103 | "type": "string" 104 | } 105 | ], 106 | "stateMutability": "view", 107 | "type": "function"}, 108 | { 109 | 110 | "inputs": [ 111 | { 112 | "internalType": "address", 113 | "name": "account", 114 | "type": "address" 115 | } 116 | ], 117 | "name": "balanceOf", 118 | "outputs": [ 119 | { 120 | "internalType": "uint256", 121 | "name": "", 122 | "type": "uint256" 123 | } 124 | ], 125 | "stateMutability": "view", 126 | "type": "function" 127 | }, 128 | { 129 | "inputs": [ 130 | { 131 | "internalType": "address", 132 | "name": "recipient", 133 | "type": "address" 134 | }, 135 | { 136 | "internalType": "uint256", 137 | "name": "amount", 138 | "type": "uint256" 139 | } 140 | ], 141 | "name": "transfer", 142 | "outputs": [ 143 | { 144 | "internalType": "bool", 145 | "name": "", 146 | "type": "bool" 147 | } 148 | ], 149 | "stateMutability": "nonpayable", 150 | "type": "function" 151 | } 152 | ] 153 | 154 | # BBQ合约地址(bsc测试网) 155 | addressWETH = '0x2faf6cc1165e4d8a4aa28582c268d9a15f71ecb7' 156 | # 将地址转换为检验和格式 157 | checksum_address = w3.to_checksum_address(addressWETH) 158 | # 创建合约对象 159 | contract_bbq = bsc_test.eth.contract(address=checksum_contract_address_test, abi=contract_abi) 160 | ``` 161 | 3. 调用合约本身的`symbol()`函数,展示合约代币的简写形式。 162 | ```py 163 | info=contract_bbq.functions.symbol().call() 164 | print(info) 165 | ``` 166 | ![查看代币简写形式](img/5-1.png) 167 | 4. 读取账户此时的`bbqCoin`余额,可以看到`bbqCoin`余额为`99999700`。 168 | 169 | ```py 170 | #调用函数读取 171 | balance_bbq = contract_bbq.functions.balanceOf(address).call() 172 | #转换余额的单位 173 | balance_bbq = Web3.from_wei(balance_bbq, 'ether') 174 | 175 | print('该账户bbqCoin:', balance_bbq) 176 | ``` 177 | 178 | ![读取WETH余额](img/5-2.png) 179 | 180 | 181 | 5. 调用`BBQ`合约的`transfer()`函数,给一个账户转账`100 bbqCoin`,并打印余额。可以看到余额变为`99999600`。 182 | 183 | ```py 184 | # 指定转账目标地址和转账金额 185 | recipient_address = '0x19AbF5261c1a8a7882Ba8bE2F554C17C5036E94C' 186 | amount_in_ether = 100 187 | # 指定转账参数 188 | nonce = bsc_test.eth.get_transaction_count(address) 189 | gas_price = bsc_test.eth.gas_price 190 | gas_limit = 100000 191 | tx = contract_bbq.functions.transfer(recipient_address, Web3.to_wei(amount_in_ether, 'ether')).build_transaction( 192 | { 193 | 'from': address, 194 | 'nonce': nonce, 195 | 'gasPrice': gas_price, 196 | 'gas': gas_limit 197 | }) 198 | 199 | # 使用钱包对象进行交易签名 200 | signed_txn = wallet.sign_transaction(tx) 201 | 202 | tx_hash = bsc_test.eth.send_raw_transaction(signed_txn.rawTransaction) 203 | 204 | print('转账交易已经提交,交易哈希为:', tx_hash.hex()) 205 | balance_bbq = contract_bbq.functions.balanceOf(address).call() 206 | balance_bbq = Web3.from_wei(balance_bbq, 'ether') 207 | 208 | print('该账户转账后bbqCoin:', balance_bbq) 209 | 210 | ``` 211 | ![给另一账户转账](img/5-3.png) 212 | 213 | 214 | ## 总结 215 | 216 | 这一讲,我们介绍了如何声明可写的`Contract`合约变量,并利用它与测试网的`BBQ`合约交互。我们不仅调用`BBQ`的`balanceOf()`函数,查询账户的bbqCoin余额,并将利用`transfer()`函数转账给另一个账户。 -------------------------------------------------------------------------------- /05 _WriteContract/img/5-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/05 _WriteContract/img/5-1.png -------------------------------------------------------------------------------- /05 _WriteContract/img/5-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/05 _WriteContract/img/5-2.png -------------------------------------------------------------------------------- /05 _WriteContract/img/5-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/05 _WriteContract/img/5-3.png -------------------------------------------------------------------------------- /05 _WriteContract/write_contract.py: -------------------------------------------------------------------------------- 1 | from web3 import Web3 2 | from eth_account import Account 3 | 4 | bsc_test = Web3(Web3.HTTPProvider('https://data-seed-prebsc-1-s1.binance.org:8545')) 5 | print(bsc_test.is_connected()) 6 | private_key = "e08ca922fedbc3d37fa677f1d8f7e8fcbe42d031186bcbbc763d20cbdac81f9d" 7 | # 创建钱包对象 8 | wallet = Account.from_key(private_key) 9 | # 定义钱包地址 10 | address = wallet.address 11 | 12 | contract_address_test = '0x2faf6cc1165e4d8a4aa28582c268d9a15f71ecb7' 13 | # 获取合约地址 14 | checksum_contract_address_test = Web3.to_checksum_address(contract_address_test) 15 | 16 | contract_abi = [ 17 | { 18 | "inputs": [], 19 | "name": "symbol", 20 | "outputs": [ 21 | { 22 | "internalType": "string", 23 | "name": "", 24 | "type": "string" 25 | } 26 | ], 27 | "stateMutability": "view", 28 | "type": "function"}, 29 | { 30 | 31 | "inputs": [ 32 | { 33 | "internalType": "address", 34 | "name": "account", 35 | "type": "address" 36 | } 37 | ], 38 | "name": "balanceOf", 39 | "outputs": [ 40 | { 41 | "internalType": "uint256", 42 | "name": "", 43 | "type": "uint256" 44 | } 45 | ], 46 | "stateMutability": "view", 47 | "type": "function" 48 | }, 49 | { 50 | "inputs": [ 51 | { 52 | "internalType": "address", 53 | "name": "recipient", 54 | "type": "address" 55 | }, 56 | { 57 | "internalType": "uint256", 58 | "name": "amount", 59 | "type": "uint256" 60 | } 61 | ], 62 | "name": "transfer", 63 | "outputs": [ 64 | { 65 | "internalType": "bool", 66 | "name": "", 67 | "type": "bool" 68 | } 69 | ], 70 | "stateMutability": "nonpayable", 71 | "type": "function" 72 | } 73 | ] 74 | 75 | # 创建 ERC20 合约对象 76 | 77 | contract_bbq = bsc_test.eth.contract(address=checksum_contract_address_test, abi=contract_abi) 78 | info=contract_bbq.functions.symbol().call() 79 | print(info) 80 | balance_bbq = contract_bbq.functions.balanceOf(address).call() 81 | balance_bbq = Web3.from_wei(balance_bbq, 'ether') 82 | 83 | print('该账户bbqCoin:', balance_bbq) 84 | 85 | # 指定转账目标地址和转账金额 86 | recipient_address = '0x19AbF5261c1a8a7882Ba8bE2F554C17C5036E94C' 87 | amount_in_ether = 100 88 | # 指定转账参数 89 | nonce = bsc_test.eth.get_transaction_count(address) 90 | gas_price = bsc_test.eth.gas_price 91 | gas_limit = 100000 92 | tx = contract_bbq.functions.transfer(recipient_address, Web3.to_wei(amount_in_ether, 'ether')).build_transaction( 93 | { 94 | 'from': address, 95 | 'nonce': nonce, 96 | 'gasPrice': gas_price, 97 | 'gas': gas_limit 98 | }) 99 | 100 | # 使用钱包对象进行交易签名 101 | signed_txn = wallet.sign_transaction(tx) 102 | 103 | tx_hash = bsc_test.eth.send_raw_transaction(signed_txn.rawTransaction) 104 | 105 | print('转账交易已经提交,交易哈希为:', tx_hash.hex()) 106 | balance_bbq = contract_bbq.functions.balanceOf(address).call() 107 | balance_bbq = Web3.from_wei(balance_bbq, 'ether') 108 | 109 | print('该账户转账后bbqCoin:', balance_bbq) 110 | -------------------------------------------------------------------------------- /06 _DeployContract/ERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // WTF Solidity by 0xAA 3 | 4 | pragma solidity ^0.8.4; 5 | 6 | import "./IERC20.sol"; 7 | 8 | contract ERC20 is IERC20 { 9 | 10 | mapping(address => uint256) public override balanceOf; 11 | 12 | mapping(address => mapping(address => uint256)) public override allowance; 13 | 14 | uint256 public override totalSupply; // 代币总供给 15 | 16 | string public name; // 名称 17 | string public symbol; // 符号 18 | 19 | uint8 public decimals = 18; // 小数位数 20 | 21 | // @dev 在合约部署的时候实现合约名称和符号 22 | constructor(string memory name_, string memory symbol_){ 23 | name = name_; 24 | symbol = symbol_; 25 | } 26 | 27 | // @dev 实现`transfer`函数,代币转账逻辑 28 | function transfer(address recipient, uint amount) external override returns (bool) { 29 | balanceOf[msg.sender] -= amount; 30 | balanceOf[recipient] += amount; 31 | emit Transfer(msg.sender, recipient, amount); 32 | return true; 33 | } 34 | 35 | // @dev 实现 `approve` 函数, 代币授权逻辑 36 | function approve(address spender, uint amount) external override returns (bool) { 37 | allowance[msg.sender][spender] = amount; 38 | emit Approval(msg.sender, spender, amount); 39 | return true; 40 | } 41 | 42 | // @dev 实现`transferFrom`函数,代币授权转账逻辑 43 | function transferFrom( 44 | address sender, 45 | address recipient, 46 | uint amount 47 | ) external override returns (bool) { 48 | allowance[sender][msg.sender] -= amount; 49 | balanceOf[sender] -= amount; 50 | balanceOf[recipient] += amount; 51 | emit Transfer(sender, recipient, amount); 52 | return true; 53 | } 54 | 55 | // @dev 铸造代币,从 `0` 地址转账给 调用者地址 56 | function mint(uint amount) external { 57 | balanceOf[msg.sender] += amount; 58 | totalSupply += amount; 59 | emit Transfer(address(0), msg.sender, amount); 60 | } 61 | 62 | // @dev 销毁代币,从 调用者地址 转账给 `0` 地址 63 | function burn(uint amount) external { 64 | balanceOf[msg.sender] -= amount; 65 | totalSupply -= amount; 66 | emit Transfer(msg.sender, address(0), amount); 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /06 _DeployContract/IERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // WTF Solidity by 0xAA 3 | 4 | pragma solidity ^0.8.4; 5 | 6 | /** 7 | * @dev ERC20 接口合约. 8 | */ 9 | interface IERC20 { 10 | /** 11 | * @dev 释放条件:当 `value` 单位的货币从账户 (`from`) 转账到另一账户 (`to`)时. 12 | */ 13 | event Transfer(address indexed from, address indexed to, uint256 value); 14 | 15 | /** 16 | * @dev 释放条件:当 `value` 单位的货币从账户 (`owner`) 授权给另一账户 (`spender`)时. 17 | */ 18 | event Approval(address indexed owner, address indexed spender, uint256 value); 19 | 20 | /** 21 | * @dev 返回代币总供给. 22 | */ 23 | function totalSupply() external view returns (uint256); 24 | 25 | /** 26 | * @dev 返回账户`account`所持有的代币数. 27 | */ 28 | function balanceOf(address account) external view returns (uint256); 29 | 30 | /** 31 | * @dev 转账 `amount` 单位代币,从调用者账户到另一账户 `to`. 32 | * 33 | * 如果成功,返回 `true`. 34 | * 35 | * 释放 {Transfer} 事件. 36 | */ 37 | function transfer(address to, uint256 amount) external returns (bool); 38 | 39 | /** 40 | * @dev 返回`owner`账户授权给`spender`账户的额度,默认为0。 41 | * 42 | * 当{approve} 或 {transferFrom} 被调用时,`allowance`会改变. 43 | */ 44 | function allowance(address owner, address spender) external view returns (uint256); 45 | 46 | /** 47 | * @dev 调用者账户给`spender`账户授权 `amount`数量代币。 48 | * 49 | * 如果成功,返回 `true`. 50 | * 51 | * 释放 {Approval} 事件. 52 | */ 53 | function approve(address spender, uint256 amount) external returns (bool); 54 | 55 | /** 56 | * @dev 通过授权机制,从`from`账户向`to`账户转账`amount`数量代币。转账的部分会从调用者的`allowance`中扣除。 57 | * 58 | * 如果成功,返回 `true`. 59 | * 60 | * 释放 {Transfer} 事件. 61 | */ 62 | function transferFrom( 63 | address from, 64 | address to, 65 | uint256 amount 66 | ) external returns (bool); 67 | } -------------------------------------------------------------------------------- /06 _DeployContract/deploy_contract.py: -------------------------------------------------------------------------------- 1 | from eth_account import Account 2 | from web3 import Web3, contract 3 | 4 | # 连接到以太坊节点 5 | infura_url = "https://goerli.infura.io/v3/22b26b228b004ccc9325066db8b5c468" 6 | w3 = Web3(Web3.HTTPProvider(infura_url)) 7 | 8 | 9 | # 创建钱包对象 10 | private_key = "e08ca922fedbc3d37fa677f1d8f7e8fcbe42d031186bcbbc763d20cbdac81f9d" 11 | wallet = Account.from_key(private_key) 12 | 13 | abi = [{"inputs": [{"internalType": "string","name": "name_","type": "string"}, {"internalType": "string","name": "symbol_","type": "string"}], "stateMutability": "nonpayable","type": "constructor"}, {"anonymous": False,"inputs": [{"indexed": True,"internalType": "address","name": "owner","type": "address"},{"indexed": True,"internalType": "address","name": "spender","type": "address"},{"indexed": False,"internalType": "uint256","name": "value","type": "uint256"}],"name": "Approval","type": "event"},{"anonymous": False,"inputs": [{"indexed": True,"internalType": "address","name": "from","type": "address"},{"indexed": True,"internalType": "address","name": "to","type": "address"},{"indexed": False,"internalType": "uint256","name": "value","type": "uint256"}],"name": "Transfer","type": "event"},{"inputs": [{"internalType": "address","name": "","type": "address"},{"internalType": "address","name": "","type": "address"}],"name": "allowance","outputs": [{"internalType": "uint256","name": "","type": "uint256"}],"stateMutability": "view","type": "function"},{"inputs": [{"internalType": "address","name": "spender","type": "address"},{"internalType": "uint256","name": "amount","type": "uint256"}],"name": "approve","outputs": [{"internalType": "bool","name": "","type": "bool"}],"stateMutability": "nonpayable","type": "function"},{"inputs": [{"internalType": "address","name": "","type": "address"}],"name": "balanceOf","outputs": [{"internalType": "uint256","name": "","type": "uint256"}],"stateMutability": "view","type": "function"},{"inputs": [{"internalType": "uint256","name": "amount","type": "uint256"}],"name": "burn","outputs": [],"stateMutability": "nonpayable","type": "function"},{"inputs": [],"name": "decimals","outputs": [{"internalType": "uint8","name": "","type": "uint8"}],"stateMutability": "view","type": "function"},{"inputs": [{"internalType": "uint256","name": "amount","type": "uint256"}],"name": "mint","outputs": [],"stateMutability": "nonpayable","type": "function"},{"inputs": [],"name": "name","outputs": [{"internalType": "string","name": "","type": "string"}],"stateMutability": "view","type": "function"},{"inputs": [],"name": "symbol","outputs": [{"internalType": "string","name": "","type": "string"}],"stateMutability": "view","type": "function"},{"inputs": [],"name": "totalSupply","outputs": [{"internalType": "uint256","name": "","type": "uint256"}],"stateMutability": "view","type": "function"},{"inputs": [{"internalType": "address","name": "recipient","type": "address"},{"internalType": "uint256","name": "amount","type": "uint256"}],"name": "transfer","outputs": [{"internalType": "bool","name": "","type": "bool"}],"stateMutability": "nonpayable","type": "function"},{"inputs": [{"internalType": "address","name": "sender","type": "address"},{"internalType": "address","name": "recipient","type": "address"},{"internalType": "uint256","name": "amount","type": "uint256"}],"name": "transferFrom","outputs": [{"internalType": "bool","name": "","type": "bool"}],"stateMutability": "nonpayable","type": "function"}] 14 | 15 | bytecode = '60806040526012600560006101000a81548160ff021916908360ff1602179055503480156200002d57600080fd5b506040516200111b3803806200111b8339818101604052810190620000539190620001af565b81600390805190602001906200006b9291906200008d565b508060049080519060200190620000849291906200008d565b50505062000392565b8280546200009b90620002b7565b90600052602060002090601f016020900481019282620000bf57600085556200010b565b82601f10620000da57805160ff19168380011785556200010b565b828001600101855582156200010b579182015b828111156200010a578251825591602001919060010190620000ed565b5b5090506200011a91906200011e565b5090565b5b80821115620001395760008160009055506001016200011f565b5090565b6000620001546200014e846200024b565b62000222565b9050828152602081018484840111156200016d57600080fd5b6200017a84828562000281565b509392505050565b600082601f8301126200019457600080fd5b8151620001a68482602086016200013d565b91505092915050565b60008060408385031215620001c357600080fd5b600083015167ffffffffffffffff811115620001de57600080fd5b620001ec8582860162000182565b925050602083015167ffffffffffffffff8111156200020a57600080fd5b620002188582860162000182565b9150509250929050565b60006200022e62000241565b90506200023c8282620002ed565b919050565b6000604051905090565b600067ffffffffffffffff82111562000269576200026862000352565b5b620002748262000381565b9050602081019050919050565b60005b83811015620002a157808201518184015260208101905062000284565b83811115620002b1576000848401525b50505050565b60006002820490506001821680620002d057607f821691505b60208210811415620002e757620002e662000323565b5b50919050565b620002f88262000381565b810181811067ffffffffffffffff821117156200031a576200031962000352565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b610d7980620003a26000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c806342966c681161007157806342966c681461016857806370a082311461018457806395d89b41146101b4578063a0712d68146101d2578063a9059cbb146101ee578063dd62ed3e1461021e576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b661024e565b6040516100c39190610aee565b60405180910390f35b6100e660048036038101906100e19190610a08565b6102dc565b6040516100f39190610ad3565b60405180910390f35b6101046103ce565b6040516101119190610b10565b60405180910390f35b610134600480360381019061012f91906109b9565b6103d4565b6040516101419190610ad3565b60405180910390f35b610152610583565b60405161015f9190610b2b565b60405180910390f35b610182600480360381019061017d9190610a44565b610596565b005b61019e60048036038101906101999190610954565b61066d565b6040516101ab9190610b10565b60405180910390f35b6101bc610685565b6040516101c99190610aee565b60405180910390f35b6101ec60048036038101906101e79190610a44565b610713565b005b61020860048036038101906102039190610a08565b6107ea565b6040516102159190610ad3565b60405180910390f35b6102386004803603810190610233919061097d565b610905565b6040516102459190610b10565b60405180910390f35b6003805461025b90610c74565b80601f016020809104026020016040519081016040528092919081815260200182805461028790610c74565b80156102d45780601f106102a9576101008083540402835291602001916102d4565b820191906000526020600020905b8154815290600101906020018083116102b757829003601f168201915b505050505081565b600081600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516103bc9190610b10565b60405180910390a36001905092915050565b60025481565b600081600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546104629190610bb8565b92505081905550816000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546104b79190610bb8565b92505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461050c9190610b62565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516105709190610b10565b60405180910390a3600190509392505050565b600560009054906101000a900460ff1681565b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546105e49190610bb8565b9250508190555080600260008282546105fd9190610bb8565b92505081905550600073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516106629190610b10565b60405180910390a350565b60006020528060005260406000206000915090505481565b6004805461069290610c74565b80601f01602080910402602001604051908101604052809291908181526020018280546106be90610c74565b801561070b5780601f106106e05761010080835404028352916020019161070b565b820191906000526020600020905b8154815290600101906020018083116106ee57829003601f168201915b505050505081565b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546107619190610b62565b92505081905550806002600082825461077a9190610b62565b925050819055503373ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516107df9190610b10565b60405180910390a350565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461083a9190610bb8565b92505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461088f9190610b62565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516108f39190610b10565b60405180910390a36001905092915050565b6001602052816000526040600020602052806000526040600020600091509150505481565b60008135905061093981610d15565b92915050565b60008135905061094e81610d2c565b92915050565b60006020828403121561096657600080fd5b60006109748482850161092a565b91505092915050565b6000806040838503121561099057600080fd5b600061099e8582860161092a565b92505060206109af8582860161092a565b9150509250929050565b6000806000606084860312156109ce57600080fd5b60006109dc8682870161092a565b93505060206109ed8682870161092a565b92505060406109fe8682870161093f565b9150509250925092565b60008060408385031215610a1b57600080fd5b6000610a298582860161092a565b9250506020610a3a8582860161093f565b9150509250929050565b600060208284031215610a5657600080fd5b6000610a648482850161093f565b91505092915050565b610a7681610bfe565b82525050565b6000610a8782610b46565b610a918185610b51565b9350610aa1818560208601610c41565b610aaa81610d04565b840191505092915050565b610abe81610c2a565b82525050565b610acd81610c34565b82525050565b6000602082019050610ae86000830184610a6d565b92915050565b60006020820190508181036000830152610b088184610a7c565b905092915050565b6000602082019050610b256000830184610ab5565b92915050565b6000602082019050610b406000830184610ac4565b92915050565b600081519050919050565b600082825260208201905092915050565b6000610b6d82610c2a565b9150610b7883610c2a565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610bad57610bac610ca6565b5b828201905092915050565b6000610bc382610c2a565b9150610bce83610c2a565b925082821015610be157610be0610ca6565b5b828203905092915050565b6000610bf782610c0a565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60005b83811015610c5f578082015181840152602081019050610c44565b83811115610c6e576000848401525b50505050565b60006002820490506001821680610c8c57607f821691505b60208210811415610ca057610c9f610cd5565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f8301169050919050565b610d1e81610bec565b8114610d2957600080fd5b50565b610d3581610c2a565b8114610d4057600080fd5b5056fea264697066735822122054793defcdde024a27f6e3fee0a20e3752f26b010918f9abf6225be94424006864736f6c63430008040033' 16 | # 创建合约工厂 17 | contract_factory = w3.eth.contract(abi=abi, bytecode=bytecode) 18 | 19 | # 部署合约 20 | tx_hash = contract_factory.constructor("WTF Token", "WTF").build_transaction({ 21 | 'from': wallet.address, 22 | 'nonce': w3.eth.get_transaction_count(wallet.address), 23 | 'gas': 2000000, 24 | 'gasPrice': w3.to_wei('50', 'gwei') 25 | }) 26 | 27 | signed_tx = wallet.signTransaction(tx_hash) 28 | 29 | tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction) 30 | tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash) 31 | 32 | # 获取部署的合约地址 33 | contract_address = tx_receipt.contractAddress 34 | print("合约部署成功,合约地址为:", contract_address) 35 | # 创建合约实例 36 | contract_instance = w3.eth.contract(address=contract_address, abi=abi) 37 | 38 | # 调用合约的name()和symbol()函数打印代币名称和代号 39 | token_name = contract_instance.functions.name().call() 40 | token_symbol = contract_instance.functions.symbol().call() 41 | print("代币名称:", token_name) 42 | print("代币代号:", token_symbol) 43 | 44 | amount = 10000 45 | # 调用mint()函数给自己铸造10,000枚代币 46 | tx_hash = contract_instance.functions.mint(amount).build_transaction({ 47 | 'from': wallet.address, 48 | 'nonce': w3.eth.get_transaction_count(wallet.address), 49 | 'gas': 2000000, 50 | 'gasPrice': w3.to_wei('50', 'gwei') 51 | }) 52 | 53 | signed_tx = wallet.signTransaction(tx_hash) 54 | 55 | tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction) 56 | tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash) 57 | 58 | print("铸造代币成功") 59 | # 查询铸造后账户的WTF代币余额 60 | balance = contract_instance.functions.balanceOf(wallet.address).call() 61 | print("WTF代币余额:", balance) 62 | # 调用transfer()函数,给Vitalik转账1000代币 63 | tx_hash = contract_instance.functions.transfer("vitalik.eth", 1000).build_transaction({ 64 | 'from': wallet.address, 65 | 'nonce': w3.eth.get_transaction_count(wallet.address), 66 | 'gas': 2000000, 67 | 'gasPrice': w3.to_wei('50', 'gwei') 68 | }) 69 | 70 | signed_tx = wallet.signTransaction(tx_hash) 71 | 72 | tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction) 73 | tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash) 74 | 75 | print("等待交易上链") 76 | # 查询Vitalik钱包中的代币余额 77 | vitalik_balance = contract_instance.functions.balanceOf("vitalik.eth").call() 78 | print(f"Vitalik钱包中的代币余额: {vitalik_balance}") -------------------------------------------------------------------------------- /06 _DeployContract/img/6-1-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/06 _DeployContract/img/6-1-2.png -------------------------------------------------------------------------------- /06 _DeployContract/img/6-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/06 _DeployContract/img/6-1.png -------------------------------------------------------------------------------- /06 _DeployContract/img/6-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/06 _DeployContract/img/6-2.png -------------------------------------------------------------------------------- /06 _DeployContract/img/6-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/06 _DeployContract/img/6-3.png -------------------------------------------------------------------------------- /06 _DeployContract/img/6-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/06 _DeployContract/img/6-4.png -------------------------------------------------------------------------------- /06 _DeployContract/readme.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 6. 部署合约 3 | tags: 4 | - web3py 5 | - provider 6 | - wallet 7 | - contract 8 | - create 9 | - frontend 10 | - web 11 | --- 12 | 13 | # Ethers极简入门: 6. 部署合约 14 | 15 | **WTF Academy社群:** [官网 wtf.academy](https://wtf.academy) | [WTF Solidity教程](https://github.com/AmazingAng/WTF-Solidity) | [discord](https://discord.gg/5akcruXrsk) | [微信群申请](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) 16 | 17 | 18 | ----- 19 | 20 | 这一讲,我们将介绍`web3.py`中的合约部署过程,并以部署一个合约为例帮助大家理解本节内容。我们将介绍`we3py`中的合约工厂`ContractFactory`类型,并利用它部署合约。 21 | 22 | ## 部署智能合约 23 | 24 | 在以太坊上,智能合约的部署是一种特殊的交易:将编译智能合约得到的字节码发送到0地址。如果这个合约的构造函数有参数的话,需要利用`abi.encode`将参数编码为字节码,然后附在在合约字节码的尾部一起发送。对于ABI编码的介绍见WTF Solidity极简教程[第27讲 ABI编码](https://github.com/AmazingAng/WTFSolidity/blob/main/27_ABIEncode/readme.md)。 25 | 26 | ## 合约工厂 27 | 28 | `web3py`拥有合约工厂`ContractFactory`类型,方便开发者部署合约。你可以利用合约`abi`,编译得到的字节码`bytecode`来创建合约工厂实例,为部署合约做准备。 29 | 30 | ```py 31 | #构造合约工厂 32 | contract_factory = w3.eth.contract(abi=abi, bytecode=bytecode) 33 | ``` 34 | **注意**:其中`w3`为我们提前定义的`provider`,如果合约的构造函数有参数,那么在`abi`中必须包含构造函数。 35 | 36 | 在创建好合约工厂实例之后,可以调用它的`constructor`函数并传入合约构造函数的参数`args`来构建部署交易信息: 37 | ```py 38 | # 部署合约 39 | tx_hash = contract_factory.constructor(args).transact() 40 | # 等待交易完成 41 | tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash) 42 | #打印合约地址 43 | contract_address = tx_receipt['contractAddress'] 44 | ``` 45 | 46 | ## 例子:部署ERC20代币合约 47 | 48 | `ERC20`标准代币合约的介绍见WTF Solidity极简教程[第31讲 ERC20](https://github.com/AmazingAng/WTFSolidity/blob/main/31_ERC20/readme.md)。 49 | 50 | 1. 创建`provider`和`wallet`变量。 51 | ```py 52 | from eth_account import Account 53 | from web3 import Web3, contract 54 | # 连利用infura提供的节点连接到以太坊测试网 55 | infura_url = "https://goerli.infura.io/v3/22b26b228b004ccc9325066db8b5c468" 56 | w3 = Web3(Web3.HTTPProvider(infura_url)) 57 | 58 | # 创建钱包对象 59 | private_key = "e08ca922fedbc3d37fa677f1d8f7e8fcbe42d031186bcbbc763d20cbdac81f9d" 60 | wallet = Account.from_key(private_key) 61 | ``` 62 | 63 | 2. 准备ERC20合约的字节码和ABI。因为ERC20的构造函数含有参数,因此我们必须把它包含在ABI中。合约的`abi`可以直接在`remix`编译界面电机`abi`按钮复制,字节码可以从`remix`的编译面板中点击`Bytecode`按钮,把它复制下来,其中"object"字段对应的数据就是字节码。如果部署在链上的合约,你可以在etherscan的Contract页面的`Contract Creation Code`中找到。 64 | 65 | ```py 66 | # ERC20的合约abi 67 | abi = [{"inputs": [{"internalType": "string","name": "name_","type": "string"}, {"internalType": "string","name": "symbol_","type": "string"}], "stateMutability": "nonpayable","type": "constructor"}, {"anonymous": False,"inputs": [{"indexed": True,"internalType": "address","name": "owner","type": "address"},{"indexed": True,"internalType": "address","name": "spender","type": "address"},{"indexed": False,"internalType": "uint256","name": "value","type": "uint256"}],"name": "Approval","type": "event"},{"anonymous": False,"inputs": [{"indexed": True,"internalType": "address","name": "from","type": "address"},{"indexed": True,"internalType": "address","name": "to","type": "address"},{"indexed": False,"internalType": "uint256","name": "value","type": "uint256"}],"name": "Transfer","type": "event"},{"inputs": [{"internalType": "address","name": "","type": "address"},{"internalType": "address","name": "","type": "address"}],"name": "allowance","outputs": [{"internalType": "uint256","name": "","type": "uint256"}],"stateMutability": "view","type": "function"},{"inputs": [{"internalType": "address","name": "spender","type": "address"},{"internalType": "uint256","name": "amount","type": "uint256"}],"name": "approve","outputs": [{"internalType": "bool","name": "","type": "bool"}],"stateMutability": "nonpayable","type": "function"},{"inputs": [{"internalType": "address","name": "","type": "address"}],"name": "balanceOf","outputs": [{"internalType": "uint256","name": "","type": "uint256"}],"stateMutability": "view","type": "function"},{"inputs": [{"internalType": "uint256","name": "amount","type": "uint256"}],"name": "burn","outputs": [],"stateMutability": "nonpayable","type": "function"},{"inputs": [],"name": "decimals","outputs": [{"internalType": "uint8","name": "","type": "uint8"}],"stateMutability": "view","type": "function"},{"inputs": [{"internalType": "uint256","name": "amount","type": "uint256"}],"name": "mint","outputs": [],"stateMutability": "nonpayable","type": "function"},{"inputs": [],"name": "name","outputs": [{"internalType": "string","name": "","type": "string"}],"stateMutability": "view","type": "function"},{"inputs": [],"name": "symbol","outputs": [{"internalType": "string","name": "","type": "string"}],"stateMutability": "view","type": "function"},{"inputs": [],"name": "totalSupply","outputs": [{"internalType": "uint256","name": "","type": "uint256"}],"stateMutability": "view","type": "function"},{"inputs": [{"internalType": "address","name": "recipient","type": "address"},{"internalType": "uint256","name": "amount","type": "uint256"}],"name": "transfer","outputs": [{"internalType": "bool","name": "","type": "bool"}],"stateMutability": "nonpayable","type": "function"},{"inputs": [{"internalType": "address","name": "sender","type": "address"},{"internalType": "address","name": "recipient","type": "address"},{"internalType": "uint256","name": "amount","type": "uint256"}],"name": "transferFrom","outputs": [{"internalType": "bool","name": "","type": "bool"}],"stateMutability": "nonpayable","type": "function"}] 68 | # 合约ABI 69 | # 填入合约字节码,在remix中,你可以在两个地方找到Bytecode 70 | # 1. 编译面板的Bytecode按钮 71 | #2. 文件面板artifact文件夹下与合约同名的json文件中 72 | # 里面"bytecode"属性下的"object"字段对应的数据就是Bytecode。 73 | bytecode = '60806040526012600560006101000a81548160ff021916908360ff1602179055503480156200002d57600080fd5b506040516200111b3803806200111b8339818101604052810190620000539190620001af565b81600390805190602001906200006b9291906200008d565b508060049080519060200190620000849291906200008d565b50505062000392565b8280546200009b90620002b7565b90600052602060002090601f016020900481019282620000bf57600085556200010b565b82601f10620000da57805160ff19168380011785556200010b565b828001600101855582156200010b579182015b828111156200010a578251825591602001919060010190620000ed565b5b5090506200011a91906200011e565b5090565b5b80821115620001395760008160009055506001016200011f565b5090565b6000620001546200014e846200024b565b62000222565b9050828152602081018484840111156200016d57600080fd5b6200017a84828562000281565b509392505050565b600082601f8301126200019457600080fd5b8151620001a68482602086016200013d565b91505092915050565b60008060408385031215620001c357600080fd5b600083015167ffffffffffffffff811115620001de57600080fd5b620001ec8582860162000182565b925050602083015167ffffffffffffffff8111156200020a57600080fd5b620002188582860162000182565b9150509250929050565b60006200022e62000241565b90506200023c8282620002ed565b919050565b6000604051905090565b600067ffffffffffffffff82111562000269576200026862000352565b5b620002748262000381565b9050602081019050919050565b60005b83811015620002a157808201518184015260208101905062000284565b83811115620002b1576000848401525b50505050565b60006002820490506001821680620002d057607f821691505b60208210811415620002e757620002e662000323565b5b50919050565b620002f88262000381565b810181811067ffffffffffffffff821117156200031a576200031962000352565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b610d7980620003a26000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c806342966c681161007157806342966c681461016857806370a082311461018457806395d89b41146101b4578063a0712d68146101d2578063a9059cbb146101ee578063dd62ed3e1461021e576100a9565b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100fc57806323b872dd1461011a578063313ce5671461014a575b600080fd5b6100b661024e565b6040516100c39190610aee565b60405180910390f35b6100e660048036038101906100e19190610a08565b6102dc565b6040516100f39190610ad3565b60405180910390f35b6101046103ce565b6040516101119190610b10565b60405180910390f35b610134600480360381019061012f91906109b9565b6103d4565b6040516101419190610ad3565b60405180910390f35b610152610583565b60405161015f9190610b2b565b60405180910390f35b610182600480360381019061017d9190610a44565b610596565b005b61019e60048036038101906101999190610954565b61066d565b6040516101ab9190610b10565b60405180910390f35b6101bc610685565b6040516101c99190610aee565b60405180910390f35b6101ec60048036038101906101e79190610a44565b610713565b005b61020860048036038101906102039190610a08565b6107ea565b6040516102159190610ad3565b60405180910390f35b6102386004803603810190610233919061097d565b610905565b6040516102459190610b10565b60405180910390f35b6003805461025b90610c74565b80601f016020809104026020016040519081016040528092919081815260200182805461028790610c74565b80156102d45780601f106102a9576101008083540402835291602001916102d4565b820191906000526020600020905b8154815290600101906020018083116102b757829003601f168201915b505050505081565b600081600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516103bc9190610b10565b60405180910390a36001905092915050565b60025481565b600081600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546104629190610bb8565b92505081905550816000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546104b79190610bb8565b92505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461050c9190610b62565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516105709190610b10565b60405180910390a3600190509392505050565b600560009054906101000a900460ff1681565b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546105e49190610bb8565b9250508190555080600260008282546105fd9190610bb8565b92505081905550600073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516106629190610b10565b60405180910390a350565b60006020528060005260406000206000915090505481565b6004805461069290610c74565b80601f01602080910402602001604051908101604052809291908181526020018280546106be90610c74565b801561070b5780601f106106e05761010080835404028352916020019161070b565b820191906000526020600020905b8154815290600101906020018083116106ee57829003601f168201915b505050505081565b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546107619190610b62565b92505081905550806002600082825461077a9190610b62565b925050819055503373ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516107df9190610b10565b60405180910390a350565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461083a9190610bb8565b92505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461088f9190610b62565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516108f39190610b10565b60405180910390a36001905092915050565b6001602052816000526040600020602052806000526040600020600091509150505481565b60008135905061093981610d15565b92915050565b60008135905061094e81610d2c565b92915050565b60006020828403121561096657600080fd5b60006109748482850161092a565b91505092915050565b6000806040838503121561099057600080fd5b600061099e8582860161092a565b92505060206109af8582860161092a565b9150509250929050565b6000806000606084860312156109ce57600080fd5b60006109dc8682870161092a565b93505060206109ed8682870161092a565b92505060406109fe8682870161093f565b9150509250925092565b60008060408385031215610a1b57600080fd5b6000610a298582860161092a565b9250506020610a3a8582860161093f565b9150509250929050565b600060208284031215610a5657600080fd5b6000610a648482850161093f565b91505092915050565b610a7681610bfe565b82525050565b6000610a8782610b46565b610a918185610b51565b9350610aa1818560208601610c41565b610aaa81610d04565b840191505092915050565b610abe81610c2a565b82525050565b610acd81610c34565b82525050565b6000602082019050610ae86000830184610a6d565b92915050565b60006020820190508181036000830152610b088184610a7c565b905092915050565b6000602082019050610b256000830184610ab5565b92915050565b6000602082019050610b406000830184610ac4565b92915050565b600081519050919050565b600082825260208201905092915050565b6000610b6d82610c2a565b9150610b7883610c2a565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610bad57610bac610ca6565b5b828201905092915050565b6000610bc382610c2a565b9150610bce83610c2a565b925082821015610be157610be0610ca6565b5b828203905092915050565b6000610bf782610c0a565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60005b83811015610c5f578082015181840152602081019050610c44565b83811115610c6e576000848401525b50505050565b60006002820490506001821680610c8c57607f821691505b60208210811415610ca057610c9f610cd5565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f8301169050919050565b610d1e81610bec565b8114610d2957600080fd5b50565b610d3581610c2a565b8114610d4057600080fd5b5056fea264697066735822122054793defcdde024a27f6e3fee0a20e3752f26b010918f9abf6225be94424006864736f6c63430008040033' 74 | 75 | ``` 76 | 77 | ![Remix中获取字节码和abi](img/6-1.png) 78 | ![json](img/6-1-2.png) 79 | 80 | 3. 创建合约工厂`ContractFactory`实例。 81 | 82 | ```py 83 | 84 | # 创建合约工厂 85 | contract_factory = w3.eth.contract(abi=abi, bytecode=bytecode) 86 | ``` 87 | 88 | 4. 调用工厂合约的`construct()`函数并填入构造函数的参数(代币名称和代号),部署`ERC20`代币合约并获得合约实例。在`build_transaction`中构建参数: 89 | - `from`:表示交易发送方的以太坊地址。这是用于支付交易费用的账户。 90 | - `nonce`:表示发送方账户的交易计数器。它确保每个交易都具有唯一的标识符,并按照顺序执行。 91 | - `gas`:表示交易所能消耗的最大燃气数量。 92 | - `gasPrice`:表示以太坊网络中每单位燃气的价格。 93 | 94 | ```py 95 | # 部署合约 96 | tx_hash = contract_factory.constructor("WTF Token", "WTF").build_transaction({ 97 | 'from': wallet.address, 98 | 'nonce': w3.eth.get_transaction_count(wallet.address), 99 | 'gas': 2000000, 100 | 'gasPrice': w3.to_wei('50', 'gwei') 101 | }) 102 | 103 | signed_tx = wallet.signTransaction(tx_hash) 104 | 105 | tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction) 106 | tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash) 107 | 108 | # 获取部署的合约地址 109 | contract_address = tx_receipt.contractAddress 110 | print("合约部署成功,合约地址为:", contract_address) 111 | ``` 112 | 113 | ![部署合约](img/6-2.png) 114 | 115 | 5. 在合约上链后,调用`name()`和`symbol()`函数打印代币名称和代号,然后调用`mint()`函数给自己铸造`10,000`枚代币。 116 | 117 | ```py 118 | # 调用合约的name()和symbol()函数打印代币名称和代号 119 | token_name = contract_instance.functions.name().call() 120 | token_symbol = contract_instance.functions.symbol().call() 121 | print("代币名称:", token_name) 122 | print("代币代号:", token_symbol) 123 | # 将整数10000转换为uint256类型 124 | amount = 10000 125 | # 调用mint()函数给自己铸造10,000枚代币 126 | tx_hash = contract_instance.functions.mint(amount).build_transaction({ 127 | 'from': wallet.address, 128 | 'nonce': w3.eth.get_transaction_count(wallet.address), 129 | 'gas': 2000000, 130 | 'gasPrice': w3.to_wei('50', 'gwei') 131 | }) 132 | 133 | signed_tx = wallet.signTransaction(tx_hash) 134 | 135 | tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction) 136 | tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash) 137 | 138 | print("铸造代币成功") 139 | # 查询铸造后账户的WTF代币余额 140 | balance = contract_instance.functions.balanceOf(wallet.address).call() 141 | print("WTF代币余额:", balance) 142 | 143 | ``` 144 | ![铸造代币](img/6-3.png) 145 | 146 | 6. 调用`transfer()`函数,给Vitalik转账`1,000`枚代币。 147 | 148 | ```py 149 | # 调用transfer()函数,给Vitalik转账1000代币 150 | tx_hash = contract_instance.functions.transfer("vitalik.eth", 1000).build_transaction({ 151 | 'from': wallet.address, 152 | 'nonce': w3.eth.get_transaction_count(wallet.address), 153 | 'gas': 2000000, 154 | 'gasPrice': w3.to_wei('50', 'gwei') 155 | }) 156 | 157 | signed_tx = wallet.signTransaction(tx_hash) 158 | 159 | tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction) 160 | tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash) 161 | 162 | print("等待交易上链") 163 | # 查询Vitalik钱包中的代币余额 164 | vitalik_balance = contract_instance.functions.balanceOf("vitalik.eth").call() 165 | print(f"Vitalik钱包中的代币余额: {vitalik_balance}") 166 | ``` 167 | 168 | ![转账](img/6-4.png) 169 | 170 | ## 总结 171 | 172 | 这一讲我们介绍了web3.py中的合约工厂`ContractFactory`类型,利用它部署了一个`ERC20`代币合约,并给Vitalik转账了`1,000`枚代币。 173 | -------------------------------------------------------------------------------- /11_StaticCall/StaticCall.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from web3 import Web3\n", 10 | "from ens import ENS\n", 11 | "# 准备 alchemy API \n", 12 | "# 可以参考https://github.com/AmazingAng/WTFSolidity/blob/main/Topics/Tools/TOOL04_Alchemy/readme.md \n", 13 | "ALCHEMY_MAINNET_URL = 'https://eth-mainnet.g.alchemy.com/v2/oKmOQKbneVkxgHZfibs-iFhIlIAl6HDN'\n", 14 | "Provider = Web3(Web3.HTTPProvider(ALCHEMY_MAINNET_URL))\n", 15 | "Provider.is_connected()\n", 16 | "ns = ENS.from_web3(Provider)" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": 2, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "from eth_account import Account\n", 26 | "private_key = \"0x227dbb8586117d55284e26620bc76534dfbd2394be34cf4a09cb775d593b6f2b\"\n", 27 | "wallet = Account.from_key(private_key)" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": 3, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "# DAI合约地址\n", 37 | "AddressDAI = Provider.to_checksum_address(\"0x6B175474E89094C44Da98b954EedeAC495271d0F\")\n", 38 | "# DAI合约的abi,只包含后续调用的transfer和balanceOf方法\n", 39 | "abiDAI = [\n", 40 | " {\n", 41 | " \"inputs\": [\n", 42 | " {\"internalType\": \"address\", \"name\": \"dst\", \"type\": \"address\"},\n", 43 | " {\"internalType\": \"uint256\", \"name\": \"wad\", \"type\": \"uint256\"},\n", 44 | " ],\n", 45 | " \"name\": \"transfer\",\n", 46 | " \"outputs\": [{\"internalType\": \"bool\", \"name\": \"\", \"type\": \"bool\"}],\n", 47 | " \"stateMutability\": \"nonpayable\",\n", 48 | " \"type\": \"function\",\n", 49 | " },\n", 50 | " {\n", 51 | " \"inputs\": [{\"internalType\": \"address\", \"name\": \"\", \"type\": \"address\"}],\n", 52 | " \"name\": \"balanceOf\",\n", 53 | " \"outputs\": [{\"internalType\": \"uint256\", \"name\": \"\", \"type\": \"uint256\"}],\n", 54 | " \"stateMutability\": \"view\",\n", 55 | " \"type\": \"function\",\n", 56 | " }\n", 57 | "]\n", 58 | "# 创建DAI合约实例\n", 59 | "contractDAI = Provider.eth.contract(address = AddressDAI, abi = abiDAI)" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": 4, 65 | "metadata": {}, 66 | "outputs": [ 67 | { 68 | "name": "stdout", 69 | "output_type": "stream", 70 | "text": [ 71 | "1. 读取测试钱包的DAI余额\n" 72 | ] 73 | }, 74 | { 75 | "name": "stdout", 76 | "output_type": "stream", 77 | "text": [ 78 | "DAI的持仓为: 0\n" 79 | ] 80 | } 81 | ], 82 | "source": [ 83 | "# 获取钱包地址\n", 84 | "address = wallet.address\n", 85 | "print(\"1. 读取测试钱包的DAI余额\")\n", 86 | "balanceDAI = contractDAI.caller.balanceOf(address)\n", 87 | "print(\"DAI的持仓为:\", balanceDAI)" 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": 6, 93 | "metadata": {}, 94 | "outputs": [ 95 | { 96 | "name": "stdout", 97 | "output_type": "stream", 98 | "text": [ 99 | "2. 用call尝试调用transfer转账1 DAI,msg.sender为Vitalik地址\n", 100 | "本次交易成功\n" 101 | ] 102 | } 103 | ], 104 | "source": [ 105 | "print(\"2. 用call尝试调用transfer转账1 DAI,msg.sender为Vitalik地址\")\n", 106 | "try:\n", 107 | " tx = contractDAI.functions.transfer(ns.address(\"vitalik.eth\"),Provider.to_wei(1,'ether')).call({\"from\":ns.address(\"vitalik.eth\")})\n", 108 | " print(f\"本次交易{'成功' if tx else '失败'}\")\n", 109 | "except Exception as error:\n", 110 | " print(f\"交易失败,失败原因为{error}\")" 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": 7, 116 | "metadata": {}, 117 | "outputs": [ 118 | { 119 | "name": "stdout", 120 | "output_type": "stream", 121 | "text": [ 122 | "3. 用call尝试调用transfer转账10000 DAI,msg.sender为自己的钱包地址\n", 123 | "交易失败,失败原因为execution reverted: Dai/insufficient-balance\n" 124 | ] 125 | } 126 | ], 127 | "source": [ 128 | "print(\"3. 用call尝试调用transfer转账10000 DAI,msg.sender为自己的钱包地址\")\n", 129 | "try:\n", 130 | " tx = contractDAI.functions.transfer(ns.address(\"vitalik.eth\"),Provider.to_wei(10000,'ether')).call({\"from\":address})\n", 131 | " print(f\"本次交易{'成功' if tx else '失败'}\")\n", 132 | "except Exception as error:\n", 133 | " print(f\"交易失败,失败原因为{error}\")" 134 | ] 135 | } 136 | ], 137 | "metadata": { 138 | "kernelspec": { 139 | "display_name": "blockchain", 140 | "language": "python", 141 | "name": "python3" 142 | }, 143 | "language_info": { 144 | "codemirror_mode": { 145 | "name": "ipython", 146 | "version": 3 147 | }, 148 | "file_extension": ".py", 149 | "mimetype": "text/x-python", 150 | "name": "python", 151 | "nbconvert_exporter": "python", 152 | "pygments_lexer": "ipython3", 153 | "version": "3.12.0" 154 | } 155 | }, 156 | "nbformat": 4, 157 | "nbformat_minor": 2 158 | } 159 | -------------------------------------------------------------------------------- /11_StaticCall/img/11-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/11_StaticCall/img/11-1.png -------------------------------------------------------------------------------- /11_StaticCall/img/11-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/11_StaticCall/img/11-2.png -------------------------------------------------------------------------------- /11_StaticCall/img/11-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/11_StaticCall/img/11-3.png -------------------------------------------------------------------------------- /11_StaticCall/readme.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 11. StaticCall 3 | tags: 4 | - web3.py 5 | - python 6 | - staticcall 7 | - web 8 | --- 9 | # Web3py极简入门: 11. StaticCall 10 | 11 | 我最近在重新学 `web3.py`,巩固一下细节,也写一个 `WTF web3py极简入门`,供小白们使用。 12 | 13 | **推特**:[@0xAA_Science](https://twitter.com/0xAA_Science) 14 | 15 | **WTF Academy社群:** [官网 wtf.academy](https://wtf.academy) | [WTF Solidity教程](https://github.com/AmazingAng/WTFSolidity) | [discord](https://discord.gg/5akcruXrsk) | [微信群申请](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) 16 | 17 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-web3py](https://github.com/WTFAcademy/WTF-web3py) 18 | 19 | --- 20 | 21 | 这一讲,我们将介绍合约函数类`ContractFunction`的`call`方法,用该方法在发送交易之前检查交易是否会失败,可以节省大量gas。 22 | 23 | 在[web3py极简入门: 5. 合约交互](https://github.com/WTFAcademy/WTF-web3py/tree/main/05%20_WriteContract)中,已经使用过`call`函数进行`view`函数的调用,本节主要利用该方法进行非`pure/view`函数的调用。 24 | 25 | ## 以太坊节点的 `eth_call`方法 26 | 27 | 在调用以太坊上的智能合约时,凡是利用链上资源进行计算的操作,都需要支付相应的gas,即使调用失败,费用也不会退还。 28 | 29 | 因此,我们在进行转账等操作时,最好先在本地模拟转账操作,以减少因调用失败而造成的gas损失。 30 | 31 | 以太坊节点提供了 `eth_call`方法,可以在不发送交易(不消耗gas费)的情况下,模拟合约调用的结果,从而检查合约调用是否会失败。 32 | 33 | `web3.py`中的 `ContractFunction.call`、`ContractFunction.estimate_gas`、`Contract.fallback.call`等都是基于 `eth_call`API实现的。 34 | 35 | ## `call` 36 | 37 | `call`方法的具体调用方式如下: 38 | 39 | ```python 40 | 合约实例.functions.函数名(参数).call(transaction, block_identifier) 41 | ``` 42 | 43 | - 函数名:为模拟调用的函数名。 44 | - 参数:为合约中被调用函数的参数。 45 | - transaction:选填,可以包含以下参数: 46 | - `from`:执行时的 `msg.sender`,可以是任何人的地址,形式可以是 `bytes`、`checksum address`或 `ENS name`。 47 | - `value`:执行时的 `msg.value`。 48 | - `gas` 49 | - `maxFeePerGas` 50 | - `gasPrice` 51 | - `nonce` 52 | - `maxPriorityFeePerGas` 53 | - block_identifier: 选填,为模拟执行时的区块高度,默认值为 `"latest"`。 54 | 55 | 如果函数调用成功,将返回函数本身的返回值;如果函数调用失败,它将抛出异常。 56 | 57 | ## 用 `call`模拟 `DAI`转账 58 | 59 | 1. 创建 `Provider`。 60 | 61 | ```python 62 | from web3 import Web3 63 | from ens import ENS 64 | # 准备 alchemy API 65 | # 可以参考https://github.com/AmazingAng/WTFSolidity/blob/main/Topics/Tools/TOOL04_Alchemy/readme.md 66 | ALCHEMY_MAINNET_URL = 'https://eth-mainnet.g.alchemy.com/v2/oKmOQKbneVkxgHZfibs-iFhIlIAl6HDN' 67 | Provider = Web3(Web3.HTTPProvider(ALCHEMY_MAINNET_URL)) 68 | Provider.is_connected() 69 | # 创建以太坊域名服务对象,用于获取vitalk.eth的地址 70 | ns = ENS.from_web3(Provider) 71 | ``` 72 | 2. 创建钱包 ``wallet``对象 73 | 74 | ```PYTHON 75 | from eth_account import Account 76 | private_key = "0x227dbb8586117d55284e26620bc76534dfbd2394be34cf4a09cb775d593b6f2b" 77 | wallet = Account.from_key(private_key) 78 | ``` 79 | 3. 创建 `DAI`合约对象。 80 | 81 | ```PYTHON 82 | # DAI合约地址 83 | AddressDAI = Provider.to_checksum_address("0x6B175474E89094C44Da98b954EedeAC495271d0F") 84 | # DAI合约的abi,只包含后续调用的transfer和balanceOf方法 85 | abiDAI = [ 86 | { 87 | "inputs": [ 88 | {"internalType": "address", "name": "dst", "type": "address"}, 89 | {"internalType": "uint256", "name": "wad", "type": "uint256"}, 90 | ], 91 | "name": "transfer", 92 | "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], 93 | "stateMutability": "nonpayable", 94 | "type": "function", 95 | }, 96 | { 97 | "inputs": [{"internalType": "address", "name": "", "type": "address"}], 98 | "name": "balanceOf", 99 | "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], 100 | "stateMutability": "view", 101 | "type": "function", 102 | } 103 | ] 104 | # 创建DAI合约实例 105 | contractDAI = Provider.eth.contract(address = AddressDAI, abi = abiDAI) 106 | ``` 107 | 4. 查看钱包中 `DAI`余额,为0。 108 | 109 | ```PYTHON 110 | # 获取钱包地址 111 | address = wallet.address 112 | print("1. 读取测试钱包的DAI余额") 113 | balanceDAI = contractDAI.caller.balanceOf(address) 114 | print("DAI的持仓为:", balanceDAI) 115 | ``` 116 | 117 | ![钱包地址中DAI的持仓](img/11-1.png) 118 | 5. 用 `call`调用 `transfer()`函数,将 `from`参数填为Vitalik地址,模拟Vitalik转账 `1 DAI`。这笔交易将成功,因为Vitalik钱包有充足的 `DAI`。 119 | 120 | ```PYTHON 121 | print("2. 用call尝试调用transfer转账1 DAI,msg.sender为Vitalik地址") 122 | try: 123 | tx = contractDAI.functions.transfer(ns.address("vitalik.eth"),Provider.to_wei(1,'ether')).call({"from":ns.address("vitalik.eth")}) 124 | print(f"本次交易{'成功' if tx else '失败'}") 125 | except Exception as error: 126 | print(f"交易失败,失败原因为{error}") 127 | ``` 128 | 129 | ![交易成功](img/11-2.png) 130 | 6. 用 `call`调用 `transfer()`函数,将 `from`参数填为测试钱包地址,模拟转账 `10000 DAI`。这笔交易将失败,报错,并返回原因 `Dai/insufficient-balance`。 131 | 132 | ```PYTHON 133 | print("3. 用call尝试调用transfer转账10000 DAI,msg.sender为自己的钱包地址") 134 | try: 135 | tx = contractDAI.functions.transfer(ns.address("vitalik.eth"),Provider.to_wei(10000,'ether')).call({"from":address}) 136 | print(f"本次交易{'成功' if tx else '失败'}") 137 | except Exception as error: 138 | print(f"交易失败,失败原因为{error}") 139 | ``` 140 | 141 | ![交易失败](img/11-3.png) 142 | 143 | ## 总结 144 | 145 | 本节介绍如何通过 `call`方法在本地模拟合约函数调用,它利用了以太坊节点的 `eth_call`API,不在链上创建新的交易,因此无gas费消耗。 146 | -------------------------------------------------------------------------------- /12_ERC721Check/ERC721Check.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "data": { 10 | "text/plain": [ 11 | "True" 12 | ] 13 | }, 14 | "execution_count": 1, 15 | "metadata": {}, 16 | "output_type": "execute_result" 17 | } 18 | ], 19 | "source": [ 20 | "from web3 import Web3\n", 21 | "# 准备 alchemy API \n", 22 | "# 可以参考https://github.com/AmazingAng/WTFSolidity/blob/main/Topics/Tools/TOOL04_Alchemy/readme.md \n", 23 | "ALCHEMY_MAINNET_URL = 'https://eth-mainnet.g.alchemy.com/v2/oKmOQKbneVkxgHZfibs-iFhIlIAl6HDN'\n", 24 | "Provider = Web3(Web3.HTTPProvider(ALCHEMY_MAINNET_URL))\n", 25 | "Provider.is_connected()" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": 2, 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [ 34 | "# ERC721的合约abi,只需要name、symbol、supportsInterface三个函数\n", 35 | "abiERC721 = [\n", 36 | " {\"inputs\": [],\"name\": \"name\", \"outputs\": [{\"type\": \"string\"}], \"stateMutability\": \"view\", \"type\": \"function\"},\n", 37 | " {\"inputs\": [],\"name\": \"symbol\", \"outputs\": [{\"type\": \"string\"}], \"stateMutability\": \"view\", \"type\": \"function\"},\n", 38 | " {\n", 39 | " \"inputs\": [{\"type\": \"bytes4\"}],\n", 40 | " \"name\": \"supportsInterface\",\n", 41 | " \"outputs\": [{\"type\": \"bool\"}],\n", 42 | " \"stateMutability\": \"view\",\n", 43 | " \"type\": \"function\",\n", 44 | " }\n", 45 | "]\n", 46 | "# ERC721的合约地址,这里使用BAYC\n", 47 | "addressBAYC = Provider.to_checksum_address(\"0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d\")\n", 48 | "# 创建ERC721合约实例\n", 49 | "contractERC721 = Provider.eth.contract(address=addressBAYC, abi=abiERC721)" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": 3, 55 | "metadata": {}, 56 | "outputs": [ 57 | { 58 | "name": "stdout", 59 | "output_type": "stream", 60 | "text": [ 61 | "读取ERC721合约信息\n", 62 | "合约地址: 0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D\n", 63 | "合约名称: BoredApeYachtClub\n", 64 | "合约代号: BAYC\n" 65 | ] 66 | } 67 | ], 68 | "source": [ 69 | "# 1. 读取ERC721合约的链上信息\n", 70 | "nameERC721 = contractERC721.caller.name()\n", 71 | "symbolERC721 = contractERC721.caller.symbol()\n", 72 | "print(\"读取ERC721合约信息\")\n", 73 | "print(\"合约地址:\", addressBAYC)\n", 74 | "print(\"合约名称:\", nameERC721)\n", 75 | "print(\"合约代号:\", symbolERC721)" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": 4, 81 | "metadata": {}, 82 | "outputs": [ 83 | { 84 | "name": "stdout", 85 | "output_type": "stream", 86 | "text": [ 87 | "2. 利用ERC165的supportsInterface,确定合约是否为ERC721标准\n", 88 | "合约符合ERC721标准\n" 89 | ] 90 | } 91 | ], 92 | "source": [ 93 | "# 2. 利用ERC165的supportsInterface,确定合约是否为ERC721标准\n", 94 | "# ERC721接口的ERC165 identifier\n", 95 | "selectorERC721 = \"0x80ac58cd\"\n", 96 | "isERC721 = contractERC721.caller.supportsInterface(selectorERC721)\n", 97 | "print(\"2. 利用ERC165的supportsInterface,确定合约是否为ERC721标准\")\n", 98 | "print(f\"合约{'符合' if isERC721 else '不符合'}ERC721标准\")" 99 | ] 100 | } 101 | ], 102 | "metadata": { 103 | "kernelspec": { 104 | "display_name": "blockchain", 105 | "language": "python", 106 | "name": "python3" 107 | }, 108 | "language_info": { 109 | "codemirror_mode": { 110 | "name": "ipython", 111 | "version": 3 112 | }, 113 | "file_extension": ".py", 114 | "mimetype": "text/x-python", 115 | "name": "python", 116 | "nbconvert_exporter": "python", 117 | "pygments_lexer": "ipython3", 118 | "version": "3.12.0" 119 | } 120 | }, 121 | "nbformat": 4, 122 | "nbformat_minor": 2 123 | } 124 | -------------------------------------------------------------------------------- /12_ERC721Check/img/12-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/12_ERC721Check/img/12-1.jpg -------------------------------------------------------------------------------- /12_ERC721Check/img/12-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/12_ERC721Check/img/12-2.jpg -------------------------------------------------------------------------------- /12_ERC721Check/readme.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 12. 识别ERC721合约 3 | tags: 4 | - web3py 5 | - python 6 | - erc165 7 | - erc721 8 | - selector 9 | - web 10 | --- 11 | # web3py极简入门: 12. 识别ERC721合约 12 | 13 | 我最近在重新学 `web3.py`,巩固一下细节,也写一个 `WTF web3py极简入门`,供小白们使用。 14 | 15 | **推特**:[@0xAA_Science](https://twitter.com/0xAA_Science) 16 | 17 | **WTF Academy社群:** [官网 wtf.academy](https://wtf.academy) | [WTF Solidity教程](https://github.com/AmazingAng/WTFSolidity) | [discord](https://discord.gg/5akcruXrsk) | [微信群申请](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) 18 | 19 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-web3py](https://github.com/WTFAcademy/WTF-web3py) 20 | 21 | --- 22 | 23 | 这一讲,将介绍如何使用 `web3.py`识别一个合约是否为 `ERC721`标准。 24 | 25 | ## `ERC721` 26 | 27 | `ERC721`是以太坊上流行的非同质化代币(NFT)标准,如果对这个标准不熟悉,可以阅读[WTF Solidity第34讲 ERC721](https://github.com/AmazingAng/WTFSolidity/blob/main/34_ERC721/readme.md)。在做NFT相关产品时,我们需要筛选出符合 `ERC721`标准的合约。例如Opensea,他会自动识别 `ERC721`,并爬下它的名称、代号、metadata等数据用于展示。要识别 `ERC721`,我们先要理解 `ERC165`。 28 | 29 | ## `ERC165` 30 | 31 | 通过[ERC165标准](https://eips.ethereum.org/EIPS/eip-165),智能合约可以声明它支持的接口,供其他合约检查。因此,我们可以通过 `ERC165`来检查一个智能合约是不是支持了 `ERC721`的接口。 32 | 33 | `IERC165`接口合约只声明了一个 `supportsInterface`函数,输入要查询的 `interfaceId`接口id(类型为 `bytes4`),若合约实现了该接口id,则返回 `true`;反之,则返回 `false`: 34 | 35 | ```solidity 36 | interface IERC165 { 37 | /** 38 | * @dev 如果合约实现了查询的`interfaceId`,则返回true 39 | * 规则详见:https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] 40 | * 41 | */ 42 | function supportsInterface(bytes4 interfaceId) external view returns (bool); 43 | } 44 | ``` 45 | 46 | `ERC721`合约中会实现 `IERC165`接口合约的 `supportsInterface`函数,并且当查询 `0x80ac58cd`(`ERC721`接口id)时返回 `true`。 47 | 48 | ```solidity 49 | function supportsInterface(bytes4 interfaceId) 50 | external 51 | pure 52 | override 53 | returns (bool) 54 | { 55 | return 56 | interfaceId == type(IERC721).interfaceId 57 | } 58 | ``` 59 | 60 | ## 识别 `ERC721` 61 | 62 | 1. 创建 `provider`,连接以太坊主网。 63 | 64 | ```python 65 | from web3 import Web3 66 | # 准备 alchemy API 67 | # 可以参考https://github.com/AmazingAng/WTFSolidity/blob/main/Topics/Tools/TOOL04_Alchemy/readme.md 68 | ALCHEMY_MAINNET_URL = 'https://eth-mainnet.g.alchemy.com/v2/oKmOQKbneVkxgHZfibs-iFhIlIAl6HDN' 69 | Provider = Web3(Web3.HTTPProvider(ALCHEMY_MAINNET_URL)) 70 | Provider.is_connected() 71 | ``` 72 | 2. 创建 `ERC721`合约实例,在 `abi`接口中,我们声明要使用的 `name()`,`symbol()`,和 `supportsInterface()`函数即可,并且针对原始 `abi`进行了部分简化。这里我们用的BAYC的合约地址。 73 | 74 | ```python 75 | # ERC721的合约abi,只需要name、symbol、supportsInterface三个函数,这里对原始abi进行了简化 76 | abiERC721 = [ 77 | {"inputs": [],"name": "name", "outputs": [{"type": "string"}], "stateMutability": "view", "type": "function"}, 78 | {"inputs": [],"name": "symbol", "outputs": [{"type": "string"}], "stateMutability": "view", "type": "function"}, 79 | { 80 | "inputs": [{"type": "bytes4"}], 81 | "name": "supportsInterface", 82 | "outputs": [{"type": "bool"}], 83 | "stateMutability": "view", 84 | "type": "function", 85 | } 86 | ] 87 | # ERC721的合约地址,这里使用BAYC 88 | addressBAYC = Provider.to_checksum_address("0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d") 89 | # 创建ERC721合约实例 90 | contractERC721 = Provider.eth.contract(address=addressBAYC, abi=abiERC721) 91 | ``` 92 | 3. 读取合约的链上信息:名称和代号。 93 | 94 | ```python 95 | # 1. 读取ERC721合约的链上信息 96 | nameERC721 = contractERC721.caller.name() 97 | symbolERC721 = contractERC721.caller.symbol() 98 | print("读取ERC721合约信息") 99 | print("合约地址:", addressBAYC) 100 | print("合约名称:", nameERC721) 101 | print("合约代号:", symbolERC721) 102 | ``` 103 | 104 | ![读取合约名称和代号](img/12-1.jpg) 105 | 4. 利用 `ERC165`的 `supportsInterface()`函数,识别合约是否为ERC721标准。如果是,则返回 `ture`;反之,则报错或返回 `false`。 106 | 107 | ```python 108 | # 2. 利用ERC165的supportsInterface,确定合约是否为ERC721标准 109 | # ERC721接口的ERC165 identifier 110 | selectorERC721 = "0x80ac58cd" 111 | isERC721 = contractERC721.caller.supportsInterface(selectorERC721) 112 | print("2. 利用ERC165的supportsInterface,确定合约是否为ERC721标准") 113 | print(f"合约{'符合' if isERC721 else '不符合'}ERC721标准") 114 | ``` 115 | 116 | ![识别ERC721](img/12-2.jpg) 117 | 118 | ## 总结 119 | 120 | 这一讲,我们介绍了如何利用 `web3.py`来识别一个合约是否为 `ERC721`。其中也利用了 `ERC165`标准,因此只有支持 `ERC165`标准的合约才能用这个方法识别,包括 `ERC721`,`ERC1155`等。但是像 `ERC20`这种不支持 `ERC165`的标准,就要用别的方法识别了。 121 | -------------------------------------------------------------------------------- /13_EncodeCalldata/EncodeCalldata.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import json 3 | 4 | from eth_account import Account 5 | from hexbytes import HexBytes 6 | from web3 import Web3, contract 7 | 8 | # 连接到以太坊节点 9 | infura_url = "https://goerli.infura.io/v3/22b26b228b004ccc9325066db8b5c468" 10 | w3 = Web3(Web3.HTTPProvider(infura_url)) 11 | 12 | # 判断连接是否成功 13 | if w3.is_connected(): 14 | print("连接成功!") 15 | else: 16 | print("连接失败!") 17 | # 创建钱包对象 18 | private_key = "e08ca922fedbc3d37fa677f1d8f7e8fcbe42d031186bcbbc763d20cbdac81f9d" 19 | wallet = Account.from_key(private_key) 20 | 21 | w3.eth.default_account = wallet.address 22 | 23 | 24 | # WETH合约地址(Goerli测试网) 25 | addressWETH = '0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6' 26 | addressWETH=w3.to_checksum_address(addressWETH) 27 | abiWETH = [ 28 | {"constant": True, "inputs": [{"name": "_owner", "type": "address"}], "name": "balanceOf", "outputs":[{"name": "", "type": "uint256"}], "payable": False, "stateMutability": "view", "type": "function"}, 29 | {"constant": False, "inputs": [], "name": "deposit", "outputs": [], "payable": True, "stateMutability": "payable", "type": "function"} 30 | ] 31 | contract_instance = w3.eth.contract(address=addressWETH, abi=abiWETH) 32 | 33 | # 编码查询余额调用数据 34 | data= contract_instance.encodeABI(fn_name="balanceOf", args=[wallet.address]) 35 | print(f"编码结果: {data}") 36 | # 创建查询交易 37 | transaction = { 38 | "to": addressWETH, 39 | "data": data 40 | } 41 | 42 | # 发送交易并获取余额 43 | balanceWETH_hex = w3.eth.call(transaction) 44 | # 解码返回信息 45 | balanceWETH = int.from_bytes(balanceWETH_hex, byteorder='big') 46 | formatted_balance = w3.from_wei(balanceWETH, "ether") 47 | print(f"存款前WETH持仓: {formatted_balance}") 48 | 49 | # 编码余额转化调用数据 50 | data_deposit= contract_instance.encodeABI( 51 | "deposit", 52 | [] 53 | ) 54 | 55 | # 获取发送者地址的交易序号 56 | nonce = w3.eth.get_transaction_count(wallet.address) 57 | 58 | # 创建交易 59 | transaction_deposit= { 60 | 'to': addressWETH, 61 | 'data': data_deposit, 62 | 'value': w3.to_wei(0.001, 'ether'), 63 | 'gas': 28312, 64 | 'gasPrice': w3.to_wei('50', 'gwei'), 65 | 'nonce': nonce # 添加 nonce 字段 66 | } 67 | # 估算交易gas 68 | # gas=w3.eth.estimate_gas(transaction_deposit) 69 | # print(gas) 70 | # 发起交易 71 | signed_txn = w3.eth.account.sign_transaction(transaction_deposit, private_key) 72 | tx_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction) 73 | 74 | # 等待交易上链 75 | tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash) 76 | 77 | # 获取交易详情 78 | print("交易详情:") 79 | print(tx_receipt) 80 | 81 | 82 | # 查询存款后的 WETH 持仓 83 | 84 | # 发送交易并获取余额 85 | balanceWETH_hex = w3.eth.call(transaction) 86 | # 解码返回信息 87 | balanceWETH = int.from_bytes(balanceWETH_hex, byteorder='big') 88 | formatted_balance = w3.from_wei(balanceWETH, "ether") 89 | print(f"存款后WETH持仓: {formatted_balance}") 90 | 91 | -------------------------------------------------------------------------------- /13_EncodeCalldata/img/13-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/13_EncodeCalldata/img/13-1.png -------------------------------------------------------------------------------- /13_EncodeCalldata/img/13-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/13_EncodeCalldata/img/13-2.png -------------------------------------------------------------------------------- /13_EncodeCalldata/readme.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 13. 编码calldata 3 | tags: 4 | - web3 5 | - python 6 | - encode 7 | - calldata 8 | - frontend 9 | --- 10 | 11 | # web3.py极简入门: 13. 编码calldata 12 | 13 | **WTF Academy社群:** [官网 wtf.academy](https://wtf.academy) | [WTF Solidity教程](https://github.com/AmazingAng/WTFSolidity) | [discord](https://discord.gg/5akcruXrsk) | [微信群申请](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) 14 | 15 | 16 | ----- 17 | 18 | 这一讲,我们将介绍`web3.py`中的编码`calldata`。 19 | 20 | ## 编码方法 encodeABI 21 | 22 | `web3.py`的合约提供了与以太坊网络上的合约交互所需的`ABI`编码功能`encodeABI()`。`ABI`(Application Binary Interface)与`API`类似,是一格式,用于对合约可以处理的各种类型的数据进行编码,以便它们可以交互。`encodeABI()` 是 `web3.py `中 `Contract` 类的一个类方法,用于根据给定的函数名(`fn_name`)和参数(`args`)使用以太坊` ABI `编码方式对参数进行编码。 23 | 24 | 25 | ```py 26 | # 函数调用示例: 27 | contract.encodeABI(fn_name="register", args=["rainbows", 10]) 28 | ``` 29 | 30 | 上面的代码将会返回一个字符串,这个字符串是根据传入的函数名和参数进行 ABI 编码后的结果。编码后的结果通常用于构建以太坊交易的 data 字段,以便执行合约中的函数调用。 31 | 32 | ## 例子:与测试网`WETH`合约交互 33 | 34 | 这里,我们利用编码`calldata`的方法,与测试网`WETH`合约进行合约交互。 35 | 36 | 1. 创建`provider`,`wallet`变量。 37 | 38 | ```py 39 | #连接到以太坊节点 40 | infura_url = "https://goerli.infura.io/v3/22b26b228b004ccc9325066db8b5c468" 41 | w3 = Web3(Web3.HTTPProvider(infura_url)) 42 | 43 | # 判断连接是否成功 44 | if w3.is_connected(): 45 | print("连接成功!") 46 | else: 47 | print("连接失败!") 48 | # 创建钱包对象 49 | private_key = "e08ca922fedbc3d37fa677f1d8f7e8fcbe42d031186bcbbc763d20cbdac81f9d" 50 | wallet = Account.from_key(private_key) 51 | ``` 52 | 53 | 2. 创建`WETH`合约实例 54 | ```py 55 | # WETH合约地址(Goerli测试网) 56 | addressWETH = '0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6' 57 | addressWETH=w3.to_checksum_address(addressWETH) 58 | abiWETH = [ 59 | {"constant": True, "inputs": [{"name": "_owner", "type": "address"}], "name": "balanceOf", "outputs":[{"name": "", "type": "uint256"}], "payable": False, "stateMutability": "view", "type": "function"}, 60 | {"constant": False, "inputs": [], "name": "deposit", "outputs": [], "payable": True, "stateMutability": "payable", "type": "function"} 61 | ] 62 | contract_instance = w3.eth.contract(address=addressWETH, abi=abiWETH) 63 | ``` 64 | 65 | 3. 调用`balanceOf()`函数,读取钱包地址`address`的`WETH`余额。 66 | 67 | ```py 68 | # 编码查询余额调用数据 69 | data= contract_instance.encodeABI(fn_name="balanceOf", args=[wallet.address]) 70 | print(f"编码结果: {data}") 71 | # 创建查询交易 72 | transaction = { 73 | "to": addressWETH, 74 | "data": data 75 | } 76 | 77 | # 发送交易并获取余额 78 | balanceWETH_hex = w3.eth.call(transaction) 79 | # 解码返回信息 80 | balanceWETH = int.from_bytes(balanceWETH_hex, byteorder='big') 81 | formatted_balance = w3.from_wei(balanceWETH, "ether") 82 | print(f"存款前WETH持仓: {formatted_balance}") 83 | ``` 84 | ![查看WETH余额](img/13-1.png) 85 | 86 | 4. 调用`deposit()`函数,将`0.001 ETH`转换为`0.001 WETH`,打印交易详情和余额。可以看到余额变化。 87 | 88 | ```py 89 | # 编码余额转化调用数据 90 | data_deposit= contract_instance.encodeABI( 91 | "deposit", 92 | [] 93 | ) 94 | # 获取发送者地址的交易序号 95 | nonce = w3.eth.get_transaction_count(wallet.address) 96 | # 创建交易 97 | transaction_deposit= { 98 | 'to': addressWETH, 99 | 'data': data_deposit, 100 | 'value': w3.to_wei(0.001, 'ether'), 101 | 'gas': 28312, 102 | 'gasPrice': w3.to_wei('50', 'gwei'), 103 | 'nonce': nonce # 添加 nonce 字段 104 | } 105 | # 发起交易 106 | signed_txn = w3.eth.account.sign_transaction(transaction_deposit, private_key) 107 | tx_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction) 108 | # 等待交易上链 109 | tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash) 110 | # 获取交易详情 111 | print("交易详情:") 112 | print(tx_receipt) 113 | # 查询存款后的 WETH 持仓 114 | # 发送交易并获取余额 115 | balanceWETH_hex = w3.eth.call(transaction) 116 | # 解码返回信息 117 | balanceWETH = int.from_bytes(balanceWETH_hex, byteorder='big') 118 | formatted_balance = w3.from_wei(balanceWETH, "ether") 119 | print(f"存款后WETH持仓: {formatted_balance}") 120 | ``` 121 | ![调用deposit()函数](img/13-2.png) 122 | 123 | ## 总结 124 | 125 | 这一讲,我们介绍了`web3.py`中的`encodeABI`,并利用它编码`calldata`与`WETH`合约交互。与一些特殊的合约交互时(比如代理合约),你需要用这类方法编码参数,然后解码返回值。 -------------------------------------------------------------------------------- /14_HDwallet/HDwallet.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from eth_account import Account 4 | from web3 import Web3, contract 5 | 6 | # 连接到以太坊节点 7 | infura_url = "https://goerli.infura.io/v3/22b26b228b004ccc9325066db8b5c468" 8 | w3 = Web3(Web3.HTTPProvider(infura_url)) 9 | Account.enable_unaudited_hdwallet_features() 10 | acct, mnemonic = Account.create_with_mnemonic() 11 | print(acct.address) 12 | print(mnemonic) 13 | print(acct == Account.from_mnemonic(mnemonic)) 14 | 15 | # 批量生成钱包 16 | iterator = 0 17 | for i in range(10): 18 | acct = Account.from_mnemonic(mnemonic, 19 | account_path=f"m/44'/60'/0'/0/{iterator}") 20 | iterator = iterator + 1 21 | print("第"+str(i)+"个钱包地址"+acct.address) 22 | 23 | 24 | # 打印钱包账户私钥 25 | print(acct.key.hex()) 26 | # 加密 JSON 用的密码 27 | password = "password" 28 | 29 | # 生成加密 JSON 30 | encrypted_json = Account.encrypt(acct.key.hex(), password) 31 | json_string = json.dumps(encrypted_json,indent=4) 32 | print("钱包的加密 JSON:") 33 | print(json_string) 34 | # 解密 JSON 用的密码 35 | password = "password" 36 | 37 | # 将加密 JSON 转换为字符串 38 | json_string = json.dumps(encrypted_json) 39 | 40 | # 从加密 JSON 读取钱包 41 | private_key = w3.eth.account.decrypt(json_string, password) 42 | 43 | print("从加密 JSON 读取账户私钥:") 44 | print(private_key.hex()) 45 | 46 | -------------------------------------------------------------------------------- /14_HDwallet/img/14-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/14_HDwallet/img/14-1.png -------------------------------------------------------------------------------- /14_HDwallet/img/14-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/14_HDwallet/img/14-2.png -------------------------------------------------------------------------------- /14_HDwallet/img/14-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/14_HDwallet/img/14-3.png -------------------------------------------------------------------------------- /14_HDwallet/img/14-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/14_HDwallet/img/14-4.png -------------------------------------------------------------------------------- /14_HDwallet/img/14-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/14_HDwallet/img/14-5.png -------------------------------------------------------------------------------- /14_HDwallet/readme.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 14. 批量生成钱包 3 | tags: 4 | - web3 5 | - python 6 | - wallet 7 | - hdwallet 8 | - bip32 9 | - bip44 10 | - bip39 11 | - frontend 12 | --- 13 | 14 | # web3.py极简入门: 14. 批量生成钱包 15 | **WTF Academy社群:** [官网 wtf.academy](https://wtf.academy) | [WTF Solidity教程](https://github.com/AmazingAng/WTF-Solidity) | [discord](https://discord.gg/5akcruXrsk) | [微信群申请](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) 16 | 17 | ----- 18 | 19 | 这一讲,我们将介绍HD钱包,并写一个批量生成钱包的脚本。 20 | 21 | ## HD钱包 22 | 23 | HD钱包(Hierarchical Deterministic Wallet,多层确定性钱包)是一种数字钱包 ,通常用于存储比特币和以太坊等加密货币持有者的数字密钥。通过它,用户可以从一个随机种子创建一系列密钥对,更加便利、安全、隐私。要理解HD钱包,我们需要简单了解比特币的[BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki),[BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki),和[BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki)。 24 | 25 | ### BIP32 26 | 27 | 在`BIP32`推出之前,用户需要记录一堆的私钥才能管理很多钱包。`BIP32`提出可以用一个随机种子衍生多个私钥,更方便的管理多个钱包。钱包的地址由衍生路径决定,例如`“m/0/0/1”`。 28 | 29 | ![BIP32](img/14-1.png) 30 | 31 | ### BIP44 32 | 33 | `BIP44`为`BIP32`的衍生路径提供了一套通用规范,适配比特币、以太坊等多链。这一套规范包含六级,每级之间用"/"分割: 34 | ``` 35 | m / purpose' / coin_type' / account' / change / address_index 36 | ``` 37 | 其中: 38 | - m: 固定为"m" 39 | - purpose:固定为"44" 40 | - coin_type:代币类型,比特币主网为0,比特币测试网为1,以太坊主网为60 41 | - account:账户索引,从0开始。 42 | - change:是否为外部链,0为外部链,1为内部链,一般填0. 43 | - address_index:地址索引,从0开始,想生成新地址就把这里改为1,2,3。 44 | 45 | 举个例子,以太坊的默认衍生路径为`"m/44'/60'/0'/0/0"`。 46 | 47 | ### BIP39 48 | 49 | `BIP39`让用户能以一些人类可记忆的助记词的方式保管私钥,而不是一串16进制的数字: 50 | 51 | ``` 52 | //私钥 53 | 0x813f8f0a4df26f6455814fdd07dd2ab2d0e2d13f4d2f3c66e7fd9e3856060f89 54 | //助记词 55 | air organ twist rule prison symptom jazz cheap rather dizzy verb glare jeans orbit weapon universe require tired sing casino business anxiety seminar hunt 56 | ``` 57 | 58 | ## 批量生成账户 59 | 60 | `web3.py`提供了`create_with_mnemonic`方法,方便开发者创建助记词钱包账户。下面我们利用它从一个助记词批量生成20个钱包。 61 | 62 | 1. 创建`acct`助记词账户变量,可以看到助记词为`'pink skull pilot peace assist deposit toe recipe clever lunch drum until'` 63 | ```py 64 | # 连接到以太坊节点 65 | infura_url = "https://goerli.infura.io/v3/22b26b228b004ccc9325066db8b5c468" 66 | w3 = Web3(Web3.HTTPProvider(infura_url)) 67 | # 启用未经审计的 HD 钱包功能 68 | Account.enable_unaudited_hdwallet_features() 69 | 创建账户并获得其助记词 70 | acct, mnemonic = Account.create_with_mnemonic() 71 | print(acct.address) 72 | print(mnemonic) 73 | # 验证生成的钱包与生成助记词是否一致 74 | print(acct == Account.from_mnemonic(mnemonic)) 75 | ``` 76 | ![HDNode](img/14-2.png) 77 | 78 | 2. 通过HD钱包派生10个钱包。 79 | 80 | ```py 81 | # 批量生成钱包 82 | iterator = 0 83 | for i in range(10): 84 | acct = Account.from_mnemonic(mnemonic, 85 | account_path=f"m/44'/60'/0'/0/{iterator}") 86 | iterator = iterator + 1 87 | print("第"+str(i)+"个钱包地址"+acct.address) 88 | ``` 89 | ![批量生成钱包](img/14-3.png) 90 | 91 | 3. 保存钱包为加密json: 92 | 93 | ```py 94 | # 打印钱包账户私钥 95 | print(acct.key.hex()) 96 | # 加密 JSON 用的密码 97 | password = "password" 98 | # 生成加密 JSON 99 | encrypted_json = Account.encrypt(acct.key.hex(), password) 100 | json_string = json.dumps(encrypted_json,indent=4) 101 | print("钱包的加密 JSON:") 102 | print(json_string) 103 | ``` 104 | ![保存钱包](img/14-4.png) 105 | 106 | 4. 从加密json中读取钱包: 107 | ```js 108 | # 解密 JSON 用的密码 109 | password = "password" 110 | # 将加密 JSON 转换为字符串 111 | json_string = json.dumps(encrypted_json) 112 | # 从加密 JSON 读取钱包 113 | private_key = w3.eth.account.decrypt(json_string, password) 114 | print("从加密 JSON 读取账户私钥:") 115 | print(private_key.hex()) 116 | ``` 117 | ![读取钱包](img/14-5.png) 118 | 119 | ## 总结 120 | 这一讲我们介绍了HD钱包(BIP32,BIP44,BIP39),并利用它使用`web3.py`批量生成了10个钱包。 121 | -------------------------------------------------------------------------------- /15_MultiTransfer/img/15-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/15_MultiTransfer/img/15-2.png -------------------------------------------------------------------------------- /15_MultiTransfer/img/15-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/15_MultiTransfer/img/15-3.png -------------------------------------------------------------------------------- /15_MultiTransfer/img/15-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/15_MultiTransfer/img/15-4.png -------------------------------------------------------------------------------- /15_MultiTransfer/img/15-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/15_MultiTransfer/img/15-5.png -------------------------------------------------------------------------------- /15_MultiTransfer/img/15-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/15_MultiTransfer/img/15-6.png -------------------------------------------------------------------------------- /15_MultiTransfer/readme.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 15. 批量转账 3 | tags: 4 | - web3.py 5 | - python 6 | - multitransfer 7 | --- 8 | 9 | # web3.py极简入门: 15. 批量转账 10 | 11 | 我最近在重新学`web3.py`,巩固一下细节,也写一个`WTF web3py极简入门`,供小白们使用。 12 | 13 | **推特**:[@0xAA_Science](https://twitter.com/0xAA_Science), [0xXQ](https://twitter.com/0xXQ1) 14 | 15 | **WTF Academy社群:** [官网 wtf.academy](https://wtf.academy) | [WTF Solidity教程](https://github.com/AmazingAng/WTF-Solidity) | [discord](https://discord.gg/5akcruXrsk) | [微信群申请](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) 16 | 17 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-web3py](https://github.com/WTFAcademy/WTF-web3py) 18 | 19 | ----- 20 | 21 | 这一讲,我们将介绍用`web3.py`进行批量转账。通过调用[WTF Solidity极简入门第33讲:空投](https://github.com/AmazingAng/WTF-Solidity/blob/main/33_Airdrop/readme.md)中的`Airdrop`合约,可以在一笔交易中实现批量转账,节省gas费。 22 | 23 | ## Airdrop合约 24 | 25 | 这里简单介绍下`Airdrop`合约,细节可以去Solidity教程中看。我们会用到`1`个函数: 26 | 27 | - `multiTransferETH()`:批量发送`ETH`,包含`2`个参数: 28 | - `_addresses`:接收空投的用户地址数组(`address[]`类型) 29 | - `_amounts`:空投数量数组,对应`_addresses`里每个地址的数量(`uint[]`类型) 30 | 31 | 32 | 我们在`Goerli`测试网部署了一个`Airdrop`合约,地址为: 33 | ``` 34 | 0x71C2aD976210264ff0468d43b198FD69772A25fa 35 | ``` 36 | 37 | ## 批量转账 38 | 39 | 下面我们写一个脚本,调用`Airdrop`合约将`ETH`(原生代币)转账给`10`个地址。 40 | 41 | 1. 创建HD钱包,用于批量生成地址。 42 | ```python 43 | # 1. 创建HD钱包,用于批量生成地址。 44 | print("1. 创建HD钱包,用于批量生成地址。") 45 | 46 | from web3 import Web3 47 | w3 = Web3(Web3.HTTPProvider('https://goerli.infura.io/v3/5be23a050401499fb951be2e12178e01')) 48 | # test mnemonic from ganache (don't use it!) 49 | mnemonic = "air organ twist rule prison symptom jazz cheap rather dizzy verb glare jeans orbit weapon universe require tired sing casino business anxiety seminar hunt" 50 | w3.eth.account.enable_unaudited_hdwallet_features() 51 | ``` 52 | 53 | 2. 利用HD钱包,生成10个钱包地址。 54 | ```python 55 | # 2. 利用HD钱包,生成10个钱包地址。 56 | print("2. 利用HD钱包,生成10个钱包地址。") 57 | 58 | addresses = [] 59 | for i in range(10): 60 | address = w3.eth.account.from_mnemonic(mnemonic, account_path=f"m/44'/60'/0'/0/{i}") 61 | addresses.append(address.address) 62 | print(addresses) 63 | ``` 64 | ![生成10个地址](img/15-2.png) 65 | 66 | 3. 创建provider和wallet,发送代币用。 67 | 68 | ```python 69 | # 3. 创建wallet,发送代币用。 70 | print("3. 创建wallet,发送代币用。") 71 | 72 | private_key = "这里填写你自己的私钥" 73 | account = w3.eth.account.from_key(private_key) 74 | account.address 75 | ``` 76 | 77 | 4. 创建Airdrop合约。 78 | ```python 79 | # 4. 创建Airdrop合约。 80 | print("4. 创建Airdrop合约。") 81 | 82 | # 合约 abi 83 | airdrop_abi = '[{"inputs":[{"internalType":"uint256[]","name":"_arr","type":"uint256[]"}],"name":"getSum","outputs":[{"internalType":"uint256","name":"sum","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address payable[]","name":"_addresses","type":"address[]"},{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"}],"name":"multiTransferETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"address[]","name":"_addresses","type":"address[]"},{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"}],"name":"multiTransferToken","outputs":[],"stateMutability":"nonpayable","type":"function"}]' 84 | 85 | # 合约地址 测试网 86 | airdrop_address = '0x71C2aD976210264ff0468d43b198FD69772A25fa' 87 | 88 | # 创建合约实例 89 | airdrop_contract = w3.eth.contract(address=airdrop_address, abi=airdrop_abi) 90 | ``` 91 | 92 | 5. 读取一个地址的ETH余额。 93 | ```python 94 | # 5. 读取一个地址的ETH余额。 95 | print("5. 读取一个地址的ETH余额。") 96 | 97 | # 目标地址 98 | target_address = addresses[8] 99 | 100 | # 读取 ETH 余额 101 | balanceETH = w3.eth.get_balance(target_address) 102 | 103 | print(balanceETH) 104 | ``` 105 | ![读取ETH持仓](img/15-5.png) 106 | 107 | 108 | 6. 调用`multiTransferETH()`函数,给每个钱包转`0.0001 ETH`,可以看到发送后余额发生变化。 109 | ```python 110 | # 6. 调用multiTransferETH()函数,给每个钱包转 0.0001 ETH 111 | print("6. 调用multiTransferETH()函数,给每个钱包转 0.0001 ETH") 112 | 113 | # 发起交易 114 | amounts = (len(addresses)) * [w3.to_wei(0.0001, 'ether')] 115 | # 调用方法 116 | tx = airdrop_contract.functions.multiTransferETH(addresses, amounts).build_transaction({ 117 | 'from': account.address, 118 | 'value': w3.to_wei(0.001, 'ether'), 119 | 'gas': 2000000, # 根据需要调整 120 | 'nonce': w3.eth.get_transaction_count(account.address) 121 | }) 122 | 123 | # 发起交易 124 | signed_tx = w3.eth.account.sign_transaction(tx, private_key=private_key) 125 | tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction) 126 | 127 | # 等待交易上链 128 | tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash) 129 | 130 | balanceETH2 = w3.eth.get_balance(addresses[8]) 131 | print("转账后余额:", balanceETH2) 132 | ``` 133 | ![批量发送ETH](img/15-6.png) 134 | 135 | 136 | ## 总结 137 | 138 | 这一讲,我们介绍了如何利用`ethers.js`调用`Airdrop`合约进行批量转账。在例子中,我们将`ETH`发送给了`10`个不同地址,省事且省钱(gas费)。 -------------------------------------------------------------------------------- /16_MultiCollect/MultiCollect.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "1. 创建provider和wallet,其中wallet是接收资产的钱包。\n" 13 | ] 14 | }, 15 | { 16 | "data": { 17 | "text/plain": [ 18 | "'0x338f8891D6BdC58eEB4754352459cC461EfD2a5E'" 19 | ] 20 | }, 21 | "execution_count": 1, 22 | "metadata": {}, 23 | "output_type": "execute_result" 24 | } 25 | ], 26 | "source": [ 27 | "# 1. 创建provider和wallet,其中wallet是接收资产的钱包。\n", 28 | "print(\"1. 创建provider和wallet,其中wallet是接收资产的钱包。\")\n", 29 | "\n", 30 | "from web3 import Web3\n", 31 | "w3 = Web3(Web3.HTTPProvider('https://goerli.infura.io/v3/5be23a050401499fb951be2e12178e01'))\n", 32 | "\n", 33 | "private_key = \"0x21ac72b6ce19661adf31ef0d2bf8c3fcad003deee3dc1a1a64f5fa3d6b049c06\"\n", 34 | "account = w3.eth.account.from_key(private_key)\n", 35 | "account.address" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": 3, 41 | "metadata": {}, 42 | "outputs": [ 43 | { 44 | "name": "stdout", 45 | "output_type": "stream", 46 | "text": [ 47 | "2. 创建HD钱包,用于管理多个钱包。\n" 48 | ] 49 | } 50 | ], 51 | "source": [ 52 | "# 2. 创建HD钱包,用于管理多个钱包。\n", 53 | "print(\"2. 创建HD钱包,用于管理多个钱包。\")\n", 54 | "\n", 55 | "mnemonic = \"air organ twist rule prison symptom jazz cheap rather dizzy verb glare jeans orbit weapon universe require tired sing casino business anxiety seminar hunt\"\n", 56 | "w3.eth.account.enable_unaudited_hdwallet_features()\n" 57 | ] 58 | }, 59 | { 60 | "cell_type": "code", 61 | "execution_count": 6, 62 | "metadata": {}, 63 | "outputs": [ 64 | { 65 | "name": "stdout", 66 | "output_type": "stream", 67 | "text": [ 68 | "3. 利用HD钱包,生成10个钱包地址。\n" 69 | ] 70 | }, 71 | { 72 | "data": { 73 | "text/plain": [ 74 | "[,\n", 75 | " ,\n", 76 | " ,\n", 77 | " ,\n", 78 | " ,\n", 79 | " ,\n", 80 | " ,\n", 81 | " ,\n", 82 | " ,\n", 83 | " ]" 84 | ] 85 | }, 86 | "execution_count": 6, 87 | "metadata": {}, 88 | "output_type": "execute_result" 89 | } 90 | ], 91 | "source": [ 92 | "# 3. 利用HD钱包,生成10个钱包地址。\n", 93 | "print(\"3. 利用HD钱包,生成10个钱包地址。\")\n", 94 | "\n", 95 | "addresses = []\n", 96 | "for i in range(10):\n", 97 | " address = w3.eth.account.from_mnemonic(mnemonic, account_path=f\"m/44'/60'/0'/0/{i}\")\n", 98 | " addresses.append(address)\n", 99 | "addresses" 100 | ] 101 | }, 102 | { 103 | "cell_type": "code", 104 | "execution_count": 5, 105 | "metadata": {}, 106 | "outputs": [ 107 | { 108 | "name": "stdout", 109 | "output_type": "stream", 110 | "text": [ 111 | "4. 读取一个地址的ETH余额。\n", 112 | "300000000000000\n" 113 | ] 114 | } 115 | ], 116 | "source": [ 117 | "# 4. 读取一个地址的ETH余额。\n", 118 | "print(\"4. 读取一个地址的ETH余额。\")\n", 119 | "\n", 120 | "# 目标地址\n", 121 | "target_address = addresses[3]\n", 122 | "\n", 123 | "# 读取 ETH 余额\n", 124 | "balanceETH = w3.eth.get_balance(target_address)\n", 125 | "\n", 126 | "print(balanceETH)" 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": 14, 132 | "metadata": {}, 133 | "outputs": [ 134 | { 135 | "name": "stdout", 136 | "output_type": "stream", 137 | "text": [ 138 | "5. 利用钱包类的sendTransaction()发送交易,归集每个钱包中的ETH。\n", 139 | "第 1 个钱包 0x83E5B09c54C4EB904B9bC842Acab9218c2297d6d ETH 归集开始\n", 140 | "第 2 个钱包 0xF44F814ABa3e6BC091487Cf313B49F109550d086 ETH 归集开始\n", 141 | "第 3 个钱包 0xC08A302438EaA60adE93196A527A837AA1CA5A3f ETH 归集开始\n", 142 | "第 4 个钱包 0x57171534c9616bB635351deB9d4AA009Fc0d6931 ETH 归集开始\n", 143 | "第 5 个钱包 0xFECb70fD6b9414ff7B58C6989D44AFA4a0511D6d ETH 归集开始\n" 144 | ] 145 | }, 146 | { 147 | "ename": "TimeExhausted", 148 | "evalue": "Transaction HexBytes('0xbe08723f60699d146e7d2a7e8edf18dfbb2ec1259a5052604f6162df8adc1277') is not in the chain after 120 seconds", 149 | "output_type": "error", 150 | "traceback": [ 151 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 152 | "\u001b[0;31mTimeout\u001b[0m Traceback (most recent call last)", 153 | "\u001b[0;32m~/opt/anaconda3/lib/python3.9/site-packages/web3/eth/eth.py\u001b[0m in \u001b[0;36mwait_for_transaction_receipt\u001b[0;34m(self, transaction_hash, timeout, poll_latency)\u001b[0m\n\u001b[1;32m 473\u001b[0m \u001b[0;32mbreak\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 474\u001b[0;31m \u001b[0m_timeout\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpoll_latency\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 475\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mtx_receipt\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 154 | "\u001b[0;32m~/opt/anaconda3/lib/python3.9/site-packages/web3/_utils/threads.py\u001b[0m in \u001b[0;36msleep\u001b[0;34m(self, seconds)\u001b[0m\n\u001b[1;32m 98\u001b[0m \u001b[0mtime\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mseconds\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 99\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcheck\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 100\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", 155 | "\u001b[0;32m~/opt/anaconda3/lib/python3.9/site-packages/web3/_utils/threads.py\u001b[0m in \u001b[0;36mcheck\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 91\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 92\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 93\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", 156 | "\u001b[0;31mTimeout\u001b[0m: 120 seconds", 157 | "\nDuring handling of the above exception, another exception occurred:\n", 158 | "\u001b[0;31mTimeExhausted\u001b[0m Traceback (most recent call last)", 159 | "\u001b[0;32m/var/folders/c1/03136d3x6vx54k1qf754sjj00000gn/T/ipykernel_57388/828982655.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0msigned_tx\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mwallet\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msign_transaction\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 19\u001b[0m \u001b[0mtx_hash\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mw3\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0meth\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msend_raw_transaction\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msigned_tx\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrawTransaction\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 20\u001b[0;31m \u001b[0mtx_receipt\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mw3\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0meth\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwait_for_transaction_receipt\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtx_hash\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 21\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 22\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"第 {i+1} 个钱包 {addresses[i].address} ETH 归集开始\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 160 | "\u001b[0;32m~/opt/anaconda3/lib/python3.9/site-packages/web3/eth/eth.py\u001b[0m in \u001b[0;36mwait_for_transaction_receipt\u001b[0;34m(self, transaction_hash, timeout, poll_latency)\u001b[0m\n\u001b[1;32m 476\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 477\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mTimeout\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 478\u001b[0;31m raise TimeExhausted(\n\u001b[0m\u001b[1;32m 479\u001b[0m \u001b[0;34mf\"Transaction {HexBytes(transaction_hash) !r} is not in the chain \"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 480\u001b[0m \u001b[0;34mf\"after {timeout} seconds\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 161 | "\u001b[0;31mTimeExhausted\u001b[0m: Transaction HexBytes('0xbe08723f60699d146e7d2a7e8edf18dfbb2ec1259a5052604f6162df8adc1277') is not in the chain after 120 seconds" 162 | ] 163 | } 164 | ], 165 | "source": [ 166 | "# 5. 利用钱包类的send_raw_transaction()发送交易,归集每个钱包中的ETH。\n", 167 | "print(\"5. 利用钱包类的send_raw_transaction()发送交易,归集每个钱包中的ETH。\")\n", 168 | "\n", 169 | "for i in range(10):\n", 170 | " # 设置钱包私钥\n", 171 | " wallet = w3.eth.account.from_key(addresses[i].key)\n", 172 | "\n", 173 | " # 准备交易数据\n", 174 | " tx = {\n", 175 | " 'to': account.address,\n", 176 | " 'value': w3.to_wei(0.0001, \"ether\"),\n", 177 | " 'gas': 2000000, \n", 178 | " 'nonce': w3.eth.get_transaction_count(addresses[i].address),\n", 179 | " 'gasPrice': w3.eth.gas_price\n", 180 | " }\n", 181 | "\n", 182 | " # 签名并发送交易\n", 183 | " signed_tx = wallet.sign_transaction(tx)\n", 184 | " tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction)\n", 185 | " tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)\n", 186 | "\n", 187 | " print(f\"第 {i+1} 个钱包 {addresses[i].address} ETH 归集开始\")\n", 188 | "\n", 189 | "print(\"ETH 归集结束\")" 190 | ] 191 | }, 192 | { 193 | "cell_type": "code", 194 | "execution_count": 16, 195 | "metadata": {}, 196 | "outputs": [ 197 | { 198 | "name": "stdout", 199 | "output_type": "stream", 200 | "text": [ 201 | "6. 读取一个地址在归集后的ETH和WETH余额,可以看到ETH和WETH余额减少,归集成功!\n", 202 | "转账后余额: 99998943595000\n" 203 | ] 204 | } 205 | ], 206 | "source": [ 207 | "# 6. 读取一个地址在归集后的ETH和WETH余额,可以看到ETH和WETH余额减少,归集成功!\n", 208 | "print(\"6. 读取一个地址在归集后的ETH和WETH余额,可以看到ETH和WETH余额减少,归集成功!\")\n", 209 | "balanceETH2 = w3.eth.get_balance(addresses[3].address)\n", 210 | "print(\"转账后余额:\", balanceETH2)" 211 | ] 212 | }, 213 | { 214 | "cell_type": "code", 215 | "execution_count": null, 216 | "metadata": {}, 217 | "outputs": [], 218 | "source": [] 219 | } 220 | ], 221 | "metadata": { 222 | "kernelspec": { 223 | "display_name": "base", 224 | "language": "python", 225 | "name": "python3" 226 | }, 227 | "language_info": { 228 | "codemirror_mode": { 229 | "name": "ipython", 230 | "version": 3 231 | }, 232 | "file_extension": ".py", 233 | "mimetype": "text/x-python", 234 | "name": "python", 235 | "nbconvert_exporter": "python", 236 | "pygments_lexer": "ipython3", 237 | "version": "3.9.13" 238 | }, 239 | "orig_nbformat": 4 240 | }, 241 | "nbformat": 4, 242 | "nbformat_minor": 2 243 | } 244 | -------------------------------------------------------------------------------- /16_MultiCollect/img/16-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/16_MultiCollect/img/16-1.png -------------------------------------------------------------------------------- /16_MultiCollect/img/16-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/16_MultiCollect/img/16-2.png -------------------------------------------------------------------------------- /16_MultiCollect/img/16-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/16_MultiCollect/img/16-3.png -------------------------------------------------------------------------------- /16_MultiCollect/img/16-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/16_MultiCollect/img/16-4.png -------------------------------------------------------------------------------- /16_MultiCollect/img/16-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/16_MultiCollect/img/16-6.png -------------------------------------------------------------------------------- /16_MultiCollect/readme.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 16. 批量归集 3 | tags: 4 | - web3.py 5 | - python 6 | - multitransfer 7 | --- 8 | 9 | # Ethers极简入门: 16. 批量归集 10 | 11 | 我最近在重新学`web3.py`,巩固一下细节,也写一个`WTF web3py极简入门`,供小白们使用。 12 | 13 | **推特**:[@0xAA_Science](https://twitter.com/0xAA_Science), [0xXQ](https://twitter.com/0xXQ1) 14 | 15 | **WTF Academy社群:** [官网 wtf.academy](https://wtf.academy) | [WTF Solidity教程](https://github.com/AmazingAng/WTF-Solidity) | [discord](https://discord.gg/5akcruXrsk) | [微信群申请](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) 16 | 17 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-web3py](https://github.com/WTFAcademy/WTF-web3py) 18 | 19 | ----- 20 | 21 | 这一讲,我们介绍如何使用`web3.py`将多个钱包的`ETH`和代币归集到一个钱包中。 22 | 23 | ## 批量归集 24 | 25 | 在链上交互、撸毛之后,就需要将多个钱包的资产进行归集管理。你可以用[HD钱包](https://github.com/WTFAcademy/WTF-web3py/blob/main/14_HDwallet/readme.md)或者保存多份密钥的方式操作多个钱包,然后用`web3.py`脚本完成归集。下面我们分别示范归集`ETH`(原生代币)和`WETH`(ERC20代币)。 26 | 27 | 1. 创建`provider`和`wallet`,其中`wallet`是接收资产的钱包。 28 | 29 | ```python 30 | # 1. 创建provider和wallet,其中wallet是接收资产的钱包。 31 | print("1. 创建provider和wallet,其中wallet是接收资产的钱包。") 32 | 33 | from web3 import Web3 34 | w3 = Web3(Web3.HTTPProvider('https://goerli.infura.io/v3/5be23a050401499fb951be2e12178e01')) 35 | 36 | private_key = "0x21ac72b6ce19661adf31ef0d2bf8c3fcad003deee3dc1a1a64f5fa3d6b049c06" 37 | account = w3.eth.account.from_key(private_key) 38 | account.address 39 | ``` 40 | 41 | ![Provider 钱包](img/16-1.png) 42 | 43 | 44 | 2. 创建`HD`钱包,用于管理多个钱包。 45 | 46 | ```python 47 | # 2. 创建HD钱包,用于管理多个钱包。 48 | print("2. 创建HD钱包,用于管理多个钱包。") 49 | 50 | mnemonic = "air organ twist rule prison symptom jazz cheap rather dizzy verb glare jeans orbit weapon universe require tired sing casino business anxiety seminar hunt" 51 | w3.eth.account.enable_unaudited_hdwallet_features() 52 | 53 | ``` 54 | 55 | 3. 通过`HD`钱包衍生`10`个钱包,这些钱包上需要有资产。 56 | 57 | ```python 58 | # 3. 利用HD钱包,生成10个钱包地址。 59 | print("3. 利用HD钱包,生成10个钱包地址。") 60 | 61 | addresses = [] 62 | for i in range(10): 63 | address = w3.eth.account.from_mnemonic(mnemonic, account_path=f"m/44'/60'/0'/0/{i}") 64 | addresses.append(address) 65 | addresses 66 | ``` 67 | ![生成10个地址](img/16-2.png) 68 | 69 | 4. 读取一个地址的ETH余额。 70 | 71 | ```python 72 | # 4. 读取一个地址的ETH余额。 73 | print("4. 读取一个地址的ETH余额。") 74 | 75 | # 目标地址 76 | target_address = addresses[3] 77 | 78 | # 读取 ETH 余额 79 | balanceETH = w3.eth.get_balance(target_address) 80 | 81 | print(balanceETH) 82 | ``` 83 | ![读取余额](img/16-3.png) 84 | 85 | 5. 利用钱包类的`send_raw_transaction()`发送交易,归集每个钱包中的`ETH`。 86 | 87 | ```python 88 | # 5. 利用钱包类的send_raw_transaction()发送交易,归集每个钱包中的ETH。 89 | print("5. 利用钱包类的send_raw_transaction()发送交易,归集每个钱包中的ETH。") 90 | 91 | for i in range(10): 92 | # 设置钱包私钥 93 | wallet = w3.eth.account.from_key(addresses[i].key) 94 | 95 | # 准备交易数据 96 | tx = { 97 | 'to': account.address, 98 | 'value': w3.to_wei(0.0001, "ether"), 99 | 'gas': 2000000, 100 | 'nonce': w3.eth.get_transaction_count(addresses[i].address), 101 | 'gasPrice': w3.eth.gas_price 102 | } 103 | 104 | # 签名并发送交易 105 | signed_tx = wallet.sign_transaction(tx) 106 | tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction) 107 | tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash) 108 | 109 | print(f"第 {i+1} 个钱包 {addresses[i].address} ETH 归集开始") 110 | 111 | print("ETH 归集结束") 112 | ``` 113 | ![归集ETH](img/16-4.png) 114 | 115 | 116 | 6. 读取一个地址在归集后的ETH和WETH余额,可以看到`ETH`和`WETH`余额减少,归集成功! 117 | ```python 118 | # 6. 读取一个地址在归集后的ETH和WETH余额,可以看到ETH和WETH余额减少,归集成功! 119 | print("6. 读取一个地址在归集后的ETH和WETH余额,可以看到ETH和WETH余额减少,归集成功!") 120 | balanceETH2 = w3.eth.get_balance(addresses[3].address) 121 | print("转账后余额:", balanceETH2) 122 | ``` 123 | ![归集后余额变动](img/16-6.png) 124 | 125 | ## 总结 126 | 127 | 这一讲,我们介绍了批量归集,并用`web3.py`脚本将`10`个钱包的`ETH`归集到一个钱包中。 -------------------------------------------------------------------------------- /19_Mempool/Mempool.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "id": "682b751a", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "from web3 import Web3\n", 11 | "ALCHEMY_MAINNET_URL = 'https://eth-mainnet.g.alchemy.com/v2/hjZ-SwVhjRtBk-yUJ1SkWSTrz_dJl7of'" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 2, 17 | "id": "3f281d38", 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "w3 = Web3(Web3.HTTPProvider(ALCHEMY_MAINNET_URL))" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 3, 27 | "id": "902b456d", 28 | "metadata": {}, 29 | "outputs": [ 30 | { 31 | "data": { 32 | "text/plain": [ 33 | "True" 34 | ] 35 | }, 36 | "execution_count": 3, 37 | "metadata": {}, 38 | "output_type": "execute_result" 39 | } 40 | ], 41 | "source": [ 42 | "w3.is_connected()" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 4, 48 | "id": "3d8d47a8", 49 | "metadata": {}, 50 | "outputs": [ 51 | { 52 | "name": "stdout", 53 | "output_type": "stream", 54 | "text": [ 55 | "[HexBytes('0x2b3d81ad88b0a289addf483e5cc600a8954fd25c17dd75fca52eeb0e95bd90fd')]\n" 56 | ] 57 | } 58 | ], 59 | "source": [ 60 | "pending_tx_filter= w3.eth.filter('pending')\n", 61 | "pending_tx = pending_tx_filter.get_new_entries()\n", 62 | "print(pending_tx)" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": 5, 68 | "id": "c2f1950a", 69 | "metadata": {}, 70 | "outputs": [ 71 | { 72 | "name": "stdout", 73 | "output_type": "stream", 74 | "text": [ 75 | "交易哈希: 0x2b3d81ad88b0a289addf483e5cc600a8954fd25c17dd75fca52eeb0e95bd90fd\n", 76 | "交易详情: AttributeDict({'blockHash': None, 'blockNumber': None, 'from': '0x295E4db8A9E27867F954aBf2FF7dAd9df9460244', 'gas': 282758, 'gasPrice': 22997654830, 'maxFeePerGas': 22997654830, 'maxPriorityFeePerGas': 100000000, 'hash': HexBytes('0x2b3d81ad88b0a289addf483e5cc600a8954fd25c17dd75fca52eeb0e95bd90fd'), 'input': HexBytes('0x7034d120000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c948ac8e38a552ee6f73ce4198e544280eebf0ee00000000000000000000000043fa8aeb7eaa2bb74cf1d07281ebebb76a23941cf5126e36e0cf3f8ccdf8e3d76c1501c706c15a029fb8139bca7b536776e9eafe0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000006796422300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aff3b6aec21098bb0622c27415baf697000000000000000000000000c948ac8e38a552ee6f73ce4198e544280eebf0ee00000000000000000000000043fa8aeb7eaa2bb74cf1d07281ebebb76a23941cf5126e36e0cf3f8ccdf8e3d76c1501c706c15a029fb8139bca7b536776e9eafe00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000067964156000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b69152272e586fb09e0bc4b7493a5f600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000024340000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000002386f26fc100000000000000000000000000000000000000000000000000000000000000001a80000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082743807a0c2e4305556d5fd140e9392f45326579329e22cccea138473a2da834a4f65041c530653fce7964bf950d91a57a96210129716ef1982f43a641254a4591c68b7ac05859df73a6efa918d83ca2e16ae9d1fe94d1523a6f0f214af90aa5c12539d997d24d3b786b8ae0a90630627d69083a0fd60507a010890544cd2c5d7981b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000593c320990b9bcd3e1e9557af456a17612de34be6318db8005d351de5c4f4ab0f814d95245c6cc883ac552f52f0a0f2c42f741ab384c2f1981b96315ea079a6fb01c01236b4e6af68e5d010513ff70a3aaed9afeb8661116e6ce00000000000000'), 'nonce': 628, 'to': '0xb2ecfE4E4D61f8790bbb9DE2D1259B9e2410CEA5', 'transactionIndex': None, 'value': 0, 'type': 2, 'accessList': [], 'chainId': 1, 'v': 1, 'r': HexBytes('0x30325064b28307a09d8fdcbe1779ef0b034c4bb013a2434ebdccfdbfe994c3c4'), 's': HexBytes('0x7c02bd29c48446b8f8c2e1be2cb2c175d514ff127ad9d787321e75af02a3060f'), 'yParity': 1})\n" 77 | ] 78 | } 79 | ], 80 | "source": [ 81 | "for hash in pending_tx:\n", 82 | " print('交易哈希:' , hash.hex())\n", 83 | " print('交易详情:',w3.eth.get_transaction(hash.hex()))" 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": null, 89 | "id": "78635a09", 90 | "metadata": {}, 91 | "outputs": [], 92 | "source": [] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": null, 97 | "id": "3dab4a40", 98 | "metadata": {}, 99 | "outputs": [], 100 | "source": [] 101 | } 102 | ], 103 | "metadata": { 104 | "kernelspec": { 105 | "display_name": "Python 3 (ipykernel)", 106 | "language": "python", 107 | "name": "python3" 108 | }, 109 | "language_info": { 110 | "codemirror_mode": { 111 | "name": "ipython", 112 | "version": 3 113 | }, 114 | "file_extension": ".py", 115 | "mimetype": "text/x-python", 116 | "name": "python", 117 | "nbconvert_exporter": "python", 118 | "pygments_lexer": "ipython3", 119 | "version": "3.9.12" 120 | } 121 | }, 122 | "nbformat": 4, 123 | "nbformat_minor": 5 124 | } 125 | -------------------------------------------------------------------------------- /19_Mempool/img/19-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/19_Mempool/img/19-1.png -------------------------------------------------------------------------------- /19_Mempool/img/19-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/19_Mempool/img/19-2.png -------------------------------------------------------------------------------- /19_Mempool/img/19-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/19_Mempool/img/19-3.png -------------------------------------------------------------------------------- /19_Mempool/readme.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 19、监听Mempool 3 | tags: 4 | - web3py 5 | - python 6 | - ens 7 | - vitalik 8 | --- 9 | 10 | # web3.py极简入门: 19、监听Mempool 11 | 12 | 我们最近在重新学`web3.py`,巩固一下细节,也写一个`WTF web3.py极简入门`,供小白们使用。 13 | 14 | **推特**:[@0xAA_Science](https://twitter.com/0xAA_Science) [@localcat15](https://twitter.com/localcat15) 15 | 16 | 17 | **WTF Academy社群:** [官网 wtf.academy](https://wtf.academy) | [WTF Solidity教程](https://github.com/AmazingAng/WTFSolidity) | [discord](https://discord.gg/5akcruXrsk) | [微信群申请](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) 18 | 19 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-web3py](https://github.com/WTFAcademy/WTF-web3py) 20 | 21 | ----- 22 | 23 | 这一讲,我们会介绍如何监听`mempool`(交易内存池)中的交易. 24 | 25 | ## Mempool 26 | 以太坊的内存池(Mempool)存储尚未被打包进区块的交易队列。当用户发起一笔以太坊交易时,该交易首先会被广播到网络中的节点,然后被放入内存池等待被矿工打包确认。 27 | 28 | 一般而言,矿工更愿意打包那些支付更高Gas价格的交易,因为这样他们能够获取更高的利润。在常见的代币IDO、NFT打新、铭文打新等场景中,项目方常常设置“先到先得”机制,先上链的交易才有资格获得奖励,因此科学家们常常会进行Gas War,投入几倍乃至几十倍于平均水平的gas费,或者通过其它手段直接贿赂矿工,从而让自己`mempool`中的交易能够更快的被打包上链。 29 | 30 | 此外,大家熟悉的三明治攻击也是通过监听并抢跑`mempool`中的交易实现的。 31 | ![mempool](img/19-1.png) 32 | ## 监听Mempool 33 | 下面我们实现一个监听Mempool的简单脚本: 34 | 35 | 1、创建`Provider`。这里需要注意:并非所有RPC节点都能够支持对`mempool`进行实时查询,如果后续出现错误,可以尝试更换RPC。 36 | ```python 37 | from web3 import Web3 38 | # 申请自己的RPC-URL 39 | ALCHEMY_MAINNET_URL = 'https://eth-mainnet.g.alchemy.com/v2/hjZ-SwVhjRtBk-yUJ1SkWSTrz_dJl7of' 40 | # 创建Provider 41 | w3 = Web3(Web3.HTTPProvider(ALCHEMY_MAINNET_URL)) 42 | ``` 43 | 2、创建用于监控`mempool`的过滤器,不同于之前章节中的情形,这里只需要将参数设置为`pending`即可。 44 | ```python 45 | #创建过滤器 46 | pending_tx_filter= w3.eth.filter('pending') 47 | #获取pending交易并打印 48 | pending_tx = pending_tx_filter.get_new_entries() 49 | print(pending_tx) 50 | ``` 51 | ![hash](img/19-2.png) 52 | 53 | 3、为了获取mempool中交易的更详细信息,我们可以把上面获取到的哈希传入`get_transaction`函数,返回值是一个字典,包含交易的发送方、接收方、gas费等信息。 54 | ```python 55 | for hash in pending_tx: 56 | print('交易哈希:' , hash.hex()) 57 | print('交易详情:',w3.eth.get_transaction(hash.hex())) 58 | ``` 59 | ![hash](img/19-3.png) 60 | 可以看到上述哈希的`blockNumber`和`blockHash`均为空,这说明我们监听到的交易的确尚未上链。 61 | 62 | 上述方法有一定实际应用价值:例如在某些出块很慢的链上打张数较少的铭文时,我们不仅需要关注已经上链的交易,还需要关注`mempool`中未上链的部分。通过上述方法找出自转或转给特定合约的交易,统计这些交易的gas情况,我们就能够更好的评估剩余的铭文张数,并能够灵活调整Gas进行抢跑。 63 | 64 | 65 | ## 总结 66 | 这一讲,我们简单介绍了`mempool`,并写了一个脚本实现对`mempool`的监听。 -------------------------------------------------------------------------------- /20_DecodeTx/DecodeTx.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "id": "682b751a", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "from web3 import Web3\n", 11 | "ALCHEMY_MAINNET_URL = 'wss://eth-mainnet.g.alchemy.com/v2/hjZ-SwVhjRtBk-yUJ1SkWSTrz_dJl7of'" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 2, 17 | "id": "3f281d38", 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "w3 = Web3(Web3.WebsocketProvider(ALCHEMY_MAINNET_URL))" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 10, 27 | "id": "902b456d", 28 | "metadata": {}, 29 | "outputs": [], 30 | "source": [ 31 | "transaction = w3.eth.get_transaction('0x6b2eba89b78484f6b954cf74f35ee42667363f65c0927d46b5f10ef6ab894393')" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": 11, 37 | "id": "57a76aee", 38 | "metadata": {}, 39 | "outputs": [ 40 | { 41 | "name": "stdout", 42 | "output_type": "stream", 43 | "text": [ 44 | "0xa9059cbb000000000000000000000000adf9e4ca54facab2ea57ed878e4c3b271073fa1d0000000000000000000000000000000000000000000000000000000008e18f40\n" 45 | ] 46 | } 47 | ], 48 | "source": [ 49 | "print(transaction.input.hex())" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": 12, 55 | "id": "1e1135da", 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "addressUSDT='0xdAC17F958D2ee523a2206206994597C13D831ec7'\n", 60 | "abiUSDT='[{\"constant\":true,\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_upgradedAddress\",\"type\":\"address\"}],\"name\":\"deprecate\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_spender\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"deprecated\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_evilUser\",\"type\":\"address\"}],\"name\":\"addBlackList\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_from\",\"type\":\"address\"},{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"upgradedAddress\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"balances\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"maximumFee\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"_totalSupply\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"unpause\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_maker\",\"type\":\"address\"}],\"name\":\"getBlackListStatus\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"address\"}],\"name\":\"allowed\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"paused\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"who\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"pause\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getOwner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"newBasisPoints\",\"type\":\"uint256\"},{\"name\":\"newMaxFee\",\"type\":\"uint256\"}],\"name\":\"setParams\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"issue\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"redeem\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\"},{\"name\":\"_spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"name\":\"remaining\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"basisPointsRate\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"isBlackListed\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_clearedUser\",\"type\":\"address\"}],\"name\":\"removeBlackList\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"MAX_UINT\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_blackListedUser\",\"type\":\"address\"}],\"name\":\"destroyBlackFunds\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_initialSupply\",\"type\":\"uint256\"},{\"name\":\"_name\",\"type\":\"string\"},{\"name\":\"_symbol\",\"type\":\"string\"},{\"name\":\"_decimals\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Issue\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Redeem\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"newAddress\",\"type\":\"address\"}],\"name\":\"Deprecate\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"feeBasisPoints\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"maxFee\",\"type\":\"uint256\"}],\"name\":\"Params\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_blackListedUser\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_balance\",\"type\":\"uint256\"}],\"name\":\"DestroyedBlackFunds\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_user\",\"type\":\"address\"}],\"name\":\"AddedBlackList\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_user\",\"type\":\"address\"}],\"name\":\"RemovedBlackList\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"Pause\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"Unpause\",\"type\":\"event\"}]'\n", 61 | "contractUSDT=w3.eth.contract(address=addressUSDT,abi=abiUSDT)" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": 13, 67 | "id": "903fb974", 68 | "metadata": {}, 69 | "outputs": [ 70 | { 71 | "name": "stdout", 72 | "output_type": "stream", 73 | "text": [ 74 | "(, {'_to': '0xADf9e4cA54fAcAB2eA57eD878E4C3B271073Fa1d', '_value': 149000000})\n" 75 | ] 76 | } 77 | ], 78 | "source": [ 79 | "print(contractUSDT.decode_function_input(transaction.input))" 80 | ] 81 | } 82 | ], 83 | "metadata": { 84 | "kernelspec": { 85 | "display_name": "Python 3 (ipykernel)", 86 | "language": "python", 87 | "name": "python3" 88 | }, 89 | "language_info": { 90 | "codemirror_mode": { 91 | "name": "ipython", 92 | "version": 3 93 | }, 94 | "file_extension": ".py", 95 | "mimetype": "text/x-python", 96 | "name": "python", 97 | "nbconvert_exporter": "python", 98 | "pygments_lexer": "ipython3", 99 | "version": "3.9.12" 100 | } 101 | }, 102 | "nbformat": 4, 103 | "nbformat_minor": 5 104 | } 105 | -------------------------------------------------------------------------------- /20_DecodeTx/img/20-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/20_DecodeTx/img/20-1.png -------------------------------------------------------------------------------- /20_DecodeTx/img/20-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/20_DecodeTx/img/20-2.png -------------------------------------------------------------------------------- /20_DecodeTx/img/20-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/20_DecodeTx/img/20-3.png -------------------------------------------------------------------------------- /20_DecodeTx/img/20-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/20_DecodeTx/img/20-4.png -------------------------------------------------------------------------------- /20_DecodeTx/readme.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 20、解码交易详情 3 | tags: 4 | - web3py 5 | - python 6 | - ens 7 | - vitalik 8 | --- 9 | 10 | # web3.py极简入门: 20、解码交易详情 11 | 12 | 我们最近在重新学`web3.py`,巩固一下细节,也写一个`WTF web3.py极简入门`,供小白们使用。 13 | 14 | **推特**:[@0xAA_Science](https://twitter.com/0xAA_Science) [@localcat15](https://twitter.com/localcat15) 15 | 16 | 17 | **WTF Academy社群:** [官网 wtf.academy](https://wtf.academy) | [WTF Solidity教程](https://github.com/AmazingAng/WTFSolidity) | [discord](https://discord.gg/5akcruXrsk) | [微信群申请](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) 18 | 19 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-web3py](https://github.com/WTFAcademy/WTF-web3py) 20 | 21 | ----- 22 | 23 | 这一讲,我们介绍如何解码调用智能合约时发送的`calldata`。 24 | 25 | ## Calldata 26 | 当我们调用智能合约时,本质上是向目标合约发送了一段`calldata`,`calldata`大致由两个部分组成:前4个字节代表`selector`函数选择器,旨在告诉智能合约我要调用哪个函数;而后面的字节则代表传递给函数的参数。 27 | 28 | 有关`calldata`和`selector`的详细介绍请参考[Solidity极简入门: 29. 函数选择器Selector](https://www.wtf.academy/solidity-advanced/Selector)。 29 | 30 | 下面是一笔在`Uniswap`上进行代币置换的交易,可以通过[etherscan](https://etherscan.io/tx/0xbe5af8b8885ea9d6ae8a2f3f44315554ff62daebf3f99b42eae9d4cda880208e)查看交易详情: 31 | ![swap](img/20-1.png) 32 | 最下面`Input Data`框中的16进制数就是我们所说的`calldata`。在`etherscan`中点击旁边的**Decode Input Data**按钮,即可对交易数据进行解码,结果如下: 33 | ![calldata](img/20-2.png) 34 | 可以看到,通过解码`calldata`,我们的确得到了函数名和具体的函数参数。 35 | ## 解码交易数据 36 | 在`web3.py`中解码`calldata`非常简单,只需借助`Contract.decode_function_input`函数,下面我们将展示其具体过程: 37 | 38 | 1、创建`provider`。 39 | ```python 40 | from web3 import Web3 41 | ALCHEMY_MAINNET_URL = 'https://eth-mainnet.g.alchemy.com/v2/hjZ-SwVhjRtBk-yUJ1SkWSTrz_dJl7of' 42 | w3 = Web3(Web3.HTTPProvider(ALCHEMY_MAINNET_URL)) 43 | ``` 44 | 2、我们随机选取一笔与`USDT`合约进行交互的交易,获取其`calldata`。 45 | ```python 46 | transaction = w3.eth.get_transaction('0x6b2eba89b78484f6b954cf74f35ee42667363f65c0927d46b5f10ef6ab894393') 47 | #打印出calldata 48 | print(transaction.input.hex()) 49 | ``` 50 | ![calldata2](img/20-3.png) 51 | 3、将`USDT`合约实例化,用于解码交易详情。 52 | ```python 53 | #USDT合约地址 54 | addressUSDT='0xdAC17F958D2ee523a2206206994597C13D831ec7' 55 | #USDT合约ABI 56 | abiUSDT='[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,...' 57 | #实例化合约 58 | contractUSDT=w3.eth.contract(address=addressUSDT,abi=abiUSDT) 59 | ``` 60 | 4、解码之前获取到的`calldata`。 61 | ```python 62 | print(contractUSDT.decode_function_input(transaction.input)) 63 | ``` 64 | ![result](img/20-4.png) 65 | 可以得知,之前随机选取的交易是一笔转账交易,金额为149`USDT`。 66 | ## 总结 67 | 这一讲,我们介绍了如何解码`calldata`,并给出了一个实例。 -------------------------------------------------------------------------------- /7_Event/Event.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### 1. 创建`provider`" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 1, 13 | "metadata": {}, 14 | "outputs": [ 15 | { 16 | "data": { 17 | "text/plain": [ 18 | "True" 19 | ] 20 | }, 21 | "execution_count": 1, 22 | "metadata": {}, 23 | "output_type": "execute_result" 24 | } 25 | ], 26 | "source": [ 27 | "from web3 import Web3\n", 28 | "# 利用Infura的rpc节点连接以太坊网络\n", 29 | "# 准备 infura API 可以参考https://github.com/AmazingAng/WTF-Solidity/tree/main/Topics/Tools/TOOL02_Infura/readme.md\n", 30 | "INFURA_GEORLI_URL = 'https://goerli.infura.io/v3/5cd64b21ee2e4a1fa149862681338bbf' \n", 31 | "Provider = Web3(Web3.HTTPProvider(INFURA_GEORLI_URL))\n", 32 | "Provider.is_connected()" 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "### 2. 定义合约地址与合约`abi`" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": 2, 45 | "metadata": {}, 46 | "outputs": [], 47 | "source": [ 48 | "# 测试网络WETH地址\n", 49 | "addressWETH = Provider.to_checksum_address('0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6')\n", 50 | "# WETH合约abi\n", 51 | "abiWETH = '[{\"constant\":true,\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"guy\",\"type\":\"address\"},{\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"src\",\"type\":\"address\"},{\"name\":\"dst\",\"type\":\"address\"},{\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"withdraw\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"name\":\"\",\"type\":\"uint8\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"dst\",\"type\":\"address\"},{\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"deposit\",\"outputs\":[],\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"src\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"guy\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"src\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"dst\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"dst\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Deposit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"src\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Withdrawal\",\"type\":\"event\"}]'" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "### 3. 声明`WETH`合约实例" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": 3, 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | "WETHcontract = Provider.eth.contract(address=addressWETH,abi=abiWETH)" 68 | ] 69 | }, 70 | { 71 | "cell_type": "markdown", 72 | "metadata": {}, 73 | "source": [ 74 | "### 4. 获取过去10个区块内的`Transfer`事件,并打印出第一个" 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": 4, 80 | "metadata": {}, 81 | "outputs": [ 82 | { 83 | "name": "stdout", 84 | "output_type": "stream", 85 | "text": [ 86 | "当前区块高度:10184882\n", 87 | "打印事件详情:\n", 88 | "AttributeDict({'args': AttributeDict({'src': '0xf94d0c47D2Cb6f18622426DB4F454D58CcbDFD1E', 'dst': '0x270D8F77cf968954F0F616ACB9301fCACD06bD02', 'wad': 344770451507748340}), 'event': 'Transfer', 'logIndex': 50, 'transactionIndex': 8, 'transactionHash': HexBytes('0xb510924469fc8d011a4b9c79730e1bfce7eb7a2b72f27bdfb288f21d1d30f029'), 'address': '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6', 'blockHash': HexBytes('0x9162f004869e236b6fd6c3a94cfc8b3f3099f4ffcc7d8e9bfb6648b2538034c8'), 'blockNumber': 10184872})\n" 89 | ] 90 | } 91 | ], 92 | "source": [ 93 | "# 得到当前block\n", 94 | "block = Provider.eth.block_number\n", 95 | "print(f\"当前区块高度:{block}\")\n", 96 | "print(\"打印事件详情:\")\n", 97 | "logs = WETHcontract.events.Transfer.get_logs(fromBlock=block-10, toBlock=block)\n", 98 | "print(logs[0])" 99 | ] 100 | }, 101 | { 102 | "cell_type": "markdown", 103 | "metadata": {}, 104 | "source": [ 105 | "### 5. 读取事件的解析结果" 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": 5, 111 | "metadata": {}, 112 | "outputs": [ 113 | { 114 | "name": "stdout", 115 | "output_type": "stream", 116 | "text": [ 117 | "解析事件:\n", 118 | "地址0xf94d0c47D2Cb6f18622426DB4F454D58CcbDFD1E 转账 0.34477045150774834 WETH 到地址0x270D8F77cf968954F0F616ACB9301fCACD06bD02\n" 119 | ] 120 | } 121 | ], 122 | "source": [ 123 | "# 解析Transfer事件的数据(变量在args中)\n", 124 | "print(\"解析事件:\")\n", 125 | "print(f\"地址{logs[0].args.src} 转账 {Provider.from_wei(logs[0].args.wad, 'ether')} WETH 到地址{logs[0].args.dst}\")" 126 | ] 127 | }, 128 | { 129 | "cell_type": "markdown", 130 | "metadata": {}, 131 | "source": [ 132 | "### 6. 利用argument_filters参数过滤事件" 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": 6, 138 | "metadata": {}, 139 | "outputs": [ 140 | { 141 | "name": "stdout", 142 | "output_type": "stream", 143 | "text": [ 144 | "打印从指定地址出发的Transfer事件详情:\n", 145 | "地址0xf94d0c47D2Cb6f18622426DB4F454D58CcbDFD1E 转账 0.34477045150774834 WETH 到地址0x270D8F77cf968954F0F616ACB9301fCACD06bD02\n", 146 | "地址0xf94d0c47D2Cb6f18622426DB4F454D58CcbDFD1E 转账 0.100994870419913553 WETH 到地址0x620BbfB5E551Bd84cAE59B6d26311888e148901D\n" 147 | ] 148 | } 149 | ], 150 | "source": [ 151 | "# 读取所有src地址为指定地址的Transfer事件,输出解析后的事件\n", 152 | "filteredLogs = WETHcontract.events.Transfer.get_logs(fromBlock=block-10, toBlock=block,argument_filters={'src':logs[0].args.src})\n", 153 | "print(\"打印从指定地址出发的Transfer事件详情:\")\n", 154 | "for l in filteredLogs:\n", 155 | " print(f\"地址{l.args.src} 转账 {Provider.from_wei(l.args.wad, 'ether')} WETH 到地址{l.args.dst}\")" 156 | ] 157 | } 158 | ], 159 | "metadata": { 160 | "kernelspec": { 161 | "display_name": "blockchain", 162 | "language": "python", 163 | "name": "python3" 164 | }, 165 | "language_info": { 166 | "codemirror_mode": { 167 | "name": "ipython", 168 | "version": 3 169 | }, 170 | "file_extension": ".py", 171 | "mimetype": "text/x-python", 172 | "name": "python", 173 | "nbconvert_exporter": "python", 174 | "pygments_lexer": "ipython3", 175 | "version": "3.12.0" 176 | } 177 | }, 178 | "nbformat": 4, 179 | "nbformat_minor": 2 180 | } 181 | -------------------------------------------------------------------------------- /7_Event/img/7-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/7_Event/img/7-1.png -------------------------------------------------------------------------------- /7_Event/img/7-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/7_Event/img/7-2.png -------------------------------------------------------------------------------- /7_Event/img/7-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/7_Event/img/7-3.png -------------------------------------------------------------------------------- /7_Event/img/7-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/7_Event/img/7-4.png -------------------------------------------------------------------------------- /7_Event/img/7-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/7_Event/img/7-5.png -------------------------------------------------------------------------------- /7_Event/readme.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 7. 检索事件 3 | tags: 4 | - web3.py 5 | - python 6 | - provider 7 | - event 8 | - web 9 | --- 10 | # web3py极简入门: 7. 检索事件 11 | 12 | 我最近在重新学 `web3.py`,巩固一下细节,也写一个 `WTF web3py极简入门`,供小白们使用。 13 | 14 | **推特**:[@0xAA_Science](https://twitter.com/0xAA_Science) 15 | 16 | **WTF Academy社群:** [官网 wtf.academy](https://wtf.academy) | [WTF Solidity教程](https://github.com/AmazingAng/WTF-Solidity) | [discord](https://discord.gg/5akcruXrsk) | [微信群申请](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) 17 | 18 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-web3py](https://github.com/WTFAcademy/WTF-web3py) 19 | 20 | --- 21 | 22 | 提示:本教程基于web3.py v6.6.0 ,可以参考[web3.py 官方文档](https://web3py.readthedocs.io/en/stable/)。 23 | 24 | 这一讲,我们将介绍如何使用 `web3.py`读取智能合约释放的事件。如果你不了解 `Solidity`的事件,可以阅读WTF Solidity极简教程中[第12讲:事件](https://github.com/AmazingAng/WTFSolidity/blob/main/12_Event/readme.md)。 25 | 26 | 具体可参考[web3.py文档](https://web3py.readthedocs.io/en/stable/web3.contract.html#events)。 27 | 28 | ## 事件 Event 29 | 30 | 智能合约释放出的事件存储于以太坊虚拟机的日志中。日志分为两个主题 `topics`和数据 `data`部分,其中事件哈希和 `indexed`变量存储在 `topics`中,作为索引方便以后搜索;没有 `indexed`变量存储在 `data`中,不能被直接检索,但可以存储更复杂的数据结构。 31 | 32 | 以ERC20代币中的 `Transfer`转账事件为例,在合约中它是这样声明的: 33 | 34 | ```solidity 35 | event Transfer(address indexed from, address indexed to, uint256 amount); 36 | ``` 37 | 38 | 它共记录了3个变量 `from`,`to`和 `amount`,分别对应代币的发出地址,接收地址和转账数量,其中 `from`和 `to`前面带有 `indexed`关键字。转账时,`Transfer`事件会被记录,可以在 `etherscan`中[查到](https://rinkeby.etherscan.io/tx/0x8cf87215b23055896d93004112bbd8ab754f081b4491cb48c37592ca8f8a36c7)。 39 | 40 | ![Transfer事件](img/7-1.png) 41 | 42 | 从上图中可以看到,`Transfer`事件被记录到了EVM的日志中,其中 `Topics`包含3个数据,分别对应事件哈希,发出地址 `from`,和接收地址 `to`;而 `Data`中包含一个数据,对应转账数额 `amount`。 43 | 44 | ## 检索事件 45 | 46 | `web3.py`中,通过 `web3.contract.ContractEvents`类,我们可以与合约中指定的事件进行交互,其中的 `get_logs()`方法提供了读取合约释放事件的接口,我们可以通过 `Contract`类的 `events`属性来得到指定合约的 `ContractEvents`实例,进而针对不同的事件调用 `get_logs()`进行检索,具体用法如下: 47 | 48 | ```python 49 | eventsLog = 合约实例.events.事件名.get_logs(fromBlock = 起始区块, toBlock = 结束区块, argument_filters = 事件参数键值对) 50 | ``` 51 | 52 | 其中事件名代表要检索的事件名称;`get_logs()`包含3个参数,分别是起始区块 `fromBlock`(选填),结束区块 `toBlock`(选填),事件参数键值对 `argument_filters`(选填),其中 `argument_filters`针对事件参数对检索结果进行过滤;检索结果会以 `元组(tuple)`的方式返回,每个元素对应一次事件释放的信息。 53 | 54 | **注意**:要检索的事件必须包含在合约的 `abi`中。 55 | 56 | ## 例子:检索 `WETH`合约中的 `Transfer`事件 57 | 58 | 1. 创建 `provider`。 59 | 60 | ```Python 61 | from web3 import Web3 62 | # 利用Infura的rpc节点连接以太坊网络 63 | # 准备 infura API 可以参考https://github.com/AmazingAng/WTF-Solidity/tree/main/Topics/Tools/TOOL02_Infura/readme.md 64 | INFURA_GEORLI_URL = 'https://goerli.infura.io/v3/5cd64b21ee2e4a1fa149862681338bbf' 65 | Provider = Web3(Web3.HTTPProvider(INFURA_GEORLI_URL)) 66 | Provider.is_connected() 67 | ``` 68 | 2. 定义合约地址与合约 `abi` 69 | 70 | 该合约的abi可以由[etherscan](https://goerli.etherscan.io/address/0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6#code)中的Contract ABI复制得到。 71 | 72 | ```Python 73 | # 测试网络WETH地址 74 | addressWETH = Provider.to_checksum_address('0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6') 75 | # WETH合约abi 76 | abiWETH = '[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],...太长后面省略...' 77 | ``` 78 | 79 | ![合约abi](img/7-2.png) 80 | 81 | 3. 声明 `WETH`合约实例。 82 | 83 | ```python 84 | WETHcontract = Provider.eth.contract(address=addressWETH,abi=abiWETH) 85 | ``` 86 | 4. 获取过去10个区块内的 `Transfer`事件,并打印出第一个。我们可以看到,`args`中有3个数据,分别对应 `src`,`dst`和 `wad`。 87 | 88 | ```python 89 | # 得到当前block 90 | block = Provider.eth.block_number 91 | print(f"当前区块高度:{block}") 92 | print("打印事件详情:") 93 | logs = WETHcontract.events.Transfer.get_logs(fromBlock=block-10, toBlock=block) 94 | print(logs[0]) 95 | ``` 96 | 97 | ![打印事件](img/7-3.png) 98 | 5. 读取事件的解析结果。 99 | 100 | ```python 101 | # 解析Transfer事件的数据(变量在args中) 102 | print("解析事件:") 103 | print(f"地址{logs[0].args.src} 转账 {Provider.from_wei(logs[0].args.wad, 'ether')} WETH 到地址{logs[0].args.dst}") 104 | ``` 105 | 106 | ![解析事件](img/7-4.png) 107 | 6. 利用argument_filters参数过滤事件 108 | 109 | ```PYTHON 110 | # 读取所有src地址为指定地址的Transfer事件,输出解析后的事件 111 | filteredLogs = WETHcontract.events.Transfer.get_logs(fromBlock=block-10, toBlock=block,argument_filters={'src':logs[0].args.src}) 112 | print("打印从指定地址出发的Transfer事件详情:") 113 | for l in filteredLogs: 114 | print(f"地址{l.args.src} 转账 {Provider.from_wei(l.args.wad, 'ether')} WETH 到地址{l.args.dst}") 115 | ``` 116 | 117 | ![过滤事件](img/7-5.png) 118 | 119 | ## 总结 120 | 121 | 这一讲,我们回顾了 `Solidity`中的事件,介绍如何用 `web3.py`检索智能合约释放的事件,并介绍了一种过滤事件的方法。 122 | -------------------------------------------------------------------------------- /8_ContractListener/ContractListener.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### 1. 声明`Provider`" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 128, 13 | "metadata": {}, 14 | "outputs": [ 15 | { 16 | "data": { 17 | "text/plain": [ 18 | "True" 19 | ] 20 | }, 21 | "execution_count": 128, 22 | "metadata": {}, 23 | "output_type": "execute_result" 24 | } 25 | ], 26 | "source": [ 27 | "from web3 import Web3\n", 28 | "# 准备 alchemy API \n", 29 | "# 可以参考https://github.com/AmazingAng/WTFSolidity/blob/main/Topics/Tools/TOOL04_Alchemy/readme.md \n", 30 | "ALCHEMY_MAINNET_URL = 'https://eth-mainnet.g.alchemy.com/v2/oKmOQKbneVkxgHZfibs-iFhIlIAl6HDN'\n", 31 | "Provider = Web3(Web3.HTTPProvider(ALCHEMY_MAINNET_URL))\n", 32 | "Provider.is_connected()" 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "### 2. 定义合约地址和合约`abi`" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": 131, 45 | "metadata": {}, 46 | "outputs": [], 47 | "source": [ 48 | "# 定义合约地址\n", 49 | "contractAddress = Provider.to_checksum_address(\"0xdac17f958d2ee523a2206206994597c13d831ec7\")\n", 50 | "# 定义合约abi\n", 51 | "contractABI = '[{\"constant\":true,\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_upgradedAddress\",\"type\":\"address\"}],\"name\":\"deprecate\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_spender\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"deprecated\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_evilUser\",\"type\":\"address\"}],\"name\":\"addBlackList\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_from\",\"type\":\"address\"},{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"upgradedAddress\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"balances\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"maximumFee\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"_totalSupply\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"unpause\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_maker\",\"type\":\"address\"}],\"name\":\"getBlackListStatus\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"address\"}],\"name\":\"allowed\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"paused\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"who\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"pause\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getOwner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"newBasisPoints\",\"type\":\"uint256\"},{\"name\":\"newMaxFee\",\"type\":\"uint256\"}],\"name\":\"setParams\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"issue\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"redeem\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\"},{\"name\":\"_spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"name\":\"remaining\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"basisPointsRate\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"isBlackListed\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_clearedUser\",\"type\":\"address\"}],\"name\":\"removeBlackList\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"MAX_UINT\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_blackListedUser\",\"type\":\"address\"}],\"name\":\"destroyBlackFunds\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_initialSupply\",\"type\":\"uint256\"},{\"name\":\"_name\",\"type\":\"string\"},{\"name\":\"_symbol\",\"type\":\"string\"},{\"name\":\"_decimals\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Issue\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Redeem\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"newAddress\",\"type\":\"address\"}],\"name\":\"Deprecate\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"feeBasisPoints\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"maxFee\",\"type\":\"uint256\"}],\"name\":\"Params\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_blackListedUser\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_balance\",\"type\":\"uint256\"}],\"name\":\"DestroyedBlackFunds\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_user\",\"type\":\"address\"}],\"name\":\"AddedBlackList\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_user\",\"type\":\"address\"}],\"name\":\"RemovedBlackList\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"Pause\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"Unpause\",\"type\":\"event\"}]'" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "### 3. 创建合约实例与事件过滤器实例" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": 140, 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | "# 创建合约实例\n", 68 | "USDTcontract = Provider.eth.contract(address = contractAddress, abi = contractABI)\n", 69 | "# 创建事件过滤器实例\n", 70 | "event_filter = USDTcontract.events.Transfer.create_filter(fromBlock=\"pending\",toBlock=\"pending\")" 71 | ] 72 | }, 73 | { 74 | "cell_type": "markdown", 75 | "metadata": {}, 76 | "source": [ 77 | "### 4. 监听`USDT`合约的`Transfer`事件" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": 141, 83 | "metadata": {}, 84 | "outputs": [ 85 | { 86 | "name": "stdout", 87 | "output_type": "stream", 88 | "text": [ 89 | "解析事件:\n", 90 | "0x68707909e06ba2D88Bc12CA294a5b0678Bb099AF -> 0x74de5d4FCbf63E00296fd95d33236B9794016631 600\n", 91 | "0x74de5d4FCbf63E00296fd95d33236B9794016631 -> 0x51C72848c68a965f66FA7a88855F9f7784502a7F 595.542\n", 92 | "0x74de5d4FCbf63E00296fd95d33236B9794016631 -> 0x2aCf35C9A3F4c5C3F4c78EF5Fb64c3EE82f07c45 4.458\n", 93 | "0xfFfa3567453f675b6D95054dc502a3A2A298C0B1 -> 0xd49ca47c635595c7cC907E3E6d3244537F30E781 20.2043\n", 94 | "0x7D6C706975c83B33f6f73bf7380d8ED08d717244 -> 0x32Ca2FE6a7A4e80C13217C213CFb02676e8307cA 700\n", 95 | "0x0802Dd2EDD3f9faD39A9173B4595be819f201d61 -> 0x546b0216df01778D9f8FD7A19767E3efE2ca33b2 64\n", 96 | "0x343f0a22b13E63286955085992c63917124c12e4 -> 0x0802Dd2EDD3f9faD39A9173B4595be819f201d61 263.709901\n", 97 | "0xb0239b7D0E3BBdc504076592105Ce545ad5Fa528 -> 0x0802Dd2EDD3f9faD39A9173B4595be819f201d61 14.847996\n", 98 | "0x236669c4825C7Add4C34e574F73a4c128D7dB932 -> 0x0802Dd2EDD3f9faD39A9173B4595be819f201d61 250\n", 99 | "0x97Ed4bd8449FA7335fF2849949D7a5e3528DB2E2 -> 0x056ECDA018F78D8aacFAE85a2bD6FBD28754e217 0\n", 100 | "0x27646B1C868ECcd905dD095ea21D9176ac811718 -> 0x577c2175e276B83DA2c29CfA51e17Bc587276bb8 0\n", 101 | "0xA76c5a4Fb9D344686b09A0c2c518132717966B68 -> 0xb101637bB8eF3d65DbcE4EC8F8117e02613E516F 0\n", 102 | "0x27646b1C8B488c68c67d83BA82351b70a3811718 -> 0x57746fBaa318B78eec312eEeB30f0Fa0eb766bB8 0\n", 103 | "0x46340b20830761efd32832A74d7169B29FEB9758 -> 0x3482002744d4a8d435F07Ccd7215EC0bDab89D39 10.664027\n", 104 | "0xf89d7b9c864f589bbF53a82105107622B35EaA40 -> 0x1496c19E6775C1A17130CbEf268Ce08765D5e189 53777.2845\n", 105 | "0xf89d7b9c864f589bbF53a82105107622B35EaA40 -> 0x1441200e2410273DCE5979B2485c06c6101BE75c 99.9843\n", 106 | "0xbb72F8F898cd9499a86AD15A33f4B2DEEe1Dd665 -> 0xa14EFA5ABf8cF08e0016D2FB86AEe01C0d498464 325\n", 107 | "0x68841a1806fF291314946EebD0cdA8b348E73d6D -> 0x2d83183754e0B8EcaFAEd81811717C4Af8B63307 2000\n", 108 | "0x4116Dc511fbdC34C2e5719Bb2D8E023fD8b8Bc42 -> 0x0d4a11d5EEaaC28EC3F61d100daF4d40471f1852 913.383413\n", 109 | "0x0d4a11d5EEaaC28EC3F61d100daF4d40471f1852 -> 0x5913370D8C8c0118340833f66A14207E3CcE6136 603.977332\n", 110 | "0x2008b6c3D07B061A84F790C035c2f6dC11A0be70 -> 0x22F9dCF4647084d6C31b2765F6910cd85C178C18 117.62121\n", 111 | "0x22F9dCF4647084d6C31b2765F6910cd85C178C18 -> 0xaD01C20d5886137e056775af56915de824c8fCe5 0.114092\n", 112 | "0x22F9dCF4647084d6C31b2765F6910cd85C178C18 -> 0x7B14547D01D57cCEE7Cc430FcB30D5E271a0D9e8 117.507118\n", 113 | "0x11b815efB8f581194ae79006d24E0d814B7697F6 -> 0x5933C87a4E8d3c15F7AD294D6b95054f7D077c3e 6234.956661\n", 114 | "0x62e3C242B5e903071458ad90A160493D84911C77 -> 0xD35590a0712760A29cA47aA40b95DfeFb1C03187 453.207248\n", 115 | "0x951Eb80dF3fDC3EFBE5DAADf88a9c6201DCDF943 -> 0xCd27fa7dC7625Ec54509DdC7632CccfAbd2963dd 1210.954918\n", 116 | "0xCEA5694Cb129CD9D3756134db56d65f2Da034103 -> 0x0c35339256cFc9505A851d6e6ec778903EEA5228 2782\n", 117 | "0xAF54A6EFe4510cC6B3d984D952742a8Dd7Ce3acb -> 0x78E851C35326c9296485E9720D85CB3Bd153b428 296.691478\n", 118 | "0xCAe301E7d1b9f565c47975A19357601FD1BF9479 -> 0x66C49e647e3E8C45f6aD2F3d11B28C88d018223F 1881.850965\n", 119 | "0x5F1314cF2c13517d79A828c6AAb4b173de47462D -> 0x06da0fd433C1A5d7a4faa01111c044910A184553 67.813882\n", 120 | "0x0Ab3FbC9025EcE0EA4e0f9D29fbAa94B70923e37 -> 0xAd2c908a6Abc9353DEE31405aCb3ca1887bDe425 968.411929\n", 121 | "0x4C77eFCB44eeED89AB54Cc0aeba64b2ddF919096 -> 0x92726b8a08Ac85710D622d8E14995ce777ff3391 45\n", 122 | "0x2902349a7920f38b833dF0857E22463BEDc0a6eD -> 0x64687bC8039e873b8A84d081747718181f64f86b 436.18\n", 123 | "0x68841a1806fF291314946EebD0cdA8b348E73d6D -> 0x165eB79EA2c85dB3bDda3871E2Ff28436738B9D9 2386.3\n", 124 | "0x3D55CCb2a943d88D39dd2E62DAf767C69fD0179F -> 0x5b631Cd02Af49dE7Afb3c9c22d7dC3d581f5AD20 500\n", 125 | "0x68841a1806fF291314946EebD0cdA8b348E73d6D -> 0x0FEeE58Ce28070563bDde20428e4Eca10f65f7F8 963.03\n", 126 | "0xD6D01AD997F50c14417321050D6dA5AAaC651a8E -> 0x66784429B707dC3FF5b84f57b98EC6ef38Db0411 20000\n", 127 | "0x91eb2dADb8e55d6D37d0e0C8BFD327b1B70f5c55 -> 0xD3FC3b6e17137d70Bb48255C4272d29BfEb30218 22\n", 128 | "0x3C69822B6D0A6A3C5abfc9F28eE734cb6f79c427 -> 0x6d3577877c495977C80D7672231c8bF3CC35D2EA 5955\n" 129 | ] 130 | } 131 | ], 132 | "source": [ 133 | "import time\n", 134 | "print(\"解析事件:\")\n", 135 | "start = time.time()\n", 136 | "# 通过循环,监听事件,持续25秒\n", 137 | "while True and (time.time() - start) < 25:\n", 138 | " # 通过循环,持续监听事件\n", 139 | " newEvent = event_filter.get_new_entries()\n", 140 | " # 判断是否有新的事件被释放\n", 141 | " if len(newEvent) :\n", 142 | " # 提取监听到的事件,并进行解析\n", 143 | " for log in newEvent:\n", 144 | " print(f\"{log.args[\"from\"]} -> {log.args[\"to\"]} {Provider.from_wei(log.args[\"value\"], \"mwei\")}\")\n", 145 | " # 控制查询间隔\n", 146 | " time.sleep(1)" 147 | ] 148 | }, 149 | { 150 | "cell_type": "code", 151 | "execution_count": null, 152 | "metadata": {}, 153 | "outputs": [], 154 | "source": [] 155 | } 156 | ], 157 | "metadata": { 158 | "kernelspec": { 159 | "display_name": "blockchain", 160 | "language": "python", 161 | "name": "python3" 162 | }, 163 | "language_info": { 164 | "codemirror_mode": { 165 | "name": "ipython", 166 | "version": 3 167 | }, 168 | "file_extension": ".py", 169 | "mimetype": "text/x-python", 170 | "name": "python", 171 | "nbconvert_exporter": "python", 172 | "pygments_lexer": "ipython3", 173 | "version": "3.12.0" 174 | } 175 | }, 176 | "nbformat": 4, 177 | "nbformat_minor": 2 178 | } 179 | -------------------------------------------------------------------------------- /8_ContractListener/img/8-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WTFAcademy/WTF-web3py/b1aa1c3c63c216e9db15afbef9d4d00d1c628525/8_ContractListener/img/8-1.png -------------------------------------------------------------------------------- /8_ContractListener/readme.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 8. 监听合约事件 3 | tags: 4 | - web3.py 5 | - python 6 | - provider 7 | - event 8 | - web 9 | --- 10 | # web3py极简入门: 8. 监听合约事件 11 | 12 | 我最近在重新学 `web3.py`,巩固一下细节,也写一个 `WTF web3py极简入门`,供小白们使用。 13 | 14 | **推特**:[@0xAA_Science](https://twitter.com/0xAA_Science) 15 | 16 | **WTF Academy社群:** [官网 wtf.academy](https://wtf.academy) | [WTF Solidity教程](https://github.com/AmazingAng/WTF-Solidity) | [discord](https://discord.gg/5akcruXrsk) | [微信群申请](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link) 17 | 18 | 所有代码和教程开源在github: [github.com/WTFAcademy/WTF-web3py](https://github.com/WTFAcademy/WTF-web3py) 19 | 20 | 提示:本教程基于web3.py v6.6.0 ,可以参考[web3.py 官方文档](https://web3py.readthedocs.io/en/stable/)。 21 | 22 | 这一讲,我们将介绍如何监听合约,并实现监听 `USDT`合约的 `Transfer`事件。 23 | 24 | 具体可参考[web3.py文档](https://web3py.readthedocs.io/en/stable/filters.html#examples-listening-for-events)。 25 | 26 | ## 通过合约事件创建事件过滤器 27 | 28 | 在 `web3.py`中,事件的监听需要通过过滤器 `filter`来完成,过滤器的详细介绍可以参见下一节,本节只介绍如何通过 `web3.contract.ContractEvents`对象创建事件过滤器 29 | 30 | ```PYTHON 31 | eventFilter = 合约实例.events.事件名称.create_filter(fromBlock=起始区块,toBlock = 终止区块, argument_filters = 事件参数键值对, topics = 主题集过滤条件) 32 | ``` 33 | 34 | `create_filter()`包含四个参数,分别是起始区块 `fromBlock`(必填),结束区块 `toBlock`(选填),事件参数键值对 `argument_filters`(选填),主题集过滤条件 `topics`(选填) 35 | 36 | - `fromBlock`和 `toBlock`可以是区块编号,也可以是字符串 `latest`(区块链的当前最新块),`earliest`(创世块),`pending`(正在挖掘的块),后续例子中我们将使用 `pending`来进行事件的实时监听。 37 | - `argument_filters`针对事件参数,对检索结果进行过滤。 38 | - `topics`针对事件主题集进行过滤,关于该参数的使用,可以参考[Ethers极简入门: 8. 监听合约事件](https://github.com/WTFAcademy/WTF-Ethers/tree/main/09_EventFilter)中的介绍 39 | 40 | **注意**:起始区块 `fromBlock`是必填项。 41 | 42 | ## 利用事件过滤器接口监听事件 43 | 44 | 通过调用事件过滤器的 `get_new_entries()`方法,我们可以在 `[起始区块,结束区块]`的范围内,监听自上次 `轮询(polling)`以来的所有新释放的事件 45 | 46 | ```python 47 | eventFilter.get_new_entries() 48 | ``` 49 | 50 | 这个函数不需要任何参数,因为所有参数在定义事件过滤器 `eventFilter`时已经确定,函数的返回结果是一个 `列表`,每个元素对应一次事件释放的信息。 51 | 52 | ## 监听 `USDT`合约的 `Transfer`事件 53 | 54 | 1. 声明 `Provider` 55 | 56 | 要想和链上信息交互,首先需要声明 `Provider`,这里使用节点供应商Alchemy的节点API,关于如何申请,可以参考[Solidity极简入门-工具篇4:Alchemy](https://github.com/AmazingAng/WTF-Solidity/blob/main/Topics/Tools/TOOL04_Alchemy/readme.md) 57 | 58 | ```python 59 | from web3 import Web3 60 | # 准备 alchemy API 61 | # 可以参考https://github.com/AmazingAng/WTFSolidity/blob/main/Topics/Tools/TOOL04_Alchemy/readme.md 62 | ALCHEMY_MAINNET_URL = 'https://eth-mainnet.g.alchemy.com/v2/oKmOQKbneVkxgHZfibs-iFhIlIAl6HDN' 63 | Provider = Web3(Web3.HTTPProvider(ALCHEMY_MAINNET_URL)) 64 | Provider.is_connected() 65 | ``` 66 | 67 | 2. 定义合约地址和合约 `abi` 68 | 69 | 该合约的abi可以由[etherscan](https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7#code)中的Contract ABI复制得到。 70 | 71 | ```PYTHON 72 | # 定义合约地址 73 | contractAddress = Provider.to_checksum_address('0xdac17f958d2ee523a2206206994597c13d831ec7') 74 | # 定义合约abi 75 | contractABI = '[{"constant":true,"inputs":[],"name":"name","outputs"...后面太长所以省略...' 76 | ``` 77 | 78 | 3. 创建合约实例与事件过滤器实例 79 | 80 | ```python 81 | # 创建合约实例 82 | USDTcontract = Provider.eth.contract(address = contractAddress, abi = contractABI) 83 | # 创建事件过滤器实例 84 | event_filter = USDTcontract.events.Transfer.create_filter(fromBlock="pending",toBlock="pending") 85 | ``` 86 | 87 | 4. 监听 `USDT`合约的 `Transfer`事件 88 | 89 | ```PYTHON 90 | import time 91 | print("解析事件:") 92 | start = time.time() 93 | # 通过循环,监听事件,持续25秒 94 | while True and (time.time() - start) < 25: 95 | # 通过循环,持续监听事件 96 | newEvent = event_filter.get_new_entries() 97 | # 判断是否有新的事件被释放 98 | if len(newEvent) : 99 | # 提取监听到的事件,并进行解析 100 | for log in newEvent: 101 | print(f"{log.args["from"]} -> {log.args["to"]} {Provider.from_wei(log.args["value"], "mwei")}") 102 | # 控制监听间隔 103 | time.sleep(1) 104 | ``` 105 | 106 | ![部分监听结果](img/8-1.png) 107 | 108 | ## 总结 109 | 110 | 这一讲,我们介绍了利用 `web3.py`中 `filter`类的 `get_new_entries()`接口,进行简单的链上监听功能。通过上述方法,你可以监听指定合约的指定事件。 111 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 WTF Academy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MultiTransfer.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "1. 创建HD钱包,用于批量生成地址。\n" 13 | ] 14 | } 15 | ], 16 | "source": [ 17 | "# 1. 创建HD钱包,用于批量生成地址。\n", 18 | "print(\"1. 创建HD钱包,用于批量生成地址。\")\n", 19 | "\n", 20 | "from web3 import Web3\n", 21 | "w3 = Web3(Web3.HTTPProvider('https://goerli.infura.io/v3/5be23a050401499fb951be2e12178e01'))\n", 22 | "# test mnemonic from ganache (don't use it!)\n", 23 | "mnemonic = \"air organ twist rule prison symptom jazz cheap rather dizzy verb glare jeans orbit weapon universe require tired sing casino business anxiety seminar hunt\"\n", 24 | "w3.eth.account.enable_unaudited_hdwallet_features()\n" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 3, 30 | "metadata": {}, 31 | "outputs": [ 32 | { 33 | "name": "stdout", 34 | "output_type": "stream", 35 | "text": [ 36 | "2. 利用HD钱包,生成10个钱包地址。\n" 37 | ] 38 | }, 39 | { 40 | "data": { 41 | "text/plain": [ 42 | "['0x83E5B09c54C4EB904B9bC842Acab9218c2297d6d',\n", 43 | " '0xF44F814ABa3e6BC091487Cf313B49F109550d086',\n", 44 | " '0xC08A302438EaA60adE93196A527A837AA1CA5A3f',\n", 45 | " '0x57171534c9616bB635351deB9d4AA009Fc0d6931',\n", 46 | " '0xFECb70fD6b9414ff7B58C6989D44AFA4a0511D6d',\n", 47 | " '0x897366fBfD8505dE0D772e2F34CF99ac692a9B15',\n", 48 | " '0x5412DD89dD6B707fA816a3b8E0BDFe44A46CA152',\n", 49 | " '0x9c85ee2fFB694A161b59D697ca560Aa2e1a98E6E',\n", 50 | " '0xaBEC7899686e8FE4658bcce8391Fb4e3A70C8868',\n", 51 | " '0x7610CfA2931e9D36CD1aF96599a5ed7886561147']" 52 | ] 53 | }, 54 | "execution_count": 3, 55 | "metadata": {}, 56 | "output_type": "execute_result" 57 | } 58 | ], 59 | "source": [ 60 | "# 2. 利用HD钱包,生成10个钱包地址。\n", 61 | "print(\"2. 利用HD钱包,生成10个钱包地址。\")\n", 62 | "\n", 63 | "addresses = []\n", 64 | "for i in range(10):\n", 65 | " address = w3.eth.account.from_mnemonic(mnemonic, account_path=f\"m/44'/60'/0'/0/{i}\")\n", 66 | " addresses.append(address.address)\n", 67 | "addresses" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": 8, 73 | "metadata": {}, 74 | "outputs": [ 75 | { 76 | "name": "stdout", 77 | "output_type": "stream", 78 | "text": [ 79 | "3. 创建wallet,发送代币用。\n" 80 | ] 81 | }, 82 | { 83 | "data": { 84 | "text/plain": [ 85 | "'0x338f8891D6BdC58eEB4754352459cC461EfD2a5E'" 86 | ] 87 | }, 88 | "execution_count": 8, 89 | "metadata": {}, 90 | "output_type": "execute_result" 91 | } 92 | ], 93 | "source": [ 94 | "# 3. 创建wallet,发送代币用。\n", 95 | "print(\"3. 创建wallet,发送代币用。\")\n", 96 | "\n", 97 | "private_key = \"0x21ac72b6ce19661adf31ef0d2bf8c3fcad003deee3dc1a1a64f5fa3d6b049c06\"\n", 98 | "account = w3.eth.account.from_key(private_key)\n", 99 | "account.address\n" 100 | ] 101 | }, 102 | { 103 | "cell_type": "code", 104 | "execution_count": 5, 105 | "metadata": {}, 106 | "outputs": [ 107 | { 108 | "name": "stdout", 109 | "output_type": "stream", 110 | "text": [ 111 | "4. 创建Airdrop合约。\n" 112 | ] 113 | }, 114 | { 115 | "data": { 116 | "text/plain": [ 117 | "" 118 | ] 119 | }, 120 | "execution_count": 5, 121 | "metadata": {}, 122 | "output_type": "execute_result" 123 | } 124 | ], 125 | "source": [ 126 | "# 4. 创建Airdrop合约。\n", 127 | "print(\"4. 创建Airdrop合约。\")\n", 128 | "\n", 129 | "# 合约 abi\n", 130 | "airdrop_abi = '[{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"_arr\",\"type\":\"uint256[]\"}],\"name\":\"getSum\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"sum\",\"type\":\"uint256\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address payable[]\",\"name\":\"_addresses\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"_amounts\",\"type\":\"uint256[]\"}],\"name\":\"multiTransferETH\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_token\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"_addresses\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"_amounts\",\"type\":\"uint256[]\"}],\"name\":\"multiTransferToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]'\n", 131 | "\n", 132 | "# 合约地址 测试网\n", 133 | "airdrop_address = '0x71C2aD976210264ff0468d43b198FD69772A25fa'\n", 134 | "\n", 135 | "# 创建合约实例\n", 136 | "airdrop_contract = w3.eth.contract(address=airdrop_address, abi=airdrop_abi)\n", 137 | "airdrop_contract" 138 | ] 139 | }, 140 | { 141 | "cell_type": "code", 142 | "execution_count": 6, 143 | "metadata": {}, 144 | "outputs": [ 145 | { 146 | "name": "stdout", 147 | "output_type": "stream", 148 | "text": [ 149 | "5. 读取一个地址的ETH余额。\n", 150 | "200000000000000\n" 151 | ] 152 | } 153 | ], 154 | "source": [ 155 | "# 5. 读取一个地址的ETH余额。\n", 156 | "print(\"5. 读取一个地址的ETH余额。\")\n", 157 | "\n", 158 | "# 目标地址\n", 159 | "target_address = addresses[8]\n", 160 | "\n", 161 | "# 读取 ETH 余额\n", 162 | "balanceETH = w3.eth.get_balance(target_address)\n", 163 | "\n", 164 | "print(balanceETH)" 165 | ] 166 | }, 167 | { 168 | "cell_type": "code", 169 | "execution_count": 7, 170 | "metadata": {}, 171 | "outputs": [ 172 | { 173 | "name": "stdout", 174 | "output_type": "stream", 175 | "text": [ 176 | "6. 调用multiTransferETH()函数,给每个钱包转 0.0001 ETH\n", 177 | "转账后余额: 300000000000000\n" 178 | ] 179 | } 180 | ], 181 | "source": [ 182 | "# 6. 调用multiTransferETH()函数,给每个钱包转 0.0001 ETH\n", 183 | "print(\"6. 调用multiTransferETH()函数,给每个钱包转 0.0001 ETH\")\n", 184 | "\n", 185 | "# 发起交易\n", 186 | "amounts = (len(addresses)) * [w3.to_wei(0.0001, 'ether')]\n", 187 | "# 调用方法\n", 188 | "tx = airdrop_contract.functions.multiTransferETH(addresses, amounts).build_transaction({\n", 189 | " 'from': account.address,\n", 190 | " 'value': w3.to_wei(0.001, 'ether'),\n", 191 | " 'gas': 2000000, # 根据需要调整\n", 192 | " 'nonce': w3.eth.get_transaction_count(account.address)\n", 193 | "})\n", 194 | "\n", 195 | "# 发起交易\n", 196 | "signed_tx = w3.eth.account.sign_transaction(tx, private_key=private_key)\n", 197 | "tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction)\n", 198 | "\n", 199 | "# 等待交易上链\n", 200 | "tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)\n", 201 | "\n", 202 | "balanceETH2 = w3.eth.get_balance(addresses[8])\n", 203 | "print(\"转账后余额:\", balanceETH2)" 204 | ] 205 | }, 206 | { 207 | "cell_type": "code", 208 | "execution_count": null, 209 | "metadata": {}, 210 | "outputs": [], 211 | "source": [] 212 | } 213 | ], 214 | "metadata": { 215 | "kernelspec": { 216 | "display_name": "base", 217 | "language": "python", 218 | "name": "python3" 219 | }, 220 | "language_info": { 221 | "codemirror_mode": { 222 | "name": "ipython", 223 | "version": 3 224 | }, 225 | "file_extension": ".py", 226 | "mimetype": "text/x-python", 227 | "name": "python", 228 | "nbconvert_exporter": "python", 229 | "pygments_lexer": "ipython3", 230 | "version": "3.9.13" 231 | }, 232 | "orig_nbformat": 4 233 | }, 234 | "nbformat": 4, 235 | "nbformat_minor": 2 236 | } 237 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WTF web3py 2 | 我们最近在重新学web3py,巩固一下细节,也写一个“WTF web3py极简入门”,供小白们使用,每周更新1-3讲。 3 | 4 | Lead by PKU Blockchain. 5 | 6 | ## 入门 7 | **第1讲:Hello web3py** 8 | 9 | **第2讲:Provider 提供器** 10 | 11 | **第3讲:读取合约信息** 12 | 13 | **第4讲:发送ETH** 14 | 15 | **第5讲:合约交互** 16 | 17 | **第6讲:部署合约** 18 | 19 | **第7讲:检索事件** 20 | 21 | **第8讲:合约监听** 22 | 23 | **第9讲:事件过滤** 24 | 25 | **第10讲:单位转换** 26 | 27 | 28 | ## WTF web3py贡献者 29 |
30 |

31 | 贡献者是WTF学院的基石 32 |

33 | 34 | 35 | 36 |
37 | 38 | ## 参考 39 | - [Ethers.js Docs](https://docs.ethers.org/v5/) 40 | - [A beginner’s guide: 4 ways to play with Ethers.js](https://dev.to/yakult/a-beginers-guide-four-ways-to-play-with-ethersjs-354a) 41 | --------------------------------------------------------------------------------