摘要 本文说明如何生成一笔(未签名的)交易、对其进行签名(使用一个或多个密钥),并将其广播到网络。

使用 CLI

发送交易最简单的方式是使用 CLI,如上一页与节点交互时所示。例如,运行以下命令
simd tx bank send $MY_VALIDATOR_ADDRESS $RECIPIENT 1000stake --chain-id my-test-chain --keyring-backend test
将执行以下步骤:
  • 生成一笔包含一个 Msg(即 x/bank 的 MsgSend)的交易,并将生成的交易打印到控制台。
  • 请求用户确认是否从 $MY_VALIDATOR_ADDRESS 账户发送该交易。
  • 从 keyring 中获取 $MY_VALIDATOR_ADDRESS。之所以可行,是因为此前步骤中已经配置好 CLI 的 keyring。
  • 使用 keyring 中的账户对生成的交易进行签名。
  • 将已签名的交易广播到网络。之所以可行,是因为 CLI 会连接到节点的 CometBFT RPC 端点。
CLI 将所有必要步骤封装成了一个易于使用的用户体验。不过,你也可以分别单独执行所有步骤。

生成交易

在任意 tx 命令后追加 --generate-only 标志即可生成交易,例如:
simd tx bank send $MY_VALIDATOR_ADDRESS $RECIPIENT 1000stake --chain-id my-test-chain --generate-only
这会在控制台中以 JSON 形式输出未签名交易。也可以在上述命令后追加 > unsigned_tx.json,将未签名交易保存到文件中(以便在签名者之间更方便地传递)。

签名交易

使用 CLI 对交易签名时,需要先将未签名交易保存到文件中。这里假设未签名交易位于当前目录下名为 unsigned_tx.json 的文件中(上一段已说明如何操作)。然后,只需运行以下命令:
simd tx sign unsigned_tx.json --chain-id my-test-chain --keyring-backend test --from $MY_VALIDATOR_ADDRESS
该命令会解码未签名交易,并使用 keyring 中已配置的 $MY_VALIDATOR_ADDRESS 对应密钥,以 SIGN_MODE_DIRECT 进行签名。已签名交易会以 JSON 形式输出到控制台;同样,也可以追加 --output-document signed_tx.json 将其保存到文件中。 在 tx sign 命令中,一些值得关注的实用标志包括:
  • --sign-mode:你可以使用 amino-json,以 SIGN_MODE_LEGACY_AMINO_JSON 对交易进行签名。
  • --offline:以离线模式签名。这意味着 tx sign 命令不会连接节点来获取签名者的账户编号和序列号,而这两者都是签名所必需的。在这种情况下,你必须手动提供 --account-number 和 --sequence 标志。这对于离线签名很有用,也就是在无法访问互联网的安全环境中进行签名。

使用多个签名者签名

请注意,对于多签名者交易或多签账户交易,只要其中至少有一个签名者使用 SIGN_MODE_DIRECT,目前仍无法完成签名。更多信息可参见这个 Github issue。
多个签名者签名使用 tx multisign 命令完成。该命令假设所有签名者都使用 SIGN_MODE_LEGACY_AMINO_JSON。其流程与 tx sign 命令类似,但不同之处在于:每个签名者签署的不是未签名交易文件,而是前一个或前几个签名者已经签过的文件。tx multisign 命令会将签名追加到现有交易中。重要的是,签名者必须按照交易中给定的相同顺序进行签名,这个顺序可以通过 GetSigners() 方法获取。 例如,从 unsigned_tx.json 开始,并假设该交易有 4 个签名者,我们会运行:

# Let signer1 sign the unsigned tx.
simd tx multisign unsigned_tx.json signer_key_1 --chain-id my-test-chain --keyring-backend test > partial_tx_1.json

# Now signer1 will send the partial_tx_1.json to the signer2.

# Signer2 appends their signature:
simd tx multisign partial_tx_1.json signer_key_2 --chain-id my-test-chain --keyring-backend test > partial_tx_2.json

# Signer2 sends the partial_tx_2.json file to signer3, and signer3 can append his signature:
simd tx multisign partial_tx_2.json signer_key_3 --chain-id my-test-chain --keyring-backend test > partial_tx_3.json

广播交易

广播交易可使用以下命令:
simd tx broadcast tx_signed.json
你也可以选择传入 --broadcast-mode 标志,以指定希望从节点接收哪种响应:
  • sync:CLI 只等待 CheckTx 执行响应。
  • async:CLI 立即返回(交易可能会失败)。

对交易进行编码

如果要通过 gRPC 或 REST 端点广播交易,首先需要对交易进行编码。这可以通过 CLI 完成。 对交易进行编码可使用以下命令:
simd tx encode tx_signed.json
该命令会从文件中读取交易,使用 Protobuf 进行序列化,并在控制台中以 base64 输出交易字节。

对交易进行解码

CLI 也可用于解码交易字节。 对交易进行解码可使用以下命令:
simd tx decode [protobuf-byte-string]
该命令会解码交易字节,并在控制台中以 JSON 形式输出交易。你也可以在上述命令后追加 > tx.json,将交易保存到文件中。

使用 Go 以编程方式处理

可以通过 Go 使用 Cosmos SDK 的 TxBuilder 接口,以编程方式处理交易。

生成交易

在生成交易之前,需要先创建一个新的 TxBuilder 实例。由于 Cosmos SDK 同时支持 Amino 和 Protobuf 交易,第一步是决定使用哪种编码方案。无论使用 Amino 还是 Protobuf,后续步骤都保持不变,因为 TxBuilder 对编码机制进行了抽象。在下面的代码片段中,我们将使用 Protobuf。
import (
    
	"github.com/cosmos/cosmos-sdk/simapp"
)

func sendTx()

error {
    // Choose your codec: Amino or Protobuf. Here, we use Protobuf, given by the following function.
    app := simapp.NewSimApp(...)

    // Create a new TxBuilder.
    txBuilder := app.TxConfig().NewTxBuilder()

    // --snip--
}
下面的示例会设置一些发送和接收交易所需的密钥与地址。为便于本教程说明,这里使用虚拟数据来创建密钥。
import (
    
	"github.com/cosmos/cosmos-sdk/testutil/testdata"
)

priv1, _, addr1 := testdata.KeyTestPubAddr()

priv2, _, addr2 := testdata.KeyTestPubAddr()

priv3, _, addr3 := testdata.KeyTestPubAddr()
可以通过 TxBuilder 的方法来填充其内容:
package client

import (
    
	"time"

	txsigning "cosmossdk.io/x/tx/signing"

	codectypes "github.com/cosmos/cosmos-sdk/codec/types"
	sdk "github.com/cosmos/cosmos-sdk/types"
    "github.com/cosmos/cosmos-sdk/types/tx"
	signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing"
    "github.com/cosmos/cosmos-sdk/x/auth/signing"
)

type (
	// TxEncodingConfig defines an interface that contains transaction
	// encoders and decoders
	TxEncodingConfig interface {
    TxEncoder()

sdk.TxEncoder
		TxDecoder()

sdk.TxDecoder
		TxJSONEncoder()

sdk.TxEncoder
		TxJSONDecoder()

sdk.TxDecoder
		MarshalSignatureJSON([]signingtypes.SignatureV2) ([]byte, error)

UnmarshalSignatureJSON([]byte) ([]signingtypes.SignatureV2, error)
}

	// TxConfig defines an interface a client can utilize to generate an
	// application-defined concrete transaction type. The type returned must
	// implement TxBuilder.
	TxConfig interface {
    TxEncodingConfig

		NewTxBuilder()

TxBuilder
		WrapTxBuilder(sdk.Tx) (TxBuilder, error)

SignModeHandler() *txsigning.HandlerMap
		SigningContext() *txsigning.Context
}

	// TxBuilder defines an interface which an application-defined concrete transaction
	// type must implement. Namely, it must be able to set messages, generate
	// signatures, and provide canonical bytes to sign over. The transaction must
	// also know how to encode itself.
	TxBuilder interface {
    GetTx()

signing.Tx

		SetMsgs(msgs ...sdk.Msg)

error
		SetSignatures(signatures ...signingtypes.SignatureV2)

error
		SetMemo(memo string)

SetFeeAmount(amount sdk.Coins)

SetFeePayer(feePayer sdk.AccAddress)

SetGasLimit(limit uint64)

SetTimeoutHeight(height uint64)

SetTimeoutTimestamp(timestamp time.Time)

SetUnordered(v bool)

SetFeeGranter(feeGranter sdk.AccAddress)

AddAuxSignerData(tx.AuxSignerData)

error
}

	// ExtendedTxBuilder extends the TxBuilder interface,
	// which is used to set extension options to be included in a transaction.
	ExtendedTxBuilder interface {
    SetExtensionOptions(extOpts ...*codectypes.Any)
}
)
import (
    
	banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
)

func sendTx()

error {
    // --snip--

    // Define two x/bank MsgSend messages:
    // - from addr1 to addr3
    // - from addr2 to addr3
    // This means that the transaction needs two signers: addr1 and addr2.
    msg1 := banktypes.NewMsgSend(addr1, addr3, sdk.NewCoins(sdk.NewInt64Coin("atom", 12)))
    msg2 := banktypes.NewMsgSend(addr2, addr3, sdk.NewCoins(sdk.NewInt64Coin("atom", 34)))
    err := txBuilder.SetMsgs(msg1, msg2)
    if err != nil {
    return err
}

txBuilder.SetGasLimit(...)

txBuilder.SetFeeAmount(...)

txBuilder.SetMemo(...)

txBuilder.SetTimeoutHeight(...)
}
此时,TxBuilder 底层的交易已经可以进行签名了。

生成无序交易

从 Cosmos SDK v0.53.0 开始,用户可以向已启用该特性的链发送无序交易。
无序交易的 sequence 值必须保持未设置。如果一笔交易既是无序交易,又包含非零 sequence 值, 该交易将被拒绝。依赖先前交易 sequence 值假设的外部服务应更新,以正确处理无序交易。 服务需要注意,当交易为无序交易时,交易 sequence 将始终为零。
基于上面的示例,我们可以设置必需字段,将一笔交易标记为无序交易。 默认情况下,无序交易会额外收取 2240 单位 gas,以抵消支持其功能所带来的额外存储开销。 额外 gas 单位可由链自定义,因此具体数值因链而异;如有需要,请务必检查该链的 ante handler 中设置的 gas 值。
func sendTx()

error {
    // --snip--
    expiration := 5 * time.Minute
    txBuilder.SetUnordered(true)

txBuilder.SetTimeoutTimestamp(time.Now().Add(expiration + (1 * time.Nanosecond)))
}
来自同一账户的无序交易必须使用唯一的超时戳值。不过,每个超时戳之间的差值可以小到 1 纳秒。
import (
    
	"github.com/cosmos/cosmos-sdk/client"
)

func sendMessages(txBuilders []client.TxBuilder)

error {
    // --snip--
    expiration := 5 * time.Minute
    for _, txb := range txBuilders {
    txb.SetUnordered(true)

txb.SetTimeoutTimestamp(time.Now().Add(expiration + (1 * time.Nanosecond)))
}
}

签名交易

编码配置被设置为使用 Protobuf,因此默认会使用 SIGN_MODE_DIRECT。根据 ADR-020,每个签名者都需要对所有其他签名者的 SignerInfo 进行签名。这意味着必须按顺序执行两个步骤:
  • 对于每个签名者,在 TxBuilder 中填充该签名者的 SignerInfo
  • 在所有 SignerInfo 都填充完成后,对于每个签名者,对 SignDoc(待签名的负载)进行签名。
在当前 TxBuilder 的 API 中,这两个步骤都通过同一个方法完成:SetSignatures()。当前 API 要求先执行第一轮 SetSignatures(),使用空签名,仅用于填充 SignerInfo;然后再执行第二轮 SetSignatures(),对正确的负载进行实际签名。
import (
    
    cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
    "github.com/cosmos/cosmos-sdk/types/tx/signing"
	xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
)

func sendTx()

error {
    // --snip--
    privs := []cryptotypes.PrivKey{
    priv1, priv2
}
    accNums:= []uint64{..., ...
} // The accounts' account numbers
    accSeqs:= []uint64{..., ...
} // The accounts' sequence numbers

    // First round: we gather all the signer infos. We use the "set empty
    // signature" hack to do that.
    var sigsV2 []signing.SignatureV2
    for i, priv := range privs {
    sigV2 := signing.SignatureV2{
    PubKey: priv.PubKey(),
    Data: &signing.SingleSignatureData{
    SignMode:  encCfg.TxConfig.SignModeHandler().DefaultMode(),
    Signature: nil,
},
    Sequence: accSeqs[i],
}

sigsV2 = append(sigsV2, sigV2)
}
    err := txBuilder.SetSignatures(sigsV2...)
    if err != nil {
    return err
}

    // Second round: all signer infos are set, so each signer can sign.
    sigsV2 = []signing.SignatureV2{
}
    for i, priv := range privs {
    signerData := xauthsigning.SignerData{
    ChainID:       chainID,
    AccountNumber: accNums[i],
    Sequence:      accSeqs[i],
}

sigV2, err := tx.SignWithPrivKey(
            encCfg.TxConfig.SignModeHandler().DefaultMode(), signerData,
            txBuilder, priv, encCfg.TxConfig, accSeqs[i])
    if err != nil {
    return nil, err
}

sigsV2 = append(sigsV2, sigV2)
}

err = txBuilder.SetSignatures(sigsV2...)
    if err != nil {
    return err
}
}
现在,TxBuilder 已被正确填充。要打印它,可以使用初始编码配置 encCfg 中的 TxConfig 接口:
func sendTx()

error {
    // --snip--

    // Generated Protobuf-encoded bytes.
    txBytes, err := encCfg.TxConfig.TxEncoder()(txBuilder.GetTx())
    if err != nil {
    return err
}

    // Generate a JSON string.
    txJSONBytes, err := encCfg.TxConfig.TxJSONEncoder()(txBuilder.GetTx())
    if err != nil {
    return err
}
    txJSON := string(txJSONBytes)
}

广播交易

广播交易的首选方式是使用 gRPC,不过也可以使用 REST(通过 gRPC-gateway)或 CometBFT RPC。关于这些方法差异的概览可参见这里。本教程只介绍 gRPC 方法。
import (
    
    "context"
    "fmt"
    "google.golang.org/grpc"
    "github.com/cosmos/cosmos-sdk/types/tx"
)

func sendTx(ctx context.Context)

error {
    // --snip--

    // Create a connection to the gRPC server.
    grpcConn, err := grpc.Dial(
        "127.0.0.1:9090", // Or your gRPC server address.
        grpc.WithInsecure(), // The Cosmos SDK doesn't support any transport security mechanisms.
    )
    if err != nil {
        return err
    }

defer grpcConn.Close()

    // Broadcast the tx via gRPC. We create a new client for the Protobuf Tx
    // service.
    txClient := tx.NewServiceClient(grpcConn)
    // We then call the BroadcastTx method on this client.
    grpcRes, err := txClient.BroadcastTx(
        ctx,
        &tx.BroadcastTxRequest{
    Mode:    tx.BroadcastMode_BROADCAST_MODE_SYNC,
    TxBytes: txBytes, // Proto-binary of the signed transaction, see previous step.
},
    )
    if err != nil {
    return err
}

fmt.Println(grpcRes.TxResponse.Code) // Should be `0` if the tx is successful

    return nil
}

模拟交易

在广播交易之前,我们有时希望先对交易进行一次 dry-run,以便在不实际提交交易的情况下估算交易的某些信息。这称为模拟交易,可以按如下方式完成:
import (
    
	"context"
    "fmt"
    "testing"
    "github.com/cosmos/cosmos-sdk/client"
    "github.com/cosmos/cosmos-sdk/types/tx"
	authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
)

func simulateTx()

error {
    // --snip--

    // Simulate the tx via gRPC. We create a new client for the Protobuf Tx
    // service.
    txClient := tx.NewServiceClient(grpcConn)
    txBytes := /* Fill in with your signed transaction bytes. */

    // We then call the Simulate method on this client.
    grpcRes, err := txClient.Simulate(
        context.Background(),
        &tx.SimulateRequest{
    TxBytes: txBytes,
},
    )
    if err != nil {
    return err
}

fmt.Println(grpcRes.GasInfo) // Prints estimated gas used.

    return nil
}

使用 gRPC

无法使用 gRPC 生成或签名交易,gRPC 只能用于广播交易。要通过 gRPC 广播交易,你需要先通过 CLI 或以 Go 代码的方式生成、签名并编码交易。

广播交易

通过 gRPC 端点广播交易,可以按如下方式发送 BroadcastTx 请求,其中 txBytes 是已签名交易的 protobuf 编码字节:
grpcurl -plaintext \
    -d '{"tx_bytes":"{{txBytes}}","mode":"BROADCAST_MODE_SYNC"}' \
    localhost:9090 \
    cosmos.tx.v1beta1.Service/BroadcastTx

使用 REST

无法使用 REST 生成或签名交易,REST 只能用于广播交易。要通过 REST 广播交易,你需要先通过 CLI 或以 Go 代码的方式生成、签名并编码交易。

广播交易

通过 REST 端点(由 gRPC-gateway 提供)广播交易,可以按如下方式发送 POST 请求,其中 txBytes 是已签名交易的 protobuf 编码字节:
curl -X POST \
    -H "Content-Type: application/json" \
    -d'{"tx_bytes":"{{txBytes}}","mode":"BROADCAST_MODE_SYNC"}' \
    localhost:1317/cosmos/tx/v1beta1/txs

使用 CosmJS(JavaScript 与 TypeScript)

CosmJS 旨在构建可嵌入 Web 应用中的 JavaScript 客户端库。更多信息请参见 Link。

恭喜

你已经学习了如何使用 Cosmos SDK 手动生成、签名并广播交易。这些工作流为构建自定义交易工具和集成提供了基础。

后续步骤


Synopsis This document describes how to generate an (unsigned) transaction, signing it (with one or multiple keys), and broadcasting it to the network.

Using the CLI

The easiest way to send transactions is using the CLI, as shown in the previous page when interacting with a node. For example, running the following command
simd tx bank send $MY_VALIDATOR_ADDRESS $RECIPIENT 1000stake --chain-id my-test-chain --keyring-backend test
will run the following steps:
  • generate a transaction with one Msg (x/bank’s MsgSend), and print the generated transaction to the console.
  • ask the user for confirmation to send the transaction from the $MY_VALIDATOR_ADDRESS account.
  • fetch $MY_VALIDATOR_ADDRESS from the keyring. This is possible because the CLI’s keyring was set up in a previous step.
  • sign the generated transaction with the keyring’s account.
  • broadcast the signed transaction to the network. This is possible because the CLI connects to the node’s CometBFT RPC endpoint.
The CLI bundles all the necessary steps into a simple-to-use user experience. However, it is possible to run all the steps individually too.

Generating a Transaction

Generating a transaction can simply be done by appending the --generate-only flag on any tx command, e.g.:
simd tx bank send $MY_VALIDATOR_ADDRESS $RECIPIENT 1000stake --chain-id my-test-chain --generate-only
This will output the unsigned transaction as JSON in the console. The unsigned transaction can also be saved to a file (to be passed around between signers more easily) by appending > unsigned_tx.json to the above command.

Signing a Transaction

Signing a transaction using the CLI requires the unsigned transaction to be saved in a file. For this example, assume the unsigned transaction is in a file called unsigned_tx.json in the current directory (see previous paragraph on how to do that). Then, simply run the following command:
simd tx sign unsigned_tx.json --chain-id my-test-chain --keyring-backend test --from $MY_VALIDATOR_ADDRESS
This command will decode the unsigned transaction and sign it with SIGN_MODE_DIRECT with $MY_VALIDATOR_ADDRESS’s key, which was already set up in the keyring. The signed transaction will be output as JSON to the console, and, as above, it can be saved to a file by appending --output-document signed_tx.json. Some useful flags to consider in the tx sign command:
  • --sign-mode: you may use amino-json to sign the transaction using SIGN_MODE_LEGACY_AMINO_JSON,
  • --offline: sign in offline mode. This means that the tx sign command doesn’t connect to the node to retrieve the signer’s account number and sequence, both needed for signing. In this case, you must manually supply the --account-number and --sequence flags. This is useful for offline signing, i.e. signing in a secure environment which doesn’t have access to the internet.

Signing with Multiple Signers

Please note that signing a transaction with multiple signers or with a multisig account, where at least one signer uses SIGN_MODE_DIRECT, is not yet possible. You may follow this Github issue for more info.
Signing with multiple signers is done with the tx multisign command. This command assumes that all signers use SIGN_MODE_LEGACY_AMINO_JSON. The flow is similar to the tx sign command flow, but instead of signing an unsigned transaction file, each signer signs the file signed by previous signer(s). The tx multisign command will append signatures to the existing transactions. It is important that signers sign the transaction in the same order as given by the transaction, which is retrievable using the GetSigners() method. For example, starting with the unsigned_tx.json, and assuming the transaction has 4 signers, we would run:
# Let signer1 sign the unsigned tx.
simd tx multisign unsigned_tx.json signer_key_1 --chain-id my-test-chain --keyring-backend test > partial_tx_1.json
# Now signer1 will send the partial_tx_1.json to the signer2.
# Signer2 appends their signature:
simd tx multisign partial_tx_1.json signer_key_2 --chain-id my-test-chain --keyring-backend test > partial_tx_2.json
# Signer2 sends the partial_tx_2.json file to signer3, and signer3 can append his signature:
simd tx multisign partial_tx_2.json signer_key_3 --chain-id my-test-chain --keyring-backend test > partial_tx_3.json

Broadcasting a Transaction

Broadcasting a transaction is done using the following command:
simd tx broadcast tx_signed.json
You may optionally pass the --broadcast-mode flag to specify which response to receive from the node:
  • sync: the CLI waits for a CheckTx execution response only.
  • async: the CLI returns immediately (transaction might fail).

Encoding a Transaction

In order to broadcast a transaction using the gRPC or REST endpoints, the transaction will need to be encoded first. This can be done using the CLI. Encoding a transaction is done using the following command:
simd tx encode tx_signed.json
This will read the transaction from the file, serialize it using Protobuf, and output the transaction bytes as base64 in the console.

Decoding a Transaction

The CLI can also be used to decode transaction bytes. Decoding a transaction is done using the following command:
simd tx decode [protobuf-byte-string]
This will decode the transaction bytes and output the transaction as JSON in the console. You can also save the transaction to a file by appending > tx.json to the above command.

Programmatically with Go

It is possible to manipulate transactions programmatically via Go using the Cosmos SDK’s TxBuilder interface.

Generating a Transaction

Before generating a transaction, a new instance of a TxBuilder needs to be created. Since the Cosmos SDK supports both Amino and Protobuf transactions, the first step would be to decide which encoding scheme to use. All the subsequent steps remain unchanged, whether you’re using Amino or Protobuf, as TxBuilder abstracts the encoding mechanisms. In the following snippet, we will use Protobuf.
import (
    
	"github.com/cosmos/cosmos-sdk/simapp"
)

func sendTx()

error {
    // Choose your codec: Amino or Protobuf. Here, we use Protobuf, given by the following function.
    app := simapp.NewSimApp(...)

    // Create a new TxBuilder.
    txBuilder := app.TxConfig().NewTxBuilder()

    // --snip--
}
The following example sets up some keys and addresses that will send and receive the transactions. For the purpose of this tutorial, dummy data is used to create keys.
import (
    
	"github.com/cosmos/cosmos-sdk/testutil/testdata"
)

priv1, _, addr1 := testdata.KeyTestPubAddr()

priv2, _, addr2 := testdata.KeyTestPubAddr()

priv3, _, addr3 := testdata.KeyTestPubAddr()
Populating the TxBuilder can be done via its methods:
package client

import (
    
	"time"

	txsigning "cosmossdk.io/x/tx/signing"

	codectypes "github.com/cosmos/cosmos-sdk/codec/types"
	sdk "github.com/cosmos/cosmos-sdk/types"
    "github.com/cosmos/cosmos-sdk/types/tx"
	signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing"
    "github.com/cosmos/cosmos-sdk/x/auth/signing"
)

type (
	// TxEncodingConfig defines an interface that contains transaction
	// encoders and decoders
	TxEncodingConfig interface {
    TxEncoder()

sdk.TxEncoder
		TxDecoder()

sdk.TxDecoder
		TxJSONEncoder()

sdk.TxEncoder
		TxJSONDecoder()

sdk.TxDecoder
		MarshalSignatureJSON([]signingtypes.SignatureV2) ([]byte, error)

UnmarshalSignatureJSON([]byte) ([]signingtypes.SignatureV2, error)
}

	// TxConfig defines an interface a client can utilize to generate an
	// application-defined concrete transaction type. The type returned must
	// implement TxBuilder.
	TxConfig interface {
    TxEncodingConfig

		NewTxBuilder()

TxBuilder
		WrapTxBuilder(sdk.Tx) (TxBuilder, error)

SignModeHandler() *txsigning.HandlerMap
		SigningContext() *txsigning.Context
}

	// TxBuilder defines an interface which an application-defined concrete transaction
	// type must implement. Namely, it must be able to set messages, generate
	// signatures, and provide canonical bytes to sign over. The transaction must
	// also know how to encode itself.
	TxBuilder interface {
    GetTx()

signing.Tx

		SetMsgs(msgs ...sdk.Msg)

error
		SetSignatures(signatures ...signingtypes.SignatureV2)

error
		SetMemo(memo string)

SetFeeAmount(amount sdk.Coins)

SetFeePayer(feePayer sdk.AccAddress)

SetGasLimit(limit uint64)

SetTimeoutHeight(height uint64)

SetTimeoutTimestamp(timestamp time.Time)

SetUnordered(v bool)

SetFeeGranter(feeGranter sdk.AccAddress)

AddAuxSignerData(tx.AuxSignerData)

error
}

	// ExtendedTxBuilder extends the TxBuilder interface,
	// which is used to set extension options to be included in a transaction.
	ExtendedTxBuilder interface {
    SetExtensionOptions(extOpts ...*codectypes.Any)
}
)
import (
    
	banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
)

func sendTx()

error {
    // --snip--

    // Define two x/bank MsgSend messages:
    // - from addr1 to addr3
    // - from addr2 to addr3
    // This means that the transaction needs two signers: addr1 and addr2.
    msg1 := banktypes.NewMsgSend(addr1, addr3, sdk.NewCoins(sdk.NewInt64Coin("atom", 12)))
    msg2 := banktypes.NewMsgSend(addr2, addr3, sdk.NewCoins(sdk.NewInt64Coin("atom", 34)))
    err := txBuilder.SetMsgs(msg1, msg2)
    if err != nil {
    return err
}

txBuilder.SetGasLimit(...)

txBuilder.SetFeeAmount(...)

txBuilder.SetMemo(...)

txBuilder.SetTimeoutHeight(...)
}
At this point, TxBuilder’s underlying transaction is ready to be signed.

Generating an Unordered Transaction

Starting with Cosmos SDK v0.53.0, users may send unordered transactions to chains that have the feature enabled.
Unordered transactions MUST leave sequence values unset. When a transaction is both unordered and contains a non-zero sequence value, the transaction will be rejected. External services that operate on prior assumptions about transaction sequence values should be updated to handle unordered transactions. Services should be aware that when the transaction is unordered, the transaction sequence will always be zero.
Using the example above, we can set the required fields to mark a transaction as unordered. By default, unordered transactions charge an extra 2240 units of gas to offset the additional storage overhead that supports their functionality. The extra units of gas are customizable and therefore vary by chain, so be sure to check the chain’s ante handler for the gas value set, if any.
func sendTx()

error {
    // --snip--
    expiration := 5 * time.Minute
    txBuilder.SetUnordered(true)

txBuilder.SetTimeoutTimestamp(time.Now().Add(expiration + (1 * time.Nanosecond)))
}
Unordered transactions from the same account must use a unique timeout timestamp value. The difference between each timeout timestamp value may be as small as a nanosecond, however.
import (
    
	"github.com/cosmos/cosmos-sdk/client"
)

func sendMessages(txBuilders []client.TxBuilder)

error {
    // --snip--
    expiration := 5 * time.Minute
    for _, txb := range txBuilders {
    txb.SetUnordered(true)

txb.SetTimeoutTimestamp(time.Now().Add(expiration + (1 * time.Nanosecond)))
}
}

Signing a Transaction

The encoding config is set to use Protobuf, which will use SIGN_MODE_DIRECT by default. As per ADR-020, each signer needs to sign the SignerInfos of all other signers. This means that two steps must be performed sequentially:
  • for each signer, populate the signer’s SignerInfo inside TxBuilder
  • once all SignerInfos are populated, for each signer, sign the SignDoc (the payload to be signed).
In the current TxBuilder’s API, both steps are done using the same method: SetSignatures(). The current API requires a first round of SetSignatures() with empty signatures, only to populate SignerInfos, and a second round of SetSignatures() to actually sign the correct payload.
import (
    
    cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
    "github.com/cosmos/cosmos-sdk/types/tx/signing"
	xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
)

func sendTx()

error {
    // --snip--
    privs := []cryptotypes.PrivKey{
    priv1, priv2
}
    accNums:= []uint64{..., ...
} // The accounts' account numbers
    accSeqs:= []uint64{..., ...
} // The accounts' sequence numbers

    // First round: we gather all the signer infos. We use the "set empty
    // signature" hack to do that.
    var sigsV2 []signing.SignatureV2
    for i, priv := range privs {
    sigV2 := signing.SignatureV2{
    PubKey: priv.PubKey(),
    Data: &signing.SingleSignatureData{
    SignMode:  encCfg.TxConfig.SignModeHandler().DefaultMode(),
    Signature: nil,
},
    Sequence: accSeqs[i],
}

sigsV2 = append(sigsV2, sigV2)
}
    err := txBuilder.SetSignatures(sigsV2...)
    if err != nil {
    return err
}

    // Second round: all signer infos are set, so each signer can sign.
    sigsV2 = []signing.SignatureV2{
}
    for i, priv := range privs {
    signerData := xauthsigning.SignerData{
    ChainID:       chainID,
    AccountNumber: accNums[i],
    Sequence:      accSeqs[i],
}

sigV2, err := tx.SignWithPrivKey(
            encCfg.TxConfig.SignModeHandler().DefaultMode(), signerData,
            txBuilder, priv, encCfg.TxConfig, accSeqs[i])
    if err != nil {
    return nil, err
}

sigsV2 = append(sigsV2, sigV2)
}

err = txBuilder.SetSignatures(sigsV2...)
    if err != nil {
    return err
}
}
The TxBuilder is now correctly populated. To print it, you can use the TxConfig interface from the initial encoding config encCfg:
func sendTx()

error {
    // --snip--

    // Generated Protobuf-encoded bytes.
    txBytes, err := encCfg.TxConfig.TxEncoder()(txBuilder.GetTx())
    if err != nil {
    return err
}

    // Generate a JSON string.
    txJSONBytes, err := encCfg.TxConfig.TxJSONEncoder()(txBuilder.GetTx())
    if err != nil {
    return err
}
    txJSON := string(txJSONBytes)
}

Broadcasting a Transaction

The preferred way to broadcast a transaction is to use gRPC, though using REST (via gRPC-gateway) or the CometBFT RPC is also possible. An overview of the differences between these methods is exposed here. For this tutorial, we will only describe the gRPC method.
import (
    
    "context"
    "fmt"
    "google.golang.org/grpc"
    "github.com/cosmos/cosmos-sdk/types/tx"
)

func sendTx(ctx context.Context)

error {
    // --snip--

    // Create a connection to the gRPC server.
    grpcConn, err := grpc.Dial(
        "127.0.0.1:9090", // Or your gRPC server address.
        grpc.WithInsecure(), // The Cosmos SDK doesn't support any transport security mechanisms.
    )
    if err != nil {
        return err
    }

defer grpcConn.Close()

    // Broadcast the tx via gRPC. We create a new client for the Protobuf Tx
    // service.
    txClient := tx.NewServiceClient(grpcConn)
    // We then call the BroadcastTx method on this client.
    grpcRes, err := txClient.BroadcastTx(
        ctx,
        &tx.BroadcastTxRequest{
    Mode:    tx.BroadcastMode_BROADCAST_MODE_SYNC,
    TxBytes: txBytes, // Proto-binary of the signed transaction, see previous step.
},
    )
    if err != nil {
    return err
}

fmt.Println(grpcRes.TxResponse.Code) // Should be `0` if the tx is successful

    return nil
}

Simulating a Transaction

Before broadcasting a transaction, we sometimes may want to dry-run the transaction to estimate some information about the transaction without actually committing it. This is called simulating a transaction, and can be done as follows:
import (
    
	"context"
    "fmt"
    "testing"
    "github.com/cosmos/cosmos-sdk/client"
    "github.com/cosmos/cosmos-sdk/types/tx"
	authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
)

func simulateTx()

error {
    // --snip--

    // Simulate the tx via gRPC. We create a new client for the Protobuf Tx
    // service.
    txClient := tx.NewServiceClient(grpcConn)
    txBytes := /* Fill in with your signed transaction bytes. */

    // We then call the Simulate method on this client.
    grpcRes, err := txClient.Simulate(
        context.Background(),
        &tx.SimulateRequest{
    TxBytes: txBytes,
},
    )
    if err != nil {
    return err
}

fmt.Println(grpcRes.GasInfo) // Prints estimated gas used.

    return nil
}

Using gRPC

It is not possible to generate or sign a transaction using gRPC, only to broadcast one. In order to broadcast a transaction using gRPC, you will need to generate, sign, and encode the transaction using either the CLI or programmatically with Go.

Broadcasting a Transaction

Broadcasting a transaction using the gRPC endpoint can be done by sending a BroadcastTx request as follows, where the txBytes are the protobuf-encoded bytes of a signed transaction:
grpcurl -plaintext \
    -d '{"tx_bytes":"{{txBytes}}","mode":"BROADCAST_MODE_SYNC"}' \
    localhost:9090 \
    cosmos.tx.v1beta1.Service/BroadcastTx

Using REST

It is not possible to generate or sign a transaction using REST, only to broadcast one. In order to broadcast a transaction using REST, you will need to generate, sign, and encode the transaction using either the CLI or programmatically with Go.

Broadcasting a Transaction

Broadcasting a transaction using the REST endpoint (served by gRPC-gateway) can be done by sending a POST request as follows, where the txBytes are the protobuf-encoded bytes of a signed transaction:
curl -X POST \
    -H "Content-Type: application/json" \
    -d'{"tx_bytes":"{{txBytes}}","mode":"BROADCAST_MODE_SYNC"}' \
    localhost:1317/cosmos/tx/v1beta1/txs

Using CosmJS (JavaScript & TypeScript)

CosmJS aims to build client libraries in JavaScript that can be embedded in web applications. Please see Link for more information.

Congratulations!

You have learned how to manually generate, sign, and broadcast transactions using the Cosmos SDK. These workflows provide the foundation for building custom transaction tools and integrations.

Next steps