golang学习笔记19 用Golang实现以太坊代币转账
- 在以太坊区块链中,我们称代币为Token,是以太坊区块链中每个人都可以任意发行的数字资产。并且它必须是遵循erc20标准的,至于erc20标准,大家可以参考这篇文章 https://theethereum.wiki/w/index.php/ERC20_Token_Standard
- 它实际上一段智能合约代码,智能合约代码中必须要有以下的一些function 和 event。
contract ERC20 {
function totalSupply() constant returns (uint totalSupply);
function balanceOf(address _owner) constant returns (uint balance);
function transfer(address _to, uint _value) returns (bool success);
function transferFrom(address _from, address _to, uint _value) returns (bool success);
function approve(address _spender, uint _value) returns (bool success);
function allowance(address _owner, address _spender) constant returns (uint remaining);
event Transfer(address indexed _from, address indexed _to, uint _value);
event Approval(address indexed _owner, address indexed _spender, uint _value);
}
能合约代码是运行在以太坊智能合约虚拟机中的。文档:https://solidity.readthedocs.io/en/latest/installing-solidity.html#building-from-source
我们看到上面那段类似golang中interface的代码,里面分别包含了总量、余额、转账等方法。我们今天重点讲的其实就是用golang来实现transfer、transferFrom方法。
连接以太坊RPC节点
- 目前广泛使用的是go-ethereum,他的客户端名是geth。你可以通过编译、安装等方式把节点搭建在你的电脑或者服务器中,并开启rpc服务。本文省略这一步骤,网上有很文章供你了解。
- 附上github:https://github.com/ethereum/go-ethereum
- geth默认的rpc端口是
8545
,我使用默认端口,后面我们都用http://127.0.0.1:8545
作为我们的rpc连接。
首先获取go-ethereum代码
go get github.com/ethereum/go-ethereum
- 然后我们go-ethereum目录,如果你的golang环境没有问题,那么应该是这个路径。
cd $GOPATH/src/github.com/ethereum/go-ethereum
- 当你进入目录,看到代码已经完整拉取下来,那么我们就可以进行下一步了。
连接RPC节点
import (
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/ethclient"
)
rpcDial, err := rpc.Dial("http://127.0.0.1:8545")
if err != nil {
panic(err);
}
client := ethclient.NewClient(rpcDial)
如果没有panic,那么我们已经成功连接了
创建测试账户
- 要进行转账测试,那么我们需要两个以太坊账户。我们用golang来生成,我们知道以太坊的账户私钥是放在keystore文件中的,是一段json,并且创建的时候可以设置密码。跟比特币的wallet.dat文件是一样的意思,不见哪一样,你的资产就永远留在区块链网络中,再也无法找回。
- 下面我们用代码创建两个以太坊账户。
import (
"github.com/ethereum/go-ethereum/accounts/keystore"
)
ks := keystore.NewKeyStore("/", keystore.StandardScryptN, keystore.StandardScryptP)
address, _ := ks.NewAccount("password")
account, err := ks.Export(address, "password", "password")
if err != nil {
panic(err)
}
- 从上面的代码我们可以看到,创建了一个以太坊的账户,并且密码设置为
password
,并导出。最终account
变量就是账户的私钥,是一段json文本。 - 通过
address
变量,我们可以获得账户的地址。如address.Address.Hex()
生成代币文件
- 打开
cd $GOPATH/src/github.com/ethereum/go-ethereum/cmd/abigen
你能看到main.go文件 执行
go build main.go
,会在目录下生成一个main
的二进制文件。将以下的json 保存为token.abi,并放在当前目录下。
[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"stop","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"owner_","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"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,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint128"}],"name":"push","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"name_","type":"bytes32"}],"name":"setName","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint128"}],"name":"mint","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"src","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"stopped","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"authority_","type":"address"}],"name":"setAuthority","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"wad","type":"uint128"}],"name":"pull","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint128"}],"name":"burn","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"start","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"authority","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"src","type":"address"},{"name":"guy","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[{"name":"symbol_","type":"bytes32"}],"payable":false,"type":"constructor"},{"anonymous":true,"inputs":[{"indexed":true,"name":"sig","type":"bytes4"},{"indexed":true,"name":"guy","type":"address"},{"indexed":true,"name":"foo","type":"bytes32"},{"indexed":true,"name":"bar","type":"bytes32"},{"indexed":false,"name":"wad","type":"uint256"},{"indexed":false,"name":"fax","type":"bytes"}],"name":"LogNote","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"authority","type":"address"}],"name":"LogSetAuthority","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"}],"name":"LogSetOwner","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":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"}]
执行命令
./main --abi token.abi --pkg main --type Token --out token.go
我们可以看到目录下生成了一个
token.go
文件。大功告成。
开始转账
- 首先就把上一步生成的
token.go
拖入项目中。
import (
"github.com/ethereum/go-ethereum/accounts/abi/bind"
)
//首先导入上面生成的账户密钥(json)和密码
auth, err := bind.NewTransactor(strings.NewReader("json"), "password")
//这句用的是生成的token.go里面的方法
//client变量是我们第一步连接以太坊rpc节点的时候创建的
//contractAddress 是代币地址,比如eos 的地址是0x86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0
//那么我们转账针对的就是账户里的eos代币
//具体看这里 https://etherscan.io/token/0x86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0
token, err := NewToken(common.HexToAddress("0x86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0"), client)
if err != nil {
panic(err)
}
//每个代币都会有相应的位数,例如eos是18位,那么我们转账的时候,需要在金额后面加18个0
decimal, err := token.Decimals(nil)
if err != nil {
panic(err)
}
//这是处理位数的代码段
tenDecimal := big.NewFloat(math.Pow(10, float64(decimal)))
convertAmount, _ := new(big.Float).Mul(tenDecimal, amount).Int(&big.Int{})
//然后就可以转账到你需要接受的账户上了
//toAddress 是接受eos的账户地址
txs, err := token.Transfer(auth, common.HexToAddress(toAddress), convertAmount)
到此转账就成功了。
https://golang.org/src/go/token/token.go
里面还有很多其他的方法