用Go來做以太坊開發(fā)③交易

交易(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ū)塊中的交易。

完整代碼

blocks.go

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

完整代碼

transactions.go

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

完整代碼

transfer_eth.go

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é).

完整代碼

transfer_tokens.go

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ù)字段,交易列表等等府树。

完整代碼

block_subscribe.go

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è)原始交易。


完整代碼

transaction_raw_create.go

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

完整代碼

transaction_raw_sendreate.go

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
}

文章不定期更新昼弟,小編微信:grey0805,歡迎交流

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末奕筐,一起剝皮案震驚了整個(gè)濱河市舱痘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌离赫,老刑警劉巖芭逝,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異笆怠,居然都是意外死亡铝耻,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門蹬刷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瓢捉,“玉大人,你說我怎么就攤上這事办成∨萏” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵迂卢,是天一觀的道長某弦。 經(jīng)常有香客問我桐汤,道長,這世上最難降的妖魔是什么靶壮? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任怔毛,我火速辦了婚禮,結(jié)果婚禮上腾降,老公的妹妹穿的比我還像新娘拣度。我一直安慰自己,他們只是感情好螃壤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布抗果。 她就那樣靜靜地躺著,像睡著了一般奸晴。 火紅的嫁衣襯著肌膚如雪冤馏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天寄啼,我揣著相機(jī)與錄音逮光,去河邊找鬼。 笑死辕录,一個(gè)胖子當(dāng)著我的面吹牛睦霎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播走诞,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼副女,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蚣旱?” 一聲冷哼從身側(cè)響起碑幅,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎塞绿,沒想到半個(gè)月后沟涨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡异吻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年裹赴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诀浪。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡棋返,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出雷猪,到底是詐尸還是另有隱情睛竣,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布求摇,位于F島的核電站射沟,受9級(jí)特大地震影響殊者,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜验夯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一猖吴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧簿姨,春花似錦距误、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽趁俊。三九已至域仇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間寺擂,已是汗流浹背暇务。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留怔软,地道東北人垦细。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像挡逼,于是被迫代替她去往敵國和親括改。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354