Filters

# Compose Complex Transactions

In the Interchain, a transaction is able to encapsulate multiple messages.

In this section, you will:

  • Send multiple tokens in a single transaction.
  • Sign and broadcast.
  • Assemble multiple messages.

# Send multiple tokens using sendTokens

In the previous exercise, you had Alice send tokens back to the faucet. To refresh your memory, this is what the sendTokens function takes as input:

Copy public async sendTokens( senderAddress: string, recipientAddress: string, amount: readonly Coin[], fee: StdFee | "auto" | number, memo = "", ): Promise<DeliverTxResponse>; packages stargate src signingstargateclient.ts View source

Coin (opens new window) allows Alice to send not just stake but also any number of other coins as long as she owns them. So she can:

However, there are limitations with this function. First, Alice can only target a single recipient per transaction, faucet in the previous examples. If she wants to send tokens to multiple recipients, then she needs to create as many transactions as there are recipients. Multiple transactions cost slightly more than packing transfers into the array because of transaction overhead. Additionally, in some cases it is considered a bad user experience to make users sign multiple transactions.

The second limitation is that separate transfers are not atomic. It is possible that Alice wants to send tokens to two recipients and it is important that either they both receive them or neither of them receive anything.

Fortunately, there is a way to atomically send tokens to multiple recipients.

# Introducing signAndBroadcast

SigningStargateClient has the signAndBroadcast function:

Copy public async signAndBroadcast( signerAddress: string, messages: readonly EncodeObject[], fee: StdFee | "auto" | number, memo = "", ): Promise<DeliverTxResponse>; packages stargate src signingstargateclient.ts View source

The basic components of a transaction are the signerAddress, the messages that it contains, as well as the fee and an optional memo. As such, Cosmos transactions can indeed be composed of multiple messages.

# Token transfer messages

In order to use signAndBroadcast to send tokens, you need to figure out what messages go into the messages: readonly EncodeObject[]. Examine the sendTokens function body:

Copy const sendMsg: MsgSendEncodeObject = { typeUrl: "/cosmos.bank.v1beta1.MsgSend", value: { fromAddress: senderAddress, toAddress: recipientAddress, amount: [...amount], }, }; return this.signAndBroadcast(senderAddress, [sendMsg], fee, memo); packages stargate src signingstargateclient.ts View source

Therefore, when sending back to the faucet, instead of calling:

Copy const result = await signingClient.sendTokens( alice, faucet, [{ denom: "uatom", amount: "100000" }], { amount: [{ denom: "uatom", amount: "500" }], gas: "200000", }, )

Alice may as well call:

Copy const result = await signingClient.signAndBroadcast( // the signerAddress alice, // the message(s) [ { typeUrl: "/cosmos.bank.v1beta1.MsgSend", value: { fromAddress: alice, toAddress: faucet, amount: [ { denom: "uatom", amount: "100000" }, ], }, }, ], // the fee { amount: [{ denom: "uatom", amount: "500" }], gas: "200000", }, )

Confirm this by making the change in your experiment.ts from the previous section, and running it again.

Building a transaction in this way is recommended. SigningStargateClient offers you convenience methods such as sendTokens for simple use cases, and to demonstrate how to build messages.

If you are wondering whether there could be any legitimate situation where the transaction's signer (here alice) could ever be different from the message's fromAddress (here alice too), then have a look at the tutorial on authz.

# What is this long string?

As a reminder from the previous tutorial, the typeUrl: "/cosmos.bank.v1beta1.MsgSend" string comes from the Protobuf definitions and is a mixture of:

  1. The package where MsgSend is initially declared:

    Copy package cosmos.bank.v1beta1; proto cosmos ... v1beta1 tx.proto View source
  2. And the name of the message itself, MsgSend:

    Copy message MsgSend { ... } proto cosmos ... v1beta1 tx.proto View source

To learn how to make your own types for your own blockchain project, head to Create Custom CosmJS Interfaces.

# Multiple token transfer messages

From here, you add an extra message for a token transfer from Alice to someone else:

Copy const result = await signingClient.signAndBroadcast( // signerAddress alice, [ // message 1 { typeUrl: "/cosmos.bank.v1beta1.MsgSend", value: { fromAddress: alice, toAddress: faucet, amount: [ { denom: "uatom", amount: "100000" }, ], }, }, // message 2 { typeUrl: "/cosmos.bank.v1beta1.MsgSend", value: { fromAddress: alice, toAddress: some_other_address, amount: [ { denom: "token", amount: "10" }, ], }, }, ], // the fee "auto", )

Note how the custom fee input was replaced with the auto input, which simulates the transaction to estimate the fee for you. In order to make that work well, you need to define the gasPrice you are willing to pay and its prefix when setting up your signingClient. You replace your original line of code with:

Copy const signingClient = await SigningStargateClient.connectWithSigner( rpc, aliceSigner, + { + prefix: "cosmos", + gasPrice: GasPrice.fromString("0.0025uatom") + } )

# Mixing other message types

The above example shows you two token-transfer messages in a single transaction. You can see this with their typeUrl: "/cosmos.bank.v1beta1.MsgSend".

Neither the Cosmos SDK nor CosmJS limits you to combining messages of the same type. You can decide to combine other message types together with a token transfer. For instance, in one transaction Alice could:

  1. Send tokens to the faucet.
  2. Delegate some of her tokens to a validator.

How would Alice create the second message? The SigningStargateClient contains a predefined list (a registry) of typeUrls that are supported by default (opens new window), because they're considered to be the most commonly used messages in the Cosmos SDK. Among the staking types (opens new window) there is MsgDelegate, and that is exactly what you need. Click the source links above and below to see the rest of the typeUrls that come with SigningStargateClient:

Copy export const stakingTypes: ReadonlyArray<[string, GeneratedType]> = [ ... ["/cosmos.staking.v1beta1.MsgDelegate", MsgDelegate], ... ]; packages stargate ... staking messages.ts View source

Click through to the type definition, and in the cosmjs-types repository:

Copy export interface MsgDelegate { delegatorAddress: string; validatorAddress: string; amount?: Coin; } src cosmos ... v1beta1 tx.ts View source

Now that you know the typeUrl for delegating some tokens is /cosmos.staking.v1beta1.MsgDelegate, you need to find a validator's address that Alice can delegate to. Find a list of validators in the testnet explorer (opens new window). Select a validator and set their address as a variable:

Copy const validator: string = "cosmosvaloper178h4s6at5v9cd8m9n7ew3hg7k9eh0s6wptxpcn" //01node

Use this variable in the following script, which you can copy to replace your original token transfer:

Copy const result = await signingClient.signAndBroadcast( alice, [ { typeUrl: "/cosmos.bank.v1beta1.MsgSend", value: { fromAddress: alice, toAddress: faucet, amount: [ { denom: "uatom", amount: "100000" }, ], }, }, { typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", value: { delegatorAddress: alice, validatorAddress: validator, amount: { denom: "uatom", amount: "1000", }, }, }, ], "auto" )

When you create your own message types in CosmJS, they have to follow this format and be declared in the same fashion.

synopsis

To summarize, this section has explored:

  • How to move past the one-transaction-one-recipient limitations of the previous exercise, which could compel a user to sign potentially many transactions at a time, and denies the possibility of sending atomic transactions to multiple recipients (for example, a situation in which either all recipients receive tokens or none of them do).
  • How to include two token-transfer messages in a single transaction, and how to combine messages of different types in a single transaction (for example, sending tokens to the faucet and delegating tokens to a validator).