|
| 1 | +# Building a Transaction |
| 2 | + |
| 3 | +These are the steps to build, sign and broadcast a transaction using v2 semantics. |
| 4 | + |
| 5 | +1. Correctly set up imports |
| 6 | + |
| 7 | +```go |
| 8 | +import ( |
| 9 | + "context" |
| 10 | + "fmt" |
| 11 | + "log" |
| 12 | + |
| 13 | + "google.golang.org/grpc" |
| 14 | + "google.golang.org/grpc/credentials/insecure" |
| 15 | + |
| 16 | + apisigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1" |
| 17 | + "cosmossdk.io/client/v2/broadcast/comet" |
| 18 | + "cosmossdk.io/client/v2/tx" |
| 19 | + "cosmossdk.io/core/transaction" |
| 20 | + "cosmossdk.io/math" |
| 21 | + banktypes "cosmossdk.io/x/bank/types" |
| 22 | + codectypes "github.com/cosmos/cosmos-sdk/codec/types" |
| 23 | + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" |
| 24 | + "github.com/cosmos/cosmos-sdk/crypto/keyring" |
| 25 | + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" |
| 26 | + |
| 27 | + "github.com/cosmos/cosmos-sdk/codec" |
| 28 | + addrcodec "github.com/cosmos/cosmos-sdk/codec/address" |
| 29 | + sdk "github.com/cosmos/cosmos-sdk/types" |
| 30 | +) |
| 31 | + |
| 32 | +``` |
| 33 | + |
| 34 | +2. Create a gRPC connection |
| 35 | + |
| 36 | +```go |
| 37 | +clientConn, err := grpc.NewClient("127.0.0.1:9090", grpc.WithTransportCredentials(insecure.NewCredentials())) |
| 38 | +if err != nil { |
| 39 | + log.Fatal(err) |
| 40 | +} |
| 41 | +``` |
| 42 | + |
| 43 | +3. Setup codec and interface registry |
| 44 | + |
| 45 | +```go |
| 46 | + // Setup interface registry and register necessary interfaces |
| 47 | + interfaceRegistry := codectypes.NewInterfaceRegistry() |
| 48 | + banktypes.RegisterInterfaces(interfaceRegistry) |
| 49 | + authtypes.RegisterInterfaces(interfaceRegistry) |
| 50 | + cryptocodec.RegisterInterfaces(interfaceRegistry) |
| 51 | + |
| 52 | + // Create a ProtoCodec for encoding/decoding |
| 53 | + protoCodec := codec.NewProtoCodec(interfaceRegistry) |
| 54 | + |
| 55 | +``` |
| 56 | + |
| 57 | +4. Initialize keyring |
| 58 | + |
| 59 | +```go |
| 60 | + |
| 61 | + ckr, err := keyring.New("autoclikeyring", "test", home, nil, protoCodec) |
| 62 | + if err != nil { |
| 63 | + log.Fatal("error creating keyring", err) |
| 64 | + } |
| 65 | + kr, err := keyring.NewAutoCLIKeyring(ckr, addrcodec.NewBech32Codec("cosmos")) |
| 66 | + if err != nil { |
| 67 | + log.Fatal("error creating auto cli keyring", err) |
| 68 | + } |
| 69 | + |
| 70 | + |
| 71 | +``` |
| 72 | + |
| 73 | +5. Setup transaction parameters |
| 74 | + |
| 75 | +```go |
| 76 | + |
| 77 | + // Setup transaction parameters |
| 78 | + txParams := tx.TxParameters{ |
| 79 | + ChainID: "simapp-v2-chain", |
| 80 | + SignMode: apisigning.SignMode_SIGN_MODE_DIRECT, |
| 81 | + AccountConfig: tx.AccountConfig{ |
| 82 | + FromAddress: "cosmos1t0fmn0lyp2v99ga55mm37mpnqrlnc4xcs2hhhy", |
| 83 | + FromName: "alice", |
| 84 | + }, |
| 85 | + } |
| 86 | + |
| 87 | + // Configure gas settings |
| 88 | + gasConfig, err := tx.NewGasConfig(100, 100, "0stake") |
| 89 | + if err != nil { |
| 90 | + log.Fatal("error creating gas config: ", err) |
| 91 | + } |
| 92 | + txParams.GasConfig = gasConfig |
| 93 | + |
| 94 | + // Create auth query client |
| 95 | + authClient := authtypes.NewQueryClient(clientConn) |
| 96 | + |
| 97 | + // Retrieve account information for the sender |
| 98 | + fromAccount, err := getAccount("cosmos1t0fmn0lyp2v99ga55mm37mpnqrlnc4xcs2hhhy", authClient, protoCodec) |
| 99 | + if err != nil { |
| 100 | + log.Fatal("error getting from account: ", err) |
| 101 | + } |
| 102 | + |
| 103 | + // Update txParams with the correct account number and sequence |
| 104 | + txParams.AccountConfig.AccountNumber = fromAccount.GetAccountNumber() |
| 105 | + txParams.AccountConfig.Sequence = fromAccount.GetSequence() |
| 106 | + |
| 107 | + // Retrieve account information for the recipient |
| 108 | + toAccount, err := getAccount("cosmos1e2wanzh89mlwct7cs7eumxf7mrh5m3ykpsh66m", authClient, protoCodec) |
| 109 | + if err != nil { |
| 110 | + log.Fatal("error getting to account: ", err) |
| 111 | + } |
| 112 | + |
| 113 | + // Configure transaction settings |
| 114 | + txConf, _ := tx.NewTxConfig(tx.ConfigOptions{ |
| 115 | + AddressCodec: addrcodec.NewBech32Codec("cosmos"), |
| 116 | + Cdc: protoCodec, |
| 117 | + ValidatorAddressCodec: addrcodec.NewBech32Codec("cosmosval"), |
| 118 | + EnabledSignModes: []apisigning.SignMode{apisigning.SignMode_SIGN_MODE_DIRECT}, |
| 119 | + }) |
| 120 | +``` |
| 121 | + |
| 122 | +6. Build the transaction |
| 123 | + |
| 124 | +```go |
| 125 | +// Create a transaction factory |
| 126 | + f, err := tx.NewFactory(kr, codec.NewProtoCodec(codectypes.NewInterfaceRegistry()), nil, txConf, addrcodec.NewBech32Codec("cosmos"), clientConn, txParams) |
| 127 | + if err != nil { |
| 128 | + log.Fatal("error creating factory", err) |
| 129 | + } |
| 130 | + |
| 131 | + // Define the transaction message |
| 132 | + msgs := []transaction.Msg{ |
| 133 | + &banktypes.MsgSend{ |
| 134 | + FromAddress: fromAccount.GetAddress().String(), |
| 135 | + ToAddress: toAccount.GetAddress().String(), |
| 136 | + Amount: sdk.Coins{ |
| 137 | + sdk.NewCoin("stake", math.NewInt(1000000)), |
| 138 | + }, |
| 139 | + }, |
| 140 | + } |
| 141 | + |
| 142 | + // Build and sign the transaction |
| 143 | + tx, err := f.BuildsSignedTx(context.Background(), msgs...) |
| 144 | + if err != nil { |
| 145 | + log.Fatal("error building signed tx", err) |
| 146 | + } |
| 147 | + |
| 148 | + |
| 149 | +``` |
| 150 | + |
| 151 | +7. Broadcast the transaction |
| 152 | + |
| 153 | +```go |
| 154 | +// Create a broadcaster for the transaction |
| 155 | + c, err := comet.NewCometBFTBroadcaster("http://127.0.0.1:26675", comet.BroadcastSync, protoCodec) |
| 156 | + if err != nil { |
| 157 | + log.Fatal("error creating comet broadcaster", err) |
| 158 | + } |
| 159 | + |
| 160 | + // Broadcast the transaction |
| 161 | + res, err := c.Broadcast(context.Background(), tx.Bytes()) |
| 162 | + if err != nil { |
| 163 | + log.Fatal("error broadcasting tx", err) |
| 164 | + } |
| 165 | + |
| 166 | +``` |
| 167 | + |
| 168 | +8. Helpers |
| 169 | + |
| 170 | +```go |
| 171 | +// getAccount retrieves account information using the provided address |
| 172 | +func getAccount(address string, authClient authtypes.QueryClient, codec codec.Codec) (sdk.AccountI, error) { |
| 173 | + // Query account info |
| 174 | + accountQuery, err := authClient.Account(context.Background(), &authtypes.QueryAccountRequest{ |
| 175 | + Address: string(address), |
| 176 | + }) |
| 177 | + if err != nil { |
| 178 | + return nil, fmt.Errorf("error getting account: %w", err) |
| 179 | + } |
| 180 | + |
| 181 | + // Unpack the account information |
| 182 | + var account sdk.AccountI |
| 183 | + err = codec.InterfaceRegistry().UnpackAny(accountQuery.Account, &account) |
| 184 | + if err != nil { |
| 185 | + return nil, fmt.Errorf("error unpacking account: %w", err) |
| 186 | + } |
| 187 | + |
| 188 | + return account, nil |
| 189 | +} |
| 190 | +``` |
0 commit comments