Skip to main content
This page provides a complete technical specification for Highload Wallet v2, covering storage structure, message formats, replay protection, and limitations.

What is Highload Wallet v2?

Highload Wallet v2 is a specialized wallet contract designed for services that need to send many transactions in a short time. It uses dictionary-based replay protection to enable parallel transaction submission. Key difference from standard wallets:
Unlike seqno-based wallets that require sequential transaction processing, Highload v2 stores processed request identifiers in a dictionary, enabling parallel submissions.
Replaced by: Highload Wallet v3

TL-B schema

TL-B (Type Language - Binary) is a domain-specific language designed to describe data structures in TON. The schemas below define the binary layout of the contract’s storage and external messages.

Storage structure

Query ID structure

External message structure

Storage structure

The Highload Wallet v2 contract stores four persistent fields (in this order):

subwallet_id (32 bits)

Purpose:
Allows a single keypair to control multiple wallets with different addresses.
How it works:
The subwallet_id is part of the contract’s initial state. Changing it produces a different contract address. Each external message must include the correct subwallet_id; mismatches result in transaction failure.

last_cleaned (64 bits)

Purpose:
Timestamp (in query_id format) of the oldest query that was kept during the last cleanup.
How it works:
During each transaction, the contract removes queries older than 64 seconds from the queries dictionary. The last_cleaned field tracks the last query ID that was removed.
Cleanup logic:
Queries with query_id < (now() - 64) << 32 are removed from storage.

public_key (256 bits)

Purpose:
The Ed25519 public key is used to verify signatures on incoming external messages.
How it works:
When the wallet receives an external message, it verifies that the 512-bit signature was created by the holder of the private key corresponding to this public key.

queries (HashmapE 64 Cell)

Purpose:
Stores processed query_id values for replay protection.
Structure:
  • Key: 64-bit query_id
  • Value: Cell containing metadata (typically the timestamp when processed)
How it works:
Before processing a message, the contract checks if query_id exists in queries. If found, the message is rejected (replay attack). If not found, the query_id is added to queries, and the message is processed.

External message structure

Message layout

Key point:
Unlike v3, in v2 the signature is in the same cell as the message body, not in a separate reference cell.

signature (512 bits)

Type:
Ed25519 signature (512 bits).
What is signed:
The hash of the remaining slice after the signature, containing subwallet_id, query_id, and messages.
From source code:
The contract uses slice_hash() on the message body after loading the signature.

subwallet_id (32 bits)

Purpose:
Identifies which subwallet this message targets.
Validation:
Must match the subwallet_id stored in contract storage.

query_id (64 bits)

Purpose:
Unique identifier for replay protection and timestamp validation.
Structure:
The 64-bit value is internally interpreted as a timestamped identifier:
  • High 32 bits: Unix timestamp (seconds)
  • Low 32 bits: counter within that second
Validation:
The contract checks query_id >= now() << 32, ensuring the query ID is not from the past (based on the current time shifted left by 32 bits).
Total unique IDs:
Approximately 32,000 unique query IDs (limited by the cleanup mechanism and the bitmap structure).

messages (HashmapE 16)

Purpose:
Dictionary of messages to send in this transaction.
Structure:
  • Key: uint16 (message index, 0 to 65,535)
  • Value: mode:uint8 + ^Cell (reference to internal message)
How it works:
The contract iterates through the dictionary and sends each message with its corresponding send mode:
Max batch size:
Up to 255 messages (limited by action list size, not dictionary structure).

Replay protection mechanism

Validation sequence

  1. Check query_id timestamp: query_id >= now() << 32 (exit code 35 if too old)
  2. Check replay: query_id must not be in queries (exit code 32 if already processed)
  3. Check subwallet: subwallet_id == stored_subwallet (exit code 34 if mismatch)
  4. Verify signature: Ed25519 signature verification (exit code 35 if invalid)
  5. Mark as processed: Add query_id to queries
  6. Send messages: Iterate through the message dictionary and send each message
  7. Cleanup: Remove queries older than 64 seconds

Exit codes


Limitations and constraints

Storage size limit

Limit:
The queries dictionary cannot exceed 65,535 cells.
What happens if exceeded:
An exception is thrown during the action phase, and the transaction fails. The failed transaction may be replayed, potentially locking funds.

Gas limit for cleanup

Limit:
Transaction gas limit is 1,000,000 gas.
What happens if exceeded:
Cleanup operations that exceed this limit will fail, preventing the contract from processing new transactions.
Recommended limits:
  • Queries within expiration window: ≤ 1,000
  • Queries cleaned per transaction: ≤ 100

Query ID expiration

Expiration time:
Queries older than 64 seconds are removed from storage during cleanup.
Effective limit:
With the 64-second expiration window and recommended limit of ≤1,000 queries per window, the effective query ID space is approximately 32,000 unique IDs before cleanup is required.

Get methods

processed? method details

Returns:
  • -1 (true) — the query_id was processed and is still stored in queries
  • 0 (false) — the query_id has not been processed yet
  • 1 (unknown) — the query_id is older than last_cleaned and was forgotten during cleanup

Implementation

Source code:
ton-blockchain/ton (highload-wallet-v2-code.fc)
SDK wrappers:
  • Go: tonutils-go — includes Highload v2 wrapper
  • Python: pytoniq — includes Highload v2 wrapper
For new projects, consider using Highload Wallet v3 instead.

See also