Skip to main content
Lockup wallet is a specialized wallet contract that locks funds until a specified time. The repository contains two implementations with different unlocking mechanisms.

Universal Lockup Wallet

Universal Lockup Wallet implements time-based fund locking with allowlist functionality. All funds unlock simultaneously when the time restrictions expire. Source code: universal/uni-lockup-wallet.fc

Use cases

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

Persistent memory layout

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:
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.
Internal messages with other opcodes from allowlisted addresses are silently ignored.

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.
There is no get-method for config_public_key. This is by design — the configuration key is only used internally for adding locked funds.

Exit codes

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

Vesting Wallet

Vesting Wallet implements gradual fund unlocking over time with an optional cliff period. The funds unlock linearly according to a vesting schedule. Available through web interface. Source code.

Use cases

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

Persistent memory layout

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 and 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 codeDescription
33Sequence number mismatch
34Subwallet ID mismatch
35Signature verification failed
36Message expired (valid_until check failed)
37Invalid number of message references
38Invalid send mode (must be 3 if message present)