Locazia: release
This commit is contained in:
commit
540ab55120
|
|
@ -0,0 +1,5 @@
|
|||
node_modules
|
||||
.saved_wallet
|
||||
build
|
||||
.idea
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# WalletV3CR3
|
||||
|
||||
## Project structure
|
||||
|
||||
- `contracts` - source code of all the smart contracts of the project and their dependencies.
|
||||
- `wrappers` - wrapper classes (implementing `Contract` from ton-core) for the contracts, including any [de]serialization primitives and compilation functions.
|
||||
- `tests` - tests for the contracts.
|
||||
- `scripts` - scripts used by the project, mainly the deployment scripts.
|
||||
|
||||
## How to use
|
||||
|
||||
### Build
|
||||
|
||||
`npx blueprint build` or `yarn blueprint build`
|
||||
|
||||
### Test
|
||||
|
||||
`npx blueprint test` or `yarn blueprint test`
|
||||
|
||||
### Deploy or run another script
|
||||
|
||||
`npx blueprint run` or `yarn blueprint run`
|
||||
|
||||
### Add a new contract
|
||||
|
||||
`npx blueprint create ContractName` or `yarn blueprint create ContractName`
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
;; Standard library for funC
|
||||
;;
|
||||
|
||||
{-
|
||||
This file is part of TON FunC Standard Library.
|
||||
|
||||
FunC Standard Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
FunC Standard Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
-}
|
||||
|
||||
forall X -> tuple cons(X head, tuple tail) asm "CONS";
|
||||
forall X -> (X, tuple) uncons(tuple list) asm "UNCONS";
|
||||
forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS";
|
||||
forall X -> X car(tuple list) asm "CAR";
|
||||
tuple cdr(tuple list) asm "CDR";
|
||||
tuple empty_tuple() asm "NIL";
|
||||
forall X -> tuple tpush(tuple t, X value) asm "TPUSH";
|
||||
forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH";
|
||||
forall X -> [X] single(X x) asm "SINGLE";
|
||||
forall X -> X unsingle([X] t) asm "UNSINGLE";
|
||||
forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR";
|
||||
forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR";
|
||||
forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE";
|
||||
forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE";
|
||||
forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE";
|
||||
forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE";
|
||||
forall X -> X first(tuple t) asm "FIRST";
|
||||
forall X -> X second(tuple t) asm "SECOND";
|
||||
forall X -> X third(tuple t) asm "THIRD";
|
||||
forall X -> X fourth(tuple t) asm "3 INDEX";
|
||||
forall X, Y -> X pair_first([X, Y] p) asm "FIRST";
|
||||
forall X, Y -> Y pair_second([X, Y] p) asm "SECOND";
|
||||
forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST";
|
||||
forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND";
|
||||
forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD";
|
||||
forall X -> X null() asm "PUSHNULL";
|
||||
forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP";
|
||||
|
||||
int now() asm "NOW";
|
||||
slice my_address() asm "MYADDR";
|
||||
[int, cell] get_balance() asm "BALANCE";
|
||||
int cur_lt() asm "LTIME";
|
||||
int block_lt() asm "BLOCKLT";
|
||||
|
||||
int cell_hash(cell c) asm "HASHCU";
|
||||
int slice_hash(slice s) asm "HASHSU";
|
||||
int string_hash(slice s) asm "SHA256U";
|
||||
|
||||
int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU";
|
||||
int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS";
|
||||
|
||||
(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE";
|
||||
(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE";
|
||||
(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT";
|
||||
(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT";
|
||||
|
||||
;; () throw_if(int excno, int cond) impure asm "THROWARGIF";
|
||||
|
||||
() dump_stack() impure asm "DUMPSTK";
|
||||
|
||||
cell get_data() asm "c4 PUSH";
|
||||
() set_data(cell c) impure asm "c4 POP";
|
||||
cont get_c3() impure asm "c3 PUSH";
|
||||
() set_c3(cont c) impure asm "c3 POP";
|
||||
cont bless(slice s) impure asm "BLESS";
|
||||
|
||||
() accept_message() impure asm "ACCEPT";
|
||||
() set_gas_limit(int limit) impure asm "SETGASLIMIT";
|
||||
() commit() impure asm "COMMIT";
|
||||
() buy_gas(int gram) impure asm "BUYGAS";
|
||||
|
||||
int min(int x, int y) asm "MIN";
|
||||
int max(int x, int y) asm "MAX";
|
||||
(int, int) minmax(int x, int y) asm "MINMAX";
|
||||
int abs(int x) asm "ABS";
|
||||
|
||||
slice begin_parse(cell c) asm "CTOS";
|
||||
() end_parse(slice s) impure asm "ENDS";
|
||||
(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF";
|
||||
cell preload_ref(slice s) asm "PLDREF";
|
||||
;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX";
|
||||
;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX";
|
||||
;; int preload_int(slice s, int len) asm "PLDIX";
|
||||
;; int preload_uint(slice s, int len) asm "PLDUX";
|
||||
;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX";
|
||||
;; slice preload_bits(slice s, int len) asm "PLDSLICEX";
|
||||
(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS";
|
||||
slice skip_bits(slice s, int len) asm "SDSKIPFIRST";
|
||||
(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST";
|
||||
slice first_bits(slice s, int len) asm "SDCUTFIRST";
|
||||
slice skip_last_bits(slice s, int len) asm "SDSKIPLAST";
|
||||
(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST";
|
||||
slice slice_last(slice s, int len) asm "SDCUTLAST";
|
||||
(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT";
|
||||
cell preload_dict(slice s) asm "PLDDICT";
|
||||
slice skip_dict(slice s) asm "SKIPDICT";
|
||||
|
||||
(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF";
|
||||
cell preload_maybe_ref(slice s) asm "PLDOPTREF";
|
||||
builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF";
|
||||
|
||||
int cell_depth(cell c) asm "CDEPTH";
|
||||
|
||||
int slice_refs(slice s) asm "SREFS";
|
||||
int slice_bits(slice s) asm "SBITS";
|
||||
(int, int) slice_bits_refs(slice s) asm "SBITREFS";
|
||||
int slice_empty?(slice s) asm "SEMPTY";
|
||||
int slice_data_empty?(slice s) asm "SDEMPTY";
|
||||
int slice_refs_empty?(slice s) asm "SREMPTY";
|
||||
int slice_depth(slice s) asm "SDEPTH";
|
||||
|
||||
int builder_refs(builder b) asm "BREFS";
|
||||
int builder_bits(builder b) asm "BBITS";
|
||||
int builder_depth(builder b) asm "BDEPTH";
|
||||
|
||||
builder begin_cell() asm "NEWC";
|
||||
cell end_cell(builder b) asm "ENDC";
|
||||
builder store_ref(builder b, cell c) asm(c b) "STREF";
|
||||
;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX";
|
||||
;; builder store_int(builder b, int x, int len) asm(x b len) "STIX";
|
||||
builder store_slice(builder b, slice s) asm "STSLICER";
|
||||
builder store_grams(builder b, int x) asm "STGRAMS";
|
||||
builder store_dict(builder b, cell c) asm(c b) "STDICT";
|
||||
|
||||
(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR";
|
||||
tuple parse_addr(slice s) asm "PARSEMSGADDR";
|
||||
(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR";
|
||||
(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR";
|
||||
|
||||
cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF";
|
||||
(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF";
|
||||
cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF";
|
||||
(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF";
|
||||
cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF";
|
||||
(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF";
|
||||
(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF";
|
||||
(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF";
|
||||
(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF";
|
||||
(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL";
|
||||
(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL";
|
||||
(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT";
|
||||
(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT";
|
||||
(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT";
|
||||
(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT";
|
||||
(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT";
|
||||
(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT";
|
||||
cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET";
|
||||
(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET";
|
||||
cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET";
|
||||
(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET";
|
||||
cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET";
|
||||
(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET";
|
||||
(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD";
|
||||
(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE";
|
||||
(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD";
|
||||
(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE";
|
||||
cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB";
|
||||
(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB";
|
||||
cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB";
|
||||
(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB";
|
||||
cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB";
|
||||
(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB";
|
||||
(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB";
|
||||
(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB";
|
||||
(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB";
|
||||
(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB";
|
||||
(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2";
|
||||
(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2";
|
||||
(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2";
|
||||
(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2";
|
||||
(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2";
|
||||
(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2";
|
||||
(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2";
|
||||
(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2";
|
||||
(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2";
|
||||
(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2";
|
||||
(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2";
|
||||
(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2";
|
||||
(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2";
|
||||
(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2";
|
||||
(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2";
|
||||
(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2";
|
||||
(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2";
|
||||
(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2";
|
||||
(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2";
|
||||
(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2";
|
||||
(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2";
|
||||
(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2";
|
||||
(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2";
|
||||
(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2";
|
||||
(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2";
|
||||
(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2";
|
||||
(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2";
|
||||
(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2";
|
||||
cell new_dict() asm "NEWDICT";
|
||||
int dict_empty?(cell c) asm "DICTEMPTY";
|
||||
|
||||
(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2";
|
||||
(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET";
|
||||
(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL";
|
||||
|
||||
cell config_param(int x) asm "CONFIGOPTPARAM";
|
||||
int cell_null?(cell c) asm "ISNULL";
|
||||
|
||||
() raw_reserve(int amount, int mode) impure asm "RAWRESERVE";
|
||||
() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX";
|
||||
() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG";
|
||||
() set_code(cell new_code) impure asm "SETCODE";
|
||||
|
||||
int random() impure asm "RANDU256";
|
||||
int rand(int range) impure asm "RAND";
|
||||
int get_seed() impure asm "RANDSEED";
|
||||
int set_seed() impure asm "SETRAND";
|
||||
() randomize(int x) impure asm "ADDRAND";
|
||||
() randomize_lt() impure asm "LTIME" "ADDRAND";
|
||||
|
||||
builder store_coins(builder b, int x) asm "STVARUINT16";
|
||||
(slice, int) load_coins(slice s) asm( -> 1 0) "LDVARUINT16";
|
||||
|
||||
int equal_slices (slice a, slice b) asm "SDEQ";
|
||||
int builder_null?(builder b) asm "ISNULL";
|
||||
builder store_builder(builder to, builder from) asm "STBR";
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
;; Wallet V3 Custom R3
|
||||
;; (c) 2024 oscux
|
||||
|
||||
;; TL-B scheme:
|
||||
;; storage#_ seqno:uint32 public_key:uint256 subwallet_id:uint32 trusted_hashpart:uint256 = Storage;
|
||||
;;
|
||||
;; new_internal#0000aac1 send_mode:uint8 message:^InternalMessage = SpendRequest;
|
||||
;; upgrade#0000aaa0 new_code:^Cell new_data:^Cell = UpgradeRequest;
|
||||
|
||||
#include "imports/stdlib.fc";
|
||||
|
||||
const int op::new_internal = 0xAAC1;
|
||||
const int op::upgrade = 0xAAA0;
|
||||
|
||||
const int error::not_allowed = 105;
|
||||
const int error::expired = 108;
|
||||
|
||||
() execute_command(slice request, int sudo?) impure {
|
||||
int op = request~load_uint(32);
|
||||
if (op == op::new_internal) {
|
||||
int mode = request~load_uint(8);
|
||||
cell message = request~load_ref();
|
||||
send_raw_message(message, mode);
|
||||
return ();
|
||||
}
|
||||
if (op == op::upgrade) {
|
||||
throw_unless(error::not_allowed, sudo?);
|
||||
cell new_code = request~load_ref();
|
||||
cell new_data = request~load_ref();
|
||||
set_code(new_code);
|
||||
set_c3(new_code.begin_parse().bless());
|
||||
set_data(new_data);
|
||||
;; but next commands executes with old code
|
||||
return ();
|
||||
}
|
||||
if (op == 0) {
|
||||
return ();
|
||||
}
|
||||
|
||||
throw(0xffff);
|
||||
}
|
||||
|
||||
() execute_commands(slice requests, int sudo?) impure {
|
||||
while (requests.slice_refs() > 0) {
|
||||
execute_command(requests~load_ref().begin_parse(), sudo?);
|
||||
}
|
||||
}
|
||||
|
||||
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
|
||||
slice cs = in_msg_full.begin_parse();
|
||||
cs~skip_bits(4);
|
||||
slice sender_address = cs~load_msg_addr();
|
||||
(_, int sender_hashpart) = parse_std_addr(sender_address);
|
||||
slice ds = get_data().begin_parse();
|
||||
ds~skip_bits(32 + 256 + 32);
|
||||
int trusted_hashpart = ds~load_uint(256);
|
||||
if (sender_hashpart == trusted_hashpart) {
|
||||
throw_if(error::not_allowed, trusted_hashpart == 0);
|
||||
execute_commands(in_msg_body~load_ref().begin_parse(), true);
|
||||
}
|
||||
return ();
|
||||
}
|
||||
|
||||
() recv_external(slice in_msg) impure {
|
||||
slice signature = in_msg~load_bits(512);
|
||||
slice cs = in_msg;
|
||||
(int subwallet_id, int valid_until, int msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32));
|
||||
throw_if(error::expired, valid_until <= now());
|
||||
slice ds = get_data().begin_parse();
|
||||
(int stored_seqno, int public_key, int stored_subwallet, int trusted_hashpart) = (ds~load_uint(32), ds~load_uint(256), ds~load_uint(32), ds~load_uint(256));
|
||||
throw_unless(error::not_allowed, subwallet_id == stored_subwallet);
|
||||
throw_unless(error::not_allowed, msg_seqno == stored_seqno);
|
||||
throw_unless(error::not_allowed, check_signature(slice_hash(in_msg), signature, public_key));
|
||||
accept_message();
|
||||
cs~touch();
|
||||
int exit_code = 0;
|
||||
try {
|
||||
execute_commands(in_msg~load_ref().begin_parse(), trusted_hashpart == 0 ? true : false);
|
||||
} catch (x, n) {
|
||||
exit_code = n;
|
||||
}
|
||||
set_data(
|
||||
begin_cell()
|
||||
.store_uint(stored_seqno + 1, 32)
|
||||
.store_uint(public_key, 256)
|
||||
.store_uint(stored_subwallet, 32)
|
||||
.store_uint(trusted_hashpart, 256)
|
||||
.store_uint(exit_code, 16)
|
||||
.end_cell()
|
||||
);
|
||||
return ();
|
||||
}
|
||||
|
||||
;; Get methods
|
||||
|
||||
int seqno() method_id {
|
||||
slice ds = get_data().begin_parse();
|
||||
return ds~load_uint(32);
|
||||
}
|
||||
|
||||
int get_public_key() method_id {
|
||||
slice ds = get_data().begin_parse();
|
||||
ds~skip_bits(32);
|
||||
return ds~load_uint(256);
|
||||
}
|
||||
|
||||
int get_trusted_hashpart() method_id {
|
||||
slice ds = get_data().begin_parse();
|
||||
ds~skip_bits(32 + 256 + 32);
|
||||
return ds~load_uint(256);
|
||||
}
|
||||
|
||||
int prev_exit_code() method_id {
|
||||
slice ds = get_data().begin_parse();
|
||||
ds~skip_bits(32 + 256 + 32 + 256);
|
||||
return ds~load_uint(16);
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import type { Config } from 'jest';
|
||||
|
||||
const config: Config = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testPathIgnorePatterns: ['/node_modules/', '/dist/'],
|
||||
};
|
||||
|
||||
export default config;
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "WalletV3CR3",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"start": "blueprint run",
|
||||
"build": "blueprint build",
|
||||
"test": "jest --verbose"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ton/blueprint": "^0.16.0",
|
||||
"@ton/sandbox": "^0.15.0",
|
||||
"@ton/test-utils": "^0.4.2",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^20.11.20",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.2.5",
|
||||
"@ton/ton": "^13.11.0",
|
||||
"@ton/core": "~0",
|
||||
"@ton/crypto": "^3.2.0",
|
||||
"ts-jest": "^29.1.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
import json
|
||||
import os
|
||||
from getpass import getpass
|
||||
|
||||
from tonsdk.contract.wallet import Wallets
|
||||
from tonsdk.crypto import mnemonic_to_wallet_key
|
||||
from tonsdk.utils import Address
|
||||
|
||||
from _core import unpack_wallet, encrypt_data, decrypt_data
|
||||
|
||||
|
||||
def mnemonic_new(subwallet_id: int=0):
|
||||
while True:
|
||||
mnemonic, public_key, private_key, _ = Wallets.create('v3r2', 0)
|
||||
wallet = unpack_wallet({
|
||||
'mnemonic': mnemonic,
|
||||
'public_key': public_key.hex(),
|
||||
'secret_key': private_key.hex(),
|
||||
'subwallet_id': subwallet_id,
|
||||
'custom_address': None,
|
||||
'is_testnet': False,
|
||||
})
|
||||
ugly = False
|
||||
for wallet_address in [
|
||||
wallet.address.to_string(1, 1, 0),
|
||||
wallet.address.to_string(1, 1, 1)
|
||||
]:
|
||||
if ('_' in wallet_address or '-' in wallet_address):
|
||||
ugly = True
|
||||
|
||||
if not ugly:
|
||||
return mnemonic
|
||||
|
||||
|
||||
def get_or_create_wallet_settings():
|
||||
wallet_settings_file = os.environ.get('ENCRYPTED_WALLET_FILE', '.saved_wallet')
|
||||
|
||||
# Check if the .saved_wallet file exists
|
||||
if os.path.exists(wallet_settings_file):
|
||||
# If the file exists, read and decrypt its contents
|
||||
with open(wallet_settings_file, 'rb') as file:
|
||||
_content = file.read()
|
||||
|
||||
hashpart = _content[:32].hex()
|
||||
_content = _content[32:]
|
||||
print(f"=== Saved wallet: {Address('0:' + hashpart).to_string(1, 1, 0)}")
|
||||
|
||||
password = getpass("Enter the password to decrypt the wallet: ")
|
||||
try:
|
||||
wallet_settings = decrypt_data(_content, password.encode("UTF-8"))
|
||||
wallet_settings = json.loads(wallet_settings.decode())
|
||||
except (ValueError, KeyError):
|
||||
print("Incorrect password or corrupted data. Please try again.")
|
||||
return get_or_create_wallet_settings()
|
||||
|
||||
print(f"=== Mainnet: {not wallet_settings.get('is_testnet', True)}")
|
||||
return wallet_settings
|
||||
else:
|
||||
# If the file doesn't exist, prompt the user for input
|
||||
try:
|
||||
subwallet_id = int(input("Enter the subwallet_id (default is 0): ") or 0)
|
||||
assert subwallet_id >= 0, "Subwallet ID must be a positive integer"
|
||||
|
||||
mnemonic = input("Enter the wallet mnemonic (24 words, separated by spaces): ")
|
||||
if mnemonic == 'new':
|
||||
mnemonic = ' '.join(mnemonic_new(subwallet_id=subwallet_id))
|
||||
|
||||
assert len(mnemonic.split()) == 24, "Mnemonic must be 24 words long"
|
||||
public_key, secret_key = mnemonic_to_wallet_key(mnemonic.split())
|
||||
|
||||
custom_address = input("Enter a custom address (if available): ")
|
||||
custom_address = Address(custom_address) if custom_address else None
|
||||
password = getpass("Enter a password to protect the wallet: ")
|
||||
assert len(password) > 3, "Password must be at least 4 characters long"
|
||||
is_testnet = input("Is this a testnet wallet? (yes/NO): ").lower() == 'yes'
|
||||
except KeyboardInterrupt:
|
||||
os._exit(1)
|
||||
except BaseException as e:
|
||||
print(f"Error: {e}")
|
||||
print("Please try again")
|
||||
return get_or_create_wallet_settings()
|
||||
|
||||
# Create a dictionary with the user input
|
||||
wallet_settings = {
|
||||
'mnemonic': mnemonic,
|
||||
'public_key': public_key.hex(),
|
||||
'secret_key': secret_key.hex(),
|
||||
'subwallet_id': subwallet_id,
|
||||
'custom_address': custom_address.to_string(1, 1, 1) if custom_address else None,
|
||||
'is_testnet': is_testnet,
|
||||
}
|
||||
print(f"=== Mainnet: {not is_testnet}")
|
||||
wallet_address = unpack_wallet(wallet_settings).address.to_string(0, 0, 0)
|
||||
|
||||
# Encrypt and save the data to the file
|
||||
encrypted_wallet = encrypt_data(json.dumps(wallet_settings).encode("UTF-8"), password.encode("UTF-8"))
|
||||
with open(wallet_settings_file, 'wb') as file:
|
||||
file.write(bytes.fromhex(wallet_address.split(':')[1]) + encrypted_wallet)
|
||||
|
||||
# Recursively call the function to read data from the file
|
||||
return get_or_create_wallet_settings()
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
import os
|
||||
import pickle
|
||||
from decimal import Decimal
|
||||
from time import sleep
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Protocol.KDF import PBKDF2
|
||||
from tonsdk.boc import begin_cell
|
||||
from tonsdk.contract import Contract
|
||||
from tonsdk.contract.token.ft import JettonWallet
|
||||
from tonsdk.contract.token.nft import NFTItem
|
||||
from tonsdk.utils import Address
|
||||
|
||||
from toncenter import TonCenter
|
||||
from wallet_contract import WalletV3CR3
|
||||
|
||||
ACTION_TYPES_DESC = {
|
||||
1: "Send TON",
|
||||
2: "Send Jetton",
|
||||
3: "Send NFT",
|
||||
4: "Update wallet contract",
|
||||
7: "New decentralized note",
|
||||
}
|
||||
|
||||
|
||||
def unpack_wallet(wallet_settings):
|
||||
kwargs = {}
|
||||
if wallet_settings['custom_address']:
|
||||
kwargs['address'] = Address(wallet_settings['custom_address'])
|
||||
|
||||
wallet = WalletV3CR3(
|
||||
public_key=bytes.fromhex(wallet_settings['public_key']),
|
||||
private_key=bytes.fromhex(wallet_settings['secret_key']),
|
||||
subwallet_id=wallet_settings['subwallet_id'],
|
||||
trusted_hashpart=0,
|
||||
**kwargs
|
||||
)
|
||||
return wallet
|
||||
|
||||
|
||||
def generate_key(password, salt, iterations=100000):
|
||||
key = PBKDF2(password, salt, dkLen=32, count=iterations)
|
||||
return key
|
||||
|
||||
|
||||
def encrypt_data(data: bytes, password: bytes) -> bytes:
|
||||
salt = os.urandom(16)
|
||||
key = generate_key(password, salt)
|
||||
cipher = AES.new(key, AES.MODE_GCM)
|
||||
ciphertext, tag = cipher.encrypt_and_digest(data)
|
||||
return pickle.dumps([salt, ciphertext, tag, cipher.nonce])
|
||||
|
||||
|
||||
def decrypt_data(data: bytes, password: bytes) -> bytes:
|
||||
salt, ciphertext, tag, nonce = pickle.loads(data)
|
||||
key = generate_key(password, salt)
|
||||
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
|
||||
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
|
||||
return plaintext
|
||||
|
||||
|
||||
async def perform_action(wallet_settings, _commands):
|
||||
wallet = unpack_wallet(wallet_settings)
|
||||
toncenter = TonCenter(testnet=wallet_settings.get("is_testnet", True))
|
||||
|
||||
async def get_wallet_seqno():
|
||||
result = await toncenter.run_get_method(wallet.address.to_string(1, 1, 1), 'seqno')
|
||||
if result.get('exit_code', -1) != 0:
|
||||
seqno = 0
|
||||
else:
|
||||
seqno = int(result['stack'][0][1], 16)
|
||||
|
||||
sleep(1)
|
||||
return seqno
|
||||
|
||||
seqno = -100
|
||||
_new_deploy = False
|
||||
while seqno < 0:
|
||||
seqno = await get_wallet_seqno()
|
||||
if seqno < 1:
|
||||
_new_deploy = True
|
||||
result = await toncenter.send_boc(
|
||||
wallet.create_external_message(
|
||||
begin_cell()
|
||||
.store_cell(wallet.create_signing_message(0, 60))
|
||||
.store_ref(begin_cell().end_cell())
|
||||
.end_cell(), 0
|
||||
)['message'].to_boc(False)
|
||||
)
|
||||
print("Init transaction broadcast result:", result)
|
||||
sleep(7)
|
||||
|
||||
if _new_deploy:
|
||||
seqno += 1
|
||||
|
||||
print(f"=== Current seqno: {seqno} ===")
|
||||
assert input("Broadcast transaction? (yes/NO): ").lower() == 'yes'
|
||||
|
||||
signing_message = begin_cell().store_cell(wallet.create_signing_message(seqno, 60))
|
||||
signing_message = signing_message.store_ref(_commands)
|
||||
|
||||
query = wallet.create_external_message(
|
||||
signing_message.end_cell(), seqno, False
|
||||
)
|
||||
print("Raw signed transaction:", query['message'].to_boc(False).hex())
|
||||
|
||||
result = await toncenter.send_boc(query['message'].to_boc(False))
|
||||
print("Transaction broadcast result:", result)
|
||||
|
||||
|
||||
def serialize_command(action, response_address: Address = None):
|
||||
if action['type'] == 1:
|
||||
return (
|
||||
begin_cell()
|
||||
.store_uint(0xAAC1, 32)
|
||||
.store_uint(action['args'].get('send_mode', 1), 8)
|
||||
.store_ref(
|
||||
Contract.create_common_msg_info(
|
||||
Contract.create_internal_message_header(
|
||||
Address(action['args']['destination']), Decimal(action['args']['amount'])
|
||||
), action['args'].get('state_init'), action['args'].get('payload_cell')
|
||||
)
|
||||
)
|
||||
.end_cell()
|
||||
)
|
||||
elif action['type'] == 2:
|
||||
return (
|
||||
begin_cell()
|
||||
.store_uint(0xAAC1, 32)
|
||||
.store_uint(action['args'].get('send_mode', 1), 8)
|
||||
.store_ref(
|
||||
Contract.create_common_msg_info(
|
||||
Contract.create_internal_message_header(
|
||||
Address(action['args']['jetton_wallet']), Decimal(1e8)
|
||||
), None, JettonWallet().create_transfer_body(
|
||||
Address(action['args']['recipient']), action['args']['amount'],
|
||||
forward_amount=1e7, forward_payload=(
|
||||
(bytes(4) + action['args']['comment']) if action['args'].get('comment') else None
|
||||
),
|
||||
response_address=response_address
|
||||
)
|
||||
)
|
||||
)
|
||||
.end_cell()
|
||||
)
|
||||
elif action['type'] == 3:
|
||||
return (
|
||||
begin_cell()
|
||||
.store_uint(0xAAC1, 32)
|
||||
.store_uint(action['args'].get('send_mode', 1), 8)
|
||||
.store_ref(
|
||||
Contract.create_common_msg_info(
|
||||
Contract.create_internal_message_header(
|
||||
Address(action['args']['nft_address']), Decimal(1e8)
|
||||
), None, NFTItem().create_transfer_body(
|
||||
Address(action['args']['recipient']), response_address=response_address,
|
||||
forward_amount = 1e7
|
||||
)
|
||||
)
|
||||
)
|
||||
.end_cell()
|
||||
)
|
||||
elif action['type'] == 4:
|
||||
return (
|
||||
begin_cell()
|
||||
.store_uint(0xAAA0, 32)
|
||||
.store_ref(action['args']['new_code'])
|
||||
.store_ref(action['args']['new_data'])
|
||||
.end_cell()
|
||||
)
|
||||
|
||||
raise Exception('Unsupported action type')
|
||||
|
||||
|
||||
def print_actions(actions, prefix='\n' + "=== {actions_count} actions selected"):
|
||||
if not actions: return
|
||||
print(prefix.format(actions_count=len(actions)))
|
||||
for action in actions:
|
||||
print(f"Action {ACTION_TYPES_DESC[action['type']]}: {action['args']}")
|
||||
|
||||
print("===")
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
from asyncio import run
|
||||
|
||||
from tonsdk.boc import Cell, begin_cell
|
||||
from tonsdk.utils import Address
|
||||
|
||||
from _auth import get_or_create_wallet_settings
|
||||
from _core import unpack_wallet, serialize_command, perform_action, print_actions
|
||||
|
||||
|
||||
async def main():
|
||||
print('\n' * 30)
|
||||
wallet_settings = get_or_create_wallet_settings()
|
||||
wallet = unpack_wallet(wallet_settings)
|
||||
print(f"=== Successfully loaded wallet {wallet.address.to_string(1, 1, 0)}")
|
||||
# print(wallet_settings)
|
||||
actions = []
|
||||
while len(actions) < 4:
|
||||
print_actions(actions)
|
||||
print("Available actions:")
|
||||
print("1) Send TON")
|
||||
print("2) Send Jetton")
|
||||
print("3) Send NFT")
|
||||
print("4) Update wallet contract")
|
||||
print("5) Export mnemonic phrase")
|
||||
print("7) New decentralized note")
|
||||
print("8) Read decentralized note")
|
||||
print("0) View actions and exit | send transactions")
|
||||
|
||||
choice = input("Select an action (1/2/3/4/0): ")
|
||||
|
||||
if choice == "0":
|
||||
break
|
||||
elif choice in {"5"}:
|
||||
print("=== Mnemonic phrase:")
|
||||
print(wallet_settings['mnemonic'])
|
||||
elif choice in {"1", "2", "3", "4", "7"}:
|
||||
action_type = int(choice)
|
||||
action = {"type": action_type, "args": []}
|
||||
|
||||
if action_type == 1:
|
||||
destination = input("Enter the destination address: ")
|
||||
Address(destination).to_string(1, 1, 1)
|
||||
amount = float(input("Enter the amount in TON: "))
|
||||
comment = input("Enter a comment (or leave it empty): ")
|
||||
if comment:
|
||||
raw_payload = (
|
||||
begin_cell()
|
||||
.store_uint(0, 32)
|
||||
.store_bytes(comment.encode())
|
||||
.end_cell()
|
||||
)
|
||||
else:
|
||||
raw_payload = input("Enter raw_payload in HEX (or leave it empty): ")
|
||||
try:
|
||||
raw_payload = Cell.one_from_boc(raw_payload)
|
||||
except:
|
||||
raw_payload = None
|
||||
|
||||
action["args"] = {
|
||||
"destination": destination,
|
||||
"amount": int(amount * 10 ** 9),
|
||||
"payload_cell": raw_payload
|
||||
}
|
||||
elif action_type == 2:
|
||||
token_contract = input("Enter the token contract address: ")
|
||||
recipient = input("Enter the recipient address: ")
|
||||
Address(token_contract).to_string(1, 1, 1)
|
||||
Address(recipient).to_string(1, 1, 1)
|
||||
amount = float(input("Enter the amount in tokens: "))
|
||||
decimals = int(input("Enter token decimals: "))
|
||||
amount = int(amount * 10 ** decimals)
|
||||
comment = input("Enter a comment: ")
|
||||
action["args"] = {
|
||||
"token_contract": token_contract,
|
||||
"recipient": recipient,
|
||||
"amount": amount,
|
||||
"comment": comment
|
||||
}
|
||||
elif action_type == 3:
|
||||
nft_address = input("Enter the NFT address: ")
|
||||
recipient = input("Enter the recipient address: ")
|
||||
Address(nft_address).to_string(1, 1, 1)
|
||||
Address(recipient).to_string(1, 1, 1)
|
||||
action["args"] = {
|
||||
"nft_address": nft_address,
|
||||
"recipient": recipient
|
||||
}
|
||||
elif action_type == 4:
|
||||
hex_code = input("Enter the smart contract code in HEX: ")
|
||||
hex_data = input("Enter smart contract data in HEX: ")
|
||||
action["args"] = {
|
||||
'new_code': Cell.one_from_boc(hex_code),
|
||||
'new_data': Cell.one_from_boc(hex_data)
|
||||
}
|
||||
elif action_type == 7:
|
||||
note_key = input("Enter the note key: ")
|
||||
print(f"""[?] Please, enter plain text & input 0 for write the note:""")
|
||||
plain_text = ""
|
||||
while True:
|
||||
new_input = input()
|
||||
if new_input == '0':
|
||||
break
|
||||
|
||||
plain_text += new_input + '\n'
|
||||
|
||||
action['args'] = {
|
||||
'note_key': note_key.strip(),
|
||||
'plain_text': plain_text.strip(),
|
||||
'private_key': bytes.fromhex(wallet_settings['secret_key'])
|
||||
}
|
||||
|
||||
actions.append(action)
|
||||
|
||||
if not actions:
|
||||
print("No actions selected")
|
||||
return
|
||||
|
||||
_commands = begin_cell()
|
||||
for action in actions:
|
||||
_commands = _commands.store_ref(
|
||||
serialize_command(action, response_address=wallet.address)
|
||||
)
|
||||
|
||||
await perform_action(wallet_settings, _commands.end_cell())
|
||||
|
||||
if __name__ == "__main__":
|
||||
run(main())
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
httpx==0.25.0
|
||||
tonsdk==1.0.13
|
||||
pycryptodome==3.19.0
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
import os
|
||||
|
||||
from base64 import b64encode
|
||||
|
||||
from httpx import AsyncClient
|
||||
|
||||
|
||||
class TonCenter:
|
||||
def __init__(self, api_key: str = None, testnet: bool = False):
|
||||
self.host = os.getenv("TONCENTER_HOST", "https://toncenter.com/api/v1/")
|
||||
if not (self.host[-1] == '/'):
|
||||
self.host += '/'
|
||||
|
||||
self.api_key = api_key
|
||||
|
||||
async def request(self, method: str, endpoint: str, *args, **kwargs) -> dict:
|
||||
async with AsyncClient() as client:
|
||||
response = await client.request(method, f"{self.host}{endpoint}", *args, **kwargs)
|
||||
if response.status_code != 200:
|
||||
raise Exception(f'Error while TONCENTER request {endpoint}: {response.text}')
|
||||
|
||||
return response.json()
|
||||
|
||||
async def send_boc(self, src: bytes):
|
||||
return await self.request(
|
||||
"POST", 'sendBoc',
|
||||
json={'boc': b64encode(src).decode()}
|
||||
)
|
||||
|
||||
async def get_account(self, addr: str):
|
||||
return (await self.request(
|
||||
"GET", 'getAddressInformation',
|
||||
params={'address': addr}
|
||||
)).get('result', {})
|
||||
|
||||
async def get_seqno(self, addr: str):
|
||||
return (await self.request(
|
||||
"GET", 'getWalletInformation',
|
||||
json={'address': addr}
|
||||
)).get('result', {}).get('seqno', 0)
|
||||
|
||||
async def run_get_method(self, addr, method, stack=[]):
|
||||
return (await self.request(
|
||||
"POST", 'runGetMethod', json={
|
||||
'address': addr,
|
||||
'method': method,
|
||||
'stack': stack if type(stack) == list else []
|
||||
}
|
||||
)).get('result', {})
|
||||
|
||||
async def get_transactions(
|
||||
self, addr: str, limit: int = 100, lt: str = None, hash: str = None,
|
||||
offset: int = 0, to_lt: str = None
|
||||
):
|
||||
return (await self.request(
|
||||
"GET", 'getTransactions', params={
|
||||
'address': addr,
|
||||
}
|
||||
)).get('result', {})
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
from decimal import Decimal
|
||||
from time import time
|
||||
|
||||
from tonsdk.boc import Cell, begin_cell
|
||||
from tonsdk.contract import Contract
|
||||
from tonsdk.contract.wallet import WalletContract
|
||||
from tonsdk.utils import Address
|
||||
|
||||
WALLET_V3_CR3_CODE_HEX = 'b5ee9c7241021001000162000114ff00f4a413f4bcf2c80b01020120020f02014803080202ce0407020120050600510ccc741d35c87e900c3e910c7b513420405035c874ffcc19aea6f0003cb41a750c341ffc00a456f8a000730074c7c860802ab06ea65b0874c1f50c007ec0380860802aa82ea384cc407cb81a75350c087ec100743b47bb54fb55380c0c7000372103fcbc20002349521d74ac2009801d401d022f00101e85b8020120090c0201200a0b0019bb39ced44d08020d721d3ff3080011b8c97ed44d0d31f3080201580d0e001bb71e3da89a1020481ae43a61e610001bb49f3da89a1020281ae43a7fe61000f6f28308d71820d31fd31fd31f3001f823bbf2d06ced44d0d31fd3ffd31fd3ff305151baf2e0695132baf2e06924f901541066f910f2e069f8007054715226ed44ed45ed479131ed67ed65ed64747fed118e1104d430d023c000917f9170e2f002045023ed41edf101f2ff04a4c8cb1f13cbffcb1fcbffcb0fc9ed542675fc7e'
|
||||
|
||||
|
||||
class WalletV3CR3(WalletContract):
|
||||
def __init__(self, **kwargs):
|
||||
kwargs['code'] = Cell.one_from_boc(WALLET_V3_CR3_CODE_HEX)
|
||||
kwargs['subwallet_id'] = kwargs.get('subwallet_id', 0)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def create_data_cell(self):
|
||||
return (
|
||||
begin_cell()
|
||||
.store_uint(0, 32)
|
||||
.store_bytes(self.options['public_key'])
|
||||
.store_uint(self.options['subwallet_id'], 32)
|
||||
.store_uint(0, 256)
|
||||
.end_cell()
|
||||
)
|
||||
|
||||
def create_signing_message(self, seqno=None, timeout=60) -> Cell:
|
||||
seqno = seqno or 0
|
||||
return begin_cell().store_uint(self.options['subwallet_id'], 32).store_uint(int(time() + timeout), 32).store_uint(seqno, 32).end_cell()
|
||||
|
||||
def create_transfer_message(self, recipients_list: list, seqno: int, timeout=60, dummy_signature=False) -> dict:
|
||||
signing_message = begin_cell().store_cell(self.create_signing_message(seqno=seqno, timeout=timeout))
|
||||
_commands = begin_cell()
|
||||
for i, recipient in enumerate(recipients_list):
|
||||
if not recipient: continue
|
||||
payload_cell = Cell()
|
||||
if recipient.get('payload'):
|
||||
if type(recipient['payload']) == str:
|
||||
if len(recipient['payload']) > 0:
|
||||
payload_cell.bits.write_uint(0, 32)
|
||||
payload_cell.bits.write_string(recipient['payload'])
|
||||
elif hasattr(recipient['payload'], 'refs'):
|
||||
payload_cell = recipient['payload']
|
||||
else:
|
||||
payload_cell.bits.write_bytes(recipient['payload'])
|
||||
|
||||
order_header = Contract.create_internal_message_header(
|
||||
Address(recipient['address']), Decimal(recipient['amount'])
|
||||
)
|
||||
order = Contract.create_common_msg_info(
|
||||
order_header, recipient.get('state_init'), payload_cell
|
||||
)
|
||||
_commands = _commands.store_ref(
|
||||
begin_cell()
|
||||
.store_uint(0xAAC1, 32)
|
||||
.store_uint8(recipient.get('send_mode', 0))
|
||||
.store_ref(order).end_cell()
|
||||
)
|
||||
|
||||
signing_message = signing_message.store_ref(_commands.end_cell())
|
||||
return self.create_external_message(signing_message.end_cell(), seqno, dummy_signature)
|
||||
|
||||
def create_upgrade_message(self, new_code: Cell, new_data: Cell) -> dict:
|
||||
signing_message = begin_cell().store_cell(self.create_signing_message())
|
||||
_commands = begin_cell()
|
||||
_commands = _commands.store_ref(
|
||||
begin_cell()
|
||||
.store_uint(0xAAA0, 32)
|
||||
.store_ref(new_code)
|
||||
.store_ref(new_data)
|
||||
.end_cell()
|
||||
)
|
||||
signing_message = signing_message.store_ref(_commands.end_cell())
|
||||
return self.create_external_message(signing_message.end_cell(), 0, True)
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox';
|
||||
import { Cell, toNano } from '@ton/core';
|
||||
import { WalletV3 } from '../wrappers/WalletV3';
|
||||
import '@ton/test-utils';
|
||||
import { compile } from '@ton/blueprint';
|
||||
|
||||
describe('WalletV3', () => {
|
||||
let code: Cell;
|
||||
|
||||
beforeAll(async () => {
|
||||
code = await compile('WalletV3');
|
||||
});
|
||||
|
||||
let blockchain: Blockchain;
|
||||
let deployer: SandboxContract<TreasuryContract>;
|
||||
let walletV3: SandboxContract<WalletV3>;
|
||||
|
||||
beforeEach(async () => {
|
||||
blockchain = await Blockchain.create();
|
||||
|
||||
walletV3 = blockchain.openContract(WalletV3.createFromConfig({}, code));
|
||||
|
||||
deployer = await blockchain.treasury('deployer');
|
||||
|
||||
const deployResult = await walletV3.sendDeploy(deployer.getSender(), toNano('0.05'));
|
||||
|
||||
expect(deployResult.transactions).toHaveTransaction({
|
||||
from: deployer.address,
|
||||
to: walletV3.address,
|
||||
deploy: true,
|
||||
success: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should deploy', async () => {
|
||||
// the check is done inside beforeEach
|
||||
// blockchain and walletV3 are ready to use
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"outDir": "dist",
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { CompilerConfig } from '@ton/blueprint';
|
||||
|
||||
export const compile: CompilerConfig = {
|
||||
lang: 'func',
|
||||
targets: ['contracts/wallet.fc'],
|
||||
};
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode } from '@ton/core';
|
||||
|
||||
export type WalletV3Config = {};
|
||||
|
||||
export function walletV3ConfigToCell(config: WalletV3Config): Cell {
|
||||
return beginCell().endCell();
|
||||
}
|
||||
|
||||
export class WalletV3 implements Contract {
|
||||
constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) {}
|
||||
|
||||
static createFromAddress(address: Address) {
|
||||
return new WalletV3(address);
|
||||
}
|
||||
|
||||
static createFromConfig(config: WalletV3Config, code: Cell, workchain = 0) {
|
||||
const data = walletV3ConfigToCell(config);
|
||||
const init = { code, data };
|
||||
return new WalletV3(contractAddress(workchain, init), init);
|
||||
}
|
||||
|
||||
async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) {
|
||||
await provider.internal(via, {
|
||||
value,
|
||||
sendMode: SendMode.PAY_GAS_SEPARATELY,
|
||||
body: beginCell().endCell(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue