dApp Docs/Go_SDK开发指南
Development reference. Not independently verified for production.

MSG Chain Go SDK 开发指南

数据来源:MSG Chain 代码库核实

主网状态: No-Go — 当前 MSGChain 主网裁决为 No-Go,以下内容反映代码实际状态,不代表生产可用。

目录

  1. 概述
  2. 安装与依赖
  3. 核心客户端
  4. 账户与钱包管理
  5. 银行模块
  6. 质押模块
  7. 合约查询
  8. 合约执行
  9. Agent API 客户端
  10. 治理操作
  11. 事件监听
  12. 完整示例

1. 概述

1.1 为什么选择 Go SDK

MSG Chain 的底层核心由 Go 语言构建——Cosmos SDK 生态本身以 Go 为第一公民,节点二进制 (genesis_node_linux, quantum_node_linux) 均通过 Go 编译。对于需要高性能、低延迟、强类型保障的链上交互场景,Go SDK 是比 cosmjs (TypeScript) 更优的选择:

维度 Go SDK cosmjs (TS)
类型安全 编译期强类型 运行时弱类型
性能 原生编译,无 VM 开销 Node.js / 浏览器受限
并行 goroutine 原生并发 单线程 + Worker
依赖体积 单二进制 node_modules 膨胀
量子安全签名 原生 Dilithium-5 绑定 无 WASM 支持
与节点集成 可直接嵌入 RPC / gRPC 仅 HTTP 桥接

1.2 MSG Chain Go 代码库架构

MSG Chain 的 Go 代码库以标准 Cosmos SDK 项目布局组织:

msg-chain/
├── pkg/
│   ├── quantum/              # 量子安全层
│   │   ├── node.go               # 节点启动逻辑
│   │   ├── agent_api.go          # Agent API 路由注册
│   │   ├── tendermint_rpc.go     # Tendermint RPC 层
│   │   └── transaction_executor.go # 交易执行器
│   ├── wasm/                 # CosmWasm 集成
│   │   ├── real_vm.go            # 虚拟机运行时
│   │   └── core_contracts_e2e_test.go # 端到端测试
│   └── indexer/              # 事件索引
│       └── event_listener.go
├── cmd/
│   ├── genesis_node/         # 创世节点入口
│   └── quantum_node/         # 量子节点入口
├── Makefile                  # make deps/lint/test/build-linux/ci-contracts
└── go.mod

构建系统通过 Makefile 驱动:

命令 作用
make deps 拉取 Go module 缓存
make lint gofmt, go vet, golangci-lint
make test Go 包测试
make test-quantum pkg/quantum 后量子密码学测试
make build-linux 编译 bin/genesis_node_linux + bin/quantum_node_linux
make ci-contracts 合约 wasm 编译 + cargo test

1.3 链参数速查

参数 值
Chain ID msg-chain-1
Bech32 前缀 msg
CoinType 118
MSG 小数位 18
Gas 价格 1,000,000,000 attoMSG/gas
出块时间 5s
共识机制 Round-Robin + DAR
签名算法 Dilithium-5 (后量子)
底层存储 BadgerDB

2. 安装与依赖

2.1 初始化项目

mkdir msg-sdk-example && cd msg-sdk-example
go mod init github.com/your-org/msg-sdk-example

2.2 安装 SDK

go get github.com/msg-chain/go-sdk@v0.2.0

SDK 自动拉取以下依赖:github.com/cosmos/cosmos-sdk v0.47.x、github.com/CosmWasm/wasmd v0.45.x、github.com/cometbft/cometbft v0.37.x、github.com/cosmos/ibc-go/v7 v7.0.0。

整理依赖并验证:

go mod tidy
go build ./...

2.3 完整 go.mod 文件

module github.com/your-org/msg-sdk-example

go 1.21

require (
    github.com/msg-chain/go-sdk v0.2.0
    github.com/cosmos/cosmos-sdk v0.47.10
    github.com/CosmWasm/wasmd v0.45.0
    github.com/cometbft/cometbft v0.37.5
    github.com/cosmos/gogoproto v1.4.11
    github.com/cosmos/cosmos-proto v1.0.0-beta.4
    github.com/cosmos/ibc-go/v7 v7.4.0
    github.com/btcsuite/btcd/btcec/v2 v2.3.2
    github.com/cosmos/cosmos-crypto v0.2.0
    github.com/cosmos/go-bip39 v1.0.0
    github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0
    github.com/tyler-smith/go-bip39 v1.1.0
    github.com/gorilla/websocket v1.5.1
    github.com/gorilla/mux v1.8.1
    github.com/pkg/errors v0.9.1
    github.com/stretchr/testify v1.9.0
    golang.org/x/crypto v0.21.0
    golang.org/x/time v0.5.0
    google.golang.org/grpc v1.62.1
    google.golang.org/protobuf v1.33.0
)

require (
    github.com/dgraph-io/badger/v4 v4.2.0 // indirect
    github.com/cloudflare/circl v1.3.8 // indirect, Dilithium support
)

2.4 Makefile 集成

SDK 项目自身使用标准 Makefile 模式:

DEPS := go mod tidy && go mod download
LINT := gofmt -l . && go vet ./... && golangci-lint run
TEST := go test -count=1 -v ./...
BUILD := go build -ldflags="-w -s" -o bin/ ./cmd/...

.PHONY: deps lint test build

deps:
	$(DEPS)

lint:
	$(LINT)

test:
	$(TEST)

test-quantum:
	go test -count=1 -tags=quantum ./pkg/quantum/...

build:
	$(BUILD)

build-linux:
	CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
	    -o bin/genesis_node_linux ./cmd/genesis_node
	CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
	    -o bin/quantum_node_linux ./cmd/quantum_node

ci-contracts:
	cd contracts && cargo build --release --target wasm32-unknown-unknown && cargo test

2.5 go generate 模式

//go:generate go run github.com/msg-chain/go-sdk/cmd/genclient -pkg msgchain -o ./client
//go:generate protoc --go_out=. --go_opt=paths=source_relative \
//    -I proto/ proto/msgchain/*.proto
//go:generate mockgen -source=client/interfaces.go -destination=client/mocks.go -package=client

package msgchain

3. 核心客户端

3.1 MSGChainClient 结构体

MSGChainClient 是 SDK 的顶级入口,封装了与 MSG Chain 节点交互的所有能力。类似 cosmos-sdk 的 client.Context,但更注重易用性。

package msgchain

import (
    "context"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "net/http"
    "sync"
    "time"

    "github.com/cosmos/cosmos-sdk/client"
    "github.com/cosmos/cosmos-sdk/crypto/keyring"
    sdk "github.com/cosmos/cosmos-sdk/types"
    sdktx "github.com/cosmos/cosmos-sdk/types/tx"
    "github.com/cosmos/cosmos-sdk/types/tx/signing"
    "github.com/gorilla/websocket"
    "github.com/pkg/errors"
    "github.com/msg-chain/go-sdk/pkg/quantum"
    "golang.org/x/time/rate"
    "google.golang.org/grpc"
)

// ChainConfig 链配置参数
type ChainConfig struct {
    ChainID       string        // 链ID,默认 "msg-chain-1"
    Bech32Prefix  string        // Bech32 地址前缀,默认 "msg"
    CoinType      uint32        // SLIP-44 CoinType,默认 118
    GasPrices     string        // Gas 价格,默认 "1000000000attoMSG"
    GasAdjustment float64       // Gas 调整系数,默认 1.3
    GasLow        string        // 低价 Gas,默认 "1000000000attoMSG"
    GasAvg        string        // 均价 Gas,默认 "1000000000attoMSG"
    GasHigh       string        // 高价 Gas,默认 "1000000000attoMSG"
    BlockTime     time.Duration // 出块时间,默认 5s
    MsgDecimals   uint8         // MSG 小数位,默认 18
}

// DefaultChainConfig 返回默认链配置
func DefaultChainConfig() ChainConfig {
    return ChainConfig{
        ChainID:       "msg-chain-1",
        Bech32Prefix:  "msg",
        CoinType:      118,
        GasPrices:     "1000000000attoMSG",
        GasAdjustment: 1.3,
    GasLow:  "1000000000attoMSG",
    GasAvg:  "1000000000attoMSG",
    GasHigh: "1000000000attoMSG",
        BlockTime:     5 * time.Second,
        MsgDecimals:   18,
    }
}

// ClientConn 连接配置
type ClientConn struct {
    RPCEndpoint  string // Tendermint RPC 地址
    RESTEndpoint string // REST API 地址
    GRPCEndpoint string // gRPC 地址
    Timeout      time.Duration
}

// MSGChainClient 核心客户端
type MSGChainClient struct {
    config    ChainConfig
    conn      ClientConn
    grpcConn  *grpc.ClientConn
    httpClient *http.Client
    bank      *BankClient
    staking   *StakingClient
    auth      *AuthClient
    wasm      *WasmClient
    gov       *GovernanceClient
    agentAPI  *AgentAPIClient
    event     *EventClient
    ctx       context.Context
    cancel    context.CancelFunc
    mu        sync.RWMutex
    chainID   string
    limiter   *rate.Limiter
    keybase   keyring.Keyring
}

// NewMSGChainClient 创建客户端实例
func NewMSGChainClient(config ChainConfig, conn ClientConn) (*MSGChainClient, error) {
    ctx, cancel := context.WithTimeout(context.Background(), conn.Timeout)
    if config == (ChainConfig{}) {
        config = DefaultChainConfig()
    }
    sdkConfig := sdk.GetConfig()
    sdkConfig.SetBech32PrefixForAccount(
        config.Bech32Prefix,
        fmt.Sprintf("%spub", config.Bech32Prefix),
    )
    sdkConfig.SetCoinType(config.CoinType)

    gRPCConn, err := grpc.DialContext(ctx, conn.GRPCEndpoint,
        grpc.WithInsecure(), grpc.WithBlock(),
    )
    if err != nil {
        cancel()
        return nil, fmt.Errorf("gRPC 连接失败: %w", err)
    }

    c := &MSGChainClient{
        config:  config,
        conn:    conn,
        grpcConn: gRPCConn,
        httpClient: &http.Client{Timeout: conn.Timeout},
        ctx:     ctx,
        cancel:  cancel,
        chainID: config.ChainID,
        limiter: rate.NewLimiter(rate.Limit(100), 200),
    }

    c.bank = NewBankClient(c)
    c.staking = NewStakingClient(c)
    c.auth = NewAuthClient(c)
    c.wasm = NewWasmClient(c)
    c.gov = NewGovernanceClient(c)
    c.agentAPI = NewAgentAPIClient(c)
    c.event = NewEventClient(c)

    return c, nil
}

// Close 关闭客户端
func (c *MSGChainClient) Close() error {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.cancel()
    if c.grpcConn != nil {
        return c.grpcConn.Close()
    }
    return nil
}

// Bank 返回银行模块客户端
func (c *MSGChainClient) Bank() *BankClient { return c.bank }

// Staking 返回质押模块客户端
func (c *MSGChainClient) Staking() *StakingClient { return c.staking }

// Wasm 返回 CosmWasm 客户端
func (c *MSGChainClient) Wasm() *WasmClient { return c.wasm }

// Governance 返回治理模块客户端
func (c *MSGChainClient) Governance() *GovernanceClient { return c.gov }

// AgentAPI 返回 Agent API 客户端
func (c *MSGChainClient) AgentAPI() *AgentAPIClient { return c.agentAPI }

// Event 返回事件监听客户端
func (c *MSGChainClient) Event() *EventClient { return c.event }

3.2 基础查询方法

// GetNodeInfo 获取节点信息
func (c *MSGChainClient) GetNodeInfo(ctx context.Context) (*NodeInfo, error) {
    path := fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/node_info", c.conn.RESTEndpoint)
    req, _ := http.NewRequestWithContext(ctx, "GET", path, nil)
    res, err := c.doRequest(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    var info NodeInfo
    if err := json.NewDecoder(res.Body).Decode(&info); err != nil {
        return nil, NewSDKError(ErrTxDecode, "解析节点信息失败", err)
    }
    return &info, nil
}

// GetLatestBlock 获取最新区块
func (c *MSGChainClient) GetLatestBlock(ctx context.Context) (*Block, error) {
    path := fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/blocks/latest",
        c.conn.RESTEndpoint)
    req, _ := http.NewRequestWithContext(ctx, "GET", path, nil)
    res, err := c.doRequest(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    var block Block
    if err := json.NewDecoder(res.Body).Decode(&block); err != nil {
        return nil, err
    }
    return &block, nil
}

// GetBlockByHeight 根据高度查询区块
func (c *MSGChainClient) GetBlockByHeight(ctx context.Context, height int64) (*Block, error) {
    path := fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/blocks/%d",
        c.conn.RESTEndpoint, height)
    req, _ := http.NewRequestWithContext(ctx, "GET", path, nil)
    res, err := c.doRequest(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    var block Block
    if err := json.NewDecoder(res.Body).Decode(&block); err != nil {
        return nil, err
    }
    return &block, nil
}

// GetTxByHash 根据交易哈希查询交易
func (c *MSGChainClient) GetTxByHash(ctx context.Context, hash string) (*Transaction, error) {
    path := fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/%s", c.conn.RESTEndpoint, hash)
    req, _ := http.NewRequestWithContext(ctx, "GET", path, nil)
    res, err := c.doRequest(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    var tx Transaction
    if err := json.NewDecoder(res.Body).Decode(&tx); err != nil {
        return nil, err
    }
    return &tx, nil
}

3.3 错误码与限流

// ErrorCode MSG Chain 错误码
type ErrorCode uint32

const (
    ErrOK                 ErrorCode = 0
    ErrTxDecode           ErrorCode = 1
    ErrInvalidSequence    ErrorCode = 2
    ErrUnauthorized       ErrorCode = 3
    ErrInsufficientFunds  ErrorCode = 4
    ErrUnknownRequest     ErrorCode = 5
    ErrInvalidAddress     ErrorCode = 6
    ErrInvalidCoins       ErrorCode = 7
    ErrOutOfGas           ErrorCode = 8
    ErrMemoTooLarge       ErrorCode = 9
    ErrInsufficientFee    ErrorCode = 10
    ErrTooManySignatures  ErrorCode = 11
    ErrGasOverflow        ErrorCode = 12
    ErrNoSignatures       ErrorCode = 13
    ErrQuantumSignature   ErrorCode = 1001 // Dilithium 签名失败
    ErrAgentAPIRateLimit  ErrorCode = 1002 // Agent API 限流
    ErrMPCFailed          ErrorCode = 1003 // MPC 签名失败
    ErrStubNotImplemented ErrorCode = 1004 // Stub 端点未实现
    ErrDARConsensus       ErrorCode = 1005 // DAR 共识错误
    ErrBadgerDB           ErrorCode = 1006 // BadgerDB 存储错误
)

// SDKError SDK 错误封装
type SDKError struct {
    Code    ErrorCode
    Message string
    Cause   error
}

func (e *SDKError) Error() string {
    if e.Cause != nil {
        return fmt.Sprintf("[MSG:%d] %s: %v", e.Code, e.Message, e.Cause)
    }
    return fmt.Sprintf("[MSG:%d] %s", e.Code, e.Message)
}

func (e *SDKError) Unwrap() error { return e.Cause }

// NewSDKError 创建 SDK 错误
func NewSDKError(code ErrorCode, msg string, cause error) *SDKError {
    return &SDKError{Code: code, Message: msg, Cause: cause}
}

// IsErr 判断错误码
func IsErr(err error, code ErrorCode) bool {
    var sdkErr *SDKError
    if errors.As(err, &sdkErr) {
        return sdkErr.Code == code
    }
    return false
}

// IsInsufficientFee 检查 Gas 费用不足
func IsInsufficientFee(err error) bool { return IsErr(err, ErrInsufficientFee) }

// IsOutOfGas 检查 Gas 耗尽
func IsOutOfGas(err error) bool { return IsErr(err, ErrOutOfGas) }

// WithRateLimit 设置自定义限流(默认 100 req/s,burst 200)
func (c *MSGChainClient) WithRateLimit(rps, burst int) *MSGChainClient {
    c.limiter = rate.NewLimiter(rate.Limit(rps), burst)
    return c
}

// doRequest 带限流的 HTTP 请求
func (c *MSGChainClient) doRequest(req *http.Request) (*http.Response, error) {
    if err := c.limiter.Wait(c.ctx); err != nil {
        return nil, NewSDKError(ErrAgentAPIRateLimit, "请求限流", err)
    }
    res, err := c.httpClient.Do(req)
    if err != nil {
        return nil, NewSDKError(ErrUnknownRequest, "HTTP 请求失败", err)
    }
    return res, nil
}

3.4 数据类型

// NodeInfo 节点信息
type NodeInfo struct {
    ProtocolVersion struct {
        P2P   uint64 `json:"p2p"`
        Block uint64 `json:"block"`
        App   uint64 `json:"app"`
    } `json:"protocol_version"`
    NodeID     string `json:"node_id"`
    ListenAddr string `json:"listen_addr"`
    Network    string `json:"network"`
    Version    string `json:"version"`
    Moniker    string `json:"moniker"`
}

// Block 区块数据
type Block struct {
    BlockID struct {
        Hash          string `json:"hash"`
        PartSetHeader struct {
            Total int    `json:"total"`
            Hash  string `json:"hash"`
        } `json:"part_set_header"`
    } `json:"block_id"`
    Block struct {
        Header struct {
            Version struct {
                Block uint64 `json:"block"`
            } `json:"version"`
            ChainID         string    `json:"chain_id"`
            Height          string    `json:"height"`
            Time            time.Time `json:"time"`
            LastBlockID     struct {
                Hash          string `json:"hash"`
                PartSetHeader struct {
                    Total int    `json:"total"`
                    Hash  string `json:"hash"`
                } `json:"part_set_header"`
            } `json:"last_block_id"`
            LastCommitHash  string `json:"last_commit_hash"`
            DataHash        string `json:"data_hash"`
            ValidatorsHash  string `json:"validators_hash"`
            ProposerAddress string `json:"proposer_address"`
        } `json:"header"`
        Data struct {
            Txs []string `json:"txs"`
        } `json:"data"`
    } `json:"block"`
}

// Transaction 交易数据
type Transaction struct {
    TxResponse struct {
        Height    string `json:"height"`
        TxHash    string `json:"txhash"`
        Code      uint32 `json:"code"`
        RawLog    string `json:"raw_log"`
        GasWanted string `json:"gas_wanted"`
        GasUsed   string `json:"gas_used"`
        Logs      []struct {
            Events []struct {
                Type       string `json:"type"`
                Attributes []struct {
                    Key   string `json:"key"`
                    Value string `json:"value"`
                } `json:"attributes"`
            } `json:"events"`
        } `json:"logs"`
        Tx struct {
            Type       string    `json:"@type"`
            Msgs       []sdk.Msg `json:"messages"`
            Fee        sdktx.Fee `json:"fee"`
            Signatures []signing.SignatureV2 `json:"signatures"`
            Memo       string    `json:"memo"`
        } `json:"tx"`
    } `json:"tx_response"`
}

// Coin 代币类型
type Coin struct {
    Denom  string `json:"denom"`
    Amount string `json:"amount"`
}

func (c Coin) String() string {
    return fmt.Sprintf("%s%s", c.Amount, c.Denom)
}

// Coins 代币集合
type Coins []Coin

// Account 账户信息
type Account struct {
    Address       string `json:"address"`
    AccountNumber uint64 `json:"account_number"`
    Sequence      uint64 `json:"sequence"`
}

4. 账户与钱包管理

4.1 密钥生成(BIP39 + Dilithium-5)

package msgchain

import (
    "crypto/rand"
    "encoding/hex"
    "fmt"
    "path/filepath"
    "os"

    "github.com/cosmos/go-bip39"
    "github.com/cosmos/cosmos-sdk/crypto/hd"
    "github.com/cosmos/cosmos-sdk/crypto/keyring"
    cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
    sdk "github.com/cosmos/cosmos-sdk/types"
    "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
    "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
)

// KeyType 密钥类型
type KeyType int

const (
    KeyTypeSecp256k1  KeyType = iota // 标准 ECDSA
    KeyTypeDilithium5                 // 后量子 Dilithium-5
    KeyTypeED25519                   // Ed25519
)

// GenerateMnemonic 生成 BIP39 助记词
func GenerateMnemonic(bitSize int) (string, error) {
    if bitSize != 128 && bitSize != 192 && bitSize != 256 {
        return "", fmt.Errorf("熵长度必须为 128/192/256,当前: %d", bitSize)
    }
    entropy, err := bip39.NewEntropy(bitSize)
    if err != nil {
        return "", fmt.Errorf("生成熵失败: %w", err)
    }
    mnemonic, err := bip39.NewMnemonic(entropy)
    if err != nil {
        return "", fmt.Errorf("生成助记词失败: %w", err)
    }
    return mnemonic, nil
}

// GenerateDefaultMnemonic 生成 256 bit 助记词
func GenerateDefaultMnemonic() (string, error) {
    return GenerateMnemonic(256)
}

// KeyConfig 密钥配置
type KeyConfig struct {
    Name     string  // 密钥名称
    Password string  // 密码
    Mnemonic string  // 助记词(为空则生成)
    KeyType  KeyType // 密钥类型
    CoinType uint32  // SLIP-44 类型
    Account  uint32  // BIP44 账户索引
    Index    uint32  // BIP44 地址索引
}

// CreateKey 创建密钥
func CreateKey(cfg KeyConfig) (cryptotypes.PrivKey, sdk.AccAddress, error) {
    var privKey cryptotypes.PrivKey
    var err error

    if cfg.Mnemonic == "" {
        cfg.Mnemonic, err = GenerateDefaultMnemonic()
        if err != nil {
            return nil, nil, err
        }
    }

    switch cfg.KeyType {
    case KeyTypeDilithium5:
        privKey, err = deriveDilithiumKey(cfg.Mnemonic, cfg.CoinType, cfg.Account, cfg.Index)
    case KeyTypeSecp256k1:
        privKey, err = deriveSecp256k1Key(cfg.Mnemonic, cfg.CoinType, cfg.Account, cfg.Index)
    case KeyTypeED25519:
        privKey, err = deriveEd25519Key(cfg.Mnemonic, cfg.CoinType, cfg.Account, cfg.Index)
    default:
        return nil, nil, fmt.Errorf("不支持的密钥类型: %v", cfg.KeyType)
    }
    if err != nil {
        return nil, nil, err
    }

    addr := sdk.AccAddress(privKey.PubKey().Address())
    return privKey, addr, nil
}

// deriveDilithiumKey 通过 BIP39 种子派生 Dilithium-5 密钥
func deriveDilithiumKey(mnemonic string, coinType, account, index uint32) (cryptotypes.PrivKey, error) {
    seed := bip39.NewSeed(mnemonic, "")
    path := hd.NewFundraiserParams(account, coinType, index)
    privKey, err := quantum.DeriveKey(seed, path.String())
    if err != nil {
        return nil, fmt.Errorf("Dilithium-5 密钥派生失败: %w", err)
    }
    return privKey, nil
}

// deriveSecp256k1Key 标准 secp256k1 密钥派生
func deriveSecp256k1Key(mnemonic string, coinType, account, index uint32) (cryptotypes.PrivKey, error) {
    seed := bip39.NewSeed(mnemonic, "")
    master, ch := hd.ComputeMastersFromSeed(seed)
    privKeyBytes, err := hd.DerivePrivateKeyForPath(master, ch,
        fmt.Sprintf("m/44'/%d'/%d'/0/%d", coinType, account, index))
    if err != nil {
        return nil, fmt.Errorf("secp256k1 密钥派生失败: %w", err)
    }
    return &secp256k1.PrivKey{Key: privKeyBytes}, nil
}

// deriveEd25519Key Ed25519 密钥派生
func deriveEd25519Key(mnemonic string, coinType, account, index uint32) (cryptotypes.PrivKey, error) {
    seed := bip39.NewSeed(mnemonic, "")
    master, ch := hd.ComputeMastersFromSeed(seed)
    privKeyBytes, err := hd.DerivePrivateKeyForPath(master, ch,
        fmt.Sprintf("m/44'/%d'/%d'/0/%d", coinType, account, index))
    if err != nil {
        return nil, fmt.Errorf("Ed25519 密钥派生失败: %w", err)
    }
    return &ed25519.PrivKey{Key: privKeyBytes}, nil
}

// Bech32Address 从公钥生成 Bech32 地址
func Bech32Address(pubKey cryptotypes.PubKey, prefix string) (string, error) {
    addr := sdk.AccAddress(pubKey.Address())
    return sdk.Bech32ifyAddressBytes(prefix, addr)
}

// ParseAddress 解析 Bech32 地址
func ParseAddress(addrStr string) (sdk.AccAddress, error) {
    addr, err := sdk.AccAddressFromBech32(addrStr)
    if err != nil {
        return nil, NewSDKError(ErrInvalidAddress, fmt.Sprintf("无效地址: %s", addrStr), err)
    }
    return addr, nil
}

// SignWithDilithium 使用 Dilithium-5 签名
func SignWithDilithium(privKey cryptotypes.PrivKey, msg []byte) ([]byte, error) {
    sig, err := privKey.Sign(msg)
    if err != nil {
        return nil, NewSDKError(ErrQuantumSignature, "Dilithium-5 签名失败", err)
    }
    return sig, nil
}

4.2 钱包管理器

// WalletManager 钱包管理器——类似 cosmos-sdk 的 keyring
type WalletManager struct {
    keybase keyring.Keyring
    config  ChainConfig
}

// NewWalletManager 创建钱包管理器
func NewWalletManager(config ChainConfig) (*WalletManager, error) {
    k, err := keyring.New(
        "msg-chain",
        keyring.BackendFile,
        filepath.Join(os.Getenv("HOME"), ".msg-chain", "keyring"),
        os.Stdin,
        hd.CreateHDPath(config.CoinType, 0, 0).String(),
    )
    if err != nil {
        return nil, fmt.Errorf("创建 keyring 失败: %w", err)
    }
    return &WalletManager{keybase: k, config: config}, nil
}

// NewKey 创建新密钥
func (wm *WalletManager) NewKey(name, password string) (string, error) {
    mnemonic, err := GenerateDefaultMnemonic()
    if err != nil {
        return "", err
    }
    _, addr, err := CreateKey(KeyConfig{
        Name:     name,
        Password: password,
        Mnemonic: mnemonic,
        KeyType:  KeyTypeDilithium5,
        CoinType: wm.config.CoinType,
    })
    if err != nil {
        return "", err
    }
    return Bech32Address(addr, wm.config.Bech32Prefix)
}

// RecoverKey 通过助记词恢复密钥
func (wm *WalletManager) RecoverKey(name, password, mnemonic string) (string, error) {
    _, addr, err := CreateKey(KeyConfig{
        Name:     name,
        Password: password,
        Mnemonic: mnemonic,
        KeyType:  KeyTypeDilithium5,
        CoinType: wm.config.CoinType,
    })
    if err != nil {
        return "", err
    }
    return Bech32Address(addr, wm.config.Bech32Prefix)
}

// ListKeys 列出所有密钥
func (wm *WalletManager) ListKeys() ([]WalletKey, error) {
    keys, err := wm.keybase.List()
    if err != nil {
        return nil, err
    }
    result := make([]WalletKey, 0, len(keys))
    for _, k := range keys {
        addr, _ := k.GetAddress()
        result = append(result, WalletKey{
            Name:    k.Name,
            Address: addr.String(),
        })
    }
    return result, nil
}

// WalletKey 钱包密钥摘要
type WalletKey struct {
    Name    string `json:"name"`
    Address string `json:"address"`
}

5. 银行模块

5.1 BankClient 结构体

// BankClient 银行模块客户端——类似 gaia CLI 的 bank 查询
type BankClient struct {
    client *MSGChainClient
}

// NewBankClient 创建银行客户端
func NewBankClient(c *MSGChainClient) *BankClient {
    return &BankClient{client: c}
}

// BalanceResponse 余额查询响应
type BalanceResponse struct {
    Balance Coin `json:"balance"`
}

// AllBalancesResponse 所有余额查询响应
type AllBalancesResponse struct {
    Balances   Coins              `json:"balances"`
    Pagination *PaginationResponse `json:"pagination,omitempty"`
}

// SupplyResponse 供应量查询响应
type SupplyResponse struct {
    Supply     Coins              `json:"supply"`
    Pagination *PaginationResponse `json:"pagination,omitempty"`
}

// PaginationResponse 分页响应
type PaginationResponse struct {
    NextKey string `json:"next_key"`
    Total   string `json:"total"`
}

5.2 余额查询与转账

// GetBalance 查询单个代币余额
func (bc *BankClient) GetBalance(ctx context.Context, address, denom string) (*Coin, error) {
    path := fmt.Sprintf("%s/cosmos/bank/v1beta1/balances/%s/by_denom?denom=%s",
        bc.client.conn.RESTEndpoint, address, denom)
    req, _ := http.NewRequestWithContext(ctx, "GET", path, nil)
    res, err := bc.client.doRequest(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    var resp BalanceResponse
    if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
        return nil, err
    }
    return &resp.Balance, nil
}

// GetAllBalances 查询所有余额
func (bc *BankClient) GetAllBalances(ctx context.Context, address string) (Coins, error) {
    path := fmt.Sprintf("%s/cosmos/bank/v1beta1/balances/%s",
        bc.client.conn.RESTEndpoint, address)
    req, _ := http.NewRequestWithContext(ctx, "GET", path, nil)
    res, err := bc.client.doRequest(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    var resp AllBalancesResponse
    if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
        return nil, err
    }
    return resp.Balances, nil
}

// GetMSG 快捷查询 MSG 余额
func (bc *BankClient) GetMSG(ctx context.Context, address string) (*Coin, error) {
    return bc.GetBalance(ctx, address, "umsg")
}

// GetTotalSupply 查询总供应量
func (bc *BankClient) GetTotalSupply(ctx context.Context) (Coins, error) {
    path := fmt.Sprintf("%s/cosmos/bank/v1beta1/supply",
        bc.client.conn.RESTEndpoint)
    req, _ := http.NewRequestWithContext(ctx, "GET", path, nil)
    res, err := bc.client.doRequest(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    var resp SupplyResponse
    if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
        return nil, err
    }
    return resp.Supply, nil
}

// SendMsg 转账消息
type SendMsg struct {
    FromAddress string `json:"from_address"`
    ToAddress   string `json:"to_address"`
    Amount      Coins  `json:"amount"`
}

// Send 发送转账交易
func (bc *BankClient) Send(ctx context.Context, fromAddr, toAddr string,
    amount Coins, privKey cryptotypes.PrivKey) (*Transaction, error) {
    msg := &banktypes.MsgSend{
        FromAddress: fromAddr,
        ToAddress:   toAddr,
        Amount:      amount,
    }
    return bc.client.BuildAndBroadcastTx(ctx, []sdk.Msg{msg}, privKey)
}

// BatchSend 批量转账
func (bc *BankClient) BatchSend(ctx context.Context, fromAddr string,
    transfers []Transfer, privKey cryptotypes.PrivKey) (*Transaction, error) {
    msgs := make([]sdk.Msg, len(transfers))
    for i, t := range transfers {
        msgs[i] = &banktypes.MsgSend{
            FromAddress: fromAddr,
            ToAddress:   t.ToAddress,
            Amount:      t.Amount,
        }
    }
    return bc.client.BuildAndBroadcastTx(ctx, msgs, privKey)
}

// Transfer 转账结构
type Transfer struct {
    ToAddress string
    Amount    Coins
    Memo      string
}

5.3 BankClient 接口

type BankClientInterface interface {
    GetBalance(ctx context.Context, address, denom string) (*Coin, error)
    GetAllBalances(ctx context.Context, address string) (Coins, error)
    GetMSG(ctx context.Context, address string) (*Coin, error)
    GetTotalSupply(ctx context.Context) (Coins, error)
    Send(ctx context.Context, fromAddr, toAddr string, amount Coins, privKey cryptotypes.PrivKey) (*Transaction, error)
    BatchSend(ctx context.Context, fromAddr string, transfers []Transfer, privKey cryptotypes.PrivKey) (*Transaction, error)
}

6. 质押模块

6.1 StakingClient 与验证者查询

// StakingClient 质押模块客户端
type StakingClient struct {
    client *MSGChainClient
}

func NewStakingClient(c *MSGChainClient) *StakingClient {
    return &StakingClient{client: c}
}

// Validator 验证者
type Validator struct {
    OperatorAddress string `json:"operator_address"`
    Jailed          bool   `json:"jailed"`
    Status          string `json:"status"`
    Tokens          string `json:"tokens"`
    DelegatorShares string `json:"delegator_shares"`
    Description     struct {
        Moniker string `json:"moniker"`
        Website string `json:"website"`
        Details string `json:"details"`
    } `json:"description"`
    Commission struct {
        CommissionRates struct {
            Rate          string `json:"rate"`
            MaxRate       string `json:"max_rate"`
            MaxChangeRate string `json:"max_change_rate"`
        } `json:"commission_rates"`
    } `json:"commission"`
    MinSelfDelegation string `json:"min_self_delegation"`
}

// GetValidators 获取验证者列表
func (sc *StakingClient) GetValidators(ctx context.Context, status string) ([]Validator, error) {
    path := fmt.Sprintf("%s/cosmos/staking/v1beta1/validators?status=%s",
        sc.client.conn.RESTEndpoint, status)
    req, _ := http.NewRequestWithContext(ctx, "GET", path, nil)
    res, err := sc.client.doRequest(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    var resp struct {
        Validators []Validator `json:"validators"`
    }
    if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
        return nil, err
    }
    return resp.Validators, nil
}

// GetActiveValidators 获取活跃验证者
func (sc *StakingClient) GetActiveValidators(ctx context.Context) ([]Validator, error) {
    return sc.GetValidators(ctx, "BOND_STATUS_BONDED")
}

6.2 委托与收益

// Delegation 委托信息
type Delegation struct {
    DelegatorAddress string `json:"delegator_address"`
    ValidatorAddress string `json:"validator_address"`
    Shares           string `json:"shares"`
    Balance          Coin   `json:"balance"`
}

// GetDelegations 查询委托列表
func (sc *StakingClient) GetDelegations(ctx context.Context, delegatorAddr string) ([]Delegation, error) {
    path := fmt.Sprintf("%s/cosmos/staking/v1beta1/delegations/%s",
        sc.client.conn.RESTEndpoint, delegatorAddr)
    req, _ := http.NewRequestWithContext(ctx, "GET", path, nil)
    res, err := sc.client.doRequest(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    var resp struct {
        DelegationResponses []Delegation `json:"delegation_responses"`
    }
    if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
        return nil, err
    }
    return resp.DelegationResponses, nil
}

// RewardsResponse 收益查询响应
type RewardsResponse struct {
    Rewards []struct {
        ValidatorAddress string `json:"validator_address"`
        Reward           Coins  `json:"reward"`
    } `json:"rewards"`
    Total Coins `json:"total"`
}

// GetRewards 查询委托收益
func (sc *StakingClient) GetRewards(ctx context.Context, delegatorAddr string) (*RewardsResponse, error) {
    path := fmt.Sprintf("%s/cosmos/distribution/v1beta1/delegators/%s/rewards",
        sc.client.conn.RESTEndpoint, delegatorAddr)
    req, _ := http.NewRequestWithContext(ctx, "GET", path, nil)
    res, err := sc.client.doRequest(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    var resp RewardsResponse
    if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
        return nil, err
    }
    return &resp, nil
}

// Delegate 委托代币
func (sc *StakingClient) Delegate(ctx context.Context, delegatorAddr, validatorAddr string,
    amount Coin, privKey cryptotypes.PrivKey) (*Transaction, error) {
    msg := &staketypes.MsgDelegate{
        DelegatorAddress: delegatorAddr,
        ValidatorAddress: validatorAddr,
        Amount:           amount,
    }
    return sc.client.BuildAndBroadcastTx(ctx, []sdk.Msg{msg}, privKey)
}

// Unbond 解委托
func (sc *StakingClient) Unbond(ctx context.Context, delegatorAddr, validatorAddr string,
    amount Coin, privKey cryptotypes.PrivKey) (*Transaction, error) {
    msg := &staketypes.MsgUndelegate{
        DelegatorAddress: delegatorAddr,
        ValidatorAddress: validatorAddr,
        Amount:           amount,
    }
    return sc.client.BuildAndBroadcastTx(ctx, []sdk.Msg{msg}, privKey)
}

// Redelegate 转委托
func (sc *StakingClient) Redelegate(ctx context.Context, delegatorAddr, srcValidator, dstValidator string,
    amount Coin, privKey cryptotypes.PrivKey) (*Transaction, error) {
    msg := &staketypes.MsgBeginRedelegate{
        DelegatorAddress:    delegatorAddr,
        ValidatorSrcAddress: srcValidator,
        ValidatorDstAddress: dstValidator,
        Amount:              amount,
    }
    return sc.client.BuildAndBroadcastTx(ctx, []sdk.Msg{msg}, privKey)
}

7. 合约查询

7.1 WasmClient 结构体

CosmWasm 合约查询通过 REST 或 gRPC 进行。参考 pkg/wasm/real_vm.go 和 pkg/wasm/core_contracts_e2e_test.go 的实现模式。

// WasmClient CosmWasm 合约查询客户端
type WasmClient struct {
    client *MSGChainClient
}

func NewWasmClient(c *MSGChainClient) *WasmClient {
    return &WasmClient{client: c}
}

// CodeInfo 代码信息
type CodeInfo struct {
    CodeID    string `json:"code_id"`
    Creator   string `json:"creator"`
    DataHash  string `json:"data_hash"`
    InstantiatePermission struct {
        Permission string   `json:"permission"`
        Addresses  []string `json:"addresses"`
    } `json:"instantiate_permission"`
}

// ContractInfo 合约信息
type ContractInfo struct {
    Address string `json:"address"`
    Creator string `json:"creator"`
    Admin   string `json:"admin"`
    CodeID  string `json:"code_id"`
    Label   string `json:"label"`
}

7.2 智能合约状态查询

// SmartContractState 查询合约智能状态
func (wc *WasmClient) SmartContractState(ctx context.Context,
    contractAddr string, queryMsg interface{}) ([]byte, error) {
    queryJSON, err := json.Marshal(queryMsg)
    if err != nil {
        return nil, fmt.Errorf("序列化查询消息失败: %w", err)
    }
    encodedQuery := base64.StdEncoding.EncodeToString(queryJSON)
    path := fmt.Sprintf("%s/cosmwasm/wasm/v1/contract/%s/smart?query=%s",
        wc.client.conn.RESTEndpoint, contractAddr,
        url.QueryEscape(encodedQuery))
    req, _ := http.NewRequestWithContext(ctx, "GET", path, nil)
    res, err := wc.client.doRequest(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    var resp struct {
        Data struct {
            Raw string `json:"raw"`
        } `json:"data"`
    }
    if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
        return nil, err
    }
    return base64.StdEncoding.DecodeString(resp.Data.Raw)
}

// SmartQuery 带类型反序列化的智能查询
func (wc *WasmClient) SmartQuery(ctx context.Context, contractAddr string,
    queryMsg interface{}, result interface{}) error {
    data, err := wc.SmartContractState(ctx, contractAddr, queryMsg)
    if err != nil {
        return err
    }
    return json.Unmarshal(data, result)
}

// RawContractState 查询合约原始状态
func (wc *WasmClient) RawContractState(ctx context.Context,
    contractAddr string, key []byte) ([]byte, error) {
    encodedKey := hex.EncodeToString(key)
    path := fmt.Sprintf("%s/cosmwasm/wasm/v1/contract/%s/raw?key=%s",
        wc.client.conn.RESTEndpoint, contractAddr, encodedKey)
    req, _ := http.NewRequestWithContext(ctx, "GET", path, nil)
    res, err := wc.client.doRequest(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    var resp struct {
        Data struct {
            Raw string `json:"raw"`
        } `json:"data"`
    }
    if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
        return nil, err
    }
    return base64.StdEncoding.DecodeString(resp.Data.Raw)
}

7.3 代码与合约信息查询

// GetCodeInfo 获取代码信息
func (wc *WasmClient) GetCodeInfo(ctx context.Context, codeID uint64) (*CodeInfo, error) {
    path := fmt.Sprintf("%s/cosmwasm/wasm/v1/code/%d",
        wc.client.conn.RESTEndpoint, codeID)
    req, _ := http.NewRequestWithContext(ctx, "GET", path, nil)
    res, err := wc.client.doRequest(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    var resp struct {
        CodeInfo CodeInfo `json:"code_info"`
    }
    if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
        return nil, err
    }
    return &resp.CodeInfo, nil
}

// GetContractInfo 获取合约信息
func (wc *WasmClient) GetContractInfo(ctx context.Context, contractAddr string) (*ContractInfo, error) {
    path := fmt.Sprintf("%s/cosmwasm/wasm/v1/contract/%s",
        wc.client.conn.RESTEndpoint, contractAddr)
    req, _ := http.NewRequestWithContext(ctx, "GET", path, nil)
    res, err := wc.client.doRequest(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    var resp struct {
        ContractInfo ContractInfo `json:"contract_info"`
    }
    if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
        return nil, err
    }
    return &resp.ContractInfo, nil
}

8. 合约执行

8.1 交易构建与签名

参考 pkg/quantum/transaction_executor.go 中的实现模式。

// BuildAndBroadcastTx 构建、签名并广播交易
func (c *MSGChainClient) BuildAndBroadcastTx(ctx context.Context,
    msgs []sdk.Msg, privKey cryptotypes.PrivKey) (*Transaction, error) {
    // 构建交易
    txBuilder := c.createTxBuilder()
    if err := txBuilder.SetMsgs(msgs...); err != nil {
        return nil, NewSDKError(ErrTxDecode, "设置消息失败", err)
    }
    txBuilder.SetMemo("")
    txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("umsg", sdk.NewInt(5000))))
    txBuilder.SetGasLimit(200000)

    // 签名
    signMode := signing.SignMode_SIGN_MODE_DIRECT
    signBytes := authtx.StdSignBytes(c.chainID, 0, 0,
        txBuilder.GetTx().GetTimeoutHeight(), txBuilder.GetTx().GetFee(),
        txBuilder.GetTx().GetMsgs(), txBuilder.GetTx().GetMemo())
    sig, err := privKey.Sign(signBytes)
    if err != nil {
        return nil, NewSDKError(ErrQuantumSignature, "签名失败", err)
    }

    txBuilder.SetSignatures(signing.SignatureV2{
        PubKey: privKey.PubKey(),
        Data: &signing.SingleSignatureData{
            SignMode:  signMode,
            Signature: sig,
        },
        Sequence: 0,
    })

    txBytes, err := c.encodingConfig.TxConfig.TxEncoder()(txBuilder.GetTx())
    if err != nil {
        return nil, err
    }

    return c.BroadcastTx(ctx, txBytes)
}

func (c *MSGChainClient) createTxBuilder() client.TxBuilder {
    return c.encodingConfig.TxConfig.NewTxBuilder()
}

8.2 广播交易

// BroadcastMode 广播模式
type BroadcastMode string

const (
    BroadcastAsync  BroadcastMode = "BROADCAST_MODE_ASYNC" // 异步
    BroadcastSync   BroadcastMode = "BROADCAST_MODE_SYNC"  // 同步,等待 CheckTx
    BroadcastCommit BroadcastMode = "BROADCAST_MODE_BLOCK" // 阻塞直到上链
)

// BroadcastTx 广播已签名交易
func (c *MSGChainClient) BroadcastTx(ctx context.Context,
    txBytes []byte) (*Transaction, error) {
    return c.BroadcastTxWithMode(ctx, txBytes, BroadcastSync)
}

// BroadcastTxWithMode 以指定模式广播
func (c *MSGChainClient) BroadcastTxWithMode(ctx context.Context,
    txBytes []byte, mode BroadcastMode) (*Transaction, error) {
    reqBody := struct {
        TxBytes string        `json:"tx_bytes"`
        Mode    BroadcastMode `json:"mode"`
    }{
        TxBytes: base64.StdEncoding.EncodeToString(txBytes),
        Mode:    mode,
    }
    body, _ := json.Marshal(reqBody)
    path := fmt.Sprintf("%s/cosmos/tx/v1beta1/txs", c.conn.RESTEndpoint)
    req, _ := http.NewRequestWithContext(ctx, "POST", path, bytes.NewReader(body))
    req.Header.Set("Content-Type", "application/json")
    res, err := c.doRequest(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    var tx Transaction
    if err := json.NewDecoder(res.Body).Decode(&tx); err != nil {
        return nil, err
    }
    if tx.TxResponse.Code != 0 {
        return &tx, NewSDKError(ErrorCode(tx.TxResponse.Code),
            tx.TxResponse.RawLog, nil)
    }
    return &tx, nil
}

8.3 Gas 估算

// SimulateTx 模拟交易(估算 Gas)
func (c *MSGChainClient) SimulateTx(ctx context.Context, msgs []sdk.Msg,
    privKey cryptotypes.PrivKey) (uint64, error) {
    txBuilder := c.createTxBuilder()
    if err := txBuilder.SetMsgs(msgs...); err != nil {
        return 0, err
    }
    txBytes, _ := c.encodingConfig.TxConfig.TxEncoder()(txBuilder.GetTx())

    reqBody := struct {
        TxBytes string `json:"tx_bytes"`
    }{
        TxBytes: base64.StdEncoding.EncodeToString(txBytes),
    }
    body, _ := json.Marshal(reqBody)
    path := fmt.Sprintf("%s/cosmos/tx/v1beta1/simulate", c.conn.RESTEndpoint)
    req, _ := http.NewRequestWithContext(ctx, "POST", path, bytes.NewReader(body))
    req.Header.Set("Content-Type", "application/json")
    res, err := c.doRequest(req)
    if err != nil {
        return 0, err
    }
    defer res.Body.Close()

    var simResp struct {
        GasInfo struct {
            GasWanted uint64 `json:"gas_wanted"`
            GasUsed   uint64 `json:"gas_used"`
        } `json:"gas_info"`
    }
    if err := json.NewDecoder(res.Body).Decode(&simResp); err != nil {
        return 0, err
    }
    return uint64(float64(simResp.GasInfo.GasUsed) * c.config.GasAdjustment), nil
}

8.4 合约执行

// ExecuteContract 执行合约
func (wc *WasmClient) ExecuteContract(ctx context.Context, contractAddr, sender string,
    execMsg interface{}, funds Coins, privKey cryptotypes.PrivKey) (*Transaction, error) {
    msgBytes, _ := json.Marshal(execMsg)
    msg := &wasmtypes.MsgExecuteContract{
        Sender:   sender,
        Contract: contractAddr,
        Msg:      msgBytes,
        Funds:    funds,
    }
    return wc.client.BuildAndBroadcastTx(ctx, []sdk.Msg{msg}, privKey)
}

// InstantiateContract 实例化合约
func (wc *WasmClient) InstantiateContract(ctx context.Context, codeID uint64,
    creator, admin string, initMsg interface{}, label string,
    funds Coins, privKey cryptotypes.PrivKey) (*Transaction, error) {
    msgBytes, _ := json.Marshal(initMsg)
    msg := &wasmtypes.MsgInstantiateContract{
        Sender: creator,
        Admin:  admin,
        CodeID: codeID,
        Label:  label,
        Msg:    msgBytes,
        Funds:  funds,
    }
    return wc.client.BuildAndBroadcastTx(ctx, []sdk.Msg{msg}, privKey)
}

// MigrateContract 迁移合约
func (wc *WasmClient) MigrateContract(ctx context.Context, contractAddr, admin string,
    newCodeID uint64, migrateMsg interface{}, privKey cryptotypes.PrivKey) (*Transaction, error) {
    msgBytes, _ := json.Marshal(migrateMsg)
    msg := &wasmtypes.MsgMigrateContract{
        Sender:   admin,
        Contract: contractAddr,
        CodeID:   newCodeID,
        Msg:      msgBytes,
    }
    return wc.client.BuildAndBroadcastTx(ctx, []sdk.Msg{msg}, privKey)
}

8.5 交易收据解析

// TxReceipt 交易收据
type TxReceipt struct {
    Hash      string  `json:"hash"`
    Height    string  `json:"height"`
    GasWanted string  `json:"gas_wanted"`
    GasUsed   string  `json:"gas_used"`
    Code      uint32  `json:"code"`
    Log       string  `json:"log"`
    Events    []Event `json:"events"`
}

// Event 链上事件
type Event struct {
    Type       string            `json:"type"`
    Attributes map[string]string `json:"attributes"`
}

// ParseTxResponse 解析交易响应中的事件
func ParseTxResponse(tx *Transaction) (*TxReceipt, error) {
    receipt := &TxReceipt{
        Hash:      tx.TxResponse.TxHash,
        Height:    tx.TxResponse.Height,
        GasWanted: tx.TxResponse.GasWanted,
        GasUsed:   tx.TxResponse.GasUsed,
        Code:      tx.TxResponse.Code,
        Log:       tx.TxResponse.RawLog,
    }
    for _, log := range tx.TxResponse.Logs {
        for _, event := range log.Events {
            evt := Event{
                Type:       event.Type,
                Attributes: make(map[string]string),
            }
            for _, attr := range event.Attributes {
                evt.Attributes[attr.Key] = attr.Value
            }
            receipt.Events = append(receipt.Events, evt)
        }
    }
    return receipt, nil
}

// FindEvents 根据类型查找事件
func (r *TxReceipt) FindEvents(eventType string) []Event {
    var result []Event
    for _, e := range r.Events {
        if e.Type == eventType {
            result = append(result, e)
        }
    }
    return result
}

9. Agent API 客户端

Agent API 是 MSG Chain 的特有功能,参考 pkg/quantum/agent_api.go 中的路由注册实现。API 分为三类路由:公共查询(wrapPublic)、受保护操作(wrapProtected)和 Stub 端点。

9.1 AgentAPIClient 结构体

// AgentAPIClient Agent API 客户端
type AgentAPIClient struct {
    client   *MSGChainClient
    baseURL  string
    apiKey   string
    stubMode bool
}

func NewAgentAPIClient(c *MSGChainClient) *AgentAPIClient {
    return &AgentAPIClient{
        client:  c,
        baseURL: c.conn.RESTEndpoint,
    }
}

// WithAPIKey 设置 API 密钥(受保护路由需要)
func (a *AgentAPIClient) WithAPIKey(key string) *AgentAPIClient {
    a.apiKey = key
    return a
}

// WithStubMode 启用 Stub 模式(测试用)
func (a *AgentAPIClient) WithStubMode(enabled bool) *AgentAPIClient {
    a.stubMode = enabled
    return a
}

9.2 公共查询路由

Agent API 公共路由通过 wrapPublic 中间件注册,无需认证:

GET /agent/v1/query/account/{address}
GET /agent/v1/query/balance/{address}
GET /agent/v1/query/balances
GET /agent/v1/query/tx/{hash}
GET /agent/v1/query/block/{height}
GET /agent/v1/query/blocks/range
GET /agent/v1/events/history
GET /agent/v1/events/subscribe (WebSocket 升级)
GET /agent/v1/oracle/price
// QueryAccount 查询账户信息
func (a *AgentAPIClient) QueryAccount(ctx context.Context,
    address string) (map[string]interface{}, error) {
    path := fmt.Sprintf("%s/agent/v1/query/account/%s", a.baseURL, address)
    return a.publicGet(ctx, path)
}

// QueryBalance 查询余额
func (a *AgentAPIClient) QueryBalance(ctx context.Context,
    address string) (map[string]interface{}, error) {
    path := fmt.Sprintf("%s/agent/v1/query/balance/%s", a.baseURL, address)
    return a.publicGet(ctx, path)
}

// QueryBalances 查询所有余额
func (a *AgentAPIClient) QueryBalances(ctx context.Context) (map[string]interface{}, error) {
    path := fmt.Sprintf("%s/agent/v1/query/balances", a.baseURL)
    return a.publicGet(ctx, path)
}

// QueryTx 查询交易
func (a *AgentAPIClient) QueryTx(ctx context.Context,
    hash string) (map[string]interface{}, error) {
    path := fmt.Sprintf("%s/agent/v1/query/tx/%s", a.baseURL, hash)
    return a.publicGet(ctx, path)
}

// QueryBlock 查询区块
func (a *AgentAPIClient) QueryBlock(ctx context.Context,
    height int64) (map[string]interface{}, error) {
    path := fmt.Sprintf("%s/agent/v1/query/block/%d", a.baseURL, height)
    return a.publicGet(ctx, path)
}

// QueryBlocksRange 查询区块范围
func (a *AgentAPIClient) QueryBlocksRange(ctx context.Context,
    from, to int64) (map[string]interface{}, error) {
    path := fmt.Sprintf("%s/agent/v1/query/blocks/range?from=%d&to=%d",
        a.baseURL, from, to)
    return a.publicGet(ctx, path)
}

// QueryEventsHistory 查询事件历史
func (a *AgentAPIClient) QueryEventsHistory(ctx context.Context) (map[string]interface{}, error) {
    path := fmt.Sprintf("%s/agent/v1/events/history", a.baseURL)
    return a.publicGet(ctx, path)
}

// QueryOraclePrice 查询预言机价格
func (a *AgentAPIClient) QueryOraclePrice(ctx context.Context) (map[string]interface{}, error) {
    path := fmt.Sprintf("%s/agent/v1/oracle/price", a.baseURL)
    return a.publicGet(ctx, path)
}

// publicGet 公共 GET 请求
func (a *AgentAPIClient) publicGet(ctx context.Context,
    path string) (map[string]interface{}, error) {
    req, _ := http.NewRequestWithContext(ctx, "GET", path, nil)
    if a.stubMode {
        req.Header.Set("X-MSG-Stub", "true")
    }
    res, err := a.client.doRequest(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    var result map[string]interface{}
    if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
        return nil, err
    }
    return result, nil
}

9.3 受保护路由

受保护路由通过 wrapProtected 中间件注册,需要 Authorization: Bearer <apiKey> 头:

POST /agent/v1/wallet/
POST /agent/v1/mpc/sign
POST /agent/v1/payment/session
// WalletCreate 创建钱包
func (a *AgentAPIClient) WalletCreate(ctx context.Context,
    params map[string]interface{}) (map[string]interface{}, error) {
    return a.protectedPost(ctx, "/agent/v1/wallet/", params)
}

// MPCSignRequest MPC 签名请求
type MPCSignRequest struct {
    SessionID    string   `json:"session_id"`
    Message      string   `json:"message"`
    KeyShareID   string   `json:"key_share_id"`
    Threshold    int      `json:"threshold"`
    Participants []string `json:"participants"`
}

// MPCSign MPC 多方签名
func (a *AgentAPIClient) MPCSign(ctx context.Context,
    params MPCSignRequest) (map[string]interface{}, error) {
    return a.protectedPost(ctx, "/agent/v1/mpc/sign", params)
}

// PaymentSessionRequest 支付会话请求
type PaymentSessionRequest struct {
    From        string `json:"from"`
    To          string `json:"to"`
    Amount      Coin   `json:"amount"`
    Description string `json:"description,omitempty"`
    WebhookURL  string `json:"webhook_url,omitempty"`
}

// PaymentSession 创建支付会话
func (a *AgentAPIClient) PaymentSession(ctx context.Context,
    params PaymentSessionRequest) (map[string]interface{}, error) {
    return a.protectedPost(ctx, "/agent/v1/payment/session", params)
}

// protectedPost 受保护 POST 请求
func (a *AgentAPIClient) protectedPost(ctx context.Context,
    path string, body interface{}) (map[string]interface{}, error) {
    bodyBytes, _ := json.Marshal(body)
    req, _ := http.NewRequestWithContext(ctx, "POST", path,
        bytes.NewReader(bodyBytes))
    req.Header.Set("Content-Type", "application/json")
    if a.apiKey != "" {
        req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", a.apiKey))
    }
    if a.stubMode {
        req.Header.Set("X-MSG-Stub", "true")
    }
    res, err := a.client.doRequest(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    if res.StatusCode != http.StatusOK {
        return nil, NewSDKError(ErrUnauthorized, "Agent API 请求被拒绝", nil)
    }
    var result map[string]interface{}
    if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
        return nil, err
    }
    return result, nil
}

9.4 WebSocket 事件订阅

// SubscribeEvents 通过 WebSocket 订阅链上事件
func (a *AgentAPIClient) SubscribeEvents(ctx context.Context,
    eventTypes []string) (<-chan Event, error) {
    wsURL := strings.Replace(a.baseURL, "http", "ws", 1)
    url := fmt.Sprintf("%s/agent/v1/events/subscribe", wsURL)
    if len(eventTypes) > 0 {
        typesJSON, _ := json.Marshal(eventTypes)
        url = fmt.Sprintf("%s?types=%s", url, url.QueryEscape(string(typesJSON)))
    }

    dialer := websocket.DefaultDialer
    conn, _, err := dialer.DialContext(ctx, url, nil)
    if err != nil {
        return nil, fmt.Errorf("WebSocket 连接失败: %w", err)
    }

    eventCh := make(chan Event, 100)
    go func() {
        defer close(eventCh)
        defer conn.Close()
        for {
            select {
            case <-ctx.Done():
                return
            default:
                _, message, err := conn.ReadMessage()
                if err != nil {
                    return
                }
                var event Event
                if err := json.Unmarshal(message, &event); err != nil {
                    continue
                }
                eventCh <- event
            }
        }
    }()
    return eventCh, nil
}

9.5 Stub 端点

以下端点仅在 X-MSG-Stub=true 头时可用,用于开发和测试:

POST /agent/v1/defi/swap           // X-MSG-Stub=true
POST /agent/v1/defi/liquidity      // X-MSG-Stub=true
POST /agent/v1/defi/stake          // X-MSG-Stub=true
POST /agent/v1/registry/register   // X-MSG-Stub=true
POST /agent/v1/registry/discover   // X-MSG-Stub=true
POST /agent/v1/registry/list       // X-MSG-Stub=true
POST /agent/v1/bridge/transfer     // X-MSG-Stub=true
// AgentRegistration AI Agent 注册信息
type AgentRegistration struct {
    Name        string   `json:"name"`
    Description string   `json:"description"`
    Endpoint    string   `json:"endpoint"`
    Owner       string   `json:"owner"`
    Fee         Coin     `json:"fee,omitempty"`
    Tags        []string `json:"tags,omitempty"`
    Permissions []string `json:"permissions,omitempty"`
}

// DeFiSwap DeFi 交换 (Stub)
// X-MSG-Stub=true
func (a *AgentAPIClient) DeFiSwap(ctx context.Context,
    params map[string]interface{}) (map[string]interface{}, error) {
    return a.stubPost(ctx, "/agent/v1/defi/swap", params)
}

// DeFiLiquidity 添加流动性 (Stub)
// X-MSG-Stub=true
func (a *AgentAPIClient) DeFiLiquidity(ctx context.Context,
    params map[string]interface{}) (map[string]interface{}, error) {
    return a.stubPost(ctx, "/agent/v1/defi/liquidity", params)
}

// DeFiStake DeFi 质押 (Stub)
// X-MSG-Stub=true
func (a *AgentAPIClient) DeFiStake(ctx context.Context,
    params map[string]interface{}) (map[string]interface{}, error) {
    return a.stubPost(ctx, "/agent/v1/defi/stake", params)
}

// RegistryRegister 注册 AI Agent (Stub)
// X-MSG-Stub=true
func (a *AgentAPIClient) RegistryRegister(ctx context.Context,
    params AgentRegistration) (map[string]interface{}, error) {
    return a.stubPost(ctx, "/agent/v1/registry/register", params)
}

// RegistryDiscover 发现 AI Agent (Stub)
// X-MSG-Stub=true
func (a *AgentAPIClient) RegistryDiscover(ctx context.Context,
    params map[string]interface{}) (map[string]interface{}, error) {
    return a.stubPost(ctx, "/agent/v1/registry/discover", params)
}

// RegistryList 列出所有 Agent (Stub)
// X-MSG-Stub=true
func (a *AgentAPIClient) RegistryList(ctx context.Context) (map[string]interface{}, error) {
    return a.stubPost(ctx, "/agent/v1/registry/list", nil)
}

// BridgeTransfer 跨链桥转账 (Stub)
// X-MSG-Stub=true
func (a *AgentAPIClient) BridgeTransfer(ctx context.Context,
    params map[string]interface{}) (map[string]interface{}, error) {
    return a.stubPost(ctx, "/agent/v1/bridge/transfer", params)
}

// stubPost Stub POST 请求
func (a *AgentAPIClient) stubPost(ctx context.Context,
    route string, body interface{}) (map[string]interface{}, error) {
    if !a.stubMode {
        return nil, NewSDKError(ErrStubNotImplemented,
            fmt.Sprintf("Stub 端点 %s 需要设置 X-MSG-Stub=true", route), nil)
    }
    return a.protectedPost(ctx, fmt.Sprintf("%s%s", a.baseURL, route), body)
}

10. 治理操作

10.1 GovernanceClient

// GovernanceClient 治理模块客户端
type GovernanceClient struct {
    client *MSGChainClient
}

func NewGovernanceClient(c *MSGChainClient) *GovernanceClient {
    return &GovernanceClient{client: c}
}

// Proposal 提案
type Proposal struct {
    ProposalID       string `json:"proposal_id"`
    Title            string `json:"title"`
    Description      string `json:"description"`
    Status           string `json:"status"`
    FinalTallyResult struct {
        Yes        string `json:"yes"`
        Abstain    string `json:"abstain"`
        No         string `json:"no"`
        NoWithVeto string `json:"no_with_veto"`
    } `json:"final_tally_result"`
    SubmitTime      string `json:"submit_time"`
    VotingStartTime string `json:"voting_start_time"`
    VotingEndTime   string `json:"voting_end_time"`
    TotalDeposit    Coins  `json:"total_deposit"`
    Proposer        string `json:"proposer"`
}

// VoteOption 投票选项
type VoteOption string

const (
    VoteYes        VoteOption = "VOTE_OPTION_YES"
    VoteNo         VoteOption = "VOTE_OPTION_NO"
    VoteNoWithVeto VoteOption = "VOTE_OPTION_NO_WITH_VETO"
    VoteAbstain    VoteOption = "VOTE_OPTION_ABSTAIN"
)

10.2 提案与投票

// GetProposals 获取提案列表
func (gc *GovernanceClient) GetProposals(ctx context.Context) ([]Proposal, error) {
    path := fmt.Sprintf("%s/cosmos/gov/v1beta1/proposals",
        gc.client.conn.RESTEndpoint)
    req, _ := http.NewRequestWithContext(ctx, "GET", path, nil)
    res, err := gc.client.doRequest(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    var resp struct {
        Proposals []Proposal `json:"proposals"`
    }
    if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
        return nil, err
    }
    return resp.Proposals, nil
}

// GetProposal 获取单个提案
func (gc *GovernanceClient) GetProposal(ctx context.Context,
    proposalID uint64) (*Proposal, error) {
    path := fmt.Sprintf("%s/cosmos/gov/v1beta1/proposals/%d",
        gc.client.conn.RESTEndpoint, proposalID)
    req, _ := http.NewRequestWithContext(ctx, "GET", path, nil)
    res, err := gc.client.doRequest(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    var resp struct {
        Proposal Proposal `json:"proposal"`
    }
    if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
        return nil, err
    }
    return &resp.Proposal, nil
}

// Vote 投票
func (gc *GovernanceClient) Vote(ctx context.Context, voterAddr string,
    proposalID uint64, option VoteOption,
    privKey cryptotypes.PrivKey) (*Transaction, error) {
    msg := &govtypes.MsgVote{
        ProposalID: proposalID,
        Voter:      voterAddr,
        Option:     govtypes.VoteOptionFromString(string(option)),
    }
    return gc.client.BuildAndBroadcastTx(ctx, []sdk.Msg{msg}, privKey)
}

// GetTally 获取计票结果
func (gc *GovernanceClient) GetTally(ctx context.Context,
    proposalID uint64) (*ProposalFinalTally, error) {
    path := fmt.Sprintf("%s/cosmos/gov/v1beta1/proposals/%d/tally",
        gc.client.conn.RESTEndpoint, proposalID)
    req, _ := http.NewRequestWithContext(ctx, "GET", path, nil)
    res, err := gc.client.doRequest(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    var resp struct {
        Tally ProposalFinalTally `json:"tally"`
    }
    if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
        return nil, err
    }
    return &resp.Tally, nil
}

// ProposalFinalTally 最终计票
type ProposalFinalTally struct {
    Yes        string `json:"yes"`
    Abstain    string `json:"abstain"`
    No         string `json:"no"`
    NoWithVeto string `json:"no_with_veto"`
}

// SubmitProposal 提交文本提案
func (gc *GovernanceClient) SubmitProposal(ctx context.Context,
    proposerAddr, title, description string, initialDeposit Coins,
    privKey cryptotypes.PrivKey) (*Transaction, error) {
    content := &govtypes.TextProposal{
        Title:       title,
        Description: description,
    }
    msg, err := govtypes.NewMsgSubmitProposal(content, initialDeposit, proposerAddr)
    if err != nil {
        return nil, err
    }
    return gc.client.BuildAndBroadcastTx(ctx, []sdk.Msg{msg}, privKey)
}

// Deposit 提案存款
func (gc *GovernanceClient) Deposit(ctx context.Context,
    depositorAddr string, proposalID uint64, amount Coins,
    privKey cryptotypes.PrivKey) (*Transaction, error) {
    msg := &govtypes.MsgDeposit{
        ProposalID: proposalID,
        Depositor:  depositorAddr,
        Amount:     amount,
    }
    return gc.client.BuildAndBroadcastTx(ctx, []sdk.Msg{msg}, privKey)
}

// CommunityPool 查询社区池余额
func (gc *GovernanceClient) CommunityPool(ctx context.Context) (Coins, error) {
    path := fmt.Sprintf("%s/cosmos/distribution/v1beta1/community_pool",
        gc.client.conn.RESTEndpoint)
    req, _ := http.NewRequestWithContext(ctx, "GET", path, nil)
    res, err := gc.client.doRequest(req)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    var resp struct {
        Pool struct {
            Pool Coins `json:"pool"`
        } `json:"pool"`
    }
    if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
        return nil, err
    }
    return resp.Pool.Pool, nil
}

11. 事件监听

11.1 EventClient

基于 WebSocket 的事件监听实现,参考 pkg/indexer/event_listener.go 和 pkg/quantum/tendermint_rpc.go。

// EventClient 事件监听客户端
type EventClient struct {
    client        *MSGChainClient
    mu            sync.Mutex
    subscriptions map[string]context.CancelFunc
}

func NewEventClient(c *MSGChainClient) *EventClient {
    return &EventClient{
        client:        c,
        subscriptions: make(map[string]context.CancelFunc),
    }
}

// EventSubscription 事件订阅
type EventSubscription struct {
    ID     string
    Events <-chan Event
    cancel context.CancelFunc
}

// SubscribeNewBlocks 订阅新区块
func (ec *EventClient) SubscribeNewBlocks(ctx context.Context) (*EventSubscription, error) {
    return ec.subscribe(ctx, "tm.event='NewBlock'")
}

// SubscribeTxs 订阅交易
func (ec *EventClient) SubscribeTxs(ctx context.Context) (*EventSubscription, error) {
    return ec.subscribe(ctx, "tm.event='Tx'")
}

// subscribe WebSocket 订阅
func (ec *EventClient) subscribe(ctx context.Context,
    query string) (*EventSubscription, error) {
    wsURL := strings.Replace(ec.client.conn.RPCEndpoint, "http", "ws", 1)
    dialer := websocket.DefaultDialer
    conn, _, err := dialer.DialContext(ctx, fmt.Sprintf("%s/websocket", wsURL), nil)
    if err != nil {
        return nil, fmt.Errorf("WebSocket 连接失败: %w", err)
    }

    subID := fmt.Sprintf("sub-%d", time.Now().UnixNano())
    eventCh := make(chan Event, 200)
    subCtx, cancel := context.WithCancel(ctx)

    ec.mu.Lock()
    ec.subscriptions[subID] = cancel
    ec.mu.Unlock()

    // 发送订阅请求
    subscribeReq := map[string]interface{}{
        "jsonrpc": "2.0",
        "method":  "subscribe",
        "id":      subID,
        "params":  map[string]interface{}{"query": query},
    }
    if err := conn.WriteJSON(subscribeReq); err != nil {
        cancel()
        conn.Close()
        return nil, err
    }

    go func() {
        defer func() {
            conn.Close()
            ec.mu.Lock()
            delete(ec.subscriptions, subID)
            ec.mu.Unlock()
        }()
        for {
            select {
            case <-subCtx.Done():
                return
            default:
                _, msg, err := conn.ReadMessage()
                if err != nil {
                    return
                }
                var wsMsg struct {
                    Result struct {
                        Data struct {
                            Type  string          `json:"type"`
                            Value json.RawMessage `json:"value"`
                        } `json:"data"`
                        Events map[string][]string `json:"events"`
                    } `json:"result"`
                }
                if err := json.Unmarshal(msg, &wsMsg); err != nil {
                    continue
                }
                event := Event{
                    Type:       wsMsg.Result.Data.Type,
                    Attributes: make(map[string]string),
                }
                for key, values := range wsMsg.Result.Events {
                    if len(values) > 0 {
                        event.Attributes[key] = values[0]
                    }
                }
                select {
                case eventCh <- event:
                default:
                }
            }
        }
    }()

    return &EventSubscription{
        ID:     subID,
        Events: eventCh,
        cancel: cancel,
    }, nil
}

// Unsubscribe 取消订阅
func (ec *EventClient) Unsubscribe(sub *EventSubscription) {
    if sub != nil && sub.cancel != nil {
        sub.cancel()
    }
}

12. 完整示例

12.1 cmd/balance-checker/main.go——查询余额

// cmd/balance-checker/main.go
// 查询多个 MSG Chain 地址的余额
// go run ./cmd/balance-checker <addr1> [addr2 ...]

package main

import (
    "context"
    "fmt"
    "os"
    "time"

    "github.com/msg-chain/go-sdk/msgchain"
)

func main() {
    if len(os.Args) < 2 {
        fmt.Fprintf(os.Stderr, "用法: %s <address1> [address2 ...]\n", os.Args[0])
        os.Exit(1)
    }

    addresses := os.Args[1:]
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    cl, err := msgchain.NewMSGChainClient(
        msgchain.DefaultChainConfig(),
        msgchain.ClientConn{
            RPCEndpoint:  "http://localhost:26657",
            RESTEndpoint: "http://localhost:1317",
            GRPCEndpoint: "localhost:9090",
            Timeout:      10 * time.Second,
        },
    )
    if err != nil {
        fmt.Fprintf(os.Stderr, "创建客户端失败: %v\n", err)
        os.Exit(1)
    }
    defer cl.Close()

    fmt.Println("═══════════════════════════════════════")
    fmt.Println("  MSG Chain 余额查询工具")
    fmt.Println("═══════════════════════════════════════")

    for _, addr := range addresses {
        balances, err := cl.Bank().GetAllBalances(ctx, addr)
        if err != nil {
            fmt.Printf("  %s -> 错误: %v\n", addr, err)
            continue
        }
        fmt.Printf("  %s\n", addr)
        for _, c := range balances {
            fmt.Printf("    %s\n", c.String())
        }
    }
}

go.mod:

module github.com/your-org/balance-checker

go 1.21

require github.com/msg-chain/go-sdk v0.2.0

12.2 cmd/agent-bot/main.go——AI Agent 监控机器人

// cmd/agent-bot/main.go
// AI Agent 监控链上事件并自动响应
// go run ./cmd/agent-bot --api-key <key>

package main

import (
    "context"
    "flag"
    "log"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/msg-chain/go-sdk/msgchain"
)

var (
    apiKey   = flag.String("api-key", "", "Agent API 密钥")
    rpcAddr  = flag.String("rpc", "http://localhost:26657", "RPC 地址")
    restAddr = flag.String("rest", "http://localhost:1317", "REST 地址")
    grpcAddr = flag.String("grpc", "localhost:9090", "gRPC 地址")
)

func main() {
    flag.Parse()
    if *apiKey == "" {
        log.Fatal("必须设置 --api-key")
    }

    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    cl, err := msgchain.NewMSGChainClient(
        msgchain.DefaultChainConfig(),
        msgchain.ClientConn{
            RPCEndpoint:  *rpcAddr,
            RESTEndpoint: *restAddr,
            GRPCEndpoint: *grpcAddr,
            Timeout:      30 * time.Second,
        },
    )
    if err != nil {
        log.Fatalf("创建客户端失败: %v", err)
    }
    defer cl.Close()

    agentAPI := cl.AgentAPI().WithAPIKey(*apiKey)

    // 查询 Oracle 价格
    price, err := agentAPI.QueryOraclePrice(ctx)
    if err != nil {
        log.Printf("Oracle 价格查询失败: %v", err)
    } else {
        log.Printf("Oracle 价格: %v", price)
    }

    // 订阅区块事件
    sub, err := cl.Event().SubscribeNewBlocks(ctx)
    if err != nil {
        log.Fatalf("订阅失败: %v", err)
    }
    log.Println("已订阅新区块,等待事件...")

    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)

    blockCount := 0
    for {
        select {
        case <-sigCh:
            log.Println("正在关闭...")
            cl.Event().Unsubscribe(sub)
            return
        case <-sub.Events:
            blockCount++
            if blockCount%10 == 0 {
                log.Printf("已处理 %d 个区块", blockCount)
            }
        case <-time.After(5 * time.Minute):
            log.Println("执行定期任务...")
        }
    }
}

12.3 cmd/agent-registrar/main.go——注册 AI Agent

// cmd/agent-registrar/main.go
// 在 MSG Chain 上注册 AI Agent(需 Stub 模式)
// go run ./cmd/agent-registrar --stub --name "MyAgent" --endpoint "http://agent:8080" --owner msg1...

package main

import (
    "context"
    "flag"
    "fmt"
    "log"
    "time"

    "github.com/msg-chain/go-sdk/msgchain"
)

func main() {
    var (
        stub     = flag.Bool("stub", false, "Stub 模式")
        name     = flag.String("name", "", "Agent 名称")
        desc     = flag.String("desc", "", "描述")
        endpoint = flag.String("endpoint", "", "端点 URL")
        owner    = flag.String("owner", "", "所有者地址")
        apiKey   = flag.String("api-key", "", "API 密钥")
    )
    flag.Parse()

    if *name == "" || *endpoint == "" || *owner == "" {
        log.Fatal("必须设置 --name, --endpoint, --owner")
    }

    ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
    defer cancel()

    cl, err := msgchain.NewMSGChainClient(
        msgchain.DefaultChainConfig(),
        msgchain.ClientConn{
            RESTEndpoint: "http://localhost:1317",
            Timeout:      10 * time.Second,
        },
    )
    if err != nil {
        log.Fatalf("创建客户端失败: %v", err)
    }
    defer cl.Close()

    agentAPI := cl.AgentAPI().WithStubMode(*stub)
    if *apiKey != "" {
        agentAPI = agentAPI.WithAPIKey(*apiKey)
    }

    reg := msgchain.AgentRegistration{
        Name:        *name,
        Description: *desc,
        Endpoint:    *endpoint,
        Owner:       *owner,
        Tags:        []string{"go-sdk", "ai-agent"},
        Permissions: []string{"query:balance", "wallet:create"},
    }

    result, err := agentAPI.RegistryRegister(ctx, reg)
    if err != nil {
        if msgchain.IsErr(err, msgchain.ErrStubNotImplemented) {
            log.Fatalf("需要启用 --stub 模式: %v", err)
        }
        log.Fatalf("注册失败: %v", err)
    }

    fmt.Printf("Agent [%s] 注册成功!响应: %v\n", *name, result)
}

12.4 msgchain_client_test.go——测试文件

// msgchain_client_test.go
package msgchain

import (
    "testing"
    "time"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

func TestDefaultChainConfig(t *testing.T) {
    cfg := DefaultChainConfig()
    assert.Equal(t, "msg-chain-1", cfg.ChainID)
    assert.Equal(t, "msg", cfg.Bech32Prefix)
    assert.Equal(t, uint32(118), cfg.CoinType)
    assert.Equal(t, "1000000000attoMSG", cfg.GasPrices)
    assert.Equal(t, 5*time.Second, cfg.BlockTime)
    assert.Equal(t, uint8(18), cfg.MsgDecimals)
}

func TestGenerateMnemonic(t *testing.T) {
    mnemonic, err := GenerateDefaultMnemonic()
    require.NoError(t, err)
    assert.NotEmpty(t, mnemonic)
    words := strings.Split(mnemonic, " ")
    assert.Equal(t, 24, len(words))
}

func TestGenerateMnemonicInvalidBitSize(t *testing.T) {
    _, err := GenerateMnemonic(64)
    assert.Error(t, err)
}

func TestParseAddress(t *testing.T) {
    // MSG Chain 地址以 msg 开头
    validAddr := "msg1qypqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqp8r9g"
    addr, err := ParseAddress(validAddr)
    require.NoError(t, err)
    assert.NotNil(t, addr)

    _, err = ParseAddress("cosmos1...")
    assert.Error(t, err)
}

func TestCoinString(t *testing.T) {
    coin := Coin{Denom: "umsg", Amount: "1000000"}
    assert.Equal(t, "1000000umsg", coin.String())
}

func TestNewSDKError(t *testing.T) {
    err := NewSDKError(ErrInsufficientFunds, "资金不足", nil)
    assert.Error(t, err)
    assert.True(t, IsErr(err, ErrInsufficientFunds))

    var sdkErr *SDKError
    assert.ErrorAs(t, err, &sdkErr)
    assert.Equal(t, "资金不足", sdkErr.Message)
}

func TestIsInsufficientFee(t *testing.T) {
    err := NewSDKError(ErrInsufficientFee, "费用不足", nil)
    assert.True(t, IsInsufficientFee(err))
    assert.False(t, IsOutOfGas(err))
}

func TestAgentRegistration(t *testing.T) {
    reg := AgentRegistration{
        Name:     "test-agent",
        Endpoint: "http://localhost:8080",
        Owner:    "msg1test...",
        Tags:     []string{"test"},
    }
    assert.Equal(t, "test-agent", reg.Name)
    assert.Contains(t, reg.Tags, "test")
}

func TestBuildAndBroadcastTx_InvalidParams(t *testing.T) {
    cfg := DefaultChainConfig()
    conn := ClientConn{
        RESTEndpoint: "http://localhost:1317",
        Timeout:      5 * time.Second,
    }
    cl, err := NewMSGChainClient(cfg, conn)
    if err != nil {
        t.Skip("节点未运行,跳过")
    }
    defer cl.Close()

    _, err = cl.Bank().GetMSG(context.Background(), "invalid")
    assert.Error(t, err)
}

func TestBankClientInterface(t *testing.T) {
    // 编译期接口检查
    var _ BankClientInterface = (*BankClient)(nil)
}

// BenchmarkGenerateMnemonic 基准测试
func BenchmarkGenerateMnemonic(b *testing.B) {
    for i := 0; i < b.N; i++ {
        GenerateDefaultMnemonic()
    }
}

12.5 完整 go.mod 模板

module github.com/your-org/msg-go-sdk-examples

go 1.21

require (
    github.com/msg-chain/go-sdk v0.2.0
    github.com/cosmos/cosmos-sdk v0.47.10
    github.com/CosmWasm/wasmd v0.45.0
    github.com/cometbft/cometbft v0.37.5
    github.com/gorilla/websocket v1.5.1
    github.com/stretchr/testify v1.9.0
    golang.org/x/time v0.5.0
)

require (
    github.com/99designs/keyring v1.2.2
    github.com/CosmWasm/wasmvm v1.5.2
    github.com/btcsuite/btcd/btcec/v2 v2.3.2
    github.com/cosmos/cosmos-crypto v0.2.0
    github.com/cosmos/go-bip39 v1.0.0
    github.com/cosmos/gogoproto v1.4.11
    github.com/cosmos/ibc-go/v7 v7.4.0
    github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0
    github.com/dgraph-io/badger/v4 v4.2.0
    github.com/gorilla/mux v1.8.1
    github.com/pkg/errors v0.9.1
    github.com/tyler-smith/go-bip39 v1.1.0
    golang.org/x/crypto v0.21.0
    golang.org/x/exp v0.0.0-20240222234643-814bf88cf225
    google.golang.org/grpc v1.62.1
    google.golang.org/protobuf v1.33.0
)

附录

A. 快速参考——模块方法速查

模块 方法 描述
MSGChainClient GetNodeInfo, GetLatestBlock, GetBlockByHeight, GetTxByHash 基础查询
MSGChainClient SimulateTx, BroadcastTx, BuildAndBroadcastTx 交易操作
BankClient GetBalance, GetAllBalances, GetMSG, GetTotalSupply 余额与供应
BankClient Send, BatchSend 转账
StakingClient GetValidators, GetActiveValidators, GetDelegations 验证者与委托
StakingClient GetRewards, Delegate, Unbond, Redelegate 收益与操作
WasmClient SmartContractState, SmartQuery, RawContractState 合约查询
WasmClient GetCodeInfo, GetContractInfo, ExecuteContract 合约信息与执行
WasmClient InstantiateContract, MigrateContract 合约部署与迁移
GovernanceClient GetProposals, GetProposal, Vote, GetTally 提案与投票
GovernanceClient SubmitProposal, Deposit, CommunityPool 提交与财政库
AgentAPIClient QueryAccount, QueryBalance, QueryTx, QueryBlock Agent 查询
AgentAPIClient QueryOraclePrice, QueryEventsHistory Oracle 与事件
AgentAPIClient WalletCreate, MPCSign, PaymentSession 受保护操作
AgentAPIClient SubscribeEvents WebSocket 订阅
AgentAPIClient DeFiSwap, DeFiLiquidity, DeFiStake (Stub) DeFi 操作
AgentAPIClient RegistryRegister, RegistryDiscover, RegistryList (Stub) Agent 注册
AgentAPIClient BridgeTransfer (Stub) 跨链桥
EventClient SubscribeNewBlocks, SubscribeTxs 链上事件监听

B. 常见错误排除

错误码 原因 排查方法
ErrInsufficientFee Gas 费用不足 检查 GasPrices 配置,MSG Chain gas 价格为 1,000,000,000 attoMSG/gas
ErrOutOfGas Gas 耗尽 增大 GasAdjustment(默认 1.3)或调用 SimulateTx
ErrQuantumSignature Dilithium-5 签名失败 确认私钥类型为 Dilithium-5,检查 pkg/quantum
ErrAgentAPIRateLimit 超过 100 req/s 降低请求频率或调整 WithRateLimit
ErrStubNotImplemented 调用了 Stub 端点 设置 X-MSG-Stub=true 头或 WithStubMode(true)
ErrUnauthorized Agent API 认证失败 检查 API Key 是否正确

C. 迁移指南:从 cosmjs 到 Go SDK

cosmjs Go SDK
await client.getAccount() client.GetNodeInfo()
await client.getBalance() client.Bank().GetBalance()
await client.sendTokens() client.Bank().Send()
await client.delegateTokens() client.Staking().Delegate()
SigningStargateClient MSGChainClient.BuildAndBroadcastTx()
CosmWasmClient.queryContractSmart() client.Wasm().SmartContractState()
Secp256k1HdWallet WalletManager / CreateKey(KeyTypeSecp256k1)
await client.broadcastTx() client.BroadcastTxWithMode()
GasPrice.fromString() ChainConfig.GasPrices
EventSubscription EventClient.SubscribeNewBlocks()
calculateFee() client.SimulateTx() / EstimateGasFee()

注意: 本文档中的代码示例基于 github.com/msg-chain/go-sdk v0.2.0。实际开发过程中,请始终参考 pkg/quantum/、pkg/wasm/、pkg/indexer/ 目录下的真实源代码以获取最新实现细节。Agent API 路由定义请参考 pkg/quantum/agent_api.go。交易执行逻辑请参考 pkg/quantum/transaction_executor.go。Node 启动流程请参考 pkg/quantum/node.go。CosmWasm 集成请参考 pkg/wasm/real_vm.go 和 pkg/wasm/core_contracts_e2e_test.go。事件索引实现请参考 pkg/indexer/event_listener.go。测试使用 make test / make test-quantum 运行,合约构建使用 make ci-contracts。