使用web3.py发送ETH和ERC20

    2021年,web3.py的版本更新到了v5.4,其库函数的名称改了很多,库函数名称由之前的驼峰命名法: xxxYYYzzz (错落有致,用大小写区别不同的名称),改成 蛇形命名法: xxx_yyy_zzz (名称全部小写,名字之间用_下划线连接)。
    使用web3.eth.send_transaction()来发送ETH, 使用web3.eth.wait_for_transaction_receipt()来发送ERC20。
    下面介绍,ETH和ERC20的发送语法:
    a)发送ETH

from web3 import Web3, HTTPProvider
w3 = Web3(HTTPProvider("http://localhost:8545"))
## 30 milli = 0.03 ether
w3.eth.send_transaction({
  'to': '0x6927c1c19da90EabC30D5fde4D27D98Dfe2D9866',
  'from': '0xc88D334e8045aE5791835CC6471605C7d36CEfFc',
  'value': w3.toWei('30', 'milli'),
  'gas': 6600000,
  'gasPrice': web3.toWei(2, 'gwei'),
})

    b) 发送ERC20

from web3 import Web3, HTTPProvider
w3 = Web3(HTTPProvider("http://localhost:8545"))
contract = w3.eth.contract(contract_address, abi=ABI)

alice = '0xc88D334e8045aE5791835CC6471605C7d36CEfFc'
bob = '0x6927c1c19da90EabC30D5fde4D27D98Dfe2D9866'

tx_hash = contract.functions.transfer(bob, 100).transact({'from': alice})
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)

1、版本更替

1.1 获取当前高度

原版:web3.eth.blockNumber()
新版:web3.eth.block_number()

1.2 获取链ID

原版:web3.eth.chainId
新版:web3.eth.chain_id

1.3 获取余额

原版:web3.eth.getBalance(account)
新版:web3.eth.get_balance(account)

1.4 对交易进行签名

原版:web3.eth.signTransaction
新版:web3.eth.sign_transaction

1.5 对交易进行广播

原版:web3.eth.sendRawTransaction
新版:web3.eth.send_raw_transaction

2、创建onepython工程

2.1 创建文件夹

mkdir onepython
cd onepython
npm init -y
truffle init

## 新建文件夹onepy
mkdir -p test\onepy

2.2 新建DPCToken智能合约

    在onepython/contracts目录下,新建DPCToken.sol合约
    //DPCToken.sol

// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

library SafeMath {
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");
        return c;
    }
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;
        return c;
    }
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }
        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");
        return c;
    }
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold
        return c;
    }
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }
}

interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

contract DPCToken is IERC20 {
    using SafeMath for uint256;

    string private _name = "Delta Park Chain";
    string private _symbol = "DPC";
    uint8 private _decimals = 18;
    uint256 private _totalSupply = 50000000 * (10 ** uint256(_decimals));

    mapping (address => uint256) private _balances;
    mapping (address => mapping (address => uint256)) private _allowances;

    constructor () public {
        _balances[msg.sender] = _totalSupply;
    }

    function name() public view returns (string memory) {
        return _name;
    }

    function symbol() public view returns (string memory) {
        return _symbol;
    }

    function decimals() public view returns (uint8) {
        return _decimals;
    }

    function totalSupply() public override view returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address account) public override view returns (uint256) {
        return _balances[account];
    }

    function allowance(address owner, address spender) public override view returns (uint256) {
        return _allowances[owner][spender];
    }

    function transfer(address recipient, uint256 amount) public override returns (bool) {
        _transfer(msg.sender, recipient, amount);
        return true;
    }

    function approve(address spender, uint256 amount) public override returns (bool) {
        _approve(msg.sender, spender, amount);
        return true;
    }

    function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(sender, msg.sender, _allowances[sender][msg.sender].sub(amount, "ERC20: transfer amount exceeds allowance"));
        return true;
    }

    function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
        _approve(msg.sender, spender, _allowances[msg.sender][spender].add(addedValue));
        return true;
    }

    function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
        _approve(msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
        return true;
    }

    function _transfer(address sender, address recipient, uint256 amount) internal {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
        _balances[recipient] = _balances[recipient].add(amount);
        emit Transfer(sender, recipient, amount);
    }

    function _approve(address owner, address spender, uint256 amount) internal {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }
}

2.3 编写测试脚本

2.3.1 私钥在节点内

    当私钥在节点内部时,节点可以直接访问用户的account数据,内部自动对交易进行签名。比如,使用本地网络ganache里账户和私钥,就属于这种情况。
    a) 在onepython/test/onepy目录,新建1.sendETH.py,用于发送ETH。
    // 1.sendETH.py

from web3 import Web3, HTTPProvider
# from web3.contract import ConciseContract

# web3.py instance
w3 = Web3(HTTPProvider("http://localhost:8545"))
print(w3.isConnected())

fromAddr = w3.eth.accounts[0]
toAddr   = w3.eth.accounts[1]
print('fromAddr=',fromAddr)
print('toAddr  =',toAddr)


def getEthBalance(accountAddr):
    balance = w3.eth.get_balance(accountAddr)
    return w3.toWei(balance,'ether')

def sendEth(web3obj,fromAddr,toAddr,value):
    web3obj.eth.sendTransaction({'to':toAddr,'from':fromAddr,'value':value})


def printEthBalance(web3obj,accountAddr,mark):
    balance = web3obj.eth.getBalance(accountAddr)
    markStr = mark +" balance="
    print(mark,web3obj.fromWei(balance,'ether'))


# balance2 = w3.eth.getBalance(fromAddr)
# print('before balance= ',w3.fromWei(balance2,'ether'))

# w3.eth.sendTransaction({'to':toAddr,'from':fromAddr,'value':w3.toWei(0.1,'ether')})

# balance2 = w3.eth.getBalance(fromAddr)
# print('after  balance= ',w3.fromWei(balance2,'ether'))


printEthBalance(w3,fromAddr,"#1")
sendEth(w3,fromAddr,toAddr,w3.toWei(0.1,'ether'))
printEthBalance(w3,fromAddr,"#2")

    b) 在onepython/test/onepy目录,新建2.sendErc20.py,用于发送Erc20,此处是DPC通证。
    // 2.sendErc20.py

import json
from web3 import Web3, HTTPProvider
#from web3.contract import ConciseContract

## 获取合约的abi
def getAbi(filePath):
    with open(filePath,'r') as abi_file:
        mpc_abi = json.load(abi_file)
    return mpc_abi

## 获取余额
def getBalance(contractObj,accountAddr):
    return contractObj.functions.balanceOf(accountAddr).call()

## 获取合约对象
def getContractObj(web3Obj,contractAddr,abiPath):
    con_abi = getAbi(abiPath)
    return web3Obj.eth.contract(address=contractAddr,abi=con_abi)

## 发送ERC20
def sendErc20(web3obj,fromAddr,toAddr,value,contractAddr,abiPath):
    contractAbi = getAbi(abiPath)
    contractObj = web3obj.eth.contract(address=contractAddr,abi=contractAbi)
    tx_hash = contractObj.functions.transfer(toAddr,value).transact({'from':fromAddr})
    tx_receipt = web3obj.eth.wait_for_transaction_receipt(tx_hash)
    if tx_receipt['status'] == 1:
        return 'send Success'
    else:
        return 'send Failed'

def printBalance(web3obj,contractObj,fromAddr,toAddr,markIndex):
    balanceA = getBalance(contractObj,fromAddr)
    balanceB = getBalance(contractObj,toAddr)
    fromMark = markIndex+" balanceA="
    toMark   = markIndex+" balanceB="
    print(fromMark,web3obj.fromWei(balanceA,'ether'))
    print(toMark,  web3obj.fromWei(balanceB,'ether'))

#####   发送ERC20 ######
# web3.py instance
w3 = Web3(HTTPProvider("http://localhost:8545"))
print('web3 connect:',w3.isConnected())

fromAddr = w3.eth.accounts[0]
toAddr   = w3.eth.accounts[1]
print('fromAddr=',fromAddr)
print('toAddr  =',toAddr)

value = w3.toWei(0.1,'ether')
abiPath = './myabi/DPC_abi.json'
contractAddr = '0xE250d901baeCb66F85D184D8aE9dA2bD4e705854' ##DPC合约地址
contractObj = getContractObj(w3,contractAddr,abiPath)

## 发送前
printBalance(w3,contractObj,fromAddr,toAddr,"#1")

bRet = sendErc20(w3,fromAddr,toAddr,value,contractAddr,abiPath)
print('result= ',bRet)

## 发送后
printBalance(w3,contractObj,fromAddr,toAddr,"#2")

2.3.2 私钥不在节点内

    当私钥不在节点内时,需要手动对交易进行签名。先组装一个原始的rawTx,然后使用私钥通过sign_transaction()函数对rawTx进行签名,再使用sendRawTransaction()函数对交易进行广播。比如,使用infura.io + Rinkeby里账户和私钥,就属于这种情况。
    a) 发送ETH
    //3.tx.SendEth.py

from web3 import Web3

## 获取ETH余额
def getEthBalance(accountAddr):
    balance = w3.eth.get_balance(accountAddr)
    return balance

## 打印余额信息
def printBalance(web3obj, fromAddr, toAddr, markIndex):
    balanceA = getEthBalance(fromAddr)
    balanceB = getEthBalance(toAddr)
    fromMark = markIndex + " balanceA="
    toMark = markIndex + " balanceB="
    print(fromMark, web3obj.fromWei(balanceA, 'ether'))
    print(toMark, web3obj.fromWei(balanceB, 'ether'))


## 组装原始的rawTx
def getRawTransaction(web3obj, fromAddr, toAddr, value):
    tx = {
        'nonce': web3obj.eth.getTransactionCount(fromAddr),
        'to': toAddr,
        'value': value,
        'gas': 6600000,
        'gasPrice': web3obj.toWei('2', 'gwei')
    }
    return tx

## 用fromAddr的私钥对rawTx进行签名
def signTransaction(web3obj, rawTx, privKey):
    signTx = web3obj.eth.account.sign_transaction(rawTx, privKey)
    txHash = web3obj.eth.sendRawTransaction(signTx.rawTransaction).hex()
    return txHash

## 发送ETH
def sendEth(web3obj, fromAddr, toAddr, value):
    # before
    printBalance(web3obj, fromAddr, toAddr, "#1")

    rawTx = getRawTransaction(web3obj, fromAddr, toAddr, value)
    txHash= signTransaction(web3obj, rawTx, privKey=fromPrivKey)
    print('txHash:',txHash)

    # after
    printBalance(web3obj, fromAddr, toAddr, "#2")


## 主入口
if __name__ == '__main__':
    w3 = Web3(Web3.HTTPProvider('http://127.0.0.1:8545'))
    fromAddr = '0x4595...0D66'
    toAddr   = '0x40C9...Dc82'
    fromPrivKey = 'eb..16'

    value = w3.toWei('1', 'ether')
    sendEth(w3, fromAddr, toAddr, value)

    b) 发送Erc20
    //4.tx.SendErc20.py

import json
from web3 import Web3

## 获取合约的abi
def getAbi(filePath):
    with open(filePath,'r') as abi_file:
        mpc_abi = json.load(abi_file)
    return mpc_abi

## 获取合约对象
def getContractObj(web3Obj,contractAddr,abiPath):
    con_abi = getAbi(abiPath)
    return web3Obj.eth.contract(address=contractAddr,abi=con_abi)

## 获取Erc20余额
def getErc20Balance(contractObj, accountAddr):
    return contractObj.functions.balanceOf(accountAddr).call()


## 打印余额信息
def printBalance(web3obj, fromAddr, toAddr,contractObj, markIndex):
    balanceA = getErc20Balance(contractObj,fromAddr)
    balanceB = getErc20Balance(contractObj,toAddr)
    fromMark = markIndex + " balanceA="
    toMark = markIndex + " balanceB="
    print(fromMark, web3obj.fromWei(balanceA, 'ether'))
    print(toMark, web3obj.fromWei(balanceB, 'ether'))


## 组装原始的rawTx
def getRawTransaction(web3obj, fromAddr, toAddr,value,contractObj):
    tx = contractObj.functions.transfer(toAddr,value).buildTransaction({
        'nonce': web3obj.eth.getTransactionCount(fromAddr),
        'gas': 6600000,
        'gasPrice': web3obj.toWei('2', 'gwei'),
        'chainId': web3obj.eth.chain_id
    })
    return tx

## 用fromAddr的私钥对rawTx进行签名
def signTransaction(web3obj, rawTx, privKey):
    signTx = web3obj.eth.account.sign_transaction(rawTx, privKey)
    txHash = web3obj.eth.sendRawTransaction(signTx.rawTransaction).hex()
    return txHash


## 发送Erc20
def sendErc20(web3obj, fromAddr, toAddr, value,contractObj):
    # before
    printBalance(web3obj, fromAddr, toAddr,contractObj, "#1")

    rawTx = getRawTransaction(web3obj, fromAddr, toAddr, value,contractObj)
    txHash = signTransaction(web3obj, rawTx, privKey=fromPrivKey)
    print('txHash:', txHash)

    # after
    printBalance(web3obj, fromAddr, toAddr,contractObj, "#2")

## 主入口
if __name__ == '__main__':
    w3 = Web3(Web3.HTTPProvider('http://127.0.0.1:8545'))
    fromAddr = '0x4595...0D66'
    toAddr   = '0x40C9...Dc82'
    fromPrivKey = 'eb...16'

    value = w3.toWei(0.1, 'ether')
    abiPath = './myabi/DPC_abi.json'
    contractAddr = '0xE250d901baeCb66F85D184D8aE9dA2bD4e705854'  ##DPC合约地址
    contractObj = getContractObj(w3, contractAddr, abiPath)

    sendErc20(w3, fromAddr, toAddr, value,contractObj)


2.4 编写合约的部署脚本

    在onepython/migrations目录下,新建2_deploy_DPC.js文件,内容如下:
    // 2_deploy_DPC.js

const DPCToken = artifacts.require("DPCToken");

module.exports = function (deployer) {
  deployer.deploy(DPCToken);
};

2.5 编译和部署合约

    打开一个黑框框终端,进入onepython目录,依次输入如下命令:

truffle console
compile
migrate

    得到DPC合约地址:0xE250d901baeCb66F85D184D8aE9dA2bD4e705854

2.6 拷贝abi

    DPCToken合约在migrate之后,会在本地的build/contracts/DPCToken.json文件里生成 abi:[]字段 ,将abi:[]里的内容拷贝到onepython/myabi/DPC_abi.json里即可,具体请看这篇文章: [将abi压缩为一行]

2.7 工程目录结构

    onepython的工程目录,如图(1)所示:

使用web3.py发送ETH和ERC20_第1张图片
图(1) onepython的工程目录结构

3、发送ETH或ERC20

3.1 发送ETH

cd onepython
python3 test/onepy/1.sendETH.py 

3.2 发送ERC20

cd onepython
python3 test/onepy/2.sendErc20.py 

效果如图(2)所示:

使用web3.py发送ETH和ERC20_第2张图片
图(2) 用web3.py发送ETH或ERC20

如图(2)所示,fromAddr 发了0.1 ETH给 toAddr,它自己还剩998.22 ETH
         fromAddr 发了0.1 DPC给 toAddr,它自己还剩49999999.9 DPC。

参考文献

web3.py -v5.4 - [sending-token] 手册

你可能感兴趣的:(区块链,dapp,Python编程,区块链,web3.py,发送ETH,ERC20)