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
里面還有很多其他的方法