如何用Golang在Cosmos实现助记词转密钥地址及CosmJS同款转账逻辑
需求:用Golang复刻CosmJS的Cosmos代币转账逻辑
已通过CosmJS实现Cosmos-SDK区块链的代币转账(代码如下),现需要用Golang编写同款逻辑,同时满足三个核心要求:
- 实现与
gaiad keys export --unarmored-hex --unsafe一致的私钥导出结果 - 1:1复刻CosmJS的转账流程逻辑
- 提供本地密钥存储的高效实现示例
import { Secp256k1HdWallet } from "@cosmjs/amino"; import { StargateClient, SigningStargateClient } from "@cosmjs/stargate"; (async () => { const toAddress = "your recipient address here"; const rpcEndpoint = "your rpc endpoint here"; const mnemonic = "your mnemonic here"; const wallet = await Secp256k1HdWallet.fromMnemonic(mnemonic, { prefix: "cosmos" }); console.log(wallet); const [{ address, pubkey }] = await wallet.getAccounts(); console.log(address); const client = await StargateClient.connect(rpcEndpoint); console.log("chain id: ", await client.getChainId()); const balance = await client.getAllBalances(address); console.log("balance: ", balance); const signingClient = await SigningStargateClient.connectWithSigner(rpcEndpoint, wallet); console.log("sender balance before: ", await client.getAllBalances(address)); console.log("receipient balance before: ", await client.getAllBalances(toAddress)); const result = await signingClient.sendTokens(address, toAddress, [{ denom: "denom", amount: "800" }], { amount: [{ denom: "denom", amount: "20" }], gas: "200000", }); console.log("result: ", result); console.log("sender balance after: ", await client.getAllBalances(address)); console.log("receipient balance after: ", await client.getAllBalances(toAddress)); })();
一、实现与gaiad keys export一致的私钥导出
使用Cosmos SDK官方加密库,生成和gaiad keys export --unarmored-hex --unsafe完全匹配的十六进制私钥:
package main import ( "fmt" "log" "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/go-bip39" ) // ExportUnarmoredHexPrivateKey 导出与gaiad命令一致的未装甲十六进制私钥 func ExportUnarmoredHexPrivateKey(mnemonic string, prefix string) (string, types.PrivKey, error) { // 从助记词生成种子 seed, err := bip39.NewSeedWithErrorChecking(mnemonic, "") if err != nil { return "", nil, err } // 使用Cosmos默认HD路径:m/44'/118'/0'/0/0 kr, err := keyring.NewInMemory() if err != nil { return "", nil, err } // 导入助记词到内存keyring info, err := kr.NewAccount("test", mnemonic, "", keyring.DefaultBIP39Passphrase, keyring.DefaultHDPath, prefix) if err != nil { return "", nil, err } // 获取装甲格式私钥 privKeyArmor, err := kr.ExportPrivateKeyArmor(info.GetName(), keyring.DefaultBIP39Passphrase) if err != nil { return "", nil, err } // 解析为原始私钥 unarmoredPrivKey, err := types.UnarmorPrivKey(privKeyArmor, keyring.DefaultBIP39Passphrase) if err != nil { return "", nil, err } // 转换为十六进制字符串(与gaiad输出一致) hexPrivKey := fmt.Sprintf("%X", unarmoredPrivKey.Bytes()) return hexPrivKey, unarmoredPrivKey, nil } func main() { mnemonic := "your mnemonic here" prefix := "cosmos" hexKey, _, err := ExportUnarmoredHexPrivateKey(mnemonic, prefix) if err != nil { log.Fatal(err) } fmt.Println("Unarmored Hex Private Key (matches gaiad export):", hexKey) }
二、复刻CosmJS转账逻辑的完整代码
以下代码完全对应CosmJS流程:从助记词生成钱包、连接RPC节点、查询链ID/余额、创建签名客户端、执行转账、查询转账前后余额:
package main import ( "context" "fmt" "log" "time" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/tendermint/tendermint/rpc/client/http" ) func main() { // 配置参数 toAddressStr := "your recipient address here" rpcEndpoint := "your rpc endpoint here" mnemonic := "your mnemonic here" prefix := "cosmos" denom := "denom" transferAmount := "800" feeAmount := "20" gasLimit := uint64(200000) // 1. 从助记词生成钱包 kr, err := keyring.NewInMemory() if err != nil { log.Fatalf("Failed to create in-memory keyring: %v", err) } info, err := kr.NewAccount("sender", mnemonic, "", keyring.DefaultBIP39Passphrase, keyring.DefaultHDPath, prefix) if err != nil { log.Fatalf("Failed to import mnemonic: %v", err) } senderAddr, err := info.GetAddress() if err != nil { log.Fatalf("Failed to get sender address: %v", err) } fmt.Println("Sender Address:", senderAddr.String()) // 2. 连接RPC节点 tmClient, err := http.New(rpcEndpoint, "/websocket") if err != nil { log.Fatalf("Failed to connect to RPC: %v", err) } defer tmClient.Stop() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() // 查询链ID genesis, err := tmClient.Genesis(ctx) if err != nil { log.Fatalf("Failed to get genesis: %v", err) } chainID := genesis.Genesis.ChainID fmt.Println("Chain ID:", chainID) // 3. 创建客户端查询初始余额 clientCtx := client.Context{}.WithClient(tmClient).WithChainID(chainID).WithKeyring(kr) // 查询发送方初始余额 senderBalances, err := banktypes.NewQueryClient(clientCtx).AllBalances(ctx, &banktypes.QueryAllBalancesRequest{Address: senderAddr.String()}) if err != nil { log.Fatalf("Failed to get sender balance: %v", err) } fmt.Println("Sender Balance Before:", senderBalances.Balances) // 查询接收方初始余额 toAddr, err := types.AccAddressFromBech32(toAddressStr) if err != nil { log.Fatalf("Invalid recipient address: %v", err) } recipientBalances, err := banktypes.NewQueryClient(clientCtx).AllBalances(ctx, &banktypes.QueryAllBalancesRequest{Address: toAddr.String()}) if err != nil { log.Fatalf("Failed to get recipient balance: %v", err) } fmt.Println("Recipient Balance Before:", recipientBalances.Balances) // 4. 构建并广播转账交易 amount, ok := types.NewIntFromString(transferAmount) if !ok { log.Fatalf("Invalid transfer amount: %s", transferAmount) } feeAmt, ok := types.NewIntFromString(feeAmount) if !ok { log.Fatalf("Invalid fee amount: %s", feeAmount) } msg := banktypes.NewMsgSend( senderAddr, toAddr, types.Coins{{Denom: denom, Amount: amount}}, ) txFactory := tx.Factory{}. WithChainID(chainID). WithGas(gasLimit). WithFee(types.NewCoins(types.NewCoin(denom, feeAmt))). WithSignMode(types.SignMode_SIGN_MODE_DIRECT) // 签名交易 txBytes, err := tx.BuildAndSign(info.GetName(), txFactory, msg) if err != nil { log.Fatalf("Failed to build and sign transaction: %v", err) } // 广播交易 res, err := tmClient.BroadcastTxSync(ctx, txBytes) if err != nil { log.Fatalf("Failed to broadcast transaction: %v", err) } fmt.Println("Transaction Result:", res) // 5. 查询转账后余额(等待区块确认) time.Sleep(5 * time.Second) senderBalancesAfter, err := banktypes.NewQueryClient(clientCtx).AllBalances(ctx, &banktypes.QueryAllBalancesRequest{Address: senderAddr.String()}) if err != nil { log.Fatalf("Failed to get sender balance after: %v", err) } fmt.Println("Sender Balance After:", senderBalancesAfter.Balances) recipientBalancesAfter, err := banktypes.NewQueryClient(clientCtx).AllBalances(ctx, &banktypes.QueryAllBalancesRequest{Address: toAddr.String()}) if err != nil { log.Fatalf("Failed to get recipient balance after: %v", err) } fmt.Println("Recipient Balance After:", recipientBalancesAfter.Balances) }
三、本地密钥存储高效示例
使用Cosmos SDK官方keyring包实现加密本地存储,与gaiad密钥存储机制一致,安全且兼容:
package main import ( "fmt" "log" "github.com/cosmos/cosmos-sdk/crypto/keyring" ) func main() { // 创建本地文件密钥环(默认存储路径:~/.cosmos/keyring) kr, err := keyring.New( "cosmos", keyring.BackendFile, "~/.cosmos", // 密钥环加密密码,建议从用户输入或环境变量获取 func() (string, error) { return "your-secure-keyring-password", nil }, ) if err != nil { log.Fatalf("Failed to create file keyring: %v", err) } // 导入助记词到本地密钥环 mnemonic := "your mnemonic here" prefix := "cosmos" info, err := kr.NewAccount("my-main-wallet", mnemonic, "", keyring.DefaultBIP39Passphrase, keyring.DefaultHDPath, prefix) if err != nil { log.Fatalf("Failed to import mnemonic: %v", err) } fmt.Println("Wallet imported successfully, address:", info.GetAddress().String()) // 查询本地密钥环中的账户列表 accounts, err := kr.List() if err != nil { log.Fatalf("Failed to list accounts: %v", err) } fmt.Println("Local accounts:") for _, acc := range accounts { fmt.Printf("- Name: %s, Address: %s\n", acc.GetName(), acc.GetAddress().String()) } }
内容的提问来源于stack exchange,提问作者gray
相关产品推荐
相关产品推荐

