> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ton.org/llms.txt
> Use this file to discover all available pages before exploring further.

<AgentInstructions>

## Submitting Feedback

If you encounter incorrect, outdated, or confusing documentation on this page, submit feedback:

POST https://docs.ton.org/feedback

```json
{
  "path": "/standard/wallets/highload/v3/specification",
  "feedback": "Description of the issue"
}
```

Only submit feedback when you have something specific and actionable to report.

</AgentInstructions>

# Highload Wallet v3 — specification

export const Image = ({src, darkSrc, alt = '', darkAlt, href, target, height = 342, width = 608, noZoom = false, center = false}) => {
  const isSVG = src.match(/\.svg(?:[#?].*?)?$/i) !== null;
  const shouldInvert = isSVG && !darkSrc;
  const shouldCreateLink = href !== undefined;
  const minPx = 9;
  const maxPx = 608;
  const expectedPx = `a number or a string with a number that is greater than ${minPx - 1} and less than or equal to ${maxPx}`;
  const createInvalidPropCallout = (title, received, expected) => {
    return <Danger>
        <span className="font-bold">
          Invalid <code>{title.toString()}</code> passed!
        </span>
        <br />
        <span className="font-bold">Received: </span>
        {received.toString()}
        <br />
        <span className="font-bold">Expected: </span>
        {expected.toString()}
        {}
      </Danger>;
  };
  const checkValidDimensionValue = value => {
    switch (typeof value) {
      case "string":
      case "number":
        const num = Number(value);
        return Number.isSafeInteger(num) && num >= minPx && num <= maxPx;
      default:
        return false;
    }
  };
  let callouts = [];
  if (height && !checkValidDimensionValue(height)) {
    callouts.push(createInvalidPropCallout("height", height, expectedPx));
  }
  if (width && !checkValidDimensionValue(width)) {
    callouts.push(createInvalidPropCallout("width", width, expectedPx));
  }
  if (callouts.length !== 0) {
    return callouts;
  }
  const heightPx = Number(height);
  const widthPx = Number(width);
  const shouldCenter = center === "true" || center === true ? true : false;
  const shouldNotZoom = noZoom === "true" || noZoom === true ? true : false;
  const images = <>
      <img className="block dark:hidden" src={src} alt={alt} {...height && ({
    height: heightPx
  })} {...width && ({
    width: widthPx
  })} {...(shouldCreateLink || shouldInvert || shouldNotZoom) && ({
    noZoom: "true"
  })} />
      <img className={`hidden dark:block ${shouldInvert ? "invert" : ""}`} src={darkSrc ?? src} alt={darkAlt ?? alt} {...height && ({
    height: heightPx
  })} {...width && ({
    width: widthPx
  })} {...(shouldCreateLink || shouldInvert || shouldNotZoom) && ({
    noZoom: "true"
  })} />
    </>;
  if (shouldCreateLink) {
    if (shouldCenter) {
      return <div style={{
        display: "flex",
        justifyContent: "center"
      }}>
          <a href={href} target={target ?? "_self"}>
            {images}
          </a>
        </div>;
    }
    return <a href={href} target={target ?? "_self"}>
        {images}
      </a>;
  }
  if (shouldCenter) {
    return <div style={{
      display: "flex",
      justifyContent: "center"
    }}>{images}</div>;
  }
  return images;
};

export const Aside = ({type = "note", title = "", icon = "", iconType = "regular", children}) => {
  const asideVariants = ["note", "tip", "caution", "danger"];
  const asideComponents = {
    note: {
      outerStyle: "border-sky-500/20 bg-sky-50/50 dark:border-sky-500/30 dark:bg-sky-500/10",
      innerStyle: "text-sky-900 dark:text-sky-200",
      calloutType: "note",
      icon: <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg" className="w-4 h-4 text-sky-500" aria-label="Note">
          <path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.3C10.14 1.3 12.7 3.86 12.7 7C12.7 10.14 10.14 12.7 7 12.7C5.48908 12.6974 4.0408 12.096 2.97241 11.0276C1.90403 9.9592 1.30264 8.51092 1.3 7C1.3 3.86 3.86 1.3 7 1.3ZM7 0C3.14 0 0 3.14 0 7C0 10.86 3.14 14 7 14C10.86 14 14 10.86 14 7C14 3.14 10.86 0 7 0ZM8 3H6V8H8V3ZM8 9H6V11H8V9Z"></path>
        </svg>
    },
    tip: {
      outerStyle: "border-emerald-500/20 bg-emerald-50/50 dark:border-emerald-500/30 dark:bg-emerald-500/10",
      innerStyle: "text-emerald-900 dark:text-emerald-200",
      calloutType: "tip",
      icon: <svg width="11" height="14" viewBox="0 0 11 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg" className="text-emerald-600 dark:text-emerald-400/80 w-3.5 h-auto" aria-label="Tip">
          <path d="M3.12794 12.4232C3.12794 12.5954 3.1776 12.7634 3.27244 12.907L3.74114 13.6095C3.88471 13.8248 4.21067 14 4.46964 14H6.15606C6.41415 14 6.74017 13.825 6.88373 13.6095L7.3508 12.9073C7.43114 12.7859 7.49705 12.569 7.49705 12.4232L7.50055 11.3513H3.12521L3.12794 12.4232ZM5.31288 0C2.52414 0.00875889 0.5 2.26889 0.5 4.78826C0.5 6.00188 0.949566 7.10829 1.69119 7.95492C2.14321 8.47011 2.84901 9.54727 3.11919 10.4557C3.12005 10.4625 3.12175 10.4698 3.12261 10.4771H7.50342C7.50427 10.4698 7.50598 10.463 7.50684 10.4557C7.77688 9.54727 8.48281 8.47011 8.93484 7.95492C9.67728 7.13181 10.1258 6.02703 10.1258 4.78826C10.1258 2.15486 7.9709 0.000106649 5.31288 0ZM7.94902 7.11267C7.52078 7.60079 6.99082 8.37878 6.6077 9.18794H4.02051C3.63739 8.37878 3.10743 7.60079 2.67947 7.11294C2.11997 6.47551 1.8126 5.63599 1.8126 4.78826C1.8126 3.09829 3.12794 1.31944 5.28827 1.3126C7.2435 1.3126 8.81315 2.88226 8.81315 4.78826C8.81315 5.63599 8.50688 6.47551 7.94902 7.11267ZM4.87534 2.18767C3.66939 2.18767 2.68767 3.16939 2.68767 4.37534C2.68767 4.61719 2.88336 4.81288 3.12521 4.81288C3.36705 4.81288 3.56274 4.61599 3.56274 4.37534C3.56274 3.6515 4.1515 3.06274 4.87534 3.06274C5.11719 3.06274 5.31288 2.86727 5.31288 2.62548C5.31288 2.38369 5.11599 2.18767 4.87534 2.18767Z"></path>
        </svg>
    },
    caution: {
      outerStyle: "border-amber-500/20 bg-amber-50/50 dark:border-amber-500/30 dark:bg-amber-500/10",
      innerStyle: "text-amber-900 dark:text-amber-200",
      calloutType: "warning",
      icon: <svg className="flex-none w-5 h-5 text-amber-400 dark:text-amber-300/80" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-label="Warning">
          <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
        </svg>
    },
    danger: {
      outerStyle: "border-red-500/20 bg-red-50/50 dark:border-red-500/30 dark:bg-red-500/10",
      innerStyle: "text-red-900 dark:text-red-200",
      calloutType: "danger",
      icon: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor" className="text-red-600 dark:text-red-400/80 w-4 h-4" aria-label="Danger">
          <path d="M17.1 292c-12.9-22.3-12.9-49.7 0-72L105.4 67.1c12.9-22.3 36.6-36 62.4-36l176.6 0c25.7 0 49.5 13.7 62.4 36L494.9 220c12.9 22.3 12.9 49.7 0 72L406.6 444.9c-12.9 22.3-36.6 36-62.4 36l-176.6 0c-25.7 0-49.5-13.7-62.4-36L17.1 292zm41.6-48c-4.3 7.4-4.3 16.6 0 24l88.3 152.9c4.3 7.4 12.2 12 20.8 12l176.6 0c8.6 0 16.5-4.6 20.8-12L453.4 268c4.3-7.4 4.3-16.6 0-24L365.1 91.1c-4.3-7.4-12.2-12-20.8-12l-176.6 0c-8.6 0-16.5 4.6-20.8 12L58.6 244zM256 128c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"></path>
        </svg>
    }
  };
  let variant = type;
  let gotInvalidVariant = false;
  if (!asideVariants.includes(type)) {
    gotInvalidVariant = true;
    variant = "danger";
  }
  const iconVariants = ["regular", "solid", "light", "thin", "sharp-solid", "duotone", "brands"];
  if (!iconVariants.includes(iconType)) {
    iconType = "regular";
  }
  return <>
      <div className={`callout my-4 px-5 py-4 overflow-hidden rounded-2xl flex gap-3 border ${asideComponents[variant].outerStyle}`} data-callout-type={asideComponents[variant].calloutType}>
        <div className="mt-0.5 w-4" data-component-part="callout-icon">
          {}
          {icon === "" ? asideComponents[variant].icon : <Icon icon={icon} iconType={iconType} size={14} />}
        </div>
        <div className={`text-sm prose min-w-0 w-full ${asideComponents[variant].innerStyle}`} data-component-part="callout-content">
          {gotInvalidVariant ? <p>
              <span className="font-bold">
                Invalid <code>type</code> passed!
              </span>
              <br />
              <span className="font-bold">Received: </span>
              {type}
              <br />
              <span className="font-bold">Expected one of: </span>
              {asideVariants.join(", ")}
            </p> : <>
              {title && <p className="font-bold">{title}</p>}
              {children}
            </>}
        </div>
      </div>
    </>;
};

This page provides a complete technical specification for Highload Wallet v3, covering storage structure, message formats, replay protection, limitations, and security mechanisms.

## Objective

Understand the internal architecture, data structures, and operational mechanics of Highload Wallet v3. This reference page explains how the wallet processes high-throughput transaction flows securely.

<Aside type="tip">
  For practical usage, see [How-to guides](/standard/wallets/highload/v3/create).
</Aside>

## What is Highload Wallet v3?

Highload Wallet v3 is a specialized wallet smart contract designed for services that need to send many transactions in a short time (e.g., cryptocurrency exchanges, payment processors).

**Key difference from standard wallets:**\
Unlike seqno-based wallets (v3, v4, v5) that require sequential transaction processing, Highload v3 stores processed request identifiers in a dictionary, enabling **parallel transaction submission** without blocking.

**Compared to Highload v2:**\
v3 solves architectural issues that could lock funds in v2 and consumes less gas during operations.

***

## TL-B schema

[TL-B (Type Language - Binary)](/languages/tl-b/overview) is a domain-specific language designed to describe data structures in TON. The schemas below define the binary layout of the contract's storage, external messages, and internal messages.

### Storage structure

```tlb theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
storage$_ public_key:bits256 subwallet_id:uint32 old_queries:(HashmapE 13 ^Cell)
          queries:(HashmapE 13 ^Cell) last_clean_time:uint64 timeout:uint22
          = Storage;
```

### Query ID structure

```tlb theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
_ shift:uint13 bit_number:(## 10) { bit_number >= 0 } { bit_number < 1023 } = QueryId;
```

### External message structure

```tlb theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
_ {n:#} subwallet_id:uint32 message_to_send:^Cell send_mode:uint8 query_id:QueryId
  created_at:uint64 timeout:uint22 = MsgInner;

msg_body$_ {n:#} signature:bits512 ^(MsgInner) = ExternalInMsgBody;
```

### Internal message structure (for batch transfers)

```tlb theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
internal_transfer#ae42e5a4 {n:#} query_id:uint64 actions:^(OutList n) = InternalMsgBody n;
```

<Aside type="note">
  `internal_transfer` is used when the wallet sends a message to itself with an action list for batch transfers. The opcode `0xae42e5a4` is derived from `crc32('internal_transfer n:# query_id:uint64 actions:^OutList n = InternalMsgBody n')`.

  The canonical TL-B schemas are maintained in the [highload-wallet-contract-v3 repository](https://github.com/ton-blockchain/highload-wallet-contract-v3).
</Aside>

Below, each field is explained in detail.

***

## Storage structure

The Highload Wallet v3 contract stores six persistent fields:

### `public_key` (256 bits)

**Purpose:**\
An 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.

<Aside type="caution">
  The public key is not the wallet address. The address is [derived](/foundations/addresses/derive) from the contract's [`StateInit`](/foundations/messages/deploy).
</Aside>

***

### `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 (because the address is a hash of code + data). Each external message must include the correct `subwallet_id`; mismatches result in exit code `34`.

**Common use case:**\
One private key manages multiple "virtual" wallets for organizational or accounting purposes.

**Recommendation:**\
Use `subwallet_id: 0x10ad` (4269) to avoid conflicts with other wallet types (standard wallets or vesting wallets) derived from the same keypair. See [How to create Highload Wallet v3](/standard/wallets/highload/v3/create) for details.

***

### `old_queries` (HashmapE 13 ^Cell)

**Purpose:**\
Stores previously processed `query_id` values during rotation cycles.

**Structure:**\
Hashmap with 13-bit keys, where each key holds a bitmap of processed query IDs.

**Usage:**\
Provides a second layer of replay protection. During cleanup, `queries` → `old_queries`, creating a double timeout window.

**Details:** See [Replay protection mechanism](#replay-protection-mechanism).

***

### `queries` (HashmapE 13 ^Cell)

**Purpose:**\
Stores recently processed `query_id` values to prevent replay attacks.

**Structure:**\
Hashmap with 13-bit keys, where each key holds a bitmap of processed query IDs.

**Usage:**\
When a message arrives, the contract checks if its `query_id` is already marked in `queries` or `old_queries`. If found, the message is rejected as a replay attempt.

**Details:** See [Replay protection mechanism](#replay-protection-mechanism).

***

### `last_clean_time` (64 bits)

**Purpose:**\
Unix timestamp (in seconds) of the last cleanup operation.

**Usage:**\
Tracks when the contract last rotated `queries` → `old_queries`. Cleanup triggers when `current_time >= last_clean_time + timeout`.

**Details:** See [Replay protection mechanism](#replay-protection-mechanism).

***

### `timeout` (22 bits)

**Purpose:**\
Defines the validity window (in seconds) for external messages.

**Usage:**\
Messages are valid if `created_at > now() - timeout` and `created_at <= now()`. If expired or from the future, the contract rejects them with exit code `35`.

<Aside type="note">
  22 bits allows timeout values up to \~4,194,303 seconds (\~48.6 days).
</Aside>

**Details:**

* [Replay protection mechanism](#replay-protection-mechanism) — how timeout is used
* [Timeout constraints](#timeout-constraints) — choosing the right value

***

## Replay protection mechanism

Highload v3 uses a dual-hashmap system (`queries` and `old_queries`) combined with timestamps to prevent replay attacks.

### Storage structure for replay protection

The contract stores processed `query_id` values in two hashmaps:

**`old_queries` (HashmapE 13 ^Cell):**

* Hashmap with 13-bit keys
* Each key corresponds to `shift` (13 bits from query\_id)
* Each value is a cell containing a bitmap of processed `bit_number` values
* Stores previously processed query IDs from the last rotation cycle
* Provides extended protection during rotation

**`queries` (HashmapE 13 ^Cell):**

* Same structure as `old_queries` — hashmap with 13-bit keys
* Each key corresponds to `shift = query_id >> 10`
* Each value is a cell containing a bitmap of processed `bit_number` values
* Stores recently processed query IDs

### How query\_id is checked

When an external message arrives, the contract:

1. Extracts `query_id` from the message
2. Splits it into components:
   * `shift = query_id >> 10` (13 bits, range 0–8191)
   * `bit_number = query_id & 1023` (10 bits, range 0–1022)
3. Checks if bit `bit_number` is set in `queries[shift]`:
   * If found → reject with exit code `36`
4. Checks if bit `bit_number` is set in `old_queries[shift]`:
   * If found → reject with exit code `36`
5. If not found in either → mark the bit in `queries[shift]` and proceed

**Why a hashmap structure?**\
Enables [parallel transaction submission](#what-is-highload-wallet-v3) — multiple messages can be sent simultaneously without waiting for sequential confirmation.

### Rotation mechanism

When `current_time >= last_clean_time + timeout`, the contract performs cleanup:

1. `old_queries := queries` — move current queries to old
2. `queries := {}` — clear current queries hashmap
3. `last_clean_time := current_time` — update timestamp

**Additional cleanup:**\
If `current_time >= last_clean_time + (2 × timeout)` (i.e., no cleanup for twice the timeout period), the contract also clears `old_queries` completely to prevent unbounded storage growth.

**Why two hashmaps?**\
This provides a **double timeout window** for replay protection:

* A `query_id` is protected for at least `timeout` seconds in `queries`
* After rotation, it remains in `old_queries` for another `timeout` period before deletion
* Total protection window: between `timeout` and `2 × timeout`

**Benefit:**\
Prevents replay attacks even if messages arrive near the rotation boundary.

### Timestamp validation

The `created_at` timestamp combined with `timeout` ensures that even very old messages (beyond the rotation window) are rejected. This creates a time-based boundary for message validity:

```text theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
The message is valid if:
  created_at > now() - timeout  // Not too old
  created_at <= now()           // Not from future
Otherwise: reject with exit code 35
```

<Aside type="caution">
  **Time lag consideration:** When a lite-server receives an external message, the contract executes `now()` which returns the timestamp of the **last processed block**, not the current system time. Due to network latency and block processing time, this timestamp is typically 5-30 seconds behind your system clock.

  **Best practice:** Set `created_at` to 30-60 seconds **before** the current time to ensure the message passes validation:

  ```typescript theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
  const createdAt = Math.floor(Date.now() / 1000) - 30;  // 30 seconds ago
  ```

  If `created_at` equals your current system time, it may appear to be "from the future" when validated on-chain, causing the transaction to fail with exit code `35`.
</Aside>

### Uniqueness guarantee

<Aside type="note">
  Highload v3 will never execute multiple external messages containing the same `query_id` and `created_at` — by the time it forgets any given `query_id`, the `created_at` condition will prevent execution of such a message. This effectively makes `query_id` and `created_at` together the "primary key" of a transfer request for Highload v3.
</Aside>

### Why internal messages to self?

Highload v3 uses a unique **internal message to self** pattern for a critical security reason related to TON's transaction phases.

**The problem with standard external message wallets:**

In TON, transaction processing includes several phases. Two phases are critical for understanding this problem:

1. **Compute phase** — executes smart contract code, updates storage
2. **Action phase** — performs actions (sends messages)

If the action phase fails (e.g., insufficient funds for outgoing messages), the **entire transaction is rolled back**, including all storage changes made in the compute phase.

For standard wallets that process external messages and send outgoing messages directly, this creates a problem: if you mark a message as "processed" in the compute phase but the action phase then fails (e.g., due to insufficient balance or invalid message), the rollback will **undo the replay protection**. Since the message was never marked as processed, lite-servers will keep retrying the same external message again and again, **burning gas on each attempt** for a long time or until the wallet runs out of funds.

**Highload v3's solution:**

Highload v3 uses a two-step approach with **internal messages**:

1. **Transaction 1 (external message):** Only marks `query_id` as processed and sends an **internal message to itself**
2. **Transaction 2 (internal message):** Processes the internal message and sends actual outgoing transfers

Even if Transaction 2 fails in the action phase, Transaction 1 has already succeeded, and its storage changes (replay protection) cannot be rolled back. The `query_id` remains marked as processed, preventing replay attacks.

This architecture solves a fundamental problem present in all standard external message wallets, including seqno-based wallets and earlier highload designs.

***

## External message structure

External messages sent to Highload v3 have a specific layout.

### Message layout

```text theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
signature:bits512
^[ subwallet_id:uint32
   message_to_send:^Cell
   send_mode:uint8
   query_id:QueryId
   created_at:uint64
   timeout:uint22 ]
```

**Key point:**\
The signature is in the **root cell** (512 bits); all other parameters are in a **reference cell** (`MsgInner`).

<Aside type="tip">
  **Gas optimization:** This structure saves \~500 gas units during signature verification. If the signature were in the same cell as the message body, the contract would need to use `slice_hash()` (which rebuilds the cell internally, costing extra gas) instead of simply taking `cell_hash()` of the reference.
</Aside>

***

### `signature` (512 bits)

**Type:**\
Ed25519 signature (512 bits).

**What is signed:**\
The hash of the reference cell (`MsgInner`) containing `subwallet_id`, `message_to_send`, `send_mode`, `query_id`, `created_at`, and `timeout`.

**Validation:**\
The contract verifies the signature using:

```func theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
check_signature(hash(ref_cell), signature, public_key)
```

**On failure:**\
Exit code `33`.

**Link:** [Ed25519 signature scheme](https://en.wikipedia.org/wiki/EdDSA#Ed25519)

***

### `subwallet_id` (32 bits)

**Purpose:**\
Identifies which subwallet this message targets.

**Validation:**\
Must match the `subwallet_id` stored in contract storage.

**On mismatch:**\
Exit code `34`.

***

### `query_id` (composite structure)

The `query_id` follows the `QueryId` TL-B structure and is split into two parts:

* **`shift`** (uint13, 13 bits): high-order bits (range 0 to 8191)
* **`bit_number`** (## 10): low-order bits with constraints `{ bit_number >= 0 } { bit_number < 1023 }` (range 0 to 1022)

**Total range:**\
`2^13 × 1023 = 8,380,416` possible unique query IDs.

**How it maps to the hashmap:**

```text theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
hashmap_key = shift (13 bits)
bit_index = bit_number (10 bits)
```

The contract checks if bit `bit_number` is set in the cell stored at `hashmap[shift]`.

**Recommendation:**\
Increment `query_id` sequentially using a counter-based strategy.

***

### `created_at` (64 bits)

**Purpose:**\
Unix timestamp (seconds) when the external message was created.

**Validation:**\
The contract performs two checks:

```func theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
created_at > now() - timeout  // Message not too old
created_at <= now()           // Message not from future
```

**On failure:**\
Exit code `35`.

**Why it matters:**\
Prevents replay of expired messages. Even if a `query_id` is eventually forgotten, stale messages are rejected based on `created_at`. See [Timestamp validation](#timestamp-validation) for important time lag considerations.

***

### `timeout` (22 bits)

**Purpose:**\
Defines the message validity window (in seconds).

**Validation:**\
Must match the `timeout` value stored in contract storage.

**On mismatch:**\
Exit code `38`.

<Aside type="note">
  22 bits allows timeout values up to \~4.8 million seconds (\~55 days).
</Aside>

***

### `send_mode` (8 bits)

**Purpose:**\
Specifies the [send mode](/foundations/messages/modes) for the internal message.

**Link:** [send\_raw\_message modes](/languages/func/stdlib#send-raw-message)

***

### `message_to_send` (reference cell)

**Structure:**\
A serialized internal message stored in a reference cell.

**Validation (exit code 37):**

The contract validates `message_to_send` **after committing storage** to prevent action phase errors. The following checks are performed:

1. **Must be internal message:**\
   First bit must be `0` (`int_msg_info$0`, not `ext_msg_info$10`)

2. **Source address must be none:**\
   The `src` field must be `addr_none` (empty address)

3. **State-init must not be present:**\
   State-init validation is too expensive in gas and is rarely needed. For contract deployment, use the [batch transfer pattern](/standard/wallets/highload/v3/send-batch-transfers) with an action list

4. **Bounced messages are ignored:**\
   If the `bounced` flag is set, the message is silently ignored (no error)

<Aside type="caution">
  **Why validate after commit:**\
  Validation occurs after `commit()` to ensure replay protection is saved even if the message structure is invalid. This prevents the same external message from being retried infinitely by lite-servers.
</Aside>

**Critical limitation:**\
Highload v3 can send **only ONE internal message** per external message. For batch transfers, use the [internal\_transfer pattern](#single-message-per-external) with an action list (up to 254 messages).

***

## Message sending flow

Highload v3 uses a two-transaction pattern to safely send messages:

<Image src="/resources/images/wallets/msg_flow_light.png" darkSrc="/resources/images/wallets/msg_flow_dark.png" alt="Message sending flow" />

<Aside type="note">
  **Why two transactions?**

  This pattern ensures that replay protection is never rolled back, even if the actual message sending fails due to insufficient funds or other action phase errors. Transaction 1 commits the `query_id` to storage before any outgoing messages are attempted in Transaction 2.

  **Critical detail:** Message validation (step 9) happens **after commit** (step 8) to prevent infinite retries of invalid messages by lite-servers.
</Aside>

See [Single message per external](#single-message-per-external) for details on this limitation and the batch transfer workaround.

***

## Exit codes

| Exit code | Name                   | Description                                                                | How to fix                                                                      |
| --------- | ---------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `0`       | Success                | Message processed successfully                                             | —                                                                               |
| `33`      | Invalid signature      | Ed25519 signature verification failed                                      | Check that the private key is correct and the message hash is computed properly |
| `34`      | Subwallet ID mismatch  | The `subwallet_id` in the message does not match storage                   | Verify you are using the correct `subwallet_id` for this wallet                 |
| `35`      | Invalid created\_at    | Message timestamp is invalid (too old or from the future)                  | Ensure `created_at > now() - timeout` and `created_at <= now()`                 |
| `36`      | Query already executed | The `query_id` was already processed (found in `queries` or `old_queries`) | Use a new, unique `query_id`                                                    |
| `37`      | Invalid message        | The `message_to_send` structure is invalid or cannot be processed          | Verify the message cell structure and contents                                  |
| `38`      | Invalid timeout        | The `timeout` in the message does not match storage timeout                | Verify you are using the correct `timeout` value for this wallet                |

***

## Limitations and constraints

### Single message per external

**Limitation:**\
Each external message can trigger **only one** outgoing internal message directly.

**Why this limitation?**\
Manually validating message structure is expensive in gas costs. The contract validates only the single `message_to_send` reference to keep gas consumption predictable and low.

**Why no state-init support?**\
State-init validation is complex and gas-intensive, while deploying contracts from a highload wallet is rarely needed. The feature was intentionally excluded to reduce gas costs.

**Workaround for batch transfers:**\
Send an internal message to the wallet itself with opcode `0xae42e5a4` (`internal_transfer`) and an action list containing up to **254 outgoing messages** (not 255, because one action slot is reserved for [`set_code` protection](#protection-against-set-code)).

**How to implement:** [Send batch transfers](/standard/wallets/highload/v3/send-batch-transfers)

***

### Query ID space limitations

Highload v3 supports up to **8,380,416 unique query IDs** (see [`query_id` structure](#query-id-composite-structure) for details).

**Impact on throughput:**\
If you send messages faster than `timeout`, you may exhaust available query IDs. After `timeout`, old IDs can be reused.

**Recommended strategy:**\
Use a counter-based approach, incrementing `query_id` for each message.

***

### Timeout constraints

The `timeout` value affects message validity, storage costs, and operational behavior:

**Message validity:**\
Messages are valid for `timeout` seconds after `created_at`. Expired messages are rejected with exit code 35.

**Storage costs:**\
Processed `query_id` values remain in storage for up to `2 × timeout` (across `queries` and `old_queries` hashmaps). Longer timeouts increase storage size and costs.

**Operational impact:**

* Short timeout (seconds/minutes): Fast expiration certainty, but messages may expire during network congestion
* Long timeout (hours): Messages survive congestion, but slow failure detection and higher storage costs

***

### Gas consumption

Gas costs vary depending on the number of outgoing messages sent:

| Operation    | Transaction 1 (external) | Transaction 2 (internal) | Total |
| ------------ | ------------------------ | ------------------------ | ----- |
| 1 message    | TBD                      | TBD                      | TBD   |
| 10 messages  | TBD                      | TBD                      | TBD   |
| 254 messages | TBD                      | TBD                      | TBD   |

**What affects gas costs:**

Gas consumption depends **only** on the number of entries in `queries` and `old_queries` hashmaps. Cleanup/rotation operations are highly optimized and add minimal overhead (unlike Highload v2).

**Forward fees:**

The two-transaction pattern means forward fees are spent **twice**: first when sending the external message (outside → external), then when the wallet sends an internal message to itself (external → internal). This makes Highload v3 approximately **2× more expensive in forward fees** compared to single-transaction wallets like v5.

Forward fees scale with:

* Number of outgoing messages
* Size and complexity of message content

***

## Get methods

Highload Wallet v3 provides several read-only methods for monitoring and verification.

| Method                             | Returns          | Description                                                                   |
| ---------------------------------- | ---------------- | ----------------------------------------------------------------------------- |
| `get_public_key()`                 | `int` (256 bits) | Returns the stored public key                                                 |
| `get_subwallet_id()`               | `int` (32 bits)  | Returns the subwallet ID                                                      |
| `get_timeout()`                    | `int`            | Returns the current timeout value (stored as uint22)                          |
| `get_last_clean_time()`            | `int` (64 bits)  | Returns the Unix timestamp of the last cleanup                                |
| `processed?(query_id, need_clean)` | `(int, int)`     | Checks if `query_id` was processed; optionally indicates if cleanup is needed |

### `processed?` method details

**Parameters:**

* `query_id` (int): The query ID to check.
* `need_clean` (int): If non-zero, also return whether cleanup is due.

**Returns:**

* First int: `-1` if processed, `0` if not.
* Second int: `-1` if cleanup is needed, `0` otherwise.

**Use case:**\
Before sending a message, check if a `query_id` was already used to avoid replay errors.

**Link:** [How to verify if a message is processed](/standard/wallets/highload/v3/verify-is-processed)

***

## Protection against `set_code`

**Why this protection is needed:**

The contract uses `set_actions(actions)` to execute arbitrary actions from the internal message (this allows sending batch transfers). However, leaving the ability to execute `set_code` actions would be unsafe — it creates a risk of **accidentally changing the contract code**.

**How the protection works:**

In `recv_internal` (Transaction 2), after extracting the action list from the internal message, the contract executes:

```func theme={"theme":{"light":"github-light-default","dark":"dark-plus"},"languages":{"custom":["/resources/grammars/tolk.tmLanguage.json","/resources/grammars/tlb.tmLanguage.json","/resources/grammars/fift.tmLanguage.json","/resources/grammars/tasm.tmLanguage.json","/resources/grammars/func.tmLanguage.json"]}}
cell old_code = my_code();
set_actions(actions);      // Apply action list from message
set_code(old_code);        // Immediately restore original code
```

This pattern **prevents any `set_code` action in the action list from taking effect**. Even if an action list accidentally contains a `set_code` instruction, the final `set_code(old_code)` call overwrites it, ensuring the contract code remains unchanged.

**Action list limitation:**\
Because the contract calls `set_code(old_code)` as a protection mechanism, one action slot is consumed. This is why the maximum number of outgoing messages in a batch is **254** (not 255) — one slot is reserved for the `set_code` protection.

***

## Implementation

**Source code:** [`ton-blockchain/highload-wallet-contract-v3`](https://github.com/ton-blockchain/highload-wallet-contract-v3)

**SDK wrappers:**

* **Go:** [`tonutils-go`](https://github.com/xssnick/tonutils-go) — includes Highload v3 wrapper
* **Python:** [`pytoniq`](https://github.com/yungwine/pytoniq) — includes Highload v3 wrapper
* **TypeScript/JavaScript:** Copy wrappers from the [official repository](https://github.com/ton-blockchain/highload-wallet-contract-v3/tree/main/wrappers)

**Tests and examples:** See the [tests/](https://github.com/ton-blockchain/highload-wallet-contract-v3/tree/main/tests) directory in the repository for reference implementation and usage patterns.

**Link:** [How-to guides](/standard/wallets/highload/v3/create)

***

## See also

* [How to create Highload Wallet v3](/standard/wallets/highload/v3/create)
* [How to send single transfer](/standard/wallets/highload/v3/send-single-transfer)
* [How to send batch transfers](/standard/wallets/highload/v3/send-batch-transfers)
* [How to verify if a message is processed](/standard/wallets/highload/v3/verify-is-processed)
