賬戶
以太坊上的賬戶要么是錢包地址要么是智能合約地址赴背。它們看起來像是0x71c7656ec7ab88b098defb751b7401b5f6d8976f
锈锤,它們用于將ETH發(fā)送到另一個用戶固以,并且還用于在需要和區(qū)塊鏈交互時指一個智能合約杆勇。它們是唯一的,且是從私鑰導(dǎo)出的馆衔。我們將在后面的章節(jié)更深入地介紹公私鑰對井誉。
要使用go-ethereun的賬戶地址蕉扮,您必須先將它們轉(zhuǎn)化為go-ethereum中的common.Address
類型。
address := common.HexToAddress("0x71c7656ec7ab88b098defb751b7401b5f6d8976f")
fmt.Println(address.Hex()) // 0x71C7656EC7ab88b098defB751B7401B5f6d8976F
您可以在幾乎任何地方使用這種類型颗圣,您可以將以太坊地址傳遞給go-ethereum的方法喳钟。既然您已經(jīng)了解賬戶和地址的基礎(chǔ)知識,那么讓我們在下一節(jié)中學(xué)習(xí)如何檢索ETH賬戶余額在岂。
完整代碼
package main
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
)
func main() {
address := common.HexToAddress("0x71c7656ec7ab88b098defb751b7401b5f6d8976f")
fmt.Println(address.Hex()) // 0x71C7656EC7ab88b098defB751B7401B5f6d8976F
fmt.Println(address.Hash().Hex()) // 0x00000000000000000000000071c7656ec7ab88b098defb751b7401b5f6d8976f
fmt.Println(address.Bytes()) // [113 199 101 110 199 171 136 176 152 222 251 117 27 116 1 181 246 216 151 111]
}
賬戶余額
讀取一個賬戶的余額相當簡單奔则。調(diào)用客戶端的BalanceAt
方法,給它傳遞賬戶地址和可選的區(qū)塊號蔽午。將區(qū)塊號設(shè)置為nil
將返回最新的余額易茬。
account := common.HexToAddress("0x71c7656ec7ab88b098defb751b7401b5f6d8976f")
balance, err := client.BalanceAt(context.Background(), account, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(balance) // 25893180161173005034
傳區(qū)塊號能讓您讀取該區(qū)塊時的賬戶余額。區(qū)塊號必須是big.Int
類型及老。
blockNumber := big.NewInt(5532993)
balance, err := client.BalanceAt(context.Background(), account, blockNumber)
if err != nil {
log.Fatal(err)
}
fmt.Println(balance) // 25729324269165216042
以太坊中的數(shù)字是使用盡可能小的單位來處理的抽莱,因為它們是定點精度绊起,在ETH中它是wei银锻。要讀取ETH值,您必須做計算wei/10^18
狰腌。因為我們正在處理大數(shù)僧鲁,我們得導(dǎo)入原生的Gomath
和math/big
包虐呻。這是您做的轉(zhuǎn)換。
fbalance := new(big.Float)
fbalance.SetString(balance.String())
ethValue := new(big.Float).Quo(fbalance, big.NewFloat(math.Pow10(18)))
fmt.Println(ethValue) // 25.729324269165216041
待處理的余額
有時您想知道待處理的賬戶余額是多少寞秃,例如斟叼,在提交或等待交易確認后⊥筛茫客戶端提供了類似BalanceAt
的方法犁柜,名為PendingBalanceAt
洲鸠,它接收賬戶地址作為參數(shù)堂淡。
pendingBalance, err := client.PendingBalanceAt(context.Background(), account)
fmt.Println(pendingBalance) // 25729324269165216042
完整代碼
package main
import (
"context"
"fmt"
"log"
"math"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
client, err := ethclient.Dial("https://mainnet.infura.io")
if err != nil {
log.Fatal(err)
}
account := common.HexToAddress("0x71c7656ec7ab88b098defb751b7401b5f6d8976f")
balance, err := client.BalanceAt(context.Background(), account, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(balance) // 25893180161173005034
blockNumber := big.NewInt(5532993)
balanceAt, err := client.BalanceAt(context.Background(), account, blockNumber)
if err != nil {
log.Fatal(err)
}
fmt.Println(balanceAt) // 25729324269165216042
fbalance := new(big.Float)
fbalance.SetString(balanceAt.String())
ethValue := new(big.Float).Quo(fbalance, big.NewFloat(math.Pow10(18)))
fmt.Println(ethValue) // 25.729324269165216041
pendingBalance, err := client.PendingBalanceAt(context.Background(), account)
fmt.Println(pendingBalance) // 25729324269165216042
}
賬戶代幣余額
要學(xué)習(xí)如何讀取賬戶代幣(ECR20)余額,請前往ECR20代幣智能合約章節(jié)扒腕。
生成新錢包
要首先生成一個新的錢包绢淀,我們需要導(dǎo)入go-ethereumcrypto
包,該包提供用于生成隨機私鑰的GenerateKey
方法瘾腰。
privateKey, err := crypto.GenerateKey()
if err != nil {
log.Fatal(err)
}
然后我們可以通過導(dǎo)入golangcrypto/ecdsa
包并使用FromECDSA
方法將其轉(zhuǎn)換為字節(jié)皆的。
privateKeyBytes := crypto.FromECDSA(privateKey)
我們現(xiàn)在可以使用go-ethereumhexutil
包將它轉(zhuǎn)換為十六進制字符串,該包提供了一個帶有字節(jié)切片的Encode
方法蹋盆。 然后我們在十六進制編碼之后刪除“0x”费薄。
fmt.Println(hexutil.Encode(privateKeyBytes)[2:]) // fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19
這就是用于簽署交易的私鑰硝全,將被視為密碼,永遠不應(yīng)該被共享給別人楞抡,因為誰擁有它可以訪問你的所有資產(chǎn)伟众。
由于公鑰是從私鑰派生的,因此go-ethereum的加密私鑰具有一個返回公鑰的Public
方法召廷。
publicKey := privateKey.Public()
將其轉(zhuǎn)換為十六進制的過程與我們使用轉(zhuǎn)化私鑰的過程類似凳厢。 我們剝離了0x
和前2個字符04
,它始終是EC前綴竞慢,不是必需的先紫。
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
log.Fatal("error casting public key to ECDSA")
}
publicKeyBytes := crypto.FromECDSAPub(publicKeyECDSA)
fmt.Println(hexutil.Encode(publicKeyBytes)[4:]) // 9a7df67f79246283fdc93af76d4f8cdd62c4886e8cd870944e817dd0b97934fdd7719d0810951e03418205868a5c1b40b192451367f28e0088dd75e15de40c05
現(xiàn)在我們擁有公鑰,就可以輕松生成你經(jīng)吵镏螅看到的公共地址遮精。 為了做到這一點,go-ethereum加密包有一個PubkeyToAddress
方法败潦,它接受一個ECDSA公鑰仑鸥,并返回公共地址。
address := crypto.PubkeyToAddress(*publicKeyECDSA).Hex()
fmt.Println(address) // 0x96216849c49358B10257cb55b28eA603c874b05E
公共地址其實就是公鑰的Keccak-256哈希变屁,然后我們?nèi)∽詈?0個字符(20個字節(jié))并用“0x”作為前綴眼俊。 以下是使用go-ethereum的crypto/sha3
Keccak256函數(shù)手動完成的方法。
hash := sha3.NewKeccak256()
hash.Write(publicKeyBytes[1:])
fmt.Println(hexutil.Encode(hash.Sum(nil)[12:])) // 0x96216849c49358b10257cb55b28ea603c874b05e
完整代碼
package main
import (
"crypto/ecdsa"
"fmt"
"log"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/sha3"
)
func main() {
privateKey, err := crypto.GenerateKey()
if err != nil {
log.Fatal(err)
}
privateKeyBytes := crypto.FromECDSA(privateKey)
fmt.Println(hexutil.Encode(privateKeyBytes)[2:]) // 0xfad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
log.Fatal("error casting public key to ECDSA")
}
publicKeyBytes := crypto.FromECDSAPub(publicKeyECDSA)
fmt.Println(hexutil.Encode(publicKeyBytes)[4:]) // 0x049a7df67f79246283fdc93af76d4f8cdd62c4886e8cd870944e817dd0b97934fdd7719d0810951e03418205868a5c1b40b192451367f28e0088dd75e15de40c05
address := crypto.PubkeyToAddress(*publicKeyECDSA).Hex()
fmt.Println(address) // 0x96216849c49358B10257cb55b28eA603c874b05E
hash := sha3.NewKeccak256()
hash.Write(publicKeyBytes[1:])
fmt.Println(hexutil.Encode(hash.Sum(nil)[12:])) // 0x96216849c49358b10257cb55b28ea603c874b05e
}
密鑰庫 Keystores
keystore是一個包含經(jīng)過加密了的錢包私鑰粟关。go-ethereum中的keystore疮胖,每個文件只能包含一個錢包密鑰對。要生成keystore闷板,首先您必須調(diào)用NewKeyStore
澎灸,給它提供保存keystore的目錄路徑。然后遮晚,您可調(diào)用NewAccount
方法創(chuàng)建新的錢包性昭,并給它傳入一個用于加密的口令。您每次調(diào)用NewAccount
县遣,它將在磁盤上生成新的keystore文件糜颠。
這是一個完整的生成新的keystore賬戶的示例。
ks := keystore.NewKeyStore("./wallets", keystore.StandardScryptN, keystore.StandardScryptP)
password := "secret"
account, err := ks.NewAccount(password)
if err != nil {
log.Fatal(err)
}
fmt.Println(account.Address.Hex()) // 0x20F8D42FB0F667F2E53930fed426f225752453b3
現(xiàn)在要導(dǎo)入您的keystore萧求,您基本上像往常一樣再次調(diào)用NewKeyStore
其兴,然后調(diào)用Import
方法,該方法接收keystore的JSON數(shù)據(jù)作為字節(jié)夸政。第二個參數(shù)是用于加密私鑰的口令元旬。第三個參數(shù)是指定一個新的加密口令,但我們在示例中使用一樣的口令。導(dǎo)入賬戶將允許您按期訪問該賬戶匀归,但它將生成新keystore文件坑资!有兩個相同的事物是沒有意義的,所以我們將刪除舊的穆端。
這是一個導(dǎo)入keystore和訪問賬戶的示例盐茎。
file := "./wallets/UTC--2018-07-04T09-58-30.122808598Z--20f8d42fb0f667f2e53930fed426f225752453b3"
ks := keystore.NewKeyStore("./tmp", keystore.StandardScryptN, keystore.StandardScryptP)
jsonBytes, err := ioutil.ReadFile(file)
if err != nil {
log.Fatal(err)
}
password := "secret"
account, err := ks.Import(jsonBytes, password, password)
if err != nil {
log.Fatal(err)
}
fmt.Println(account.Address.Hex()) // 0x20F8D42FB0F667F2E53930fed426f225752453b3
if err := os.Remove(file); err != nil {
log.Fatal(err)
}
完整代碼
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"github.com/ethereum/go-ethereum/accounts/keystore"
)
func createKs() {
ks := keystore.NewKeyStore("./tmp", keystore.StandardScryptN, keystore.StandardScryptP)
password := "secret"
account, err := ks.NewAccount(password)
if err != nil {
log.Fatal(err)
}
fmt.Println(account.Address.Hex()) // 0x20F8D42FB0F667F2E53930fed426f225752453b3
}
func importKs() {
file := "./tmp/UTC--2018-07-04T09-58-30.122808598Z--20f8d42fb0f667f2e53930fed426f225752453b3"
ks := keystore.NewKeyStore("./tmp", keystore.StandardScryptN, keystore.StandardScryptP)
jsonBytes, err := ioutil.ReadFile(file)
if err != nil {
log.Fatal(err)
}
password := "secret"
account, err := ks.Import(jsonBytes, password, password)
if err != nil {
log.Fatal(err)
}
fmt.Println(account.Address.Hex()) // 0x20F8D42FB0F667F2E53930fed426f225752453b3
if err := os.Remove(file); err != nil {
log.Fatal(err)
}
}
func main() {
createKs()
//importKs()
}
硬件錢包
分層確定性(HD)Wallet
關(guān)于創(chuàng)建或使用一個分層確定性(HD)錢包,請參考Go包: https://github.com/miguelmota/go-ethereum-hdwallet
地址檢查
本節(jié)將介紹如何確認一個地址并確定其是否為智能合約地址徙赢。
檢查地址是否有效
我們可以使用簡單的正則表達式來檢查以太坊地址是否有效:
We can use a simple regular expression to check if the ethereum address is valid:
re := regexp.MustCompile("^0x[0-9a-fA-F]{40}$")
fmt.Printf("is valid: %v\n", re.MatchString("0x323b5d4c32345ced77393b3530b1eed0f346429d")) // is valid: true
fmt.Printf("is valid: %v\n", re.MatchString("0xZYXb5d4c32345ced77393b3530b1eed0f346429d")) // is valid: false
檢查地址是否為賬戶或智能合約
我們可以確定字柠,若在該地址存儲了字節(jié)碼,該地址是智能合約狡赐。這是一個示例窑业,在例子中,我們獲取一個代幣智能合約的字節(jié)碼并檢查其長度以驗證它是一個智能合約:
// 0x Protocol Token (ZRX) smart contract address
address := common.HexToAddress("0xe41d2489571d322189246dafa5ebde1f4699f498")
bytecode, err := client.CodeAt(context.Background(), address, nil) // nil is latest block
if err != nil {
log.Fatal(err)
}
isContract := len(bytecode) > 0
fmt.Printf("is contract: %v\n", isContract) // is contract: true
當?shù)刂飞蠜]有字節(jié)碼時枕屉,我們知道它不是一個智能合約常柄,它是一個標準的以太坊賬戶。
// a random user account address
address := common.HexToAddress("0x8e215d06ea7ec1fdb4fc5fd21768f4b34ee92ef4")
bytecode, err := client.CodeAt(context.Background(), address, nil) // nil is latest block
if err != nil {
log.Fatal(err)
}
isContract = len(bytecode) > 0
fmt.Printf("is contract: %v\n", isContract) // is contract: false
完整代碼
package main
import (
"context"
"fmt"
"log"
"regexp"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
re := regexp.MustCompile("^0x[0-9a-fA-F]{40}$")
fmt.Printf("is valid: %v\n", re.MatchString("0x323b5d4c32345ced77393b3530b1eed0f346429d")) // is valid: true
fmt.Printf("is valid: %v\n", re.MatchString("0xZYXb5d4c32345ced77393b3530b1eed0f346429d")) // is valid: false
client, err := ethclient.Dial("https://mainnet.infura.io")
if err != nil {
log.Fatal(err)
}
// 0x Protocol Token (ZRX) smart contract address
address := common.HexToAddress("0xe41d2489571d322189246dafa5ebde1f4699f498")
bytecode, err := client.CodeAt(context.Background(), address, nil) // nil is latest block
if err != nil {
log.Fatal(err)
}
isContract := len(bytecode) > 0
fmt.Printf("is contract: %v\n", isContract) // is contract: true
// a random user account address
address = common.HexToAddress("0x8e215d06ea7ec1fdb4fc5fd21768f4b34ee92ef4")
bytecode, err = client.CodeAt(context.Background(), address, nil) // nil is latest block
if err != nil {
log.Fatal(err)
}
isContract = len(bytecode) > 0
fmt.Printf("is contract: %v\n", isContract) // is contract: false
}