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

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

</AgentInstructions>

# Lockup wallet

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

Lockup wallet is a specialized wallet contract that locks funds until a specified time. The [repository](https://github.com/ton-blockchain/lockup-wallet-contract) contains two implementations with different unlocking mechanisms.

## Universal Lockup Wallet

[Universal Lockup Wallet](https://github.com/ton-blockchain/lockup-wallet-contract/tree/c2c9f73394853780621e6215410a95475ac7cf4f/universal) implements time-based fund locking with allowlist functionality. All funds unlock simultaneously when the time restrictions expire.

Source code: [`universal/uni-lockup-wallet.fc`](https://github.com/ton-blockchain/lockup-wallet-contract/blob/c2c9f73394853780621e6215410a95475ac7cf4f/universal/uni-lockup-wallet.fc)

### Use cases

Escrow services. Lock funds until conditions are met, with allowlist of valid recipients.

### Persistent memory layout

```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$_
  seqno:uint32
  subwallet_id:uint32
  public_key:uint256
  config_public_key:uint256
  allowed_destinations:(PfxHashmapE 267 ^Cell)
  total_locked_value:Coins
  locked:HashmapE 32 Coins
  total_restricted_value:Coins
  restricted:HashmapE 32 Coins
  = Storage;
```

* `seqno`: 32-bit sequence number for replay protection.
* `subwallet_id`: 32-bit wallet identifier.
* `public_key`: 256-bit Ed25519 public key for signing external messages (wallet operations).
* `config_public_key`: 256-bit Ed25519 public key for signing internal messages that add locked funds. This separation allows a third party to initialize and fund the lockup wallet without having access to spend the funds.
* `allowed_destinations`: Prefix dictionary of allowlisted destination addresses (uses `pfxdict_get?` for prefix matching).
* `total_locked_value`: Total amount of locked funds (unrestricted destinations).
* `locked`: Dictionary mapping unlock timestamps to locked amounts.
* `total_restricted_value`: Total amount of restricted funds (allowlist-only).
* `restricted`: Dictionary mapping unlock timestamps to restricted amounts.

### Message layout

#### External message body layout

* `signature`: 512-bit Ed25519 signature.
* `subwallet_id`: 32-bit subwallet identifier.
* `valid_until`: 32-bit Unix timestamp.
* `msg_seqno`: 32-bit sequence number.
* Message list: References to messages to send.

The contract unlocks expired funds, reserves locked amounts using `raw_reserve(effectively_locked, 2)`, and sends messages. Each message is sent with its specified mode, but if mode is not 2, it's forced to mode 3 (pay fees separately, ignore errors).

#### Internal message body layout

Internal messages with `op = 0x82eaf9c4` (`rwallet_op`) allow adding locked funds:

```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"]}}
rwallet_op#82eaf9c4
  signature:(## 512)
  cmd:(## 32)
  only_restrict:(## 1)
  timestamp:(## 32)
  = InternalMsgBody;
```

Message requirements:

* Must carry ≥1 TON value.
* Contain valid signature from `config_public_key`.
* `cmd` must be `0x373aa9f4` (`restricted_transfer`).
* `only_restrict`: Flag determining lock type: `1` for restricted funds, `0` for locked funds.
* `timestamp`: Unix timestamp for unlock.

<Aside type="note">
  Internal messages with other opcodes from allowlisted addresses are silently ignored.
</Aside>

### Get methods

1. `int seqno()` returns current sequence number.
2. `int wallet_id()` returns current subwallet ID.
3. `int get_public_key()` returns stored public key.
4. `(int, int, int) get_balances_at(int time)` returns balance, restricted value, and locked value at specified time.
5. `(int, int, int) get_balances()` returns current balance, restricted value, and locked value.
6. `int check_destination(slice destination)` returns whether destination address is allowlisted.

<Aside type="note">
  There is no get-method for `config_public_key`. This is by design — the configuration key is only used internally for adding locked funds.
</Aside>

### Exit codes

| Exit code | Description                                       |
| --------- | ------------------------------------------------- |
| 31        | Signature verification failed (`wrong_signature`) |
| 32        | Config signature verification failed              |
| 33        | Message value too small (\< 1 TON)                |
| 34        | Sequence number mismatch (`wrong_seqno`)          |
| 35        | Subwallet ID mismatch (`wrong_subwallet_id`)      |
| 36        | Message expired (`replay_protection`)             |
| 40        | Unknown operation code (`unknown_op`)             |
| 41        | Unknown command (`unknown_cmd`)                   |

## Vesting Wallet

[Vesting Wallet](https://github.com/ton-blockchain/lockup-wallet-contract/tree/c2c9f73394853780621e6215410a95475ac7cf4f/vesting) implements gradual fund unlocking over time with an optional cliff period. The funds unlock linearly according to a vesting schedule.

Available through [web interface](https://toncenter.github.io/lockup-sender). [Source code](https://github.com/ton-blockchain/lockup-wallet-contract/blob/c2c9f73394853780621e6215410a95475ac7cf4f/vesting/vesting-lockup-wallet.fc).

### Use cases

Employee token vesting. Lock employee tokens with vesting schedule (e.g., 4 years with 1-year cliff).

### Persistent memory layout

```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$_
  stored_seqno:uint32
  stored_subwallet:uint32
  public_key:uint256
  start_time:uint64
  total_duration:uint32
  unlock_period:uint32
  cliff_duration:uint32
  total_amount:Coins
  allow_elector:Bool
  = Storage;
```

* `stored_seqno`: 32-bit sequence number (replay protection).
* `stored_subwallet`: 32-bit wallet identifier.
* `public_key`: 256-bit Ed25519 public key for signing external messages.
* `start_time`: 64-bit Unix timestamp when vesting begins.
* `total_duration`: 32-bit total vesting duration in seconds.
* `unlock_period`: 32-bit period between unlocks in seconds.
* `cliff_duration`: 32-bit cliff period before first unlock.
* `total_amount`: Total amount subject to vesting.
* `allow_elector`: Boolean flag that bypasses vesting restrictions for transfers to [Elector](/foundations/system#elector) and [Config](/foundations/system#config) contracts.

### Message layout

#### External message body layout

* `signature`: 512-bit Ed25519 signature.
* `subwallet_id`: 32-bit subwallet identifier.
* `valid_until`: 32-bit Unix timestamp.
* `msg_seqno`: 32-bit sequence number.
* Optional: One message reference. If present, mode MUST be 3 (pay fees separately, ignore errors).

The contract calculates locked amount based on vesting schedule:

* Before `start_time + cliff_duration`: All funds locked.
* During vesting: Linear unlock based on `unlock_period`.
* After `start_time + total_duration`: All funds unlocked.

When `allow_elector` is enabled, vesting restrictions are bypassed for transfers to system contracts (see `allow_elector` field description above).

#### Internal message body layout

Internal messages are ignored (no operations performed).

### Get methods

1. `int seqno()` returns current sequence number.
2. `int get_public_key()` returns stored public key.
3. `int get_locked_amount(int now_time)` returns locked amount at specified time.
4. `(int, int, int, int, int, int) get_lockup_data()` returns `(start_time, total_duration, unlock_period, cliff_duration, total_amount, allow_elector)`.

### Exit codes

| Exit code | Description                                      |
| --------- | ------------------------------------------------ |
| 33        | Sequence number mismatch                         |
| 34        | Subwallet ID mismatch                            |
| 35        | Signature verification failed                    |
| 36        | Message expired (`valid_until` check failed)     |
| 37        | Invalid number of message references             |
| 38        | Invalid send mode (must be 3 if message present) |
