Understanding how wallet smart contracts function is essential for any developer building on The Open Network (TON). These contracts are not just digital wallets—they are intelligent, self-executing programs that enable secure transactions, support advanced features like plugins and subscriptions, and interact seamlessly with other smart contracts across the blockchain.
In this comprehensive guide, we'll explore the inner workings of TON wallet smart contracts, from message types and transaction validation to deployment and high-load use cases. Whether you're building decentralized applications (dApps), managing bulk transactions, or simply want to understand how your wallet processes transfers, this article will provide deep technical insights while maintaining clarity.
🔍 Core Concepts of TON Wallets
All wallets on TON are implemented as smart contracts. This means they follow predefined logic written in FunC or processed via libraries like @ton/core. Unlike traditional blockchains where wallets are simple key-based accounts, TON wallets can be fully customized and extended.
Why Smart Contract Wallets Matter
Smart contract wallets unlock powerful capabilities:
- Programmable transactions: Automate fund transfers based on conditions.
- Multi-signature support: Require multiple approvals for critical operations.
- Plugin integration: Wallet v4 supports installable plugins for subscription models.
- Replay protection: Prevent duplicate transaction execution using sequence numbers or query IDs.
👉 Discover how smart contract wallets power next-gen blockchain interactions.
📡 Internal vs External Messages
Two primary message types govern communication on TON: internal and external messages.
External Messages
External messages originate outside the blockchain—typically from a user interface or API—and are used to trigger actions within a smart contract.
When a wallet receives an external message:
- It verifies the sender's signature.
- Checks the
seqno(sequence number) to prevent replay attacks. - Validates expiration time (
valid_until) to ensure timeliness. - Accepts the message only if all checks pass.
Example structure in code:
var signature = in_msg~load_bits(512);
var cs = in_msg;
var (subwallet_id, valid_until, msg_seqno) = (
cs~load_uint(32),
cs~load_uint(32),
cs~load_uint(32)
);
throw_if(35, valid_until <= now());Internal Messages
Internal messages are generated by smart contracts themselves and are used to transfer value or data between contracts.
They include:
- Recipient address
- Amount of TON or tokens
- Optional payload (e.g., comments, instructions)
- Forwarding rules (bounce settings)
Each external message can carry up to four internal messages due to cell reference limits.
🔐 Security Mechanisms in Wallet Contracts
Wallets employ several security layers to protect user funds.
Replay Protection with Seqno
The seqno field ensures each transaction is processed only once. When sending a transaction, the wallet increments its current seqno. If a duplicate message arrives with an outdated seqno, it's rejected.
throw_unless(33, msg_seqno == stored_seqno);This mechanism prevents malicious actors from resubmitting old transactions.
Signature Verification
Every incoming external message must be signed with the wallet owner’s private key. The contract retrieves the public key from storage and validates the signature using:
throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key));Only validly signed messages proceed to execution.
Transaction Expiry
Messages include a valid_until timestamp. If the blockchain processes the message after this time, it's automatically rejected:
throw_if(35, valid_until <= now());This prevents delayed or stuck transactions from executing unexpectedly.
🔄 Wallet v3 vs Wallet v4: Key Differences
| Feature | Wallet v3 | Wallet v4 |
|---|---|---|
| Plugins | ❌ Not supported | ✅ Supported |
| Subscription Model | ❌ No | ✅ Yes |
| Flexibility | Basic | High |
| Use Case | Simple transfers | dApp integrations |
Wallet v4 introduces plugin functionality, allowing third-party services to request periodic fund withdrawals—ideal for recurring payments or subscriptions.
🧱 Building Transactions from Scratch
To understand wallet operations deeply, let's construct a transaction manually using @ton/core.
Step 1: Set Up Your Environment
Install required dependencies:
npm i --save @ton/ton @ton/core @ton/cryptoInitialize a TypeScript project:
npm init -y
npx tsc --init --rootDir src --outDir build --esModuleInterop --target es2020Create nodemon.json:
{
"watch": ["src"],
"ext": ".ts,.js",
"exec": "npx ts-node ./src/index.ts"
}Add a start script in package.json:
"scripts": {
"start:dev": "npx nodemon"
}Step 2: Generate Mnemonic and Keys
import { mnemonicNew, mnemonicToWalletKey } from '@ton/crypto';
const mnemonicArray = await mnemonicNew(24);
const keyPair = await mnemonicToWalletKey(mnemonicArray);
console.log('Mnemonic:', mnemonicArray.join(' '));Step 3: Create an Internal Message
import { beginCell, toNano, Address } from '@ton/core';
const internalBody = beginCell()
.storeUint(0, 32)
.storeStringTail('Hello, TON!')
.endCell();
const internalMsg = beginCell()
.storeUint(0x18, 6) // Bounceable message
.storeAddress(Address.parse('UQ...'))
.storeCoins(toNano('0.5'))
.storeBit(1)
.storeRef(internalBody)
.endCell();Step 4: Sign and Send External Message
import { TonClient } from '@ton/ton';
import { sign } from '@ton/crypto';
const client = new TonClient({
endpoint: 'https://toncenter.com/api/v2/jsonRPC',
apiKey: 'your-api-key'
});
let toSign = beginCell()
.storeUint(698983191, 32) // subwallet_id
.storeUint(Math.floor(Date.now() / 1e3) + 60, 32)
.storeUint(seqno, 32)
.storeUint(3, 8)
.storeRef(internalMsg);
const signature = sign(toSign.endCell().hash(), keyPair.secretKey);
const body = beginCell()
.storeBuffer(signature)
.storeBuilder(toSign)
.endCell();
const externalMsg = beginCell()
.storeUint(0b10, 2)
.storeAddress(walletAddress)
.storeBit(1)
.storeRef(body)
.endCell();
client.sendFile(externalMsg.toBoc());👉 Learn how to securely sign and broadcast blockchain transactions.
🚀 Deploying a Wallet Contract
You can deploy a wallet smart contract directly via code.
Compile Wallet Code
Use @ton-community/func-js to compile .fc files:
import { compileFunc } from '@ton-community/func-js';
import * as fs from 'fs';
const result = await compileFunc({
targets: ['wallet_v3.fc'],
sources: {
'stdlib.fc': fs.readFileSync('./src/stdlib.fc', 'utf8'),
'wallet_v3.fc': fs.readFileSync('./src/wallet_v3.fc', 'utf8')
}
});
if (result.status === 'error') throw new Error(result.message);
const codeCell = Cell.fromBoc(Buffer.from(result.codeBoc, 'base64'))[0];Generate Wallet Address
const dataCell = beginCell()
.storeUint(0, 32) // seqno
.storeUint(698983191, 32) // subwallet_id
.storeBuffer(keyPair.publicKey)
.endCell();
const stateInit = beginCell()
.storeBit(0).storeBit(0).storeBit(1).storeRef(codeCell)
.storeBit(1).storeRef(dataCell).storeBit(0)
.endCell();
const address = new Address(0, stateInit.hash());
console.log('Wallet address:', address.toString());💬 Frequently Asked Questions
Q: What is the purpose of subwallet_id?
A: It allows multiple wallets to be derived from a single key pair. The default value is 698983191.
Q: How do I prevent transaction loss when sending rapidly?
A: Use high-load wallets with query_id instead of seqno for parallel processing.
Q: Can I send more than four messages at once?
A: No—each cell supports up to four references. For bulk operations, use high-load wallets.
Q: Why does my transaction fail with exit code 33?
A: This indicates a seqno mismatch. Always fetch the latest seqno before signing.
Q: Are there gas fees for wallet operations?
A: Yes—computation and storage incur fees paid in TON. Use accept_message() to optimize costs.
🔥 High-Load Wallets: Handling Bulk Transactions
High-load wallets (v2/v3) are designed for exchanges and platforms requiring thousands of withdrawals per minute.
Key Features
- Uses
query_idinstead ofseqno - Supports up to 255 actions per message
- Automatic cleanup of expired queries
- Optimized for low-latency environments
Use Case: Exchange Withdrawals
An exchange can pre-sign hundreds of withdrawal requests using unique query_ids. Even if network delays occur, requests won't be duplicated thanks to built-in deduplication logic.
🛠 Best Practices for Developers
- Always verify wallet state before sending transactions.
- Use testnet first (
testnet.toncenter.com) for development. - Monitor gas usage to avoid failed executions.
- Implement retry logic with exponential backoff for failed broadcasts.
👉 Explore tools that simplify smart contract interaction and deployment.
✅ Conclusion
TON’s smart contract wallets offer unparalleled flexibility and security. By understanding their architecture—from message types and cryptographic verification to deployment mechanics—you gain full control over how assets move across the network.
Whether you're building a simple payment app or a complex DeFi protocol, mastering wallet operations is foundational. With libraries like @ton/core and tools like Blueprint, you can automate much of this process—but knowing what happens under the hood empowers better design and debugging.
Start experimenting today, and take full advantage of TON’s scalable, secure infrastructure.