Skip to main content

Overview

This guide shows you how to sign TON transactions using Dynamic’s Node SDK. You build your transaction cell using @ton/ton, serialize it to a base64 string, and pass it to signTransaction(). The method returns a base64-encoded Ed25519 signature that you then attach to the cell before broadcasting.

Prerequisites

Step 1: Install Dependencies

bun add @ton/ton @ton/crypto

Step 2: Build, Sign, and Send a Transaction

The approach for signing transactions depends on how you created your wallet: If you created your wallet with backUpToClientShareService: true, you can sign directly without retrieving key shares:
import { DynamicTonWalletClient } from '@dynamic-labs-wallet/node-ton';
import { WalletContractV5R1, internal, beginCell, toNano } from '@ton/ton';

export const authenticatedTonClient = async () => {
  const client = new DynamicTonWalletClient({
    environmentId: process.env.DYNAMIC_ENVIRONMENT_ID!,
  });

  await client.authenticateApiToken(process.env.DYNAMIC_AUTH_TOKEN!);
  return client;
};

const tonClient = await authenticatedTonClient();

// Build the transfer message body
const transferBody = beginCell()
  .storeUint(0, 32)   // op (0 = text comment)
  .storeStringTail('Hello, TON!')
  .endCell();

// Serialize to base64 string for signing
const transactionString = transferBody.toBoc().toString('base64');

// ✅ Simple signing - no externalServerKeyShares needed
// Returns a base64-encoded Ed25519 signature
const signature = await tonClient.signTransaction({
  senderAddress: 'YourTonWalletAddress',
  transaction: transactionString,
});

console.log('Signature (base64):', signature);

With Manual Backup

If you created your wallet with backUpToClientShareService: false, you must retrieve and provide external key shares:
// ⚠️ First, get external server key shares (required for manual backup)
const keyShares = await tonClient.getExternalServerKeyShares({
  accountAddress: 'YourTonWalletAddress',
});

const signature = await tonClient.signTransaction({
  senderAddress: 'YourTonWalletAddress',
  externalServerKeyShares: keyShares, // Required for manual backup!
  transaction: transactionString,
  password: 'your-password', // Only if wallet was created with password
});
Password Handling Notes:
  • If your wallet was created without a password, omit the password parameter
  • If your wallet was created with a password, you must provide it for all operations
  • The password parameter is always optional in the API, but required if the wallet is password-protected

Step 3: Broadcast the Transaction

After signing, attach the signature to the cell and broadcast via the TON HTTP API:
import { WalletContractV5R1, Cell } from '@ton/ton';

// Reconstruct the signed external message
const signatureBuffer = Buffer.from(signature, 'base64');

// Build the signed message cell
const signedCell = beginCell()
  .storeBuffer(signatureBuffer)
  .storeSlice(transferBody.beginParse())
  .endCell();

const boc = signedCell.toBoc().toString('base64');

const response = await fetch('https://toncenter.com/api/v2/sendBoc', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ boc }),
});

const result = await response.json();
console.log('Broadcast result:', result);

Common Transaction Bodies

Simple TON Transfer (no comment)

const transferBody = beginCell().endCell();
const transactionString = transferBody.toBoc().toString('base64');

TON Transfer with Comment

const transferBody = beginCell()
  .storeUint(0, 32)
  .storeStringTail('Payment for services')
  .endCell();
const transactionString = transferBody.toBoc().toString('base64');

Next Steps