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.
- Raw format: Includes the workchain ID and state hash in hexadecimal (e.g.,
0:ca6e321c7cce9ecedf0a8ca2492ec8592494aa5fb5ce0387dff96ef6af982a3e). - User-friendly format: A Base64-encoded string with flags indicating bounceability and testnet usage (e.g.,
EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF).
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:
| Prefix | Bounceable | Testnet |
|---|---|---|
E... | Yes | No |
U... | No | No |
k... | Yes | Yes |
0... | No | Yes |
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 versionValidating 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:
- Jetton Master Address
- User Address
- Jetton Wallet Code
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:
- Get Vault and Pool addresses from Factory.
- Ensure readiness.
- 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.