TON DocsTON Docs
OnboardingNodesApplicationsAPIsContractsSmart contractsTolkTolk languageTVMTON Virtual MachineFoundationsBlockchain foundations

How to create a Highload Wallet v3

This guide shows how to create a Highload Wallet v3 instance from scratch: choose configuration parameters, generate a mnemonic, and calculate the wallet address. The examples use testnet.

Objective

By the end of this guide, you will have:

  • A 24-word mnemonic phrase (seed phrase) for the wallet
  • A calculated wallet address (but not yet deployed on-chain)
  • Configuration parameters: subwalletId and timeout

Prerequisites

No deployment yet: Creating a wallet calculates its future address but does not deploy it. The wallet will auto-deploy on the first external message (when you send a first transfer).

This guide uses TypeScript with the official wrapper. The same logic applies to other SDKs (Go/Python): generate or load a mnemonic, derive a keypair, choose parameters, and calculate the address.

Step 1: Set up dependencies

In a new project directory, install the required packages:

npm init -y
npm pkg set type=module
npm install --save-exact @ton/[email protected] @ton/[email protected] @ton/[email protected] [email protected]
mkdir -p wrappers/ tests/imports/

Preserve these paths so imports resolve. The guide uses the inline compiled contract BoC below.

In wrappers/HighloadWalletV3.ts, change the signing import from ton-crypto to @ton/crypto. Run the scripts with tsx — the wrapper uses TypeScript parameter properties, which Node's type-stripping mode does not support.

The guide copies the repository's HighloadWalletV3 and HighloadQueryId wrappers because those classes define this wallet's configuration and message encoding, while not being a part of the @ton/ton SDK.

Step 2: Choose configuration parameters

Highload Wallet v3 requires two configuration parameters at creation time:

timeout

Type: uint22 (0 to 4,194,303 seconds)
Purpose: Validity window for external messages and cleanup cycle duration

The timeout determines:

  • How long a signed external message remains valid: created_at must be within (now - timeout, now]
  • When cleanup can move processed-message markers to old_queries: after timeout seconds, during a later external request
  • When cleanup can remove markers from old_queries: after 2 × timeout, during a later external request

Cleanup is request-driven, not a timer. If the wallet is idle, markers can remain on-chain beyond these thresholds until another external request triggers cleanup.

Choosing a value:

RangeUse case
Short (60–300s)Fast certainty if message expires; lower storage costs; requires tight synchronization
Medium (1–6 hours)Balanced; suitable for most production use
Long (24+ hours)High tolerance for blockchain congestion; higher storage costs; slower certainty on expiration

Although uint22 can encode zero, a zero timeout leaves no valid message timestamp. Choose a positive timeout; this guide's 24-hour value also permits the 30-second timestamp offset used when sending.

Cannot be changed later: timeout is part of the contract's initial configuration. Changing it would change the wallet address entirely.

The timeout constraints reference defines the timestamp boundary and cleanup behavior.

subwalletId

Type: uint32 (0 to 4,294,967,295)
Purpose: Isolate multiple wallets derived from the same keypair

A single mnemonic can generate multiple independent wallet addresses by varying subwalletId. Each wallet has its own balance, state, and transaction history.

The official Highload Wallet v3 repository uses 0x10ad (4269) as its recommended value. This is a convention for Highload v3 wallets, not a cross-contract collision guard.

The address is derived from the full StateInit, including code and data. Different wallet code produces a different address even when the key and subwalletId match.

Use distinct IDs to distinguish wallets that share the same code and other configuration.

The storage structure reference defines the stored fields.

Step 3: Generate or load a mnemonic

A mnemonic is the wallet's master secret. It derives the private key used to sign all transactions.

Option 1: Generate a new mnemonic

// Not runnable: add to wallet-create.ts.
import { mnemonicNew } from '@ton/crypto';

const mnemonic = await mnemonicNew(24);
process.stdout.write(`Save this phrase securely: ${mnemonic.join(' ')}\n`);

Option 2: Load an existing mnemonic

// Not runnable: add to wallet-create.ts.
const mnemonicPhrase = process.env.TON_WALLET_MNEMONIC;
if (!mnemonicPhrase) throw new Error('Inject TON_WALLET_MNEMONIC from protected storage.');
const mnemonic = mnemonicPhrase.trim().split(/\s+/);

For a new phrase, run the script in a private local terminal that does not record output, then store it in a password manager, OS keychain, or encrypted keystore. For an existing phrase, inject TON_WALLET_MNEMONIC from protected storage. Do not store either phrase in .wallet.json, shell history, or logs.

Protect the mnemonic: Anyone with access to the mnemonic can control the wallet and all funds. Store it securely (password manager, hardware wallet, encrypted storage). Never commit it to version control.

Step 4: Derive the keypair

Convert the mnemonic to an Ed25519 keypair:

// Not runnable: add to wallet-create.ts after the selected mnemonic block.
import { mnemonicToPrivateKey } from '@ton/crypto';

const keyPair = await mnemonicToPrivateKey(mnemonic);
// keyPair.publicKey  — used in contract state
// keyPair.secretKey  — used to sign external messages

Step 5: Create the wallet instance

Create a Highload Wallet v3 contract instance with the chosen parameters:

// Not runnable: add to wallet-create.ts after the keypair block.
import { Cell } from '@ton/core';
import { HighloadWalletV3 } from './wrappers/HighloadWalletV3';

// Configuration
// The official repository recommends 0x10ad for Highload Wallet v3.
const SUBWALLET_ID = 0x10ad;
const TIMEOUT = 60 * 60 * 24;

// Compiled contract code (BoC)
const CODE = Cell.fromBoc(
    Buffer.from(
        'b5ee9c7241021001000228000114ff00f4a413f4bcf2c80b01020120020d02014803040078d020d74bc00101c060b0915be101d0d3030171b0915be0fa4030f828c705b39130e0d31f018210ae42e5a4ba9d8040d721d74cf82a01ed55fb04e030020120050a02027306070011adce76a2686b85ffc00201200809001aabb6ed44d0810122d721d70b3f0018aa3bed44d08307d721d70b1f0201200b0c001bb9a6eed44d0810162d721d70b15800e5b8bf2eda2edfb21ab09028409b0ed44d0810120d721f404f404d33fd315d1058e1bf82325a15210b99f326df82305aa0015a112b992306dde923033e2923033e25230800df40f6fa19ed021d721d70a00955f037fdb31e09130e259800df40f6fa19cd001d721d70a00937fdb31e0915be270801f6f2d48308d718d121f900ed44d0d3ffd31ff404f404d33fd315d1f82321a15220b98e12336df82324aa00a112b9926d32de58f82301de541675f910f2a106d0d31fd4d307d30cd309d33fd315d15168baf2a2515abaf2a6f8232aa15250bcf2a304f823bbf2a35304800df40f6fa199d024d721d70a00f2649130e20e01fe5309800df40f6fa18e13d05004d718d20001f264c858cf16cf8301cf168e1030c824cf40cf8384095005a1a514cf40e2f800c94039800df41704c8cbff13cb1ff40012f40012cb3f12cb15c9ed54f80f21d0d30001f265d3020171b0925f03e0fa4001d70b01c000f2a5fa4031fa0031f401fa0031fa00318060d721d300010f0020f265d2000193d431d19130e272b1fb00b585bf03',
        'hex'
    )
)[0];

const wallet = HighloadWalletV3.createFromConfig(
    {
        publicKey: keyPair.publicKey,
        subwalletId: SUBWALLET_ID,
        timeout: TIMEOUT,
    },
    CODE
);

Step 6: Get the wallet address

Calculate the wallet's address:

// Not runnable: add to wallet-create.ts after the wallet block.
// Get non-bounceable address for funding
const address = wallet.address.toString({ bounceable: false, testOnly: true });
console.log('Wallet address:', address);

This address is deterministic: it depends only on CODE, publicKey, subwalletId, and timeout. The same parameters always produce the same address.

Why non-bounceable? When funding a nonexist account, use the non-bounceable format so funds do not bounce if the account does not exist. The address formats page explains the alternatives.

Account status: nonexist

The calculated address exists only as a deterministic value. No account exists on the blockchain yet — no balance, no code, no data.

Saving wallet data

Save the public wallet configuration separately from the mnemonic:

// Not runnable: add to wallet-create.ts after the address block.
import * as fs from 'fs';

const walletData = {
    address,
    subwalletId: SUBWALLET_ID,
    timeout: TIMEOUT,
    nextQuerySeqno: 0,
};

fs.writeFileSync('.wallet.json', JSON.stringify(walletData, null, 2));
console.log('Wallet saved to .wallet.json');

Create wallet-create.ts and add the TypeScript blocks in order, choosing one mnemonic block. Then run:

npx tsx wallet-create.ts

The script calculates the address and saves .wallet.json; it does not contact the blockchain or fund the wallet.

.wallet.json contains the address, contract parameters, and the next query ID sequence number. It does not contain a secret. Keep the mnemonic in protected storage and inject it into sending processes from a secret manager.

Step 7: Fund the wallet

Funds at risk

Test on testnet first. Verify the wallet address carefully — blockchain transactions cannot be reversed.

Required before deployment: Send GRAM to the wallet address (from Step 6) to prepare it for deployment.

External messages (which deploy the wallet) require gas to execute. Funding changes the account from nonexist to uninit and provides the balance needed for deployment; the account status reference explains these states.

Send testnet GRAM from a faucet or another testnet wallet. After funding, the account transitions to uninit status — it has a balance and can accept external messages, but no code or data yet.

Next steps

The wallet will initialize itself upon receiving the first external message.

On this page