07-3 Cosmos 钱包开发流程

Cosmos钱包开发

钱包的交易逻辑都是一样的,

  • 第一步:离线地址生成

  • 第二步:构建交易模型

  • 第三步:生成一个未签名的msg消息

  • 第四步:对msg消息进行签名,返回一个已签名的消息

  • 第五步:对已签名的消息希进行广播

离线地址生成

Cosmos支持两种格式的地址:

Edd25519和Secp256k1

以前只支持Secp256k1后来扩展支持Edd25519

bech32是一种格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package hdwallet

import (
"crypto/sha256"
"encoding/hex"
"fmt"
"github.com/cosmos/cosmos-sdk/types/bech32"
"github.com/tyler-smith/go-bip32"
"github.com/tyler-smith/go-bip39"
"golang.org/x/crypto/ripemd160"
)

// 生成一个以太坊的钱包
func cosmosWalletPrivate() {
fmt.Println("开始")
// 首先你可以生成一个随机熵,熵源助记词是BIP-39,
//entropy, _ := bip39.NewEntropy(128)
//fmt.Println("entroy:", entropy)
// 通过熵源生成助记词 ==> 注意,不一定要有助记词才有种子,只是助记词方便备份,可以转成种子,你要直接由种子也行,但不好记
//mnemonic, _ := bip39.NewMnemonic(entropy)
mnemonic := "rural neither robot good glove bracket fee harsh bird iron segment rug"
fmt.Println("mnemonic:", mnemonic)
// 通过助记词生成种子Seed
seed := bip39.NewSeed(mnemonic, "") // password盐值不要加
fmt.Println("seed", seed)

// 接下来就是将种子恢复出主私钥 masterKey 这里进入到了BIP-32了 a
masterKey, _ := bip32.NewMasterKey(seed)
// 注意,此时还是主私钥,接下来要派生子私钥,派生出来的子私钥才是真正的“私钥”才能对应链的公钥,才能解压缩出地址
fmt.Println("masterKey", masterKey)

// 现在要派生出对应以太坊的子私钥,遵循BIP-44
// 接下来进入BIP-44 完成派生,完成对应path参数 m / purpose' / coin_type' / account' / change / address_index
// 通过主私钥派生出子私钥,FirstHardenedChild = uint32(0x80000000) 是一个常量,对应强化派生范围
key, _ := masterKey.NewChildKey(bip32.FirstHardenedChild + 44) // purpose' : 44 是固定值,即BIP-44标准,强化派生
key, _ = key.NewChildKey(bip32.FirstHardenedChild + uint32(118)) // coin_type' :118是cosmos标识', 继续强化派生
key, _ = key.NewChildKey(bip32.FirstHardenedChild + uint32(0)) // account' : 0 标记账户类型,从0开始,强化派生
key, _ = key.NewChildKey(uint32(0)) // change :0 外部可见地址, 1 找零地址(外部不可见),通常是 0,普通派生
key, _ = key.NewChildKey(uint32(0)) // 地址索引 0 1 2 3 这样索引,普通派生
// 派生完毕,对应的path 就是 " m/44'/118'/0'/0/0 "

// 子私钥key 已经出来了,先打印私钥,key.Key就是私钥,注意要转化进制
cosmosPrivateKey := hex.EncodeToString(key.Key) // 编码成字符串 , 这里打印出来的私钥就可以
fmt.Println("privateKey", cosmosPrivateKey)

fmt.Println("--------------COSMOS在公钥转地址的地方这里会有些不同---------------------")
// 要对公钥进行 SHA-256处理----> cosmos 的公钥要进行sha256 然后 ripemd哈希
sha256Hasher := sha256.New() // 相当于在创建sha256这个对象
sha256Hasher.Write(key.PublicKey().Key) // 这里就是在对公钥的字节进行sha256哈希加密
sha256Hash := sha256Hasher.Sum(nil) //

// 然后对 SHA-256 哈希结果进行 RIPEMD-160 哈希加密处理
ripemd160Hasher := ripemd160.New() // 同样 创建ripemd-160这个对象
ripemd160Hasher.Write(sha256Hash) // 这里是在对之前sha256加密后的公钥再进行RIPEMD-160
pubKeyHash := ripemd160Hasher.Sum(nil) //

// Bech32 编码
bech32Addr, err := bech32.ConvertAndEncode("cosmos", pubKeyHash)
if err != nil {
fmt.Println(err)
}
fmt.Println("cosmosAddress", bech32Addr)
// 最后把 cosmosPrivateKey 导入到 开普勒钱包 里面去验证下,私钥和地址对上了没

}

Python实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def generate_wallet():
privkey = PrivateKey().serialize()
return {
"private_key": privkey,
"public_key": privkey_to_pubkey(privkey),
"address": privkey_to_address(privkey),
}

def privkey_to_pubkey(privkey: str) -> str:
privkey_obj = PrivateKey(bytes.fromhex(privkey))
return privkey_obj.pubkey.serialize().hex()

def pubkey_to_address(pubkey: str) -> str:
pubkey_bytes = bytes.fromhex(pubkey)
s = hashlib.new("sha256", pubkey_bytes).digest()
r = hashlib.new("ripemd160", s).digest()
return bech32.bech32_encode("cosmos", bech32.convertbits(r, 8, 5))

def privkey_to_address(privkey: str) -> str:
pubkey = privkey_to_pubkey(privkey)
return pubkey_to_address(pubkey)

wallet = generate_wallet()
print(wallet)

离线签名 go编写步骤基于RPC

0 编写protoBuf 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
syntax = "proto3";

package cosmos;
option go_package = "github.com/wangxiaowang01/web3learning/hdwallet/cosmos";
import "google/protobuf/empty.proto";

service MyService {
rpc MyMethod (google.protobuf.Empty) returns (MyResponse) {}
}

message MyResponse {
string result = 1;
}

1 将protoBuf转成 pb.go

或者官方库里面也有pb.go文件

2 建立连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// NewClient 新建链接,用于创建一个 grpc 客户端
func NewClient() (grpcClient *grpc.ClientConn, err error) {
// gRPC 服务器的地址
serverAddress := "cosmos-grpc.publicnode.com:443"
var opts []grpc.DialOption // 创建一个空的opts切片,用于存储gRPC客户端连接选项。
// 这里的append是奖默认凭证添加到gRPC客户端链接选项中,意味着客户端奖使用默认的凭证进行身份认证
opts = append(opts, grpc.WithCredentialsBundle(google.NewDefaultCredentials()))

// 创建一个gRPC客户端连接,传入服务器地址和这个opts选项,也即是客户端凭证
grpcConn, err := grpc.NewClient(serverAddress, opts...)
if err != nil {
panic(err)
}
//接下来,代码中创建了一个 typetx.ServiceClient 客户端对象 txClient,用于与服务端的 Service 进行交互。这里的 typetx 可能是根据您的 .proto 文件生成的 Go 代码中的命名空间或包名。
// 这里才是最后的创建里一个客户端连接,生成的一个客户端类型
//txClient := typetx.NewServiceClient(grpcConn)

//然后,代码创建了一个 typetx.GetBlockWithTxsRequest 请求对象 req,并设置了其 Height 属性为要查询的块的高度(在这里是 20802601)。
//req := &typetx.GetBlockWithTxsRequest{Height: 20802601} // 这里应该是定义一个请求对象,就是说你的请求是什么
//rep2 := &typetx.GetHeight{}
//最后,代码通过调用 txClient.GetBlockWithTxs() 方法,传入一个 context 对象默认组和请求对象 req,向服务端发送请求并获取响应。响应对象存储在 resp 变量中,错误信息存储在 err 变量中。
//resp, err := txClient.GetBlockWithTxs(context.Background(), req) // 这里应该就是发送这个请求对象出去,接收响应
//if err != nil {
// panic(err)
//}
//fmt.Println(resp.Txs[0].Body)

bankClient := banktypes.NewQueryClient(grpcConn)
bankReq := &banktypes.QueryBalanceRequest{Address: "cosmos1sphlm2dp2a4v9hy7fzvqznus26g03vgz006ldt", Denom: "uatom"}
bankResp, err := bankClient.Balance(context.Background(), bankReq)
if err != nil {
panic(err)
}
fmt.Println("bank str=", bankResp.String())
return grpcConn, nil

}

3 组交易广播

要建立gRPC连接就要编protoBuff 文件转pb.go文件

要去链上拿用户信息就要建立连接—建立gRPC

组装交易之前要去链上查到用户信息,获取到Account_number 和 sequence gaslimit 等参数

先组装交易-—>生成一个未签名的交易哈希—->对未签名交易进行私钥签名—->签名后广播出去

发交易

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
package cosmos

import (
"context"
"encoding/hex"
"fmt"

"github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
sdk "github.com/cosmos/cosmos-sdk/types"
typetx "github.com/cosmos/cosmos-sdk/types/tx"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/gogo/protobuf/proto"
)

// func NewGrpcClient() (grpcClient *grpc.ClientConn, err error) {
// // 创建grpc连接
// target := "cosmos-grpc.publicnode.com:443"
// var opts []grpc.DialOption
// opts = append(opts, grpc.WithCredentialsBundle(google.NewDefaultCredentials()))
// grpcConn, err := grpc.NewClient(target, opts...)
// if err != nil {
// panic(err)
// }
// txClient := typetx.NewServiceClient(grpcConn)
//
// req := &typetx.GetBlockWithTxsRequest{Height: 20758858}
// resp, err := txClient.GetBlockWithTxs(context.Background(), req)
// if err != nil {
// panic(err)
// }
// fmt.Println(resp.Txs[0].Body)
// return grpcConn, nil
// }
//
// Import 根据十六进制字符串导入私钥。
// 参数 privKeyHex 是一个十六进制编码的私钥字符串。
// 返回值是一个指向 secp256k1 私钥结构的指针,以及可能的错误。
// 该函数的目的是将十六进制表示的私钥转换为 secp256k1 私钥结构,以供后续加密操作使用。
func Import(privKeyHex string) (*secp256k1.PrivKey, error) {
// 将十六进制字符串解码为字节序列
priBytes, err := hex.DecodeString(privKeyHex)
if err != nil {

return nil, err
}
// 创建并返回一个包含解码后字节序列的 secp256k1 私钥结构
return &secp256k1.PrivKey{Key: priBytes}, nil
}

// transaction 示例展示了如何在Cosmos SDK中创建和广播一个基本的交易。
// 它包括生成交易消息、构建交易、签名交易以及最后广播交易。
func transaction() {

// 导入私钥,用于生成交易的签名。私钥是已经派生完的子私钥
pk, _ := Import("9f904a195e9a0b8dc6f3305928c7654f7ac714370c4a5d7f97a4ae2b7e06e4f8")
// 从私钥派生账户地址。
var privKeyAccAddr sdk.AccAddress = pk.PubKey().Address().Bytes()

// 定义交易的接收方地址和转移的代币数量。
toAddr := sdk.MustAccAddressFromBech32("cosmos1sphlm2dp2a4v9hy7fzvqznus26g03vgz006ldt")
coin := sdk.NewInt64Coin("uatom", 1)
amount := sdk.NewCoins(coin)

// 创建一个转移代币的消息。定义msg 实例化msgSend
msg := banktypes.NewMsgSend(privKeyAccAddr, toAddr, amount)

// 通过gRPC连接到链的查询客户端。
grpcConn, _ := NewClient() // 会返回之前定义的gRPC连接
authClient := authtypes.NewQueryClient(grpcConn) // 新建客户端

// 查询发送方账户信息,以获取序列号和账户号,这些都是构建交易必需的。
fromAddrInfoAny, err := authClient.Account(context.Background(), &authtypes.QueryAccountRequest{Address: privKeyAccAddr.String()})
// 解析查询结果中的账户信息。
var f authtypes.BaseAccount
// 定义f为一个BaseAccount结构体,把fromAddrInfoAny的用户的信息实例化进对象f,这个时候f就有很多属性了
if err := proto.Unmarshal(fromAddrInfoAny.Account.Value, &f); err != nil {
panic(err)
}

// 定义交易费用和气体限制。定义gas fee 和 gas limit
fee := sdk.NewInt64Coin("uatom", 2000)
gasLimit := int64(100000)

// 构建交易,包括签名。
// 这里是构建一个完整的签名后的转账消息,后面广播用
txRaw, err := BuildTxV2(
"cosmoshub-4",
f.Sequence,
f.AccountNumber,
pk,
fee,
gasLimit,
[]sdk.Msg{msg},
)
if err != nil {
panic(err)
}

// 序列化交易,准备广播。
txBytes, err := proto.Marshal(txRaw)
if err != nil {
panic(err)
}

// 广播交易到链上。
// 广播这笔签名后的交易出去
txClient := typetx.NewServiceClient(grpcConn)
req := &typetx.BroadcastTxRequest{
TxBytes: txBytes,
Mode: typetx.BroadcastMode_BROADCAST_MODE_SYNC,
}
txResp, err := txClient.BroadcastTx(context.Background(), req)
if err != nil {
panic(err)
}

// 打印交易响应,包括交易的哈希和状态码。
fmt.Println(txResp.TxResponse)
}

// BuildTxV2 构建一笔交易
// BuildTxV2 根据给定参数构建交易v2版本。
// 参数:
//
// chainId - 区块链ID。
// sequence - 账户序列号。
// accountNumber - 账户编号。
// privKey - 私钥,用于交易签名。
// fee - 交易费用。
// gaslimit - 交易燃气限制。
// msgs - 交易消息数组。
//
// 返回:
//
// *typetx.TxRaw - 构建的交易原始数据。
// error - 如果构建过程中出现错误,则返回错误。
func BuildTxV2(chainId string, sequence, accountNumber uint64, privKey *secp256k1.PrivKey, fee sdk.Coin, gaslimit int64, msgs []sdk.Msg) (*typetx.TxRaw, error) {
// 初始化消息数组,用于存储不同类型的消息。
txBodyMessage := make([]*types.Any, 0)
// 遍历消息数组,将每个消息封装为Any类型,并添加到消息数组中。
for i := 0; i < len(msgs); i++ {
msgAnyValue, err := types.NewAnyWithValue(msgs[i])
if err != nil {
return nil, err
}
txBodyMessage = append(txBodyMessage, msgAnyValue)
}
// 初始化TxBody结构体,设置消息数组和其他必要字段。
// 实例化TxBody
txBody := &typetx.TxBody{
Messages: txBodyMessage,
Memo: "",
TimeoutHeight: 0,
ExtensionOptions: nil,
NonCriticalExtensionOptions: nil,
}
// 序列化TxBody为字节流。
txBodyBytes, err := proto.Marshal(txBody)
if err != nil {
return nil, err
}
// 将公钥封装为Any类型。
pubAny, err := types.NewAnyWithValue(privKey.PubKey())
if err != nil {
return nil, err
}
// 初始化AuthInfo结构体,设置公钥、签名信息和交易费用。定义交易模型
authInfo := &typetx.AuthInfo{
SignerInfos: []*typetx.SignerInfo{
{
PublicKey: pubAny,
ModeInfo: &typetx.ModeInfo{
Sum: &typetx.ModeInfo_Single_{
Single: &typetx.ModeInfo_Single{Mode: signing.SignMode_SIGN_MODE_DIRECT},
},
},
Sequence: sequence,
},
},
Fee: &typetx.Fee{
Amount: sdk.NewCoins(fee),
GasLimit: uint64(gaslimit),
Payer: "",
Granter: "",
},
}
// 序列化AuthInfo为字节流。
// 将账户信息模型序列化
txAuthInfoBytes, err := proto.Marshal(authInfo)
if err != nil {
return nil, err
}

// 初始化SignDoc结构体,包含交易体、授权信息、链ID和账户序列号。
signDoc := &typetx.SignDoc{
BodyBytes: txBodyBytes,
AuthInfoBytes: txAuthInfoBytes,
ChainId: chainId,
AccountNumber: accountNumber,
}
// 对SignDoc进行签名。
// 签名序列化
signatures, err := proto.Marshal(signDoc)
if err != nil {
return nil, err
}
// 使用私钥对签名数据进行签名。
sign, err := privKey.Sign(signatures)
if err != nil {
return nil, err
}
// 构建TxRaw结构体,包含交易体字节流、授权信息字节流和签名。
// 把签名后的TxRaw返回出去 下一步再是广播
return &typetx.TxRaw{
BodyBytes: txBodyBytes,
AuthInfoBytes: signDoc.AuthInfoBytes,
Signatures: [][]byte{sign},
}, nil
}

使用python代码实现,构建待广播的已签名交易

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# -*- coding: utf-8 -*-
import base64
import json
from secp256k1 import PrivateKey
from offlinAddre import privkey_to_address, privkey_to_pubkey


class Transaction:
def __init__(self, *, privkey: str, account_num: int, sequence: int, fee: int, gas: int, memo: str = "",
chain_id: str = "cosmoshub-2", sync_mode="sync"):
self.privkey = privkey
self.account_num = account_num
self.sequence = sequence
self.fee = fee
self.gas = gas
self.memo = memo
self.chain_id = chain_id
self.sync_mode = sync_mode
self.msgs = []

def add_atom_transfer(self, recipient: str, amount: int) -> None:
self.msgs.append(
{
"type": "cosmos-sdk/MsgSend",
"value": {
"from_address": privkey_to_address(self.privkey),
"to_address": recipient,
"amount": [{"denom": "uatom", "amount": str(amount)}],
},
}
)

def _get_sign_message(self):
return {
"chain_id": self.chain_id,
"account_number": str(self.account_num),
"fee": {"gas": str(self.gas), "amount": [{"amount": str(self.fee), "denom": "uatom"}]},
"memo": self.memo,
"sequence": str(self.sequence),
"msgs": self.msgs,
}

def _sign(self) -> str:
message_str = json.dumps(self._get_sign_message(), separators=(",", ":"), sort_keys=True)
message_bytes = message_str.encode("utf-8")
privkey = PrivateKey(bytes.fromhex(self.privkey))
signature = privkey.ecdsa_sign(message_bytes)
signature_compact = privkey.ecdsa_serialize_compact(signature)
signature_base64_str = base64.b64encode(signature_compact).decode("utf-8")
return signature_base64_str

def get_pushable_tx(self) -> str:
pubkey = privkey_to_pubkey(self.privkey)
base64_pubkey = base64.b64encode(bytes.fromhex(pubkey)).decode("utf-8")
pushable_tx = {
"tx": {
"msg": self.msgs,
"fee": {
"gas": str(self.gas),
"amount": [{"denom": "uatom", "amount": str(self.fee)}],
},
"memo": self.memo,
"signatures": [
{
"signature": self._sign(),
"pub_key": {"type": "tendermint/PubKeySecp256k1", "value": base64_pubkey},
"account_number": str(self.account_num),
"sequence": str(self.sequence),
}
],
},
"mode": self.sync_mode,
}
return json.dumps(pushable_tx, separators=(",", ":"))


tx = Transaction(
privkey="your_private_key",
account_num=11335,
sequence=0,
fee=1000,
gas=37000,
memo="hello cosmos",
chain_id="cosmoshub-2",
sync_mode="sync",
)
if __name__ == '__main__':

tx.add_atom_transfer(recipient="cosmosxxxxxxxx", amount=1)
pushable_tx = tx.get_pushable_tx()
print(pushable_tx)

开发中要用到的接口(HTTP)

去Cosmos官网

找到developer 找到Rest API

cosmos-rest.publicnode

1 获取账户信息 (addresNumber 、sequence)

请求:

1
curl --location 'https://cosmos-rest.publicnode.com/cosmos/auth/v1beta1/account_info/cosmos1z79jxnsw64c20upyfu8rfe89pdsel48kfmzjgu'

响应

1
2
3
4
5
6
7
8
9
10
11
{
"info": {
"address": "cosmos1z79jxnsw64c20upyfu8rfe89pdsel48kfmzjgu",
"pub_key": {
"@type": "/cosmos.crypto.secp256k1.PubKey",
"key": "A26PAcbmjZxcZqsXL/CJgjTHqImZeAJDe85ufR+JFh/B"
},
"account_number": "2782398",
"sequence": "1"
}
}

2 获取最新块高

1
curl --location 'https://cosmos-rest.publicnode.com/cosmos/base/tendermint/v1beta1/blocks/latest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"block_id": {
"hash": "RRihq+OoBPgRKlT2Pkw76bWYRBtPqQcxzzYG1XiwPaA=",
"part_set_header": {
"total": 1,
"hash": "e2LsR5+z12RKKt7f+bOs0R41yotYUoQXOpI9HJ2x+V4="
}
},
"block": {
"header": {
"version": {
"block": "11",
"app": "0"
},
"chain_id": "cosmoshub-4",
"height": "20801304",
"time": "2024-06-10T05:59:32.319047221Z",
"last_block_id": {
"hash": "PpuKAWOd7D3mchFXqm/IZDxW7VH0WGrX11/Kmdg6qlU=",
"part_set_header": {
"total": 1,
"hash": "cbxhE23bFrh1M7ezxSUDki5FO6+hUUYEXXdpQ6Bl4Y0="
}
},

3 查询指定块高内的交易

1
curl --location 'https://cosmos-rest.publicnode.com/cosmos/base/tendermint/v1beta1/blocks/20801304

拿到对应块高的txs即是对呀的交易

4 把你的交易解码,

你查询块高的时候返回的交易是base64的,你可以解码出来

1
2
3
4
5
curl --location 'https://cosmos-rest.publicnode.com/cosmos/tx/v1beta1/decode' \
--header 'Content-Type: application/json' \
--data '{
"tx_bytes": "CrsBCooBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmoKLWNvc21vczE4Y2p3ZWN4Y2c1Mmp6OTVkaHBkZzl1Y3JxM2NjeGYybHV1NjVnNRItY29zbW9zMW1ydHRhOXpjMGRzaDMwdmZkcXZmYW04a3djZ3g2cmdrYW0yam51GgoKBXVhdG9tEgExEixwcnl6bTE4Y2p3ZWN4Y2c1Mmp6OTVkaHBkZzl1Y3JxM2NjeGYybHl2ZG5rOBJnClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiECjHhA4pLCWWUbidhR9yMjsBSUnSRHSrCcmuDFcj3ZMp0SBAoCCH8YAxITCg0KBXVhdG9tEgQxNjkyENqQBBpAMRNaVaMWbdUgfpiRkC9LTB93FMWztlsIaTEk82zIMzZdRch9SqKbiEH433Ar9xyPQUrsM40OUWR+rNTRlXggqw=="
}'

你的交易签名之后返回的数据是base64的,可以直接广播出去,广播出去的交易哈希

5 根据 哈希 查询对应的交易

1
2
/cosmos/tx/v1beta1/txs/{hash}
curl --location 'https://cosmos-rest.publicnode.com/cosmos/tx/v1beta1/txs/{hash}

6 广播交易

广播的传参是你的签名构建的交易

1
2
3
4
curl --location 'https://cosmos-rest.publicnode.com/cosmos/tx/v1beta1/txs' \
--header 'Content-Type: application/json' \
--data ' {"tx_bytes":"CpgBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLWNvc21vczF6NzlqeG5zdzY0YzIwdXB5ZnU4cmZlODlwZHNlbDQ4a2ZtempndRItY29zbW9zMWVyNDB3cjN2Nzhhd3h0MDJrNGhxNmV2bDNxanJwZDRma2RtbXEzGg8KBXVhdG9tEgYxMDAwMDASBDEwMTASZwpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohA26PAcbmjZxcZqsXL/CJgjTHqImZeAJDe85ufR+JFh/BEgQKAggBGAESEwoNCgV1YXRvbRIEMTAwMBDj7AUaQP2sdB0DnYNOGSbHWoRbsEexeRbHEDNnyC4rt7EkVLDUde9xRHYNhda5DOd+q+C/n9O7muLaqvHq/FYifanfz+I=","mode":"BROADCAST_MODE_SYNC"}
'

中心化钱包:

充值

提现

归集

转冷热

扫块

去中心化钱包

获取账户余额

获取交易记录

获取签名参数


07-3 Cosmos 钱包开发流程
http://example.com/2024/06/10/07-3Cosmos钱包开发流程/
作者
Wangxiaowang
发布于
2024年6月10日
许可协议