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

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

</AgentInstructions>

# Preprocessed 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="caution" title="Community implementation">
  This wallet is a community-created implementation. Use at your own risk. Always test thoroughly on testnet before using with real funds.
</Aside>

This page provides a complete technical specification for Preprocessed Wallet V2, covering storage structure, message formats, replay protection, limitations, and implementation details.

## Objective

Understand the internal architecture, data structures, and operational mechanics of Preprocessed Wallet V2. This reference page explains how the wallet processes batch transactions efficiently with minimal gas consumption.

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

## What is Preprocessed Wallet V2?

Preprocessed Wallet V2 is a wallet smart contract designed for efficiency with minimal code complexity. It provides low transaction costs while maintaining the ability to send up to 255 actions in a single transaction.

**Key difference from standard wallets:**\
Preprocessed V2 enables batch transaction processing with up to 255 actions per message, making it suitable for services that need to send multiple payments efficiently.

**Optimization focus:**\
Preprocessed V2 prioritizes gas efficiency and simplicity, making it suitable for applications that need batch processing with minimal overhead.

**Gas consumption:**\
The wallet consumes 1537 gas units per transaction.

## 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$_ pub_key:bits256 seq_no:uint16 = Storage;
```

### 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:#} valid_until:uint64 seq_no:uint16 actions:^(OutList n) { n <= 255 } = MsgInner n;

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

<Aside type="note">
  The canonical TL-B schemas are maintained in the [ton-preprocessed-wallet-v2 repository](https://github.com/pyAndr3w/ton-preprocessed-wallet-v2).
</Aside>

Below, each field is explained in detail.

## Storage structure

The Preprocessed Wallet V2 contract stores two persistent fields:

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

### `seq_no` (16 bits)

**Purpose:**\
Prevents replay attacks by ensuring each message has a unique sequence number.

**How it works:**\
Each external message must contain the correct `seq_no` that matches the current value stored in the contract. After successful processing, the sequence number is incremented (with modulo 2^16 to prevent overflow).

**Range:** 0 to 65535 (wraps from 65535 to 0)

**Size optimization:**\
The sequence number is limited to 16 bits (instead of 32 bits used in other wallets) to minimize storage size and message size, reducing gas costs.

**Protection mechanism:**\
Even if an attacker intercepts an old message, they cannot replay it because the sequence number will no longer match the current value in storage.

**Wrap‑around note:**\
See [Replay protection mechanism](#replay-protection-mechanism) for guidance on wrap‑around and `valid_until` windows.

## External message structure

External messages sent to Preprocessed Wallet V2 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"]}}
sign:bits512
^[ valid_until:uint64
   seq_no:uint16
   actions:^(OutList n) ]
```

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

<Aside type="tip" title="Gas optimization">
  This structure saves 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>

***

### `sign` (512 bits)

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

**What is signed:**\
The hash of the reference cell (`MsgInner`) containing `valid_until`, `seq_no`, and `actions`.

**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 `35`.

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

***

### `valid_until` (64 bits)

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

**Validation:**\
The contract performs a single check:

```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"]}}
now() > valid_until  // Message expired
```

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

**Why it matters:**\
Prevents replay of expired messages. If `now() > valid_until`, the message is considered expired and rejected.

**Recommendation:**\
Set `valid_until` to 1 minute (60 seconds) from message creation time.

***

### `seq_no` (16 bits)

**Purpose:**\
Expected sequence number for this message.

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

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

### `actions` (reference cell)

**Structure:**\
A serialized `OutList` containing up to 255 actions to execute.

**Validation:**\
No validation.

**Supported actions:**
All standard TVM actions are supported without restrictions:

* `action_send_msg` — Send messages
* `action_set_code` — Update contract code
* `action_reserve_currency` — Reserve currency
* `action_change_library` — Change library

<Aside type="danger" title="Critical security warning">
  Preprocessed Wallet V2 does not protect against `action_set_code` actions. If you accidentally include a `set_code` action in your message, the wallet contract code will be changed, potentially making your funds inaccessible. Always verify your action lists before sending.
</Aside>

**Maximum actions:** 255 (maximum number of out actions in TON)

## Replay protection mechanism

Preprocessed Wallet V2 uses sequence numbers and time-based expiration to prevent replay attacks.

### Sequence number protection

**How it works:**

1. Each message must contain the correct `seq_no`
2. After successful processing, `seq_no` is incremented
3. Old messages with incorrect sequence numbers are rejected

**Overflow protection:**

* Uses modulo 2^16 operation: `(seq_no + 1) % 65536`
* Wraps from 65535 to 0

### Time-based expiration

**How it works:**

1. Each message includes `valid_until` timestamp
2. Messages are rejected if `now() > valid_until`
3. Prevents replay of expired messages

**Recommended expiration:**

* 1 minute (60 seconds) for most use cases
* Shorter for high-frequency operations
* Longer for batch operations with network delays

### Wrap‑around and validity window

When `seq_no` wraps (65535 → 0), previously sent messages that used the same numeric `seq_no` remain non‑applicable if their `valid_until` has already expired. Safety relies on the dual check: current `seq_no` equality and unexpired `valid_until`.

Do not set `valid_until` excessively far in the future. A very long validity window increases the chance that, after wrap‑around, an old message with the same `seq_no` is still valid. Keep the window short (for example, ≤ 60 seconds) unless you have a specific operational reason.

### Signature verification

**How it works:**

1. All messages must be signed with the wallet's private key
2. Signature covers the entire message content (hash of `MsgInner`)
3. Prevents unauthorized message creation

## Exit codes

| Exit code | Name                      | Description                                        | How to fix                                                                      |
| --------- | ------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------- |
| `0`       | Success                   | Message processed successfully                     | —                                                                               |
| `33`      | Incorrect sequence number | The `seq_no` in the message does not match storage | Use the correct `seq_no` from contract storage                                  |
| `34`      | Message expired           | The `valid_until` timestamp has passed             | Ensure `valid_until >= now()` when creating the message                         |
| `35`      | Invalid signature         | Ed25519 signature verification failed              | Check that the private key is correct and the message hash is computed properly |

## Limitations and constraints

### Maximum actions per transaction

**Limitation:**\
Each external message can trigger up to 255 actions.

**Why this limitation?**\
255 is the maximum number of out actions supported by TON blockchain. This is a fundamental limitation of the TVM execution environment.

**Impact:**\
Suitable for batch operations with up to 255 actions per transaction.

<Aside type="note" title="Sequential processing">
  Preprocessed Wallet V2 uses sequential `seq_no` processing. This means you must wait for each transaction to complete before sending the next one.
</Aside>

## Implementation

### Source code

* Repository: - [pyAndr3w/ton-preprocessed-wallet-v2](https://github.com/pyAndr3w/ton-preprocessed-wallet-v2)

## See also

* [How to interact with Preprocessed Wallet V2](/standard/wallets/preprocessed-v2/interact)
