├── .gitignore ├── LICENSE ├── README.md └── func ├── build.sh ├── print-hex.fif ├── simple-subscription-plugin.fc ├── stdlib.fc └── wallet-v4-code.fc /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | func/*.boc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 TON CORE 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wallet V4 2 | Wallet v4 is proposed version of wallet to replace v3 or older wallets. 3 | 4 | The main difference from previous versions consist in plugins functionality: 5 | trusted conjugated contracts may implement complex logic while being able to use all funds from main wallet. 6 | 7 | That way wallet can be extended in numerous ways, including partial, infinite or programmatic allowances, special connectors to specific DApps, custom user-governed add-ons. 8 | 9 | More info see in [TIPS-38](https://github.com/newton-blockchain/TIPs/issues/38). 10 | 11 | ## Interface 12 | ### External messages 13 | 1. Send arbitrary owner-formed message (the same functionality as v1, v2, v3) 14 | 2. Deploy and install plugin 15 | 3. Install deployed plugin 16 | 4. Remove plugin 17 | 18 | ### Internal messages 19 | 1. Upon receiving message with `0x706c7567` op from plugin (list of plugins is stored in wallet storage), wallet sends requested funds to plugin. 20 | 21 | ## Plugins 22 | ### Subscription plugin 23 | Plugin implements logic of periodic predefined payments to fixed destination address. Payment is initiated by anyone-can-send external message, 24 | while plugin's logic ensures that funds will be sent not more often than desired. Fees are subtracted from transferred amount (payee pays for fees) 25 | including 1 Toncoin which stays on plugin balance until plugin destruction. Upon subscription destruction, remnants of 1 Toncoins are transferred to 26 | destination address. 27 | -------------------------------------------------------------------------------- /func/build.sh: -------------------------------------------------------------------------------- 1 | func -o wallet-v4-code.fif -SPA stdlib.fc wallet-v4-code.fc 2 | func -o subscription-plugin-code.fif -SPA stdlib.fc simple-subscription-plugin.fc 3 | 4 | fift print-hex.fif -------------------------------------------------------------------------------- /func/print-hex.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | "Asm.fif" include 4 | 5 | ."subscription:" cr 6 | 7 | "subscription-plugin-code.fif" include 8 | 2 boc+>B dup Bx. cr 9 | 10 | ."wallet:" cr 11 | 12 | "wallet-v4-code.fif" include 13 | 2 boc+>B dup Bx. cr 14 | -------------------------------------------------------------------------------- /func/simple-subscription-plugin.fc: -------------------------------------------------------------------------------- 1 | #pragma version =0.2.0; 2 | ;; Simple subscription plugin for wallet-v4 3 | ;; anyone can ask to send a subscription payment 4 | 5 | (int) slice_data_equal?(slice s1, slice s2) asm "SDEQ"; 6 | 7 | int op:destruct() asm "0x64737472 PUSHINT"; 8 | int op:payment_request() asm "0x706c7567 PUSHINT"; 9 | int op:fallback() asm "0x756e6b77 PUSHINT"; 10 | int op:subscription() asm "0x73756273 PUSHINT"; 11 | int max_failed_attempts() asm "2 PUSHINT"; 12 | int max_reserved_funds() asm "67108864 PUSHINT"; ;; 0.0671 TON 13 | 14 | int short_msg_fwd_fee(int workchain) inline { 15 | int config_index = 25 + workchain; 16 | int lump_price = config_param(config_index).begin_parse().skip_bits(8).preload_uint(64); 17 | return lump_price; 18 | } 19 | 20 | int gas_to_coins(int workchain, int gas) inline_ref { 21 | int config_index = 21 + workchain; 22 | var cs = config_param(config_index).begin_parse(); 23 | if (cs.preload_uint(8) == 0xd1) { ;; gas_flat_pfx 24 | cs~skip_bits(8 + 64 + 64); 25 | } 26 | int tag = cs~load_uint(8); 27 | throw_unless(71, (tag == 0xdd) | (tag == 0xde)); ;; gas_prices or gas_prices_ext 28 | int gas_price = cs~load_uint(64); 29 | return (gas * gas_price) >> 16; 30 | } 31 | 32 | ;; storage$_ wallet:MsgAddressInt 33 | ;; beneficiary:MsgAddressInt 34 | ;; amount:Grams 35 | ;; period:uint32 start_time:uint32 timeout:uint32 36 | ;; last_payment_time:uint32 37 | ;; last_request_time:uint32 38 | ;; failed_attempts:uint8 39 | ;; subscription_id:uint32 = Storage; 40 | 41 | (slice, slice, int, int, int, int, int, int, int, int) load_storage() impure inline_ref { 42 | var ds = get_data().begin_parse(); 43 | return (ds~load_msg_addr(), ds~load_msg_addr(), ds~load_grams(), 44 | ds~load_uint(32), ds~load_uint(32), ds~load_uint(32), 45 | ds~load_uint(32), ds~load_uint(32), ds~load_uint(8), ds~load_uint(32)); 46 | } 47 | 48 | () save_storage(slice wallet, slice beneficiary, int amount, 49 | int period, int start_time, int timeout, int last_payment_time, 50 | int last_request_time, int failed_attempts, int subscription_id) impure inline_ref { 51 | set_data(begin_cell() 52 | .store_slice(wallet) 53 | .store_slice(beneficiary) 54 | .store_grams(amount) 55 | .store_uint(period, 32) 56 | .store_uint(start_time, 32) 57 | .store_uint(timeout, 32) 58 | .store_uint(last_payment_time, 32) 59 | .store_uint(last_request_time, 32) 60 | .store_uint(failed_attempts, 8) 61 | .store_uint(subscription_id, 32) ;; to differ subscriptions to the same beneficiary (acts as a nonce) 62 | .end_cell()); 63 | } 64 | 65 | () forward_funds(slice beneficiary, int self_destruct?, int op) impure inline_ref { 66 | if ~(self_destruct?) { 67 | raw_reserve(max_reserved_funds(), 2); ;; reserve at most `max_reserved_funds` nanocoins 68 | } 69 | var msg = begin_cell() 70 | .store_uint(0x10, 6) ;; non-bounce message 71 | .store_slice(beneficiary) 72 | .store_grams(0) 73 | .store_dict(pair_second(get_balance())) 74 | .store_uint(0, 4 + 4 + 64 + 32 + 1 + 1) 75 | .store_uint(op, 32); 76 | int mode = 128; ;; carry all the remaining balance of the current smart contract 77 | if (self_destruct?) { 78 | mode += 32; ;; must be destroyed if its resulting balance is zero 79 | } 80 | send_raw_message(msg.end_cell(), mode); 81 | } 82 | 83 | () request_payment(slice wallet, int requested_amount) impure inline_ref { 84 | (int wc, _) = wallet.parse_std_addr(); 85 | int amount = gas_to_coins(wc, 15000) + short_msg_fwd_fee(wc); 86 | 87 | var msg = begin_cell().store_uint(0x18, 6) 88 | .store_slice(wallet) 89 | .store_grams(amount) 90 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 91 | .store_uint(op:payment_request(), 32) ;; request op 92 | .store_uint(cur_lt(), 64) ;; query_id 93 | .store_grams(requested_amount) 94 | .store_uint(0, 1); ;; empty extra 95 | send_raw_message(msg.end_cell(), 3); 96 | } 97 | 98 | () self_destruct(slice wallet, slice beneficiary) impure { 99 | ;; send event to wallet 100 | (int wc, _) = wallet.parse_std_addr(); 101 | int amount = gas_to_coins(wc, 10000); 102 | 103 | var msg = begin_cell().store_uint(0x10, 6) ;; non-bounce - we dont need answer from wallet 104 | .store_slice(wallet) 105 | .store_grams(amount) 106 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 107 | .store_uint(op:destruct(), 32) ;; request op 108 | .store_uint(cur_lt(), 64); ;; query_id 109 | send_raw_message(msg.end_cell(), 3); 110 | 111 | ;; forward all the remaining funds to the beneficiary & destroy 112 | 113 | forward_funds(beneficiary, true, op:destruct()); 114 | } 115 | 116 | () recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { 117 | var (wallet, beneficiary, amount, period, start_time, timeout, last_payment_time, last_request_time, failed_attempts, subscription_id) = load_storage(); 118 | var cs = in_msg_cell.begin_parse(); 119 | var flags = cs~load_uint(4); 120 | slice s_addr = cs~load_msg_addr(); 121 | 122 | if (slice_data_equal?(s_addr, beneficiary)) { 123 | int op = in_msg~load_uint(32); 124 | if (op == op:destruct()) { 125 | ;; end subscription 126 | return self_destruct(wallet, beneficiary); 127 | } 128 | return forward_funds(beneficiary, false, op:fallback()); 129 | } 130 | if (~ slice_data_equal?(s_addr, wallet)) { 131 | return forward_funds(beneficiary, false, op:fallback()); 132 | } 133 | if (in_msg.slice_bits() < 32) { 134 | return forward_funds(beneficiary, false, op:fallback()); 135 | } 136 | int op = in_msg~load_uint(32); 137 | 138 | if (op == (op:payment_request() | 0x80000000)) { 139 | int last_timeslot = (last_payment_time - start_time) / period; 140 | int cur_timeslot = (now() - start_time) / period; 141 | throw_if(49, last_timeslot >= cur_timeslot); 142 | (int from_wc, _) = s_addr.parse_std_addr(); 143 | 144 | if (msg_value >= amount - short_msg_fwd_fee(from_wc) ) { 145 | last_payment_time = now(); 146 | failed_attempts = 0; 147 | forward_funds(beneficiary, false, op:subscription()); 148 | } 149 | 150 | return save_storage(wallet, beneficiary, amount, period, start_time, timeout, last_payment_time, last_request_time, failed_attempts, subscription_id); 151 | } 152 | if (op == op:destruct()) { ;; self-destruct 153 | ;; forward all the remaining funds to the beneficiary & destroy 154 | return forward_funds(beneficiary, true, op:destruct()); 155 | } 156 | } 157 | 158 | () recv_external(slice in_msg) impure { 159 | var (wallet, beneficiary, amount, period, start_time, timeout, last_payment_time, last_request_time, failed_attempts, subscription_id) = load_storage(); 160 | int last_timeslot = (last_payment_time - start_time) / period; 161 | int cur_timeslot = (now() - start_time) / period; 162 | throw_unless(30, (cur_timeslot > last_timeslot) & (last_request_time + timeout < now())); ;; too early request 163 | accept_message(); 164 | if (failed_attempts >= max_failed_attempts()) { 165 | self_destruct(wallet, beneficiary); 166 | } else { 167 | request_payment(wallet, amount); 168 | failed_attempts += 1; 169 | } 170 | save_storage(wallet, beneficiary, amount, period, start_time, timeout, last_payment_time, now(), failed_attempts, subscription_id); 171 | } 172 | 173 | ;; Get methods 174 | 175 | ([int, int], [int, int], int, int, int, int, int, int, int, int) get_subscription_data() method_id { 176 | var (wallet, beneficiary, amount, period, start_time, timeout, last_payment_time, last_request_time, failed_attempts, subscription_id) = load_storage(); 177 | return (pair(parse_std_addr(wallet)), pair(parse_std_addr(beneficiary)), 178 | amount, period, start_time, timeout, last_payment_time, last_request_time, failed_attempts, subscription_id); 179 | } 180 | -------------------------------------------------------------------------------- /func/stdlib.fc: -------------------------------------------------------------------------------- 1 | ;; Standard library for funC 2 | ;; 3 | 4 | forall X -> tuple cons(X head, tuple tail) asm "CONS"; 5 | forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; 6 | forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; 7 | forall X -> X car(tuple list) asm "CAR"; 8 | tuple cdr(tuple list) asm "CDR"; 9 | tuple empty_tuple() asm "NIL"; 10 | forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; 11 | forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; 12 | forall X -> [X] single(X x) asm "SINGLE"; 13 | forall X -> X unsingle([X] t) asm "UNSINGLE"; 14 | forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; 15 | forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; 16 | forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; 17 | forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; 18 | forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; 19 | forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; 20 | forall X -> X first(tuple t) asm "FIRST"; 21 | forall X -> X second(tuple t) asm "SECOND"; 22 | forall X -> X third(tuple t) asm "THIRD"; 23 | forall X -> X fourth(tuple t) asm "3 INDEX"; 24 | forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; 25 | forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; 26 | forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; 27 | forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; 28 | forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; 29 | forall X -> X null() asm "PUSHNULL"; 30 | forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; 31 | 32 | int now() asm "NOW"; 33 | slice my_address() asm "MYADDR"; 34 | [int, cell] get_balance() asm "BALANCE"; 35 | int cur_lt() asm "LTIME"; 36 | int block_lt() asm "BLOCKLT"; 37 | 38 | int cell_hash(cell c) asm "HASHCU"; 39 | int slice_hash(slice s) asm "HASHSU"; 40 | int string_hash(slice s) asm "SHA256U"; 41 | 42 | int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; 43 | int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; 44 | 45 | (int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; 46 | (int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; 47 | (int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; 48 | (int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; 49 | 50 | ;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; 51 | 52 | () dump_stack() impure asm "DUMPSTK"; 53 | 54 | cell get_data() asm "c4 PUSH"; 55 | () set_data(cell c) impure asm "c4 POP"; 56 | cont get_c3() impure asm "c3 PUSH"; 57 | () set_c3(cont c) impure asm "c3 POP"; 58 | cont bless(slice s) impure asm "BLESS"; 59 | 60 | () accept_message() impure asm "ACCEPT"; 61 | () set_gas_limit(int limit) impure asm "SETGASLIMIT"; 62 | () commit() impure asm "COMMIT"; 63 | () buy_gas(int gram) impure asm "BUYGAS"; 64 | 65 | int min(int x, int y) asm "MIN"; 66 | int max(int x, int y) asm "MAX"; 67 | (int, int) minmax(int x, int y) asm "MINMAX"; 68 | int abs(int x) asm "ABS"; 69 | 70 | slice begin_parse(cell c) asm "CTOS"; 71 | () end_parse(slice s) impure asm "ENDS"; 72 | (slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; 73 | cell preload_ref(slice s) asm "PLDREF"; 74 | ;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; 75 | ;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; 76 | ;; int preload_int(slice s, int len) asm "PLDIX"; 77 | ;; int preload_uint(slice s, int len) asm "PLDUX"; 78 | ;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; 79 | ;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; 80 | (slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; 81 | slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; 82 | (slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; 83 | slice first_bits(slice s, int len) asm "SDCUTFIRST"; 84 | slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; 85 | (slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; 86 | slice slice_last(slice s, int len) asm "SDCUTLAST"; 87 | (slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; 88 | cell preload_dict(slice s) asm "PLDDICT"; 89 | slice skip_dict(slice s) asm "SKIPDICT"; 90 | 91 | (slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; 92 | cell preload_maybe_ref(slice s) asm "PLDOPTREF"; 93 | builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; 94 | 95 | int cell_depth(cell c) asm "CDEPTH"; 96 | 97 | int slice_refs(slice s) asm "SREFS"; 98 | int slice_bits(slice s) asm "SBITS"; 99 | (int, int) slice_bits_refs(slice s) asm "SBITREFS"; 100 | int slice_empty?(slice s) asm "SEMPTY"; 101 | int slice_data_empty?(slice s) asm "SDEMPTY"; 102 | int slice_refs_empty?(slice s) asm "SREMPTY"; 103 | int slice_depth(slice s) asm "SDEPTH"; 104 | 105 | int builder_refs(builder b) asm "BREFS"; 106 | int builder_bits(builder b) asm "BBITS"; 107 | int builder_depth(builder b) asm "BDEPTH"; 108 | 109 | builder begin_cell() asm "NEWC"; 110 | cell end_cell(builder b) asm "ENDC"; 111 | builder store_ref(builder b, cell c) asm(c b) "STREF"; 112 | ;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; 113 | ;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; 114 | builder store_slice(builder b, slice s) asm "STSLICER"; 115 | builder store_grams(builder b, int x) asm "STGRAMS"; 116 | builder store_dict(builder b, cell c) asm(c b) "STDICT"; 117 | 118 | (slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; 119 | tuple parse_addr(slice s) asm "PARSEMSGADDR"; 120 | (int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; 121 | (int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; 122 | 123 | cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; 124 | (cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; 125 | cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; 126 | (cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; 127 | cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; 128 | (cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF"; 129 | (cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF"; 130 | (cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; 131 | (cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; 132 | (cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; 133 | (cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; 134 | (slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; 135 | (slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; 136 | (cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; 137 | (cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; 138 | (cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; 139 | (cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; 140 | cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; 141 | (cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; 142 | cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; 143 | (cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; 144 | cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; 145 | (cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; 146 | (cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; 147 | (cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; 148 | (cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; 149 | (cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; 150 | cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; 151 | (cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; 152 | cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; 153 | (cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; 154 | cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; 155 | (cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; 156 | (cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; 157 | (cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; 158 | (cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; 159 | (cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; 160 | (cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; 161 | (cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; 162 | (cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; 163 | (cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; 164 | (cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; 165 | (cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; 166 | (cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; 167 | (cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; 168 | (cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; 169 | (cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; 170 | (cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; 171 | (cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; 172 | (int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; 173 | (int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; 174 | (int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; 175 | (int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; 176 | (int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; 177 | (int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; 178 | (int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; 179 | (int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; 180 | (int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; 181 | (int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; 182 | (int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; 183 | (int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; 184 | (int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; 185 | (int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; 186 | (int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; 187 | (int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; 188 | cell new_dict() asm "NEWDICT"; 189 | int dict_empty?(cell c) asm "DICTEMPTY"; 190 | 191 | (slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; 192 | (cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; 193 | (cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; 194 | 195 | cell config_param(int x) asm "CONFIGOPTPARAM"; 196 | int cell_null?(cell c) asm "ISNULL"; 197 | 198 | () raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; 199 | () raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; 200 | () send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; 201 | () set_code(cell new_code) impure asm "SETCODE"; 202 | 203 | int random() impure asm "RANDU256"; 204 | int rand(int range) impure asm "RAND"; 205 | int get_seed() impure asm "RANDSEED"; 206 | int set_seed() impure asm "SETRAND"; 207 | () randomize(int x) impure asm "ADDRAND"; 208 | () randomize_lt() impure asm "LTIME" "ADDRAND"; 209 | 210 | -------------------------------------------------------------------------------- /func/wallet-v4-code.fc: -------------------------------------------------------------------------------- 1 | #pragma version =0.2.0; 2 | ;; Wallet smart contract with plugins 3 | 4 | (slice, int) dict_get?(cell dict, int key_len, slice index) asm(index dict key_len) "DICTGET" "NULLSWAPIFNOT"; 5 | (cell, int) dict_add_builder?(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTADDB"; 6 | (cell, int) dict_delete?(cell dict, int key_len, slice index) asm(index dict key_len) "DICTDEL"; 7 | 8 | () recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { 9 | var cs = in_msg_cell.begin_parse(); 10 | var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool 11 | if (flags & 1) { 12 | ;; ignore all bounced messages 13 | return (); 14 | } 15 | if (in_msg.slice_bits() < 32) { 16 | ;; ignore simple transfers 17 | return (); 18 | } 19 | int op = in_msg~load_uint(32); 20 | if (op != 0x706c7567) & (op != 0x64737472) { ;; "plug" & "dstr" 21 | ;; ignore all messages not related to plugins 22 | return (); 23 | } 24 | slice s_addr = cs~load_msg_addr(); 25 | (int wc, int addr_hash) = parse_std_addr(s_addr); 26 | slice wc_n_address = begin_cell().store_int(wc, 8).store_uint(addr_hash, 256).end_cell().begin_parse(); 27 | var ds = get_data().begin_parse().skip_bits(32 + 32 + 256); 28 | var plugins = ds~load_dict(); 29 | var (_, success?) = plugins.dict_get?(8 + 256, wc_n_address); 30 | if ~(success?) { 31 | ;; it may be a transfer 32 | return (); 33 | } 34 | int query_id = in_msg~load_uint(64); 35 | var msg = begin_cell(); 36 | if (op == 0x706c7567) { ;; request funds 37 | 38 | (int r_toncoins, cell r_extra) = (in_msg~load_grams(), in_msg~load_dict()); 39 | 40 | [int my_balance, _] = get_balance(); 41 | throw_unless(80, my_balance - msg_value >= r_toncoins); 42 | 43 | msg = msg.store_uint(0x18, 6) 44 | .store_slice(s_addr) 45 | .store_grams(r_toncoins) 46 | .store_dict(r_extra) 47 | .store_uint(0, 4 + 4 + 64 + 32 + 1 + 1) 48 | .store_uint(0x706c7567 | 0x80000000, 32) 49 | .store_uint(query_id, 64); 50 | send_raw_message(msg.end_cell(), 64); 51 | 52 | } 53 | 54 | if (op == 0x64737472) { ;; remove plugin by its request 55 | 56 | plugins~dict_delete?(8 + 256, wc_n_address); 57 | var ds = get_data().begin_parse().first_bits(32 + 32 + 256); 58 | set_data(begin_cell().store_slice(ds).store_dict(plugins).end_cell()); 59 | ;; return coins only if bounce expected 60 | if (flags & 2) { 61 | msg = msg.store_uint(0x18, 6) 62 | .store_slice(s_addr) 63 | .store_grams(0) 64 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 65 | .store_uint(0x64737472 | 0x80000000, 32) 66 | .store_uint(query_id, 64); 67 | send_raw_message(msg.end_cell(), 64); 68 | } 69 | } 70 | } 71 | 72 | () recv_external(slice in_msg) impure { 73 | var signature = in_msg~load_bits(512); 74 | var cs = in_msg; 75 | var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); 76 | throw_if(36, valid_until <= now()); 77 | var ds = get_data().begin_parse(); 78 | var (stored_seqno, stored_subwallet, public_key, plugins) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256), ds~load_dict()); 79 | ds.end_parse(); 80 | throw_unless(33, msg_seqno == stored_seqno); 81 | throw_unless(34, subwallet_id == stored_subwallet); 82 | throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key)); 83 | accept_message(); 84 | set_data(begin_cell() 85 | .store_uint(stored_seqno + 1, 32) 86 | .store_uint(stored_subwallet, 32) 87 | .store_uint(public_key, 256) 88 | .store_dict(plugins) 89 | .end_cell()); 90 | commit(); 91 | cs~touch(); 92 | int op = cs~load_uint(8); 93 | 94 | if (op == 0) { ;; simple send 95 | while (cs.slice_refs()) { 96 | var mode = cs~load_uint(8); 97 | send_raw_message(cs~load_ref(), mode); 98 | } 99 | return (); ;; have already saved the storage 100 | } 101 | 102 | if (op == 1) { ;; deploy and install plugin 103 | int plugin_workchain = cs~load_int(8); 104 | int plugin_balance = cs~load_grams(); 105 | (cell state_init, cell body) = (cs~load_ref(), cs~load_ref()); 106 | int plugin_address = cell_hash(state_init); 107 | slice wc_n_address = begin_cell().store_int(plugin_workchain, 8).store_uint(plugin_address, 256).end_cell().begin_parse(); 108 | var msg = begin_cell() 109 | .store_uint(0x18, 6) 110 | .store_uint(4, 3).store_slice(wc_n_address) 111 | .store_grams(plugin_balance) 112 | .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) 113 | .store_ref(state_init) 114 | .store_ref(body); 115 | send_raw_message(msg.end_cell(), 3); 116 | (plugins, int success?) = plugins.dict_add_builder?(8 + 256, wc_n_address, begin_cell()); 117 | throw_unless(39, success?); 118 | } 119 | 120 | if (op == 2) { ;; install plugin 121 | slice wc_n_address = cs~load_bits(8 + 256); 122 | int amount = cs~load_grams(); 123 | int query_id = cs~load_uint(64); 124 | 125 | (plugins, int success?) = plugins.dict_add_builder?(8 + 256, wc_n_address, begin_cell()); 126 | throw_unless(39, success?); 127 | 128 | builder msg = begin_cell() 129 | .store_uint(0x18, 6) 130 | .store_uint(4, 3).store_slice(wc_n_address) 131 | .store_grams(amount) 132 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 133 | .store_uint(0x6e6f7465, 32) ;; op 134 | .store_uint(query_id, 64); 135 | send_raw_message(msg.end_cell(), 3); 136 | } 137 | 138 | if (op == 3) { ;; remove plugin 139 | slice wc_n_address = cs~load_bits(8 + 256); 140 | int amount = cs~load_grams(); 141 | int query_id = cs~load_uint(64); 142 | 143 | (plugins, int success?) = plugins.dict_delete?(8 + 256, wc_n_address); 144 | throw_unless(39, success?); 145 | 146 | builder msg = begin_cell() 147 | .store_uint(0x18, 6) 148 | .store_uint(4, 3).store_slice(wc_n_address) 149 | .store_grams(amount) 150 | .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) 151 | .store_uint(0x64737472, 32) ;; op 152 | .store_uint(query_id, 64); 153 | send_raw_message(msg.end_cell(), 3); 154 | } 155 | 156 | set_data(begin_cell() 157 | .store_uint(stored_seqno + 1, 32) 158 | .store_uint(stored_subwallet, 32) 159 | .store_uint(public_key, 256) 160 | .store_dict(plugins) 161 | .end_cell()); 162 | } 163 | 164 | ;; Get methods 165 | 166 | int seqno() method_id { 167 | return get_data().begin_parse().preload_uint(32); 168 | } 169 | 170 | int get_subwallet_id() method_id { 171 | return get_data().begin_parse().skip_bits(32).preload_uint(32); 172 | } 173 | 174 | int get_public_key() method_id { 175 | var cs = get_data().begin_parse().skip_bits(64); 176 | return cs.preload_uint(256); 177 | } 178 | 179 | int is_plugin_installed(int wc, int addr_hash) method_id { 180 | var ds = get_data().begin_parse().skip_bits(32 + 32 + 256); 181 | var plugins = ds~load_dict(); 182 | var (_, success?) = plugins.dict_get?(8 + 256, begin_cell().store_int(wc, 8).store_uint(addr_hash, 256).end_cell().begin_parse()); 183 | return success?; 184 | } 185 | 186 | tuple get_plugin_list() method_id { 187 | var list = null(); 188 | var ds = get_data().begin_parse().skip_bits(32 + 32 + 256); 189 | var plugins = ds~load_dict(); 190 | do { 191 | var (wc_n_address, _, f) = plugins~dict::delete_get_min(8 + 256); 192 | if (f) { 193 | (int wc, int addr) = (wc_n_address~load_int(8), wc_n_address~load_uint(256)); 194 | list = cons(pair(wc, addr), list); 195 | } 196 | } until (~ f); 197 | return list; 198 | } 199 | --------------------------------------------------------------------------------