交易(Transaction)
這些部分將討論如何使用go-ethereumethclient包在以太坊上查詢和發(fā)送交易溉奕。注意這里的交易transaction 是指廣義的對(duì)以太坊狀態(tài)的更改有决,它既可以指具體的以太幣轉(zhuǎn)賬允耿,代幣的轉(zhuǎn)賬,或者其他對(duì)智能合約的創(chuàng)建或者調(diào)用枕磁。而不僅僅是傳統(tǒng)意義的買賣交易呐粘。
查詢區(qū)塊
正如我們所見十偶,您可以有兩種方式查詢區(qū)塊信息热某。
區(qū)塊頭
您可以調(diào)用客戶端的HeadByNumber
來返回有關(guān)一個(gè)區(qū)塊的頭信息腻菇。若您傳入nil
,它將返回最新的區(qū)塊頭苫拍。
header, err := client.HeaderByNumber(context.Background(), nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(header.Number.String()) // 5671744
完整區(qū)塊
調(diào)用客戶端的BlockByNumber
方法來獲得完整區(qū)塊芜繁。您可以讀取該區(qū)塊的所有內(nèi)容和元數(shù)據(jù),例如绒极,區(qū)塊號(hào),區(qū)塊時(shí)間戳蔬捷,區(qū)塊摘要垄提,區(qū)塊難度以及交易列表等等。
blockNumber := big.NewInt(5671744)
block, err := client.BlockByNumber(context.Background(), blockNumber)
if err != nil {
log.Fatal(err)
}
fmt.Println(block.Number().Uint64()) // 5671744
fmt.Println(block.Time().Uint64()) // 1527211625
fmt.Println(block.Difficulty().Uint64()) // 3217000136609065
fmt.Println(block.Hash().Hex()) // 0x9e8751ebb5069389b855bba72d94902cc385042661498a415979b7b6ee9ba4b9
fmt.Println(len(block.Transactions())) // 144
調(diào)用Transaction
只返回一個(gè)區(qū)塊的交易數(shù)目周拐。
count, err := client.TransactionCount(context.Background(), block.Hash())
if err != nil {
log.Fatal(err)
}
fmt.Println(count) // 144
在下個(gè)章節(jié)铡俐,我們將學(xué)習(xí)查詢區(qū)塊中的交易。
完整代碼
package main
import (
"context"
"fmt"
"log"
"math/big"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
client, err := ethclient.Dial("https://mainnet.infura.io")
if err != nil {
log.Fatal(err)
}
header, err := client.HeaderByNumber(context.Background(), nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(header.Number.String()) // 5671744
blockNumber := big.NewInt(5671744)
block, err := client.BlockByNumber(context.Background(), blockNumber)
if err != nil {
log.Fatal(err)
}
fmt.Println(block.Number().Uint64()) // 5671744
fmt.Println(block.Time().Uint64()) // 1527211625
fmt.Println(block.Difficulty().Uint64()) // 3217000136609065
fmt.Println(block.Hash().Hex()) // 0x9e8751ebb5069389b855bba72d94902cc385042661498a415979b7b6ee9ba4b9
fmt.Println(len(block.Transactions())) // 144
count, err := client.TransactionCount(context.Background(), block.Hash())
if err != nil {
log.Fatal(err)
}
fmt.Println(count) // 144
}
查詢交易
在上個(gè)章節(jié) 我們學(xué)習(xí)了如何在給定區(qū)塊編號(hào)的情況下讀取塊及其所有數(shù)據(jù)妥粟。 我們可以通過調(diào)用Transactions
方法來讀取塊中的事務(wù)审丘,該方法返回一個(gè)Transaction
類型的列表。 然后勾给,重復(fù)遍歷集合并獲取有關(guān)事務(wù)的任何信息就變得簡單了滩报。
for _, tx := range block.Transactions() {
fmt.Println(tx.Hash().Hex()) // 0x5d49fcaa394c97ec8a9c3e7bd9e8388d420fb050a52083ca52ff24b3b65bc9c2
fmt.Println(tx.Value().String()) // 10000000000000000
fmt.Println(tx.Gas()) // 105000
fmt.Println(tx.GasPrice().Uint64()) // 102000000000
fmt.Println(tx.Nonce()) // 110644
fmt.Println(tx.Data()) // []
fmt.Println(tx.To().Hex()) // 0x55fE59D8Ad77035154dDd0AD0388D09Dd4047A8e
}
為了讀取發(fā)送方的地址锅知,我們需要在事務(wù)上調(diào)用AsMessage
,它返回一個(gè)Message
類型脓钾,其中包含一個(gè)返回sender(from)地址的函數(shù)售睹。 AsMessage
方法需要EIP155簽名者,這個(gè)我們從客戶端拿到鏈ID可训。
chainID, err := client.NetworkID(context.Background())
if err != nil {
log.Fatal(err)
}
if msg, err := tx.AsMessage(types.NewEIP155Signer(chainID)); err != nil {
fmt.Println(msg.From().Hex()) // 0x0fD081e3Bb178dc45c0cb23202069ddA57064258
}
每個(gè)事務(wù)都有一個(gè)收據(jù)昌妹,其中包含執(zhí)行事務(wù)的結(jié)果,例如任何返回值和日志握截,以及為“1”(成功)或“0”(失敺裳隆)的事件結(jié)果狀態(tài)。
receipt, err := client.TransactionReceipt(context.Background(), tx.Hash())
if err != nil {
log.Fatal(err)
}
fmt.Println(receipt.Status) // 1
fmt.Println(receipt.Logs) // ...
在不獲取塊的情況下遍歷事務(wù)的另一種方法是調(diào)用客戶端的TransactionInBlock
方法谨胞。 此方法僅接受塊哈希和塊內(nèi)事務(wù)的索引值蚜厉。 您可以調(diào)用TransactionCount
來了解塊中有多少個(gè)事務(wù)。
blockHash := common.HexToHash("0x9e8751ebb5069389b855bba72d94902cc385042661498a415979b7b6ee9ba4b9")
count, err := client.TransactionCount(context.Background(), blockHash)
if err != nil {
log.Fatal(err)
}
for idx := uint(0); idx < count; idx++ {
tx, err := client.TransactionInBlock(context.Background(), blockHash, idx)
if err != nil {
log.Fatal(err)
}
fmt.Println(tx.Hash().Hex()) // 0x5d49fcaa394c97ec8a9c3e7bd9e8388d420fb050a52083ca52ff24b3b65bc9c2
}
您還可以使用TransactionByHash
在給定具體事務(wù)哈希值的情況下直接查詢單個(gè)事務(wù)畜眨。
txHash := common.HexToHash("0x5d49fcaa394c97ec8a9c3e7bd9e8388d420fb050a52083ca52ff24b3b65bc9c2")
tx, isPending, err := client.TransactionByHash(context.Background(), txHash)
if err != nil {
log.Fatal(err)
}
fmt.Println(tx.Hash().Hex()) // 0x5d49fcaa394c97ec8a9c3e7bd9e8388d420fb050a52083ca52ff24b3b65bc9c2
fmt.Println(isPending) // false
完整代碼
package main
import (
"context"
"fmt"
"log"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
client, err := ethclient.Dial("https://mainnet.infura.io")
if err != nil {
log.Fatal(err)
}
blockNumber := big.NewInt(5671744)
block, err := client.BlockByNumber(context.Background(), blockNumber)
if err != nil {
log.Fatal(err)
}
for _, tx := range block.Transactions() {
fmt.Println(tx.Hash().Hex()) // 0x5d49fcaa394c97ec8a9c3e7bd9e8388d420fb050a52083ca52ff24b3b65bc9c2
fmt.Println(tx.Value().String()) // 10000000000000000
fmt.Println(tx.Gas()) // 105000
fmt.Println(tx.GasPrice().Uint64()) // 102000000000
fmt.Println(tx.Nonce()) // 110644
fmt.Println(tx.Data()) // []
fmt.Println(tx.To().Hex()) // 0x55fE59D8Ad77035154dDd0AD0388D09Dd4047A8e
chainID, err := client.NetworkID(context.Background())
if err != nil {
log.Fatal(err)
}
if msg, err := tx.AsMessage(types.NewEIP155Signer(chainID)); err == nil {
fmt.Println(msg.From().Hex()) // 0x0fD081e3Bb178dc45c0cb23202069ddA57064258
}
receipt, err := client.TransactionReceipt(context.Background(), tx.Hash())
if err != nil {
log.Fatal(err)
}
fmt.Println(receipt.Status) // 1
}
blockHash := common.HexToHash("0x9e8751ebb5069389b855bba72d94902cc385042661498a415979b7b6ee9ba4b9")
count, err := client.TransactionCount(context.Background(), blockHash)
if err != nil {
log.Fatal(err)
}
for idx := uint(0); idx < count; idx++ {
tx, err := client.TransactionInBlock(context.Background(), blockHash, idx)
if err != nil {
log.Fatal(err)
}
fmt.Println(tx.Hash().Hex()) // 0x5d49fcaa394c97ec8a9c3e7bd9e8388d420fb050a52083ca52ff24b3b65bc9c2
}
txHash := common.HexToHash("0x5d49fcaa394c97ec8a9c3e7bd9e8388d420fb050a52083ca52ff24b3b65bc9c2")
tx, isPending, err := client.TransactionByHash(context.Background(), txHash)
if err != nil {
log.Fatal(err)
}
fmt.Println(tx.Hash().Hex()) // 0x5d49fcaa394c97ec8a9c3e7bd9e8388d420fb050a52083ca52ff24b3b65bc9c2
fmt.Println(isPending) // false
}
ETH轉(zhuǎn)賬
轉(zhuǎn)賬以太幣ETH
在本課程中昼牛,您將學(xué)習(xí)如何將ETH從一個(gè)帳戶轉(zhuǎn)移到另一個(gè)帳戶。如果您已熟悉以太坊康聂,那么您就知道如何交易包括您打算轉(zhuǎn)賬的以太幣數(shù)量量贰健,燃?xì)庀揞~,燃?xì)鈨r(jià)格恬汁,一個(gè)隨機(jī)數(shù)(nonce)伶椿,接收地址以及可選擇性的添加的數(shù)據(jù)。 在廣告發(fā)送到網(wǎng)絡(luò)之前氓侧,必須使用發(fā)送方的私鑰對(duì)該交易進(jìn)行簽名脊另。
假設(shè)您已經(jīng)連接了客戶端,下一步就是加載您的私鑰约巷。
privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19")
if err != nil {
log.Fatal(err)
}
之后我們需要獲得帳戶的隨機(jī)數(shù)(nonce)偎痛。 每筆交易都需要一個(gè)nonce。 根據(jù)定義独郎,nonce是僅使用一次的數(shù)字踩麦。 如果是發(fā)送交易的新帳戶,則該隨機(jī)數(shù)將為“0”氓癌。 來自帳戶的每個(gè)新事務(wù)都必須具有前一個(gè)nonce增加1的nonce谓谦。很難對(duì)所有nonce進(jìn)行手動(dòng)跟蹤,于是ethereum客戶端提供一個(gè)幫助方法PendingNonceAt
贪婉,它將返回你應(yīng)該使用的下一個(gè)nonce反粥。
該函數(shù)需要我們發(fā)送的帳戶的公共地址 - 這個(gè)我們可以從私鑰派生。
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
log.Fatal("error casting public key to ECDSA")
}
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
接下來我們可以讀取我們應(yīng)該用于帳戶交易的隨機(jī)數(shù)。
nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
log.Fatal(err)
}
下一步是設(shè)置我們將要轉(zhuǎn)移的ETH數(shù)量才顿。 但是我們必須將ETH以太轉(zhuǎn)換為wei莫湘,因?yàn)檫@是以太坊區(qū)塊鏈所使用的。 以太網(wǎng)支持最多18個(gè)小數(shù)位娜膘,因此1個(gè)ETH為1加18個(gè)零逊脯。 這里有一個(gè)小工具可以幫助您在ETH和wei之間進(jìn)行轉(zhuǎn)換: https://etherconverter.online
value := big.NewInt(1000000000000000000) // in wei (1 eth)
ETH轉(zhuǎn)賬的燃?xì)鈶?yīng)設(shè)上限為“21000”單位。
gasLimit := uint64(21000) // in units
燃?xì)鈨r(jià)格必須以wei為單位設(shè)定竣贪。 在撰寫本文時(shí)军洼,將在一個(gè)區(qū)塊中比較快的打包交易的燃?xì)鈨r(jià)格為30 gwei。
gasPrice := big.NewInt(30000000000) // in wei (30 gwei)
然而演怎,燃?xì)鈨r(jià)格總是根據(jù)市場需求和用戶愿意支付的價(jià)格而波動(dòng)的匕争,因此對(duì)燃?xì)鈨r(jià)格進(jìn)行硬編碼有時(shí)并不理想。 go-ethereum客戶端提供SuggestGasPrice
函數(shù)爷耀,用于根據(jù)'x'個(gè)先前塊來獲得平均燃?xì)鈨r(jià)格甘桑。
gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
log.Fatal(err)
}
接下來我們弄清楚我們將ETH發(fā)送給誰。
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
現(xiàn)在我們最終可以通過導(dǎo)入go-ethereumcore/types
包并調(diào)用NewTransaction
來生成我們的未簽名以太坊事務(wù)歹叮,這個(gè)函數(shù)需要接收nonce跑杭,地址,值咆耿,燃?xì)馍舷拗档铝拢細(xì)鈨r(jià)格和可選發(fā)的數(shù)據(jù)。 發(fā)送ETH的數(shù)據(jù)字段為“nil”萨螺。 在與智能合約進(jìn)行交互時(shí)窄做,我們將使用數(shù)據(jù)字段,僅僅轉(zhuǎn)賬以太幣是不需要數(shù)據(jù)字段的慰技。
tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, nil)
下一步是使用發(fā)件人的私鑰對(duì)事務(wù)進(jìn)行簽名椭盏。 為此,我們調(diào)用SignTx
方法吻商,該方法接受一個(gè)未簽名的事務(wù)和我們之前構(gòu)造的私鑰掏颊。 SignTx
方法需要EIP155簽名者,這個(gè)也需要我們先從客戶端拿到鏈ID手报。
chainID, err := client.NetworkID(context.Background())
if err != nil {
log.Fatal(err)
}
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
if err != nil {
log.Fatal(err)
}
現(xiàn)在我們終于準(zhǔn)備通過在客戶端上調(diào)用“SendTransaction”來將已簽名的事務(wù)廣播到整個(gè)網(wǎng)絡(luò)蚯舱。
err = client.SendTransaction(context.Background(), signedTx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("tx sent: %s", signedTx.Hash().Hex()) // tx sent: 0x77006fcb3938f648e2cc65bafd27dec30b9bfbe9df41f78498b9c8b7322a249e
然后你可以去Etherscan看交易的確認(rèn)過程: https://rinkeby.etherscan.io/tx/0x77006fcb3938f648e2cc65bafd27dec30b9bfbe9df41f78498b9c8b7322a249e
完整代碼
package main
import (
"context"
"crypto/ecdsa"
"fmt"
"log"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
client, err := ethclient.Dial("https://rinkeby.infura.io")
if err != nil {
log.Fatal(err)
}
privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19")
if err != nil {
log.Fatal(err)
}
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
log.Fatal("error casting public key to ECDSA")
}
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
log.Fatal(err)
}
value := big.NewInt(1000000000000000000) // in wei (1 eth)
gasLimit := uint64(21000) // in units
gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
log.Fatal(err)
}
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
var data []byte
tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data)
chainID, err := client.NetworkID(context.Background())
if err != nil {
log.Fatal(err)
}
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
if err != nil {
log.Fatal(err)
}
err = client.SendTransaction(context.Background(), signedTx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("tx sent: %s", signedTx.Hash().Hex())
}
代幣的轉(zhuǎn)賬
本節(jié)將向你介紹如何轉(zhuǎn)移ERC-20代幣。了解如何轉(zhuǎn)移非ERC-20兼容的其他類型的代幣請(qǐng)查閱智能合約的章節(jié) 來了解如何與智能合約交互掩蛤。
假設(shè)您已連接客戶端,加載私鑰并配置燃?xì)鈨r(jià)格陈肛,下一步是設(shè)置具體的交易數(shù)據(jù)字段揍鸟。 如果你完全不明白我剛講的這些,請(qǐng)先復(fù)習(xí) 以太幣轉(zhuǎn)賬的章節(jié)。
代幣傳輸不需要傳輸ETH阳藻,因此將交易“值”設(shè)置為“0”晰奖。
value := big.NewInt(0)
先將您要發(fā)送代幣的地址存儲(chǔ)在變量中。
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
現(xiàn)在輪到有趣的部分腥泥。 我們需要弄清楚交易的 data 部分匾南。 這意味著我們需要找出我們將要調(diào)用的智能合約函數(shù)名,以及函數(shù)將接收的輸入蛔外。 然后我們使用函數(shù)名的keccak-256哈希來檢索 方法ID蛆楞,它是前8個(gè)字符(4個(gè)字節(jié))。 然后夹厌,我們附加我們發(fā)送的地址豹爹,并附加我們打算轉(zhuǎn)賬的代幣數(shù)量。 這些輸入需要256位長(32字節(jié))并填充左側(cè)矛纹。 方法ID不需填充臂聋。
為了演示,我創(chuàng)造了一個(gè)新的代幣(HelloToken HTN)或南,這個(gè)可以用代幣工廠服務(wù)來完成https://tokenfactory.surge.sh, 代幣我部署到了Rinkeby測試網(wǎng)孩等。
讓我們將代幣合約地址分配給變量。
tokenAddress := common.HexToAddress("0x28b149020d2152179873ec60bed6bf7cd705775d")
函數(shù)名將是傳遞函數(shù)的名稱采够,即ERC-20規(guī)范中的transfer
和參數(shù)類型肄方。 第一個(gè)參數(shù)類型是address
(令牌的接收者),第二個(gè)類型是uint256
(要發(fā)送的代幣數(shù)量)吁恍。 不需要沒有空格和參數(shù)名稱扒秸。 我們還需要用字節(jié)切片格式。
transferFnSignature := []byte("transfer(address,uint256)")
我們現(xiàn)在將從go-ethereum導(dǎo)入crypto/sha3
包以生成函數(shù)簽名的Keccak256哈希冀瓦。 然后我們只使用前4個(gè)字節(jié)來獲取方法ID伴奥。
hash := sha3.NewKeccak256()
hash.Write(transferFnSignature)
methodID := hash.Sum(nil)[:4]
fmt.Println(hexutil.Encode(methodID)) // 0xa9059cbb
接下來,我們需要將給我們發(fā)送代幣的地址左填充到32字節(jié)翼闽。
paddedAddress := common.LeftPadBytes(toAddress.Bytes(), 32)
fmt.Println(hexutil.Encode(paddedAddress)) // 0x0000000000000000000000004592d8f8d7b001e72cb26a73e4fa1806a51ac79d
接下來我們確定要發(fā)送多少個(gè)代幣拾徙,在這個(gè)例子里是1,000個(gè),并且我們需要在big.Int
中格式化為wei感局。
amount := new(big.Int)
amount.SetString("1000000000000000000000", 10) // 1000 tokens
代幣量也需要左填充到32個(gè)字節(jié)尼啡。
paddedAmount := common.LeftPadBytes(amount.Bytes(), 32)
fmt.Println(hexutil.Encode(paddedAmount)) // 0x00000000000000000000000000000000000000000000003635c9adc5dea00000
接下來我們只需將方法ID,填充后的地址和填后的轉(zhuǎn)賬量询微,接到將成為我們數(shù)據(jù)字段的字節(jié)片崖瞭。
var data []byte
data = append(data, methodID...)
data = append(data, paddedAddress...)
data = append(data, paddedAmount...)
燃?xì)馍舷拗茖⑷Q于交易數(shù)據(jù)的大小和智能合約必須執(zhí)行的計(jì)算步驟。 幸運(yùn)的是撑毛,客戶端提供了EstimateGas
方法书聚,它可以為我們估算所需的燃?xì)饬俊?這個(gè)函數(shù)從ethereum
包中獲取CallMsg
結(jié)構(gòu),我們?cè)谄渲兄付〝?shù)據(jù)和地址。 它將返回我們估算的完成交易所需的估計(jì)燃?xì)馍舷蕖?/p>
gasLimit, err := client.EstimateGas(context.Background(), ethereum.CallMsg{
To: &toAddress,
Data: data,
})
if err != nil {
log.Fatal(err)
}
fmt.Println(gasLimit) // 23256
接下來我們需要做的是構(gòu)建交易事務(wù)類型雌续,這類似于您在ETH轉(zhuǎn)賬部分中看到的斩个,除了to字段將是代幣智能合約地址。 這個(gè)常讓人困惑驯杜。我們還必須在調(diào)用中包含0 ETH的值字段和剛剛生成的數(shù)據(jù)字節(jié)受啥。
tx := types.NewTransaction(nonce, tokenAddress, value, gasLimit, gasPrice, data)
下一步是使用發(fā)件人的私鑰對(duì)事務(wù)進(jìn)行簽名。 SignTx
方法需要EIP155簽名器(EIP155 signer)鸽心,這需要我們從客戶端拿到鏈ID滚局。
chainID, err := client.NetworkID(context.Background())
if err != nil {
log.Fatal(err)
}
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
if err != nil {
log.Fatal(err)
}
最后廣播交易。
err = client.SendTransaction(context.Background(), signedTx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("tx sent: %s", signedTx.Hash().Hex()) // tx sent: 0xa56316b637a94c4cc0331c73ef26389d6c097506d581073f927275e7a6ece0bc
你可以去Etherscan看交易的確認(rèn)過程: https://rinkeby.etherscan.io/tx/0xa56316b637a94c4cc0331c73ef26389d6c097506d581073f927275e7a6ece0bc
要了解更多如何加載ERC20智能合約并與之互動(dòng)的內(nèi)容再悼,可以查看ERC20代幣的智能合約章節(jié).
完整代碼
package main
import (
"context"
"crypto/ecdsa"
"fmt"
"log"
"math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
client, err := ethclient.Dial("https://rinkeby.infura.io")
if err != nil {
log.Fatal(err)
}
privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19")
if err != nil {
log.Fatal(err)
}
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
log.Fatal("error casting public key to ECDSA")
}
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
log.Fatal(err)
}
value := big.NewInt(0) // in wei (0 eth)
gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
log.Fatal(err)
}
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
tokenAddress := common.HexToAddress("0x28b149020d2152179873ec60bed6bf7cd705775d")
transferFnSignature := []byte("transfer(address,uint256)")
hash := sha3.NewKeccak256()
hash.Write(transferFnSignature)
methodID := hash.Sum(nil)[:4]
fmt.Println(hexutil.Encode(methodID)) // 0xa9059cbb
paddedAddress := common.LeftPadBytes(toAddress.Bytes(), 32)
fmt.Println(hexutil.Encode(paddedAddress)) // 0x0000000000000000000000004592d8f8d7b001e72cb26a73e4fa1806a51ac79d
amount := new(big.Int)
amount.SetString("1000000000000000000000", 10) // 1000 tokens
paddedAmount := common.LeftPadBytes(amount.Bytes(), 32)
fmt.Println(hexutil.Encode(paddedAmount)) // 0x00000000000000000000000000000000000000000000003635c9adc5dea00000
var data []byte
data = append(data, methodID...)
data = append(data, paddedAddress...)
data = append(data, paddedAmount...)
gasLimit, err := client.EstimateGas(context.Background(), ethereum.CallMsg{
To: &toAddress,
Data: data,
})
if err != nil {
log.Fatal(err)
}
fmt.Println(gasLimit) // 23256
tx := types.NewTransaction(nonce, tokenAddress, value, gasLimit, gasPrice, data)
chainID, err := client.NetworkID(context.Background())
if err != nil {
log.Fatal(err)
}
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
if err != nil {
log.Fatal(err)
}
err = client.SendTransaction(context.Background(), signedTx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("tx sent: %s", signedTx.Hash().Hex()) // tx sent: 0xa56316b637a94c4cc0331c73ef26389d6c097506d581073f927275e7a6ece0bc
}
監(jiān)聽新區(qū)塊
訂閱新區(qū)塊
在本節(jié)中核畴,我們將討論如何設(shè)置訂閱以便在新區(qū)塊被開采時(shí)獲取事件。首先冲九,我們需要一個(gè)支持websocket RPC的以太坊服務(wù)提供者谤草。在示例中,我們將使用infura 的websocket端點(diǎn)莺奸。
client, err := ethclient.Dial("wss://ropsten.infura.io/ws")
if err != nil {
log.Fatal(err)
}
接下來丑孩,我們將創(chuàng)建一個(gè)新的通道,用于接收最新的區(qū)塊頭灭贷。
headers := make(chan *types.Header)
現(xiàn)在我們調(diào)用客戶端的SubscribeNewHead
方法温学,它接收我們剛創(chuàng)建的區(qū)塊頭通道,該方法將返回一個(gè)訂閱對(duì)象甚疟。
sub, err := client.SubscribeNewHead(context.Background(), headers)
if err != nil {
log.Fatal(err)
}
訂閱將推送新的區(qū)塊頭事件到我們的通道仗岖,因此我們可以使用一個(gè)select語句來監(jiān)聽新消息。訂閱對(duì)象還包括一個(gè)error通道览妖,該通道將在訂閱失敗時(shí)發(fā)送消息轧拄。
for {
select {
case err := <-sub.Err():
log.Fatal(err)
case header := <-headers:
fmt.Println(header.Hash().Hex()) // 0xbc10defa8dda384c96a17640d84de5578804945d347072e091b4e5f390ddea7f
}
}
要獲得該區(qū)塊的完整內(nèi)容,我們可以將區(qū)塊頭的摘要傳遞給客戶端的BlockByHash
函數(shù)讽膏。
block, err := client.BlockByHash(context.Background(), header.Hash())
if err != nil {
log.Fatal(err)
}
fmt.Println(block.Hash().Hex()) // 0xbc10defa8dda384c96a17640d84de5578804945d347072e091b4e5f390ddea7f
fmt.Println(block.Number().Uint64()) // 3477413
fmt.Println(block.Time().Uint64()) // 1529525947
fmt.Println(block.Nonce()) // 130524141876765836
fmt.Println(len(block.Transactions())) // 7
正如您所見檩电,您可以讀取整個(gè)區(qū)塊的元數(shù)據(jù)字段,交易列表等等府树。
完整代碼
package main
import (
"context"
"fmt"
"log"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
client, err := ethclient.Dial("wss://ropsten.infura.io/ws")
if err != nil {
log.Fatal(err)
}
headers := make(chan *types.Header)
sub, err := client.SubscribeNewHead(context.Background(), headers)
if err != nil {
log.Fatal(err)
}
for {
select {
case err := <-sub.Err():
log.Fatal(err)
case header := <-headers:
fmt.Println(header.Hash().Hex()) // 0xbc10defa8dda384c96a17640d84de5578804945d347072e091b4e5f390ddea7f
block, err := client.BlockByHash(context.Background(), header.Hash())
if err != nil {
log.Fatal(err)
}
fmt.Println(block.Hash().Hex()) // 0xbc10defa8dda384c96a17640d84de5578804945d347072e091b4e5f390ddea7f
fmt.Println(block.Number().Uint64()) // 3477413
fmt.Println(block.Time().Uint64()) // 1529525947
fmt.Println(block.Nonce()) // 130524141876765836
fmt.Println(len(block.Transactions())) // 7
}
}
}
創(chuàng)建裸交易
構(gòu)建原始交易(Raw Transaction)
如果你看過上個(gè)章節(jié), 那么你知道如何加載你的私鑰來簽名交易俐末。 我們現(xiàn)在假設(shè)你知道如何做到這一點(diǎn),現(xiàn)在你想讓原始交易數(shù)據(jù)能夠在以后廣播它奄侠。
首先構(gòu)造事務(wù)對(duì)象并對(duì)其進(jìn)行簽名卓箫,例如:
tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data)
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
if err != nil {
log.Fatal(err)
}
現(xiàn)在,在我們以原始字節(jié)格式獲取事務(wù)之前垄潮,我們需要初始化一個(gè)types.Transactions
類型丽柿,并將簽名后的交易作為第一個(gè)值恢准。
ts := types.Transactions{signedTx}
這樣做的原因是因?yàn)?code>Transactions類型提供了一個(gè)GetRlp
方法魂挂,用于以RLP編碼格式返回事務(wù)甫题。 RLP是以太坊用于序列化對(duì)象的特殊編碼方法。 結(jié)果是原始字節(jié)涂召。
rawTxBytes := ts.GetRlp(0)
最后坠非,我們可以非常輕松地將原始字節(jié)轉(zhuǎn)換為十六進(jìn)制字符串。
rawTxHex := hex.EncodeToString(rawTxBytes)
fmt.Printf(rawTxHex)
// f86d8202b38477359400825208944592d8f8d7b001e72cb26a73e4fa1806a51ac79d880de0b6b3a7640000802ba0699ff162205967ccbabae13e07cdd4284258d46ec1051a70a51be51ec2bc69f3a04e6944d508244ea54a62ebf9a72683eeadacb73ad7c373ee542f1998147b220e
接下來果正,你就可以廣播原始交易數(shù)據(jù)炎码。在下一章 我們將學(xué)習(xí)如何廣播一個(gè)原始交易。
完整代碼
package main
import (
"context"
"crypto/ecdsa"
"encoding/hex"
"fmt"
"log"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
client, err := ethclient.Dial("https://rinkeby.infura.io")
if err != nil {
log.Fatal(err)
}
privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19")
if err != nil {
log.Fatal(err)
}
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
log.Fatal("error casting public key to ECDSA")
}
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
log.Fatal(err)
}
value := big.NewInt(1000000000000000000) // in wei (1 eth)
gasLimit := uint64(21000) // in units
gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
log.Fatal(err)
}
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
var data []byte
tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data)
chainID, err := client.NetworkID(context.Background())
if err != nil {
log.Fatal(err)
}
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
if err != nil {
log.Fatal(err)
}
ts := types.Transactions{signedTx}
rawTxBytes := ts.GetRlp(0)
rawTxHex := hex.EncodeToString(rawTxBytes)
fmt.Printf(rawTxHex) // f86...772
}
發(fā)送裸交易
發(fā)送原始交易事務(wù)
在上個(gè)章節(jié)中 我們學(xué)會(huì)了如何創(chuàng)建原始事務(wù)秋泳。 現(xiàn)在潦闲,我們將學(xué)習(xí)如何將其廣播到以太坊網(wǎng)絡(luò),以便最終被處理和被礦工打包到區(qū)塊迫皱。
首先將原始事務(wù)十六進(jìn)制解碼為字節(jié)格式歉闰。
rawTx := "f86d8202b28477359400825208944592d8f8d7b001e72cb26a73e4fa1806a51ac79d880de0b6b3a7640000802ca05924bde7ef10aa88db9c66dd4f5fb16b46dff2319b9968be983118b57bb50562a001b24b31010004f13d9a26b320845257a6cfc2bf819a3d55e3fc86263c5f0772"
rawTxBytes, err := hex.DecodeString(rawTx)
接下來初始化一個(gè)新的types.Transaction
指針并從go-ethereumrlp
包中調(diào)用DecodeBytes
,將原始事務(wù)字節(jié)和指針傳遞給以太坊事務(wù)類型卓起。 RLP是以太坊用于序列化和反序列化數(shù)據(jù)的編碼方法和敬。
tx := new(types.Transaction)
rlp.DecodeBytes(rawTxBytes, &tx)
現(xiàn)在,我們可以使用我們的以太坊客戶端輕松地廣播交易戏阅。
err := client.SendTransaction(context.Background(), tx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("tx sent: %s", tx.Hash().Hex()) // tx sent: 0xc429e5f128387d224ba8bed6885e86525e14bfdc2eb24b5e9c3351a1176fd81f
然后你可以去Etherscan看交易的確認(rèn)過程: https://rinkeby.etherscan.io/tx/0xc429e5f128387d224ba8bed6885e86525e14bfdc2eb24b5e9c3351a1176fd81f
完整代碼
package main
import (
"context"
"encoding/hex"
"fmt"
"log"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rlp"
)
func main() {
client, err := ethclient.Dial("https://rinkeby.infura.io")
if err != nil {
log.Fatal(err)
}
rawTx := "f86d8202b28477359400825208944592d8f8d7b001e72cb26a73e4fa1806a51ac79d880de0b6b3a7640000802ca05924bde7ef10aa88db9c66dd4f5fb16b46dff2319b9968be983118b57bb50562a001b24b31010004f13d9a26b320845257a6cfc2bf819a3d55e3fc86263c5f0772"
rawTxBytes, err := hex.DecodeString(rawTx)
tx := new(types.Transaction)
rlp.DecodeBytes(rawTxBytes, &tx)
err = client.SendTransaction(context.Background(), tx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("tx sent: %s", tx.Hash().Hex()) // tx sent: 0xc429e5f128387d224ba8bed6885e86525e14bfdc2eb24b5e9c3351a1176fd81f
}