Skip to main content

Overview

TON deposits require constructing a TON-specific message payload. The flow differs depending on whether you’re transferring native TON or a Jetton (TRC-20-like token on TON). The call_data from the Layerswap API contains the information needed to build the correct payload. Prerequisites:

call_data Format

For TON, call_data is a JSON string containing:
{
  "amount": "1000000000",
  "asset": "TON",
  "comment": "layerswap_memo_identifier"
}
  • comment — A text memo that Layerswap uses to identify the deposit. This is embedded in the transaction payload.
  • amount — The transfer amount in base units (nanotons for TON, base units of the Jetton otherwise). Always present; for native TON it matches the transfer action’s amount field.
  • asset — The source asset symbol (e.g. TON, USDT). Informational; the actual contract is on the transfer action’s token.contract.

Native TON Transfer

For native TON transfers (when token.contract is null), the transaction is a simple message to the deposit address with a comment payload.

Transaction Construction

1

Parse call_data

Extract the comment field from the JSON.
2

Build the comment cell

Create a TON cell with a 32-bit zero prefix (indicates a text comment) followed by the comment string.
3

Construct the message

Send a message to to_address with the deposit amount (converted to nanotons) and the comment cell as payload.

Full Example

import { beginCell, toNano } from "@ton/ton";

function buildNativeTonTransaction(
  depositAction: any
) {
  const { call_data, to_address, amount } = depositAction;
  const { comment } = JSON.parse(call_data);

  const body = beginCell()
    .storeUint(0, 32)           // 0 opcode = text comment
    .storeStringTail(comment)
    .endCell();

  return {
    validUntil: Math.floor(Date.now() / 1000) + 360,
    messages: [
      {
        address: to_address,
        amount: toNano(amount).toString(),
        payload: body.toBoc().toString("base64"),
      },
    ],
  };
}

Jetton Transfer

For Jetton transfers (when token.contract is not null), you need to build a Jetton transfer message that targets the sender’s Jetton wallet address (not the Jetton master contract).

Transaction Construction

1

Parse call_data

Extract both comment and amount from the JSON.
2

Resolve the sender's Jetton wallet

Use the Jetton master contract to look up the sender’s Jetton wallet address via get_wallet_address.
3

Build the Jetton transfer cell

Construct a cell with the Jetton transfer opcode (0x0f8a7ea5), the Jetton amount, the destination address, and a forward payload containing the comment.
4

Send the message

Send the message to the sender’s Jetton wallet address with enough TON attached to cover fees.

Full Example

import {
  Address,
  JettonMaster,
  TonClient,
  beginCell,
  toNano,
} from "@ton/ton";

async function buildJettonTransaction(
  depositAction: any,
  senderAddress: string,
  tonClient: TonClient
) {
  const { call_data, to_address, token } = depositAction;
  const { comment, amount: jettonAmount } = JSON.parse(call_data);

  const destinationAddress = Address.parse(to_address);
  const userAddress = Address.parse(senderAddress);

  // Build the forward payload (comment)
  const forwardPayload = beginCell()
    .storeUint(0, 32)
    .storeStringTail(comment)
    .endCell();

  // Build the Jetton transfer body
  const body = beginCell()
    .storeUint(0x0f8a7ea5, 32)         // Jetton transfer opcode
    .storeUint(0, 64)                   // query_id
    .storeCoins(BigInt(jettonAmount))   // Jetton amount in base units
    .storeAddress(destinationAddress)   // destination
    .storeAddress(destinationAddress)   // response excess destination
    .storeBit(0)                        // no custom payload
    .storeCoins(toNano("0.00002"))      // forward amount for notification
    .storeBit(1)                        // forward payload as reference
    .storeRef(forwardPayload)
    .endCell();

  // Resolve the sender's Jetton wallet address
  const jettonMasterAddress = Address.parse(token.contract);
  const jettonMaster = tonClient.open(
    JettonMaster.create(jettonMasterAddress)
  );
  const jettonWalletAddress =
    await jettonMaster.getWalletAddress(userAddress);

  return {
    validUntil: Math.floor(Date.now() / 1000) + 360,
    messages: [
      {
        address: jettonWalletAddress.toString(),
        amount: toNano("0.045").toString(), // TON for fees; excess is returned
        payload: body.toBoc().toString("base64"),
      },
    ],
  };
}

Sending the Transaction

The TON Connect tab is for browser wallets like Tonkeeper or MyTonWallet — it picks between the native and Jetton builders above based on token.contract. The Server-side tab signs and sends directly with a wallet key for backend use.
import { TonConnectUI } from "@tonconnect/ui-react";
import { TonClient } from "@ton/ton";

async function executeTonDeposit(
  depositAction: any,
  senderAddress: string,
  tonConnectUI: TonConnectUI,
  tonClient: TonClient
) {
  const { token } = depositAction;
  let transaction;

  if (token.contract) {
    transaction = await buildJettonTransaction(
      depositAction,
      senderAddress,
      tonClient
    );
  } else {
    transaction = buildNativeTonTransaction(depositAction);
  }

  const result = await tonConnectUI.sendTransaction(transaction);

  // result.boc contains the sent message BOC
  // You can use it to track the transaction on-chain
  return result.boc;
}
The server-side example above shows a native TON transfer. For Jetton transfers, build the Jetton cell as shown in the Jetton section and target the sender’s Jetton wallet address.

Next Step

After the transaction is submitted, notify Layerswap so it can match your deposit faster:
curl -X POST https://api.layerswap.io/api/v2/swaps/{swap_id}/deposit_speedup \
  -H "X-LS-APIKEY: your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "transaction_id": "YOUR_TX_HASH" }'
See the full deposit flow for details.