接上面文章Geth部署以太坊测试链(testnet),下面我们将在太坊rinkeby测试链发布一套自己的数字币,当然只是在测试环境。
以太坊是一个智能合约平台,其中ERC20是以太坊应用程序级别的标准和约定,只要按照这个约定我们都可以在以太坊平台发币,以太坊支持的协议列表,这里面定了很多种标准规范,而ERC20就是其中的一种,定义的一些标准如下
function name() constant returns (string name)
返回string类型的ERC20代币的名字,例如:StatusNetwork
function symbol() constant returns (string symbol)
返回string类型的ERC20代币的符号,也就是代币的简称,例如:SNT
function decimals() constant returns (uint8 decimals)
支持几位小数点后几位。如果设置为3。也就是支持0.001表示
function totalSupply() constant returns (uint256 totalSupply)
发行代币的总量,可以通过这个函数来获取。所有智能合约发行的代币总量是一定的,totalSupply必须设置初始值。如果不设置初始值,这个代币发行就说明有问题。
function balanceOf(address _owner) constant returns (uint256 balance)
输入地址,可以获取该地址代币的余额
function transfer(address _to, uint256 _value) returns (bool success)
调用transfer函数将自己的token转账给_to地址,_value为转账个数
function approve(address _spender, uint256 _value) returns (bool success)
批准_spender账户从自己的账户转移_value个token。可以分多次转移
function transferFrom(address _from, address _to, uint256 _value) returns (bool success)
与approve搭配使用,approve批准之后,调用transferFrom函数来转移token
function allowance(address _owner, address _spender) constant returns (uint256 remaining)
返回_spender还能提取token的个数
event Transfer(address indexed _from, address indexed _to, uint256 _value)
当成功转移token时,一定要触发Transfer事件
event Approval(address indexed _owner, address indexed _spender, uint256 _value)
当调用approval函数成功时,一定要触发Approval事件
合约是使用Solidity语言进行编写,如果你不了解Solidity,你可以看一下下面的代码,如果有不懂的可以参考官方文档,我感觉语法还是挺简单的,非常简洁明了,创建一个名为token.sol
的合约文件
$ vim token.sol
pragma solidity ^0.7.0;
library SafeMath {
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
interface ERC20Standard {
// 代币的名字,例如:StatusNetwork
function name() external view returns (string memory);
// 代币的符号,也就是代币的简称,例如:SNT
function symbol() external view returns (string memory);
// 支持几位小数点后几位。如果设置为3。也就是支持0.001表示
function decimals() external view returns (uint8);
// 发行代币的总量
function totalSupply() external view returns (uint256);
// 获取该地址代币的余额
function balanceOf(address _owner) external view returns (uint256 balance);
// 将自己的token转账给_to地址,_value为转账个数
function transfer(address _to, uint256 _value)
external
returns (bool success);
// 与approve搭配使用,approve批准之后,调用transferFrom函数来转移token
function transferFrom(
address _from,
address _to,
uint256 _value
) external returns (bool success);
// 批准_spender账户从自己的账户转移_value个token。可以分多次转移
function approve(address _spender, uint256 _value)
external
returns (bool success);
// 返回_spender还能提取token的个数
function allowance(address _owner, address _spender)
external
view
returns (uint256 remaining);
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(
address indexed _owner,
address indexed _spender,
uint256 _value
);
}
contract Token is ERC20Standard {
string name_;
string symbol_;
uint8 decimals_;
uint256 totalSupply_;
using SafeMath for uint256;
mapping(address => uint256) balances;
mapping(address => mapping(address => uint256)) allowed;
// 初始化,并把初始化的发行数量分配给拥有者
constructor(
// string memory name,
// string memory symbol,
// uint8 decimals,
// uint256 totalSupply
) public {
// name_ = name;
// symbol_ = symbol;
// decimals_ = decimals;
// totalSupply_ = totalSupply;
// balances[msg.sender] = totalSupply;
name_ = "ansheng";
symbol_ = "as";
decimals_ = 18;
totalSupply_ = 10000000000000000000;
balances[msg.sender] = 10000000000000000000;
}
function name() public override view returns (string memory) {
return name_;
}
function symbol() public override view returns (string memory) {
return symbol_;
}
function decimals() public override view returns (uint8) {
return decimals_;
}
function totalSupply() public override view returns (uint256) {
return totalSupply_;
}
function balanceOf(address _owner)
public
override
view
returns (uint256 balance)
{
return balances[_owner];
}
function transfer(address _to, uint256 _value)
public
override
returns (bool success)
{
// 检查发送者账户余额是否足够
require(_value <= balances[msg.sender]);
// 发送者减少余额
balances[msg.sender] -= _value;
// 接受者增加余额
balances[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
function transferFrom(
address _from,
address _to,
uint256 _value
) public override returns (bool success) {
// 检查发送者的余额是否足够
require(_value <= balances[_from]);
require(_value <= allowed[_from][msg.sender]);
balances[_from] -= _value;
allowed[_from][msg.sender] -= _value;
balances[_to] += _value;
Transfer(_from, _to, _value);
return true;
}
function approve(address _spender, uint256 _value)
public
override
returns (bool success)
{
allowed[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
function allowance(address _owner, address _spender)
public
override
view
returns (uint256 remaining)
{
return allowed[_owner][_spender];
}
}
由于我的客户端是macOS,我还需要通过brew安装Solidity来进行代码编译,如果你是其他平台请参考官方文档Installing the Solidity Compiler
$ brew update
$ brew upgrade
$ brew tap ethereum/ethereum
$ brew install solidity
$ geth solc --version
solc, the solidity compiler commandline interface
Version: 0.7.2+commit.51b20bc0.Darwin.appleclang
忽略Warning:
$ solc --abi --bin -o solcoutput token.sol
$ ls solcoutput
ERC20Standard.abi ERC20Standard.bin SafeMath.abi SafeMath.bin Token.abi Token.bin combined.json
对我们有用的文件只有Token.abi
和Token.bin
,下面登陆节点服务器并进入console
$ geth attach /usr/local/etc/geth/geth.ipc
# 我把之前的用户都删掉了,目前是空的
> eth.accounts
[]
# 创建用户
> personal.newAccount("ansheng.me")
"0xdf3d8df4ee370e96a2338d389c5821e154b092e9"
> personal.newAccount("ansheng")
"0xda0cfe3a0772995f83399b1ae82afbbddf5aedd2"
# 解锁
> personal.unlockAccount("0xdf3d8df4ee370e96a2338d389c5821e154b092e9", "ansheng.me", 0)
true
# 发布合约需要有余额才可以,刚创建的用户余额是0,所以你需要去https://www.rinkeby.io/#faucet弄点币才行
> eth.getBalance('0xdf3d8df4ee370e96a2338d389c5821e154b092e9')
2000000000000000000
# 发布合约
# solcoutput/Token.abi 的内容
var abi = [{ "inputs": [], "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": "_owner", "type": "address" }, { "internalType": "address", "name": "_spender", "type": "address" }], "name": "allowance", "outputs": [{ "internalType": "uint256", "name": "remaining", "type": "uint256" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "_spender", "type": "address" }, { "internalType": "uint256", "name": "_value", "type": "uint256" }], "name": "approve", "outputs": [{ "internalType": "bool", "name": "success", "type": "bool" }], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "_owner", "type": "address" }], "name": "balanceOf", "outputs": [{ "internalType": "uint256", "name": "balance", "type": "uint256" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "decimals", "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], "stateMutability": "view", "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": "_to", "type": "address" }, { "internalType": "uint256", "name": "_value", "type": "uint256" }], "name": "transfer", "outputs": [{ "internalType": "bool", "name": "success", "type": "bool" }], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "_from", "type": "address" }, { "internalType": "address", "name": "_to", "type": "address" }, { "internalType": "uint256", "name": "_value", "type": "uint256" }], "name": "transferFrom", "outputs": [{ "internalType": "bool", "name": "success", "type": "bool" }], "stateMutability": "nonpayable", "type": "function" }];
# 0x后面加编译的字节码
var bytecode = "0x608060405234801561001057600080fd5b506040518060400160405280600781526020017f616e7368656e67000000000000000000000000000000000000000000000000008152506000908051906020019061005c929190610125565b506040518060400160405280600281526020017f6173000000000000000000000000000000000000000000000000000000000000815250600190805190602001906100a8929190610125565b506012600260006101000a81548160ff021916908360ff160217905550678ac7230489e80000600381905550678ac7230489e80000600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506101c2565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061016657805160ff1916838001178555610194565b82800160010185558215610194579182015b82811115610193578251825591602001919060010190610178565b5b5090506101a191906101a5565b5090565b5b808211156101be5760008160009055506001016101a6565b5090565b610b18806101d16000396000f3fe608060405234801561001057600080fd5b50600436106100935760003560e01c8063313ce56711610066578063313ce5671461022157806370a082311461024257806395d89b411461029a578063a9059cbb1461031d578063dd62ed3e1461038157610093565b806306fdde0314610098578063095ea7b31461011b57806318160ddd1461017f57806323b872dd1461019d575b600080fd5b6100a06103f9565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100e05780820151818401526020810190506100c5565b50505050905090810190601f16801561010d5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101676004803603604081101561013157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061049b565b60405180821515815260200191505060405180910390f35b61018761058d565b6040518082815260200191505060405180910390f35b610209600480360360608110156101b357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610597565b60405180821515815260200191505060405180910390f35b610229610802565b604051808260ff16815260200191505060405180910390f35b6102846004803603602081101561025857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610819565b6040518082815260200191505060405180910390f35b6102a2610862565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156102e25780820151818401526020810190506102c7565b50505050905090810190601f16801561030f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6103696004803603604081101561033357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610904565b60405180821515815260200191505060405180910390f35b6103e36004803603604081101561039757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610a5b565b6040518082815260200191505060405180910390f35b606060008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156104915780601f1061046657610100808354040283529160200191610491565b820191906000526020600020905b81548152906001019060200180831161047457829003601f168201915b5050505050905090565b600081600560003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b6000600354905090565b6000600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020548211156105e557600080fd5b600560008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205482111561066e57600080fd5b81600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600560008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b6000600260009054906101000a900460ff16905090565b6000600460008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b606060018054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156108fa5780601f106108cf576101008083540402835291602001916108fa565b820191906000526020600020905b8154815290600101906020018083116108dd57829003601f168201915b5050505050905090565b6000600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205482111561095257600080fd5b81600460003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a36001905092915050565b6000600560008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490509291505056fea2646970667358221220a2485e1b926d17cf9231b535aab86329faa059bc67d41f8c6868ea77274bb46464736f6c63430007020033";
var simpleContract = web3.eth.contract(abi);
var simple = simpleContract.new(42, {
from: "0xdf3d8df4ee370e96a2338d389c5821e154b092e9",
data: bytecode,
gas: 0x47b760
}, function (e, contract) {
if (e) {
console.log("err creating contract", e);
} else {
if (!contract.address) {
console.log("Contract transaction send: TransactionHash: " + contract.transactionHash + " waiting to be mined...");
} else {
console.log("Contract mined! Address: " + contract.address);
}
}
});
# Contract transaction send: TransactionHash: 0x5d45beeb2337d3fcc8b95d4c2e6545ce6ca890db16b91b3c962b7385595bb82e waiting to be mined...
# ......
# 这就是我们的合约地址,需要记录下面
# > Contract mined! Address: 0xe16c5613b71edaa9bf2d0edd1f7ce90708904d48
# 只要得到上面的合约地址就可以推出了
> exit
如果刚创建的账户余额不足,会提示以下错误,这个时候就需要账户有币才可以
err creating contract Error: insufficient funds for gas * price + value
我们可以通过下面的地址查看测试链的合约信息
<https://rinkeby.etherscan.io/token/0xe16c5613b71edaa9bf2d0edd1f7ce90708904d48>
合约发布之后我们就来试试下面的功能把,当然还是通过web3py进行操作,当然你可以可以参考管饭文档Working with an ERC20 Token Contract
>>> from web3 import Web3
>>> web3 = Web3(Web3.HTTPProvider("<http://34.92.29.146:23456>", request_kwargs={'timeout': 60}))
>>> import json
>>> with open('./solcoutput/Token.abi') as f:
... ABI = json.load(f)
...
>>> contract_address = Web3.toChecksumAddress('0xe16c5613b71edaa9bf2d0edd1f7ce90708904d48')
>>> contract = web3.eth.contract(contract_address, abi=ABI)
>>> contract.address
'0xe16c5613B71edaA9bF2d0EdD1f7CE90708904d48'
定义两个用户alice
和bob
,并解锁account,不然无法进行转账交易
# alice是合约的发布者,账户自带余额
>>> alice = Web3.toChecksumAddress("0xdf3d8df4ee370e96a2338d389c5821e154b092e9")
>>> bob = Web3.toChecksumAddress("0xDa0CFe3A0772995f83399b1AE82AFBbDDf5aeDD2")
>>> web3.geth.personal.unlock_account(alice, 'ansheng.me', 0)
True
>>> web3.geth.personal.unlock_account(bob, 'ansheng', 0)
True
代币的名字
>>> contract.functions.name().call()
'ansheng'
代币的简称
>>> contract.functions.symbol().call()
'as'
支持几位小数点后几位
>>> decimals = contract.functions.decimals().call()
>>> decimals
18
发行代币的总量
>>> contract.functions.totalSupply().call()
10000000000000000000
账户余额查询
>>> contract.functions.balanceOf(alice).call()
10000000000000000000
>>> contract.functions.balanceOf(bob).call()
0
alice转账100到bob账户
>>> from web3.middleware import geth_poa_middleware
>>> web3.middleware_onion.inject(geth_poa_middleware, layer=0)
>>> contract.functions.transfer(bob, 100).transact({'from': alice})
HexBytes('0x1b811756a580e4ad7bb9fcf87549a5f24545c8589fb3af0f08fd0df93830bc1c')
>>> contract.functions.balanceOf(alice).call()
9999999999999999900
>>> contract.functions.balanceOf(bob).call()
100
alice将批准bob允许消费200,钱从alice中扣除
>>> contract.functions.allowance(alice, bob).call()
0
>>> contract.functions.approve(bob, 200).transact({'from': alice})
HexBytes('0x0471a04b21655e951e63904111fd812503abbc730c475680ffabdd9206d6a48e')
>>> contract.functions.allowance(alice, bob).call()
0
bob转账的时候从alice账户扣
# 转账交易是需要消耗ETH的,我们的bob账号目前余额是0
>>> web3.eth.getBalance(bob)
0
# alice给bob转1个ETH
>>> web3.eth.sendTransaction({'to': bob,'from': alice,'value': web3.toWei('1','ether')})
HexBytes('0x56dd28db03b8a1b64affcf82f080d4879e7683a912d0e6711f30cd010558e86a')
# 等待矿工操作完成之后就有余额了
>>> web3.eth.getBalance(bob)
1000000000000000000
# 查看bob可以透支的余额
>>> contract.functions.allowance(alice, bob).call()
200
# bob现在的余额
>>> contract.functions.balanceOf(bob).call()
100
>>> contract.functions.transferFrom(alice, bob, 75).transact({'from': bob})
HexBytes('0x1c316e4156dcb8b7edefd2f08c4309169ef77358aacdb0d25af4ebd8d98fe89a')
>>> contract.functions.allowance(alice, bob).call()
125
>>> contract.functions.balanceOf(bob).call()
175
至此,本片结束,可以通过下面的连接查看交易的记录
<https://rinkeby.etherscan.io/token/0xe16c5613b71edaa9bf2d0edd1f7ce90708904d48>
如图所示