Working with Wallet Smart Contracts on The Open Network

·

Understanding how wallet smart contracts function on The Open Network (TON) is essential for any developer aiming to build decentralized applications or interact directly with the blockchain. Unlike traditional wallets, TON wallets are intelligent, programmable contracts that manage transaction logic, security, and communication. This guide dives deep into the mechanics of wallet operations, message handling, and contract deployment—without relying on pre-built functions—giving you full control and insight into the development workflow.

By mastering these core concepts, you'll be equipped to create secure, efficient, and scalable applications on TON. Whether you're building a wallet interface, deploying custom contracts, or processing bulk transactions, this knowledge forms the foundation of robust blockchain development.

Prerequisites for Development

Before diving into wallet interactions, ensure you meet the following prerequisites:

💡 Why use Mainnet?
While testnets are useful, they often suffer from instability, delayed transactions, and deployment errors. For reliable results and realistic gas behavior, conduct most of your development on TON Mainnet.

Required Libraries

Install the necessary TON SDK packages:

npm i --save @ton/ton @ton/core @ton/crypto

For Go developers, use:

go get github.com/xssnick/tonutils-go

👉 Discover how to integrate smart contract logic into real-world applications.

Setting Up Your Development Environment

To begin, initialize a TypeScript project:

  1. Create a new folder: WalletsTutorial
  2. Run:

    npm init -y
    npm install typescript @types/node ts-node nodemon --save-dev
    npx tsc --init --rootDir src --outDir build --esModuleInterop --target es2020 --resolveJsonModule --lib es6 --module commonjs --allowJs true --noImplicitAny false --allowSyntheticDefaultImports true --strict false
  3. Add nodemon.json:

    {
      "watch": ["src"],
      "ext": ".ts,.js",
      "exec": "npx ts-node ./src/index.ts"
    }
  4. Update package.json with:

    "scripts": {
      "start:dev": "npx nodemon"
    }
  5. Create src/index.ts:

    async function main() {
      console.log("Hello, TON!");
    }
    main().finally(() => console.log("Exiting..."));

Run npm run start:dev to verify the setup.

🛠️ Optional Tool: Blueprint automates contract deployment and testing but isn't needed here—we're focusing on low-level understanding.

Understanding Wallet Smart Contracts

All wallets on TON are smart contracts. They handle incoming messages, verify signatures, manage sequence numbers (seqno), and forward internal transactions. This programmability enables fully customizable wallets.

How Wallets Communicate

TON supports two message types:

Wallets accept external messages via the recv_external(slice in_msg) function. Upon receipt, they:

  1. Extract signature, seqno, subwallet ID.
  2. Retrieve stored public key and seqno.
  3. Validate message authenticity and sequence.

Example validation logic:

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());

var ds = get_data().begin_parse();
var (stored_seqno, stored_subwallet, public_key) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256));

throw_unless(33, msg_seqno == stored_seqno);
throw_unless(34, subwallet_id == stored_subwallet);
throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key));
accept_message();

Replay Protection with Seqno

The seqno field prevents message replay attacks. Each transaction must increment this number. If an old seqno is reused, the contract rejects it with exit code 33.

This ensures that even if someone intercepts your signed message, they cannot resend it after the first execution.

Signature Verification

Wallets verify ownership using digital signatures:

var signature = in_msg~load_bits(512);
var ds = get_data().begin_parse();
var public_key = ds~load_uint(256);
throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key));

Only messages signed with the correct private key will pass verification.

Transaction Expiry

The valid_until field sets a deadline for message processing. If the current time exceeds this value, the message is rejected (exit code 35). This prevents stale transactions from being executed.

Wallet v3 vs Wallet v4

The primary difference lies in plugins:

For simplicity, we’ll focus on Wallet v3 (specifically v3r2).

Building Internal Messages Manually

Internal messages transfer value or data between contracts. Here’s how to construct one:

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

const internalMessageBody = beginCell()
  .storeUint(0, 32)
  .storeStringTail("Hello, TON!")
  .endCell();

const internalMessage = beginCell()
  .storeUint(0x18, 6) // bounceable
  .storeAddress(Address.parse('UQ...'))
  .storeCoins(toNano("0.2"))
  .storeBit(0) // no extra currency
  .storeCoins(0) // IHR fee
  .storeCoins(0) // forwarding fee
  .storeUint(0, 64) // logical time
  .storeUint(0, 32) // UNIX time
  .storeBit(0) // no state init
  .storeBit(1) // body as reference
  .storeRef(internalMessageBody)
  .endCell();

Each field corresponds to specific transaction metadata required by the blockchain.

Creating External Messages for Deployment

To deploy a wallet or send a transaction from off-chain, wrap internal logic in an external message.

Step 1: Retrieve Seqno

const client = new TonClient({ endpoint: "https://toncenter.com/api/v2/jsonRPC" });
const getMethodResult = await client.runMethod(walletAddress, "seqno");
const seqno = getMethodResult.stack.readNumber();

Step 2: Sign the Message

import { sign } from '@ton/crypto';

let toSign = beginCell()
  .storeUint(698983191, 32) // subwallet_id
  .storeUint(Math.floor(Date.now()/1e3) + 60, 32) // expiration
  .storeUint(seqno, 32)
  .storeUint(3, 8)
  .storeRef(internalMessage);

const signature = sign(toSign.endCell().hash(), keyPair.secretKey);

Step 3: Build External Message

const body = beginCell()
  .storeBuffer(signature)
  .storeBuilder(toSign)
  .endCell();

const externalMessage = beginCell()
  .storeUint(0b10, 2)
  .storeUint(0, 2) // src addr_none
  .storeAddress(walletAddress)
  .storeCoins(0)
  .storeBit(0) // no state init
  .storeBit(1)
  .storeRef(body)
  .endCell();

Step 4: Send to Blockchain

client.sendFile(externalMessage.toBoc());

👉 Learn how to optimize gas usage when deploying smart contracts.

Deploying a Wallet from Scratch

You can deploy a wallet contract manually using compiled FunC code.

Generate Mnemonic and Keys

import { mnemonicNew, mnemonicToWalletKey } from '@ton/crypto';

const mnemonicArray = await mnemonicNew(24);
const keyPair = await mnemonicToWalletKey(mnemonicArray);

Compile Wallet Code

Use @ton-community/func-js to compile .fc files:

import { compileFunc } from '@ton-community/func-js';
const result = await compileFunc({ targets: ['wallet_v3.fc'] });
const codeCell = Cell.fromBoc(Buffer.from(result.codeBoc, 'base64'))[0];

Construct State Init

State Init defines initial contract state:

const dataCell = beginCell()
  .storeUint(0, 32) // seqno
  .storeUint(698983191, 32) // subwallet_id
  .storeBuffer(keyPair.publicKey)
  .endCell();

const stateInit = beginCell()
  .storeBit(0) // split_depth
  .storeBit(0) // special
  .storeBit(1) // code present
  .storeRef(codeCell)
  .storeBit(1) // data present
  .storeRef(dataCell)
  .storeBit(0) // no library
  .endCell();

Compute Wallet Address

const contractAddress = new Address(0, stateInit.hash());
console.log(`Wallet address: ${contractAddress.toString()}`);

Deploy by sending coins to this address followed by a signed initialization message.

Sending Multiple Messages at Once

A single cell can hold up to 4 references, allowing you to send up to four internal messages in one external transaction.

Loop through recipients and build messages:

for (let i = 0; i < destinations.length; i++) {
  toSign.storeUint(3, 8).storeRef(messages[i]);
}

This is ideal for batch transfers or notifications.

High-Load Wallets for Scalability

For high-frequency operations (e.g., exchanges), use Highload Wallet V3, which supports thousands of queries via query_id instead of seqno.

Query ID Mechanism

Unlike seqno, which requires sequential updates, query_id allows parallel processing:

This design avoids bottlenecks during mass withdrawals.

Deployment Example

Using wrapper libraries simplifies interaction:

import { HighloadWalletV3 } from "./wrappers/HighloadWalletV3";

const wallet = client.open(HighloadWalletV3.createFromConfig({
  publicKey: keyPair.publicKey,
  subwalletId: 0x10ad,
  timeout: 3600,
}, HIGHLOAD_V3_CODE));

Then send batches efficiently:

await wallet.sendBatch(
  secretKey,
  actions,
  subwalletId,
  queryHandler,
  timeout,
  value,
  SendMode.PAY_GAS_SEPARATELY,
  Date.now() / 1000 - 60
);

Frequently Asked Questions (FAQ)

What is the purpose of seqno in TON wallets?

Seqno (sequence number) prevents replay attacks by ensuring each transaction is processed only once. It increments with every sent message and must match the value stored in the contract.

How does signature verification work?

The wallet extracts the signature from the incoming message and compares it against the stored public key using check_signature(). Only valid signatures allow transaction execution.

Why use external messages instead of internal ones?

External messages originate off-chain (e.g., from a user’s app), while internal messages flow between contracts. To initiate actions from outside the blockchain, external messages are required.

Can I send more than four messages at once?

Yes—using high-load wallets. While standard wallets support up to four messages per transaction via cell references, high-load wallets use query-based batching to handle hundreds or thousands of operations.

What is State Init and when is it used?

State Init contains a contract’s initial code and data. It's used during deployment to define what code runs at the new address and how it starts.

How do I avoid common deployment errors?

Always:

👉 Explore advanced smart contract patterns used in production dApps.

Final Thoughts

Mastering wallet smart contracts on TON empowers you to build secure, scalable blockchain applications. By understanding message structures, signature validation, and deployment workflows at a low level, you gain full control over your projects.

While high-level libraries simplify development, knowing the underlying mechanics ensures robustness and troubleshooting efficiency. As TON evolves with greater adoption in DeFi, NFTs, and Web3 services, these skills will become increasingly valuable.

Continue exploring through official resources like FunC Overview, Smart Contract Guidelines, and real-world examples to deepen your expertise.

Core Keywords: TON wallet smart contract, The Open Network development, blockchain message handling, seqno replay protection, TON transaction structure, high-load wallet v3, deploy smart contract TON