TON Cookbook: A Developer's Guide to The Open Network

·

The Open Network (TON) is a high-performance blockchain designed for scalability, speed, and developer flexibility. As decentralized applications grow on TON, developers face common challenges when interacting with smart contracts, managing assets like Jettons and NFTs, and integrating with decentralized exchanges. This guide compiles essential best practices, code patterns, and workflow optimizations to help you build efficiently and securely on TON.

Whether you're transferring tokens, parsing transactions, or deploying NFT collections, this resource provides practical solutions with real-world code examples in JavaScript, Go, and Python—focused on clarity, correctness, and performance.

Working with TON Addresses

Converting Between User-Friendly and Raw Address Formats

TON addresses uniquely identify smart contracts and wallets across the network. They exist in two primary formats: raw and user-friendly.

To parse and convert between these formats using the @ton/core SDK:

import { Address } from "@ton/core";

const address1 = Address.parse('EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF');
const address2 = Address.parse('0:ca6e321c7cce9ecedf0a8ca2492ec8592494aa5fb5ce0387dff96ef6af982a3e');

console.log(address1.toString());         // User-friendly
console.log(address1.toRawString());      // Raw format

👉 Discover powerful tools to test your TON address logic in real time.

Understanding Address Flags and URL Safety

User-friendly addresses include metadata encoded in the first few bits, allowing quick identification of properties:

PrefixBounceableTestnet
E...YesNo
U...NoNo
k...YesYes
0...NoYes

These flags follow TEP-2 standards. The bounceable flag determines whether undelivered messages are returned; testnet-only indicates sandbox environment use.

Additionally, the urlSafe parameter ensures compatibility in URLs by replacing + with - and / with _.

console.log(address.toString());                            // Default: URL-safe bounceable
console.log(address.toString({ urlSafe: false }));          // Standard Base64
console.log(address.toString({ bounceable: false }));       // Non-bounceable
console.log(address.toString({ testOnly: true }));          // Testnet version

Validating TON Addresses

Before processing any address input, always validate it to prevent runtime errors or security issues.

Using @ton/core:

try {
  Address.parse('EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF');
  console.log("Valid address");
} catch (error) {
  console.error("Invalid address");
}

This ensures only correctly formatted addresses proceed through your application logic.

Sending Transactions and Managing Wallets

Transferring TON and Sending Messages

To send value or data between wallets, you must construct an internal message via a wallet contract. Most projects use Wallet V4 or V5.

Example using @ton/ton:

import { TonClient, WalletContractV4, internal } from "@ton/ton";
import { mnemonicToPrivateKey } from "@ton/crypto";

const client = new TonClient({
  endpoint: 'https://testnet.toncenter.com/api/v2/jsonRPC'
});

const keyPair = await mnemonicToPrivateKey(mnemonics);
const wallet = WalletContractV4.create({ workchain: 0, publicKey: keyPair.publicKey });
const contract = client.open(wallet);

const seqno = await contract.getSeqno();
await contract.sendTransfer({
  seqno,
  secretKey: keyPair.secretKey,
  messages: [internal({
    value: '1',
    to: 'EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N',
    body: 'Example transfer'
  })]
});

You can include up to four messages per transaction for batch operations.

Storing Long Strings Using Snake Format

Cells in TON can hold only 1023 bits (~127 bytes). For longer strings, use snake encoding, where data spans multiple linked cells.

function writeStringTail(str, cell) {
  const bytes = Math.floor(cell.bits.getFreeBits() / 8);
  if (bytes < str.length) {
    cell.bits.writeString(str.substring(0, bytes));
    const newCell = writeStringTail(str.substring(bytes), new TonWeb.boc.Cell());
    cell.refs.push(newCell);
  } else {
    cell.bits.writeString(str);
  }
  return cell;
}

Many SDKs provide built-in functions for this. Always remember: comment messages start with a 32-bit opcode 0.

Jetton (Token) Standards (TEP-74)

Calculating Jetton Wallet Addresses Off-Chain

Each user has a unique Jetton Wallet per token type. Instead of calling the master contract’s get_wallet_address, you can compute it locally.

Given:

import { Address, Cell, beginCell, storeStateInit } from '@ton/core';

const jettonWalletStateInit = beginCell()
  .store(storeStateInit({
    code: JETTON_WALLET_CODE,
    data: beginCell()
      .storeCoins(0)
      .storeAddress(USER_ADDRESS)
      .storeAddress(JETTON_MASTER_ADDRESS)
      .storeRef(JETTON_WALLET_CODE)
      .endCell()
  }))
  .endCell();

const walletAddress = new Address(0, jettonWalletStateInit.hash());

This avoids unnecessary API calls and improves UX in dApps.

Building Jetton Transfer Messages with Comments

Use TEP-74 to structure transfers:

const forwardPayload = beginCell()
  .storeUint(0, 32) // Comment opcode
  .storeStringTail('Hello, TON!')
  .endCell();

const messageBody = beginCell()
  .storeUint(0x0f8a7ea5, 32) // Jetton transfer opcode
  .storeUint(0, 64)
  .storeCoins(toNano(5))
  .storeAddress(destinationAddress)
  .storeAddress(responseAddress)
  .storeBit(0)
  .storeCoins(toNano('0.02')) // Forward amount
  .storeBit(1)
  .storeRef(forwardPayload)
  .endCell();

If forward_amount > 0, the recipient receives a notification with the attached payload.

👉 Explore how to integrate secure token transfers into your dApp today.

NFT Standards (TEP-62)

Batch Minting NFTs Efficiently

Deploy up to ~130 NFTs in one transaction due to gas limits. Use dictionaries to pack data:

const nftDict = Dictionary.empty(Dictionary.Keys.Uint(64), {
  serialize: (src, builder) => {
    builder.storeCoins(toNano(nftMinStorage));
    builder.storeRef(src);
  },
  parse: (src) => {/* reverse logic */}
});

nftDict.set(index, nftContent);

Total cost includes base fee + per-item storage + forward fees based on metadata size.

Updating Collection Ownership and Metadata

Change ownership with opcode 3:

.storeUint(3, 32)
.storeUint(0, 64)
.storeAddress(newOwnerAddress)

Update content (metadata URLs and royalties) with opcode 4. Royalty info must be included even if unchanged.

Interacting with Decentralized Exchanges (DEX)

Swapping Tokens on DeDust

DeDust uses Vaults and Pools. To swap TON for a Jetton:

  1. Get Vault and Pool addresses from Factory.
  2. Ensure readiness.
  3. Send swap message.
const tonVault = tonClient.open(await factory.getNativeVault());
const pool = tonClient.open(await factory.getPool(PoolType.VOLATILE, [TON, SCALE]));

await tonVault.sendSwap(sender, {
  poolAddress: pool.address,
  amount: toNano("5"),
  gasAmount: toNano("0.25")
});

For Jetton-to-Jetton swaps, send a transfer to the source Jetton Vault with a SwapPayload.

Transaction and Message Handling

Parsing Account Transactions

Use getTransactions() to fetch recent activity:

const transactions = await client.getTransactions(myAddress, { limit: 5 });

for (const tx of transactions) {
  const body = tx.inMessage?.body.beginParse();
  const op = body.loadUint(32);

  if (op === 0x7362d09c) {
    // Jetton transfer notification
    const jettonAmount = body.loadCoins();
    const comment = body.loadRef().beginParse().loadStringTail();
  }
}

Always verify message sources to prevent spoofing.

Finding Transactions by BOC Hash

When using TON Connect, retrieve the external message BOC and match its hash:

const extHash = Cell.fromBase64(exBoc).hash().toString('hex');
const inHash = beginCell().store(storeMessage(inMsg)).endCell().hash().toString('hex');

if (extHash === inHash) {
  console.log("Transaction found:", tx.hash().toString('hex'));
}

Retry periodically until confirmed or timeout.


Frequently Asked Questions

Q: What is the difference between raw and user-friendly TON addresses?
A: Raw addresses are in hexadecimal format showing workchain and hash. User-friendly ones are Base64-encoded with flags for bounceability and testnet use, making them safer for end users.

Q: How do I prevent fake Jetton transfer notifications?
A: Always verify that the sender address matches the expected Jetton Wallet derived from the user and master addresses using on-chain get methods.

Q: Can I send multiple messages in one transaction?
A: Yes, most wallet versions support up to four internal messages per sendTransfer call, useful for batch operations.

Q: Why does my snake-encoded string fail to decode?
A: Ensure all segments are properly linked via cell references and that you’re reading recursively until no more refs exist.

Q: Is it safe to compute Jetton Wallet addresses off-chain?
A: Yes—so long as you use the correct wallet code and follow TEP-74 standards. This method is widely used in wallets and dApps.

Q: How do I handle large NFT metadata?
A: Store metadata off-chain (e.g., IPFS or HTTPS) and reference it in the NFT content cell using URL-safe strings.

👉 Start building performant, secure blockchain apps on TON with advanced tools.