> ## 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/v2/specification",
  "feedback": "Description of the issue"
}
```

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

</AgentInstructions>

# Highload Wallet v2 — specification

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>
    </>;
};

<Aside type="danger">
  **Deprecated:** Highload Wallet v2 is a legacy contract. Use [Highload Wallet v3](/standard/wallets/highload/v3/specification) for new deployments.

  This specification is maintained for reference to support existing legacy systems.
</Aside>

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](/standard/wallets/highload/v3/specification)

## 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 and external 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$_ subwallet_id:uint32 last_cleaned:uint64 public_key:bits256
          queries:(HashmapE 64 Cell) = 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"]}}
_ query_id:uint64 = 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"]}}
_ mode:uint8 message:^Cell = OutListNode;

msg_body$_ signature:bits512 subwallet_id:uint32 query_id:uint64
          messages:(HashmapE 16 Cell) = ExternalInMsgBody;
```

## 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:**

```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"]}}
bound -= (64 << 32);  // Clean up records expired more than 64 seconds ago
```

Queries with `query_id < (now() - 64) << 32` are removed from storage.

<Aside type="caution">
  **Gas costs:** Cleanup operations consume gas proportional to the number of expired queries. With many expired queries, cleanup can exceed the 1,000,000 gas limit, causing the transaction to fail.
</Aside>

***

### `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.

<Aside type="caution">
  **Storage limit:** The `queries` dictionary cannot exceed 65,535 cells. If this limit is reached, the contract will fail during the action phase.
</Aside>

## External message structure

### 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
query_id:uint64
messages:(HashmapE 16 Cell)
```

**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:**

```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"]}}
throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key));
```

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](/foundations/messages/modes):

```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"]}}
int i = -1;
do {
  (i, var cs, var f) = dict.idict_get_next?(16, i);
  if (f) {
    var mode = cs~load_uint(8);
    send_raw_message(cs~load_ref(), mode);
  }
} until (~ f);
```

**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

<Aside type="caution">
  **Rollback issue:** Highload Wallet v2 does not use `commit()` to persist storage changes. If the compute phase fails after `accept_message()` (e.g., gas limit exceeded during cleanup) or if the action phase fails, **all changes roll back**, including replay protection. The `query_id` is not marked as processed, and lite-servers will retry the same message, burning gas repeatedly.

  Highload Wallet v3 solves this with `commit()` and a two-transaction pattern. See [Why internal messages to self?](/standard/wallets/highload/v3/specification#why-internal-messages-to-self) for details.
</Aside>

***

## Exit codes

| Exit code | Name                           | Description                                                     | How to fix                                                      |
| --------- | ------------------------------ | --------------------------------------------------------------- | --------------------------------------------------------------- |
| `0`       | Success                        | Message processed successfully                                  | —                                                               |
| `32`      | Query already executed         | The `query_id` was already processed (found in `queries`)       | Use a new, unique `query_id`                                    |
| `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 signature or query\_id | Ed25519 signature verification failed, or `query_id` is too old | Check the private key and ensure `query_id >= now() << 32`      |

***

## 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

| Method                 | Returns          | Description                                                                               |
| ---------------------- | ---------------- | ----------------------------------------------------------------------------------------- |
| `processed?(query_id)` | `int`            | Returns `-1` if processed, `0` if not processed, `1` if unknown (forgotten after cleanup) |
| `get_public_key()`     | `int` (256 bits) | Returns the Ed25519 public key                                                            |

### `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)](https://github.com/ton-blockchain/ton/blob/4ebd7412c52248360464c2df5f434c8aaa3edfe1/crypto/smartcont/highload-wallet-v2-code.fc)

**SDK wrappers:**

* **Go:** [`tonutils-go`](https://github.com/xssnick/tonutils-go) — includes Highload v2 wrapper
* **Python:** [`pytoniq`](https://github.com/yungwine/pytoniq) — includes Highload v2 wrapper

For new projects, consider using [Highload Wallet v3](/standard/wallets/highload/v3/specification) instead.

***

## See also

* [Highload Wallet v3 specification](/standard/wallets/highload/v3/specification) — recommended version
* [Version comparison](/standard/wallets/highload/overview#version-comparison) — v1 vs v2 vs v3
