-
Notifications
You must be signed in to change notification settings - Fork 21
feat: lockup wallet page #973
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 6 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
98ab3c7
lockup
pyAndr3w be82f9a
fmt
pyAndr3w 35d5101
rr
pyAndr3w 34dddab
Merge branch 'main' into 200-lockup-wallet
pyAndr3w d682ea0
rewrite 💀
pyAndr3w cceade0
Merge branch 'main' into 200-lockup-wallet
pyAndr3w 38ca30c
review
064a278
review
8c65686
Merge branch 'main' into 200-lockup-wallet
verytactical 917fa1e
formatting
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -287,6 +287,7 @@ | |
| } | ||
| ] | ||
| }, | ||
| "standard/wallets/lockup", | ||
| { | ||
| "group": "Preprocessed Wallet V2", | ||
| "pages": [ | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,212 @@ | ||
| --- | ||
| title: "Lockup Wallet" | ||
| --- | ||
|
|
||
| import { Aside } from '/snippets/aside.jsx'; | ||
|
|
||
| Lockup wallet is a specialized wallet contract that locks TON 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) | ||
|
|
||
| ### Persistent memory layout | ||
|
|
||
| ```tlb | ||
| 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 | ||
| rwallet_op#82eaf9c4 | ||
| signature:(## 512) | ||
| cmd:(## 32) | ||
| only_restrict:(## 1) | ||
| timestamp:(## 32) | ||
| = InternalMsgBody; | ||
| ``` | ||
|
|
||
| Message requirements: | ||
|
|
||
| - Must carry ≥1 TON value (1,000,000,000 nanotons). | ||
| - Contain valid signature from `config_public_key`. | ||
| - `cmd` must be `0x373aa9f4` (restricted\_transfer). | ||
| - `only_restrict`: Flag determining lock type (1 = restricted funds, 0 = locked funds). | ||
| - `timestamp`: Unix timestamp for unlock. | ||
|
|
||
| <Aside type="note"> | ||
| Internal messages from allowlisted addresses with different op codes are silently ignored (no error). | ||
| </Aside> | ||
|
|
||
| ### Get methods | ||
|
|
||
| 1. `int seqno()` returns current sequence number. | ||
| 1. `int wallet_id()` returns current subwallet ID. | ||
| 1. `int get_public_key()` returns stored public key. | ||
| 1. `(int, int, int) get_balances_at(int time)` returns balance, restricted value, and locked value at specified time. | ||
| 1. `(int, int, int) get_balances()` returns current balance, restricted value, and locked value. | ||
| 1. `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) | | ||
|
|
||
| ### Use cases | ||
|
|
||
| - Escrow services: Lock funds until conditions are met, with allowlist of valid recipients. | ||
| - Simple time-based lockups: Hold funds until specific unlock date. | ||
| - Temporary holds: Short-term fund restrictions with flexible unlock schedules. | ||
|
|
||
| Example: | ||
|
|
||
| ```text | ||
| Amount: 100,000 TON | ||
| Lock timestamp: Unix time 1735689600 (2025-01-01) | ||
| Result: All 100,000 TON unlock after specified time | ||
| ``` | ||
|
|
||
| ## Vesting Wallet | ||
|
|
||
| [Vesting Wallet](https://github.com/ton-blockchain/lockup-wallet-contract/tree/c2c9f73394853780621e6215410a95475ac7cf4f/vesting) implements gradual fund unlocking over time with optional cliff period. Funds unlock linearly according to vesting schedule. | ||
|
|
||
| Source code: [vesting/vesting-lockup-wallet.fc](https://github.com/ton-blockchain/lockup-wallet-contract/blob/c2c9f73394853780621e6215410a95475ac7cf4f/vesting/vesting-lockup-wallet.fc) | ||
|
|
||
| ### Persistent memory layout | ||
|
|
||
| ```tlb | ||
| 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. | ||
| 1. `int get_public_key()` returns stored public key. | ||
| 1. `int get_locked_amount(int now_time)` returns locked amount at specified time. | ||
| 1. `(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) | | ||
|
|
||
| ### Use cases | ||
|
|
||
| - Employee token vesting: Lock employee tokens with vesting schedule (e.g., 4 years with 1-year cliff). | ||
| - Investment lockups: Gradual unlock of investor funds over predetermined periods. | ||
| - Long-term savings: Personal investment strategies with scheduled unlocking. | ||
|
|
||
| Example: | ||
|
|
||
| ```text | ||
| Total: 1,000,000 TON | ||
| Vesting period: 10 years (120 months) | ||
| Unlock period: 1 month | ||
| Cliff: 1 year (12 months) | ||
| Result: | ||
| - Months 0-12: 0 TON available (cliff period) | ||
| - Month 13: ~108,333 TON unlocked (13 periods worth of vesting) | ||
| - Months 14-120: ~8,333 TON unlocks each month | ||
| - After month 120: All 1,000,000 TON fully unlocked | ||
| ``` | ||
|
|
||
| ## DApp interface | ||
|
|
||
| Lockup Sender: [toncenter.github.io/lockup-sender](https://toncenter.github.io/lockup-sender) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.