Skip to main content
This guide shows how to send multiple transfers in a single transaction using Highload Wallet v3. This is the main feature of the wallet, enabling up to 254 messages per transaction.

Objective

By the end of this guide, you will:
  • Send multiple transfers (up to 254) in a single transaction
  • Understand the two-transaction flow for batch transfers
  • Know how to calculate the compute fees for the internal transaction

Prerequisites

  • Completed wallet creation with funded balance and saved configuration in .wallet.json

Step 1: Load wallet configuration

Load the wallet data and create the wallet instance:
import { TonClient, internal, toNano, comment } from '@ton/ton';
import { mnemonicToPrivateKey } from '@ton/crypto';
import { Cell, SendMode } from '@ton/core';
import { HighloadWalletV3 } from './wrappers/HighloadWalletV3';
import { HighloadQueryId } from './wrappers/HighloadQueryId';
import * as fs from 'fs';

// Load wallet data
const walletData = JSON.parse(fs.readFileSync('.wallet.json', 'utf-8'));
const mnemonic = walletData.mnemonic.split(' ');
const keyPair = await mnemonicToPrivateKey(mnemonic);

const CODE = Cell.fromBoc(Buffer.from('b5ee9c7241021001000228000114ff00f4a413f4bcf2c80b01020120020d02014803040078d020d74bc00101c060b0915be101d0d3030171b0915be0fa4030f828c705b39130e0d31f018210ae42e5a4ba9d8040d721d74cf82a01ed55fb04e030020120050a02027306070011adce76a2686b85ffc00201200809001aabb6ed44d0810122d721d70b3f0018aa3bed44d08307d721d70b1f0201200b0c001bb9a6eed44d0810162d721d70b15800e5b8bf2eda2edfb21ab09028409b0ed44d0810120d721f404f404d33fd315d1058e1bf82325a15210b99f326df82305aa0015a112b992306dde923033e2923033e25230800df40f6fa19ed021d721d70a00955f037fdb31e09130e259800df40f6fa19cd001d721d70a00937fdb31e0915be270801f6f2d48308d718d121f900ed44d0d3ffd31ff404f404d33fd315d1f82321a15220b98e12336df82324aa00a112b9926d32de58f82301de541675f910f2a106d0d31fd4d307d30cd309d33fd315d15168baf2a2515abaf2a6f8232aa15250bcf2a304f823bbf2a35304800df40f6fa199d024d721d70a00f2649130e20e01fe5309800df40f6fa18e13d05004d718d20001f264c858cf16cf8301cf168e1030c824cf40cf8384095005a1a514cf40e2f800c94039800df41704c8cbff13cb1ff40012f40012cb3f12cb15c9ed54f80f21d0d30001f265d3020171b0925f03e0fa4001d70b01c000f2a5fa4031fa0031f401fa0031fa00318060d721d300010f0020f265d2000193d431d19130e272b1fb00b585bf03', 'hex'))[0];

const client = new TonClient({
    endpoint: 'https://testnet.toncenter.com/api/v2/jsonRPC', // This is TESTNET endpoint
    // apiKey: 'your-api-key' // Optional: get from @tonapibot or @tontestnetapibot
});

const wallet = client.open(
    HighloadWalletV3.createFromConfig(
        {
            publicKey: keyPair.publicKey,
            subwalletId: walletData.subwalletId,
            timeout: walletData.timeout,
        },
        CODE
    )
);

Step 2: Prepare the message batch

Create an array of messages to send:
const messages = [];
for (let i = 1; i <= 10; i++) {
    messages.push({
        type: 'sendMsg' as const,
        mode: SendMode.PAY_GAS_SEPARATELY,
        outMsg: internal({
            to: 'EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c', // Zero address (for testing)
            value: toNano('0.001'),
            body: comment(`#${i}`),
            bounce: false,
        }),
    });
}
Each message in the batch:
  • type: 'sendMsg' — specifies that this is an outgoing message action
  • modesend mode for each individual message
  • outMsg — the internal message to send

Step 3: Send the batch

Send the batch using the sendBatch method:
const queryId = HighloadQueryId.fromSeqno(17n); // Use a specific seqno
const createdAt = Math.floor(Date.now() / 1000) - 30; // 30 seconds ago
const value = toNano('0.0007024'); // Compute fee for internal receiver

await wallet.sendBatch(
    keyPair.secretKey,
    messages,
    walletData.subwalletId,
    queryId,
    walletData.timeout,
    createdAt,
    value
);

console.log('Batch sent: 10 transfers');
console.log(`Query ID: ${queryId.toSeqno()}`);
console.log(`Created At: ${createdAt}`);

Parameter explanation

queryId — Unique identifier for replay protection:
  • Each query_id can only be processed once within the protection window
  • HighloadQueryId is a wrapper class that represents the composite query_id as a sequential counter
  • Use HighloadQueryId.fromSeqno(n) to create a specific query ID
  • Provides getNext() method to increment to the next unique ID
  • Total range: 8,380,416 unique IDs
  • See Query ID structure for details
createdAt — Message timestamp for expiration:
  • Set to 30 seconds before current time: Math.floor(Date.now() / 1000) - 30
  • Compensates for blockchain time lag (lite-servers use last block time, not current time)
  • See Timestamp validation for why this is necessary
value — Compute fee for the internal transaction:
  • The internal receiver (Transaction 2) consumes a fixed 1,756 gas to process the action list
  • At current gas prices, this equals ~0.0007024 TON
  • This fee is sent to the wallet itself to cover internal message processing
  • If insufficient, the internal transaction may fail (but replay protection remains intact)

How it works

Batch transfers use a two-transaction pattern: the external message marks the query_id as processed and sends an internal message to itself with the action list, then the internal transaction processes the actions and sends all outgoing messages. See Message sending flow for the complete validation sequence.

Next steps

You can send multiple batches in parallel, each with a unique query_id. To verify that your batch was fully processed, see How to verify message is processed.