You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 12:59:59