订阅区块,部署合约,加载合约
订阅区块
订阅区块需要 websocket RPC URL。
client, err := ethclient.Dial("wss://eth-sepolia.g.alchemy.com/v2/<API_KEY>")
if err != nil {log.Fatal(err)
}
接下来,我们将创建一个新的通道,用于接收最新的区块头。
headers := make(chan *types.Header)
现在我们调用客户端的 SubscribeNewHead
方法,它接收我们刚创建的区块头通道,该方法将返回一个订阅对象。
sub, err := client.SubscribeNewHead(context.Background(), headers)
if err != nil {log.Fatal(err)
}
订阅将推送新的区块头事件到我们的通道,因此我们可以使用一个 select 语句来监听新消息。订阅对象还包括一个 error 通道,该通道将在订阅失败时发送消息。
for {select {case err := <-sub.Err():log.Fatal(err)case header := <-headers:fmt.Println(header.Hash().Hex()) // 0xbc10defa8dda384c96a17640d84de5578804945d347072e091b4e5f390ddea7f}
}
要获得该区块的完整内容,我们可以将区块头的摘要传递给客户端的 BlockByHash
函数。
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()) // 130524141876765836fmt.Println(len(block.Transactions())) // 7
正如您所见,您可以读取整个区块的元数据字段,交易列表等等。
完整代码
package mainimport ("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()) // 0xbc10defa8dda384c96a17640d84de5578804945d347072e091b4e5f390ddea7fblock, err := client.BlockByHash(context.Background(), header.Hash())if err != nil {log.Fatal(err)}fmt.Println(block.Hash().Hex()) // 0xbc10defa8dda384c96a17640d84de5578804945d347072e091b4e5f390ddea7ffmt.Println(block.Number().Uint64()) // 3477413fmt.Println(block.Time().Uint64()) // 1529525947fmt.Println(block.Nonce()) // 130524141876765836fmt.Println(len(block.Transactions())) // 7}}
}
合约示例代码
Store.sol
pragma solidity ^0.8.26;contract Store {event ItemSet(bytes32 key, bytes32 value);string public version;mapping (bytes32 => bytes32) public items;constructor(string memory _version) {version = _version;}function setItem(bytes32 key, bytes32 value) external {items[key] = value;emit ItemSet(key, value);}
}
使用 nodejs,安装 solc 工具:
npm install -g solc
使用命令,编译合约代码,会在当目录下生成一个编译好的二进制字节码文件 store_sol_Store.bin:
solcjs --bin Store.sol
使用命令,生成合约 abi 文件,会在当目录下生成 store_sol_Store.abi 文件:
solcjs --abi Store.sol
abigin 工具可以使用下面的命令安装:
go install github.com/ethereum/go-ethereum/cmd/abigen@latest
使用 abigen 工具根据这两个生成 bin 文件和 abi 文件,生成 go 代码:
abigen --bin=Store_sol_Store.bin --abi=Store_sol_Store.abi --pkg=store --out=store.go
当go.mod报错时,使用
go get github.com/ethereum/go-ethereum
go mod tidy -e
使用 abigen 工具
示例代码
func main() {client, err := ethclient.Dial("https://eth-sepolia.g.alchemy.com/v2/<apikey>")if err != nil {log.Fatal(err)}// privateKey, err := crypto.GenerateKey()// privateKeyBytes := crypto.FromECDSA(privateKey)// privateKeyHex := hex.EncodeToString(privateKeyBytes)// fmt.Println("Private Key:", privateKeyHex)privateKey, err := crypto.HexToECDSA("<your private key>")if err != nil {log.Fatal(err)}publicKey := privateKey.Public()publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)if !ok {log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey")}fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)nonce, err := client.PendingNonceAt(context.Background(), fromAddress)if err != nil {log.Fatal(err)}gasPrice, err := client.SuggestGasPrice(context.Background())if err != nil {log.Fatal(err)}chainId, err := client.NetworkID(context.Background())if err != nil {log.Fatal(err)}auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainId)if err != nil {log.Fatal(err)}auth.Nonce = big.NewInt(int64(nonce))auth.Value = big.NewInt(0) // in weiauth.GasLimit = uint64(300000) // in unitsauth.GasPrice = gasPriceinput := "1.0"address, tx, instance, err := store.DeployStore(auth, client, input)if err != nil {log.Fatal(err)}fmt.Println(address.Hex())fmt.Println(tx.Hash().Hex())_ = instance
}
solc version used for these examples
$ solcjs --version
0.8.26+commit.8a97fa7a.Emscripten.clang
仅使用 ethclient 工具
以太坊中,部署合约其实也是发起了一笔交易,并不是一定需要 abigen 工具生成 go 代码。
不是只能使用生成的 go 的合约代码才能部署合约。
可以仅使用 ethclient,使用更底层的方法,直接通过发送交易的方式来部署合约。
生成的 store_sol_Store.bin 文件中的字符串作为交易数据,部署 store 合约。
import ("context""crypto/ecdsa""encoding/hex""fmt""log""math/big""time""github.com/ethereum/go-ethereum""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"
)const (// store合约的字节码contractBytecode = "608060405234801561000f575f80fd5b5060405161087538038061087583398181016040528101906100319190610193565b805f908161003f91906103e7565b50506104b6565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6100a58261005f565b810181811067ffffffffffffffff821117156100c4576100c361006f565b5b80604052505050565b5f6100d6610046565b90506100e2828261009c565b919050565b5f67ffffffffffffffff8211156101015761010061006f565b5b61010a8261005f565b9050602081019050919050565b8281835e5f83830152505050565b5f610137610132846100e7565b6100cd565b9050828152602081018484840111156101535761015261005b565b5b61015e848285610117565b509392505050565b5f82601f83011261017a57610179610057565b5b815161018a848260208601610125565b91505092915050565b5f602082840312156101a8576101a761004f565b5b5f82015167ffffffffffffffff8111156101c5576101c4610053565b5b6101d184828501610166565b91505092915050565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061022857607f821691505b60208210810361023b5761023a6101e4565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f6008830261029d7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82610262565b6102a78683610262565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f6102eb6102e66102e1846102bf565b6102c8565b6102bf565b9050919050565b5f819050919050565b610304836102d1565b610318610310826102f2565b84845461026e565b825550505050565b5f90565b61032c610320565b6103378184846102fb565b505050565b5b8181101561035a5761034f5f82610324565b60018101905061033d565b5050565b601f82111561039f5761037081610241565b61037984610253565b81016020851015610388578190505b61039c61039485610253565b83018261033c565b50505b505050565b5f82821c905092915050565b5f6103bf5f19846008026103a4565b1980831691505092915050565b5f6103d783836103b0565b9150826002028217905092915050565b6103f0826101da565b67ffffffffffffffff8111156104095761040861006f565b5b6104138254610211565b61041e82828561035e565b5f60209050601f83116001811461044f575f841561043d578287015190505b61044785826103cc565b8655506104ae565b601f19841661045d86610241565b5f5b828110156104845784890151825560018201915060208501945060208101905061045f565b868310156104a1578489015161049d601f8916826103b0565b8355505b6001600288020188555050505b505050505050565b6103b2806104c35f395ff3fe608060405234801561000f575f80fd5b506004361061003f575f3560e01c806348f343f31461004357806354fd4d5014610073578063f56256c714610091575b5f80fd5b61005d600480360381019061005891906101d7565b6100ad565b60405161006a9190610211565b60405180910390f35b61007b6100c2565b604051610088919061029a565b60405180910390f35b6100ab60048036038101906100a691906102ba565b61014d565b005b6001602052805f5260405f205f915090505481565b5f80546100ce90610325565b80601f01602080910402602001604051908101604052809291908181526020018280546100fa90610325565b80156101455780601f1061011c57610100808354040283529160200191610145565b820191905f5260205f20905b81548152906001019060200180831161012857829003601f168201915b505050505081565b8060015f8481526020019081526020015f20819055507fe79e73da417710ae99aa2088575580a60415d359acfad9cdd3382d59c80281d48282604051610194929190610355565b60405180910390a15050565b5f80fd5b5f819050919050565b6101b6816101a4565b81146101c0575f80fd5b50565b5f813590506101d1816101ad565b92915050565b5f602082840312156101ec576101eb6101a0565b5b5f6101f9848285016101c3565b91505092915050565b61020b816101a4565b82525050565b5f6020820190506102245f830184610202565b92915050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f61026c8261022a565b6102768185610234565b9350610286818560208601610244565b61028f81610252565b840191505092915050565b5f6020820190508181035f8301526102b28184610262565b905092915050565b5f80604083850312156102d0576102cf6101a0565b5b5f6102dd858286016101c3565b92505060206102ee858286016101c3565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061033c57607f821691505b60208210810361034f5761034e6102f8565b5b50919050565b5f6040820190506103685f830185610202565b6103756020830184610202565b939250505056fea26469706673582212205aae308f77654b000c9d222eff2d9f2bd2ac18d990b10774842e4309d4e3e15664736f6c634300081a0033"
)func main() {// 连接到以太坊网络(这里使用 Goerli 测试网络作为示例)client, err := ethclient.Dial("https://goerli.infura.io/v3/YOUR-PROJECT-ID")if err != nil {log.Fatal(err)}// 创建私钥(在实际应用中,您应该使用更安全的方式来管理私钥)privateKey, err := crypto.HexToECDSA("YOUR-PRIVATE-KEY-HERE")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)// 获取noncenonce, err := client.PendingNonceAt(context.Background(), fromAddress)if err != nil {log.Fatal(err)}// 获取建议的gas价格gasPrice, err := client.SuggestGasPrice(context.Background())if err != nil {log.Fatal(err)}// 解码合约字节码data, err := hex.DecodeString(contractBytecode)if err != nil {log.Fatal(err)}// 创建交易tx := types.NewContractCreation(nonce, big.NewInt(0), 3000000, 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("Transaction sent: %s\n", signedTx.Hash().Hex())// 等待交易被挖矿receipt, err := waitForReceipt(client, signedTx.Hash())if err != nil {log.Fatal(err)}fmt.Printf("Contract deployed at: %s\n", receipt.ContractAddress.Hex())
}func waitForReceipt(client *ethclient.Client, txHash common.Hash) (*types.Receipt, error) {for {receipt, err := client.TransactionReceipt(context.Background(), txHash)if err == nil {return receipt, nil}if err != ethereum.NotFound {return nil, err}// 等待一段时间后再次查询time.Sleep(1 * time.Second)}
}
使用 remix 工具
也可以使用 Solidity 网页 IDE 工具 Remix。
以下是使用 Remix 工具部署合约到测试网的操作步骤:
-
首先打开 Remix IDE ,打开浏览器访问 Remix IDE 的官方网站:Remix - Ethereum IDE
-
导入智能合约到 Remix 的文件浏览器中,创建一个 store.sol 文件。把 2.10.0 步骤中的示例合约代码复制到这个 store.sol 文件中并保存。
-
在左侧菜单中选择"Solidity Compiler",然后点击"Compile"按钮编译合约。确保编译器版本与你的合约兼容。
-
在左侧菜单中选择"Deploy & Run Transactions"。在"Environment"下拉菜单中,选择"Injected Web3"。允许使用 MetaMask 或其他 Web3 钱包连接到测试网。
-
确保 MetaMask 插件已经连接到所需的测试网(如 Goerli、Sepolia 等)。如果需要,添加测试网络到 MetaMask 并获取一些测试以太币。
-
部署合约 在 Remix 的"Deploy & Run Transactions"面板中:
- 选择你要部署的合约
- 如果合约构造函数需要参数,在"Deploy"按钮下方的输入框中填入参数
- 点击"Deploy"按钮
-
MetaMask 会弹出一个窗口,要求你确认交易。检查 gas 费用和其他详情,然后确认交易。
-
等待部署完成 交易被确认后,合约将被部署到测试网。你可以在 Remix 的控制台中看到部署的详细信息,包括合约地址。
-
可以使用测试网的区块浏览器(https://sepolia.etherscan.io/)来验证合约是否成功部署,只需搜索合约地址即可。
-
部署完成后,就可以在 Remix 的"Deployed Contracts"部分与合约进行交互,调用函数或发送交易。
加载合约
加载合约是在成功部署合约后,使用合约的 abi 文件生成 go 合约代码,并且在代码中初始化这个合约的实例。
初始化合约实例时需要提供两个参数,分别是 ethclient 的实例和合约地址。
使用 2.10 中的示例合约代码生成 abi 文件:
solcjs --abi store.sol
然后使用 abigen,仅根据 abi 文件生成合约代码:
abigen --abi=Store_sol_Store.abi --pkg=store --out=store.go
注:仅使用 abi 文件生成的合约代码时,生成的代码中不会包含部署合约的代码。
2.11.1 使用生成的代码加载合约
部署的合约的地址,加载合约示例:
package mainimport ("log""github.com/ethereum/go-ethereum/common""github.com/ethereum/go-ethereum/ethclient""github.com/learn/init_order/store"
)const (contractAddr = "0x8D4141ec2b522dE5Cf42705C3010541B4B3EC24e"
)func main() {client, err := ethclient.Dial("http://127.0.0.1:7545")if err != nil {log.Fatal(err)}storeContract, err := store.NewStore(common.HexToAddress(contractAddr), client)if err != nil {log.Fatal(err)}_ = storeContract
}
使用 Remix 在线 IDE 工具加载合约
使用 Remix 在线 IDE 工具加载合约的前提是必须有合约代码,需要先在 Remix 中把合约编译一遍之后,才可以使用合约地址加载合约。
使用 Remix 在线 IDE 工具加载合约步骤:
- 导入智能合约到 Remix 的文件浏览器中,创建一个 store.sol 文件。把 2.10.0 步骤中的示例合约代码复制到这个 store.sol 文件中并保存。
- 在左侧菜单中选择"Solidity Compiler",然后点击"Compile"按钮编译合约。确保编译器版本与你的合约兼容。
- 在左侧菜单中选择"Deploy & Run Transactions"。在"Environment"下拉菜单中,选择"Injected Web3"。允许使用 MetaMask 或其他 Web3 钱包连接到测试网。
- 在"Contract"下来菜单中选中 store 合约,然后在"At Address"按钮边的输入框中,输入合约地址,点击"At Address"按钮。
- 可以看到"Deployed/Unpinned Contracts"栏新增一条记录,点击下拉按钮,即可与合约进行交互,调用函数或发送交易。