2

I'm having trouble sending tokens to many addresses simultaneously. As far as I know, Cosmos does not support batch sending, so I have to make one tx for each recipient, and make sure the account sequence (nonce) is correct for each tx.

So if I specify the account sequence manually, I can create many send transactions at once - but I still have to wait for the node to confirm one tx and update the account sequence before sending the next one.

What can one do in this case?

randomshinichi
  • 420
  • 5
  • 15
  • Do you have one or multiple source addresses? This is an important difference since every sender address needs to sign the transaction. – Simon Warta Jan 18 '21 at 20:43

3 Answers3

3

Cosmos actually does support a MultiSend operation on the bank module, unfortunately it is not wired into the clients for support typically however it is widely used by exhanges such as Coinbase to optimize transfers.

https://github.com/cosmos/cosmos-sdk/blob/e957fad1a7ffd73712cd681116c9b6e09fa3e60b/x/bank/keeper/msg_server.go#L73

Ira Miller
  • 287
  • 2
  • 4
1

I have since been reminded that a single Cosmos transaction can consist of multiple Messages, where each Message is a message to the bank module to send tokens to one account. That's how batch sending works in Cosmos.

Unfortunately this is not available via the command line, so one has to use the SDK to make such a transaction.

randomshinichi
  • 420
  • 5
  • 15
1

Here's a link to CosmJS demonstration of sending a transaction with two messages and two signatures on the v0.39.x LTS release of the Cosmos SDK here (hat tip Ethan Frey from Confio for pointing this out)

In case the link changes at any point here's the code:

describe("appendSignature", () => {
    it("works", async () => {
      pendingWithoutLaunchpad();
      const wallet0 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0));
      const wallet1 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1));
      const client0 = new SigningCosmosClient(launchpad.endpoint, faucet.address0, wallet0);
      const client1 = new SigningCosmosClient(launchpad.endpoint, faucet.address1, wallet1);

      const msg1: MsgSend = {
        type: "cosmos-sdk/MsgSend",
        value: {
          from_address: faucet.address0,
          to_address: makeRandomAddress(),
          amount: coins(1234567, "ucosm"),
        },
      };
      const msg2: MsgSend = {
        type: "cosmos-sdk/MsgSend",
        value: {
          from_address: faucet.address1,
          to_address: makeRandomAddress(),
          amount: coins(1234567, "ucosm"),
        },
      };
      const fee = {
        amount: coins(2000, "ucosm"),
        gas: "160000", // 2*80k
      };
      const memo = "This must be authorized by the two of us";

      const signed = await client0.sign([msg1, msg2], fee, memo);

      const cosigned = await client1.appendSignature(signed);
      expect(cosigned.msg).toEqual([msg1, msg2]);
      expect(cosigned.fee).toEqual(fee);
      expect(cosigned.memo).toEqual(memo);
      expect(cosigned.signatures).toEqual([
        {
          pub_key: faucet.pubkey0,
          signature: jasmine.stringMatching(base64Matcher),
        },
        {
          pub_key: faucet.pubkey1,
          signature: jasmine.stringMatching(base64Matcher),
        },
      ]);
      // Ensure signed transaction is valid
      const broadcastResult = await client0.broadcastTx(cosigned);
      assertIsBroadcastTxSuccess(broadcastResult);
    });
  });

For doing this kind of thing in Golang, like with the Cosmos SDK client CLI, you can take the distribution module as an example: https://github.com/cosmos/cosmos-sdk/blob/master/x/distribution/client/cli/tx.go#L165-L180

msgs := make([]sdk.Msg, 0, len(validators))
            for _, valAddr := range validators {
                val, err := sdk.ValAddressFromBech32(valAddr)
                if err != nil {
                    return err
                }

                msg := types.NewMsgWithdrawDelegatorReward(delAddr, val)
                if err := msg.ValidateBasic(); err != nil {
                    return err
                }
                msgs = append(msgs, msg)
            }

            chunkSize, _ := cmd.Flags().GetInt(FlagMaxMessagesPerTx)
            return newSplitAndApply(tx.GenerateOrBroadcastTxCLI, clientCtx, cmd.Flags(), msgs, chunkSize)
okwme
  • 740
  • 1
  • 7
  • 19