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)所示:
![](https://img-blog.csdnimg.cn/82466cf2154a4fd99430129100f3b33b.png)
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)所示:
![](https://img-blog.csdnimg.cn/08c52c5798594d1090e92ad8ff3d3af8.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAc2FucWltYQ==,size_20,color_FFFFFF,t_70,g_se,x_16)
如图(2)所示,fromAddr 发了0.1 ETH给 toAddr,它自己还剩998.22 ETH
fromAddr 发了0.1 DPC给 toAddr,它自己还剩49999999.9 DPC。