1 module tagion.mobile.WalletWrapperSdk;
2 
3 import tagion.mobile.DocumentWrapperApi;
4 
5 // import tagion.mobile.WalletStorage;
6 import core.runtime : rt_init, rt_term;
7 import core.stdc.stdlib;
8 import std.array;
9 import std.random;
10 import std.stdint;
11 import std.string : fromStringz, toStringz;
12 import tagion.hibon.Document;
13 
14 //import std.stdio;
15 import core.stdc.string;
16 import std.algorithm;
17 import std.file : exists, remove;
18 import std.path;
19 import std.range;
20 import std.string : splitLines;
21 import tagion.basic.Types : Buffer, FileExtension;
22 import tagion.communication.HiRPC;
23 import tagion.crypto.Cipher;
24 import tagion.crypto.SecureNet;
25 import tagion.crypto.Types : Pubkey;
26 import tagion.crypto.aes.AESCrypto;
27 import tagion.hibon.HiBON;
28 import tagion.hibon.HiBONFile : fread, fwrite;
29 import tagion.hibon.HiBONJSON;
30 import tagion.hibon.HiBONRecord : HiBONRecord;
31 import tagion.script.TagionCurrency;
32 import tagion.script.common;
33 import tagion.utils.StdTime;
34 import tagion.wallet.AccountDetails;
35 import tagion.wallet.KeyRecover;
36 import Wallet = tagion.wallet.SecureWallet;
37 import tagion.wallet.WalletException;
38 import tagion.wallet.WalletRecords : DevicePIN, RecoverGenerator;
39 
40 enum TAGION_HASH = import("revision.mixin").splitLines[2];
41 
42 /// Used for describing the d-runtime status
43 enum DrtStatus {
44     DEFAULT_STS,
45     STARTED,
46     TERMINATED
47 }
48 /// Variable, which repsresents the d-runtime status
49 __gshared DrtStatus __runtimeStatus = DrtStatus.DEFAULT_STS;
50 // Wallet global variable.
51 alias StdSecureWallet = Wallet.SecureWallet!(StdSecureNet);
52 
53 //static __wallet_storage.wallet = StdSecureWallet(DevicePIN.init);
54 
55 // Storage global variable.
56 static WalletStorage* __wallet_storage;
57 
58 static StdSecureWallet[string] wallets;
59 static Exception last_error;
60 /// Functions called from d-lang through dart:ffi
61 extern (C) {
62     enum : uint {
63         NOT_LOGGED_IN = 9,
64         PAYMENT_ERROR = 2,
65     }
66 
67     // Staritng d-runtime
68     export static int64_t start_rt() {
69         if (__runtimeStatus is DrtStatus.DEFAULT_STS) {
70             __runtimeStatus = DrtStatus.STARTED;
71             return rt_init;
72         }
73         return -1;
74     }
75 
76     // Terminating d-runtime
77     export static int64_t stop_rt() {
78         if (__runtimeStatus is DrtStatus.STARTED) {
79             __runtimeStatus = DrtStatus.TERMINATED;
80             return rt_term;
81         }
82         return -1;
83     }
84 
85     // Storage should be initialised once with correct file path
86     // before using other wallet's functionality.
87     export uint wallet_storage_init(const char* pathPtr, uint32_t pathLen) {
88         const directoryPath = cast(char[])(pathPtr[0 .. pathLen]);
89         if (directoryPath.length > 0) {
90             // Full path to stored wallet data.
91             const walletDataPath = directoryPath;
92             __wallet_storage = new WalletStorage(walletDataPath);
93             return 1;
94         }
95 
96         return 0;
97     }
98 
99     // Check if wallet was already created.
100     export uint wallet_check_exist() {
101         return __wallet_storage.isWalletExist();
102     }
103 
104     version (none) export uint wallet_create(
105             const uint8_t* pincodePtr,
106             const uint32_t pincodeLen,
107             const uint16_t* mnemonicPtr,
108             const uint32_t mnemonicLen) {
109         // Restore data from pointers.  
110         auto pincode = cast(char[])(pincodePtr[0 .. pincodeLen]);
111         auto mnemonic = mnemonicPtr[0 .. mnemonicLen];
112         scope (exit) {
113             scramble(pincode);
114         }
115         // Create a wallet from inputs.
116         __wallet_storage.wallet = StdSecureWallet(
117                 mnemonic,
118                 pincode
119         );
120 
121         return __wallet_storage.write;
122 
123     }
124 
125     export uint wallet_create(
126             const uint8_t* pincodePtr,
127             const uint32_t pincodeLen,
128             const uint8_t* mnemonicPtr,
129             const uint32_t mnemonicLen,
130             const uint8_t* saltPtr,
131             const uint32_t saltLen) nothrow {
132         try {
133             auto pincode = cast(char[])(pincodePtr[0 .. pincodeLen]);
134             auto mnemonic = cast(char[]) mnemonicPtr[0 .. mnemonicLen];
135 
136             check(saltPtr is null && saltLen is 0 || saltPtr !is null, "Casting went wrong");
137 
138             auto salt = cast(char[]) saltPtr[0 .. saltLen];
139             scope (exit) {
140             pincode[]=0;
141             mnemonic[]=0;
142             salt[]=0;
143             }
144             // Create a wallet from inputs.
145             __wallet_storage.wallet = StdSecureWallet(
146                     mnemonic,
147                     pincode,
148                     salt
149             );
150             __wallet_storage.write;
151         }
152         catch (Exception e) {
153             last_error = e;
154             return 0;
155         }
156         return 1;
157     }
158 
159     export uint wallet_login(const uint8_t* pincodePtr, const uint32_t pincodeLen) nothrow {
160 
161         // Restore data from ponters.
162         try {
163             auto pincode = cast(char[])(pincodePtr[0 .. pincodeLen]);
164             scope (exit) {
165                 pincode[]=0;
166             }
167         
168             __wallet_storage.read;
169             return __wallet_storage.wallet.login(pincode);
170         }
171         catch (Exception e) {
172             last_error = e;
173         }
174         return 0;
175     }
176 
177     export uint wallet_logout() nothrow {
178         if (__wallet_storage.wallet.isLoggedin()) {
179             __wallet_storage.wallet.logout();
180             // Set wallet to default.
181             __wallet_storage.wallet = __wallet_storage.wallet.init;
182             return 1;
183         }
184         return 0;
185     }
186 
187     export uint wallet_check_login() {
188         return __wallet_storage.wallet.isLoggedin();
189     }
190 
191     export uint wallet_delete() {
192         // Try to remove wallet file.
193         if (__wallet_storage !is null && __wallet_storage.remove()) {
194             __wallet_storage.wallet.logout();
195             // Set wallet to default.
196             //defaultWallet();
197             __wallet_storage.wallet = __wallet_storage.wallet.init;
198             return 1;
199         }
200         return 0;
201     }
202 
203     export uint validate_pin(const uint8_t* pincodePtr, const uint32_t pincodeLen) {
204         // Restore data from ponters.
205         const pincode = cast(char[])(pincodePtr[0 .. pincodeLen]);
206 
207         if (__wallet_storage.wallet.isLoggedin()) {
208             return __wallet_storage.wallet.checkPincode(pincode);
209         }
210         return 0;
211     }
212 
213     // TODO: Get info if it's possible to change a pincode without providing a current one.
214     export uint change_pin(
215             const uint8_t* pincodePtr,
216             const uint32_t pincodeLen,
217             const uint8_t* newPincodePtr,
218             const uint32_t newPincodeLen) {
219         const pincode = cast(char[])(pincodePtr[0 .. pincodeLen]);
220         const newPincode = cast(char[])(newPincodePtr[0 .. newPincodeLen]);
221 
222         if (__wallet_storage.wallet.isLoggedin()) {
223             if (__wallet_storage.wallet.changePincode(pincode, newPincode)) {
224                 __wallet_storage.write;
225                 version(NET_HACK) {
226                 __wallet_storage.read;
227                 }
228                 // Since secure_wallet do logout after pincode change
229                 // we need to perform a login manualy.
230                 __wallet_storage.wallet.login(newPincode);
231                 return 1;
232             }
233         }
234         return 0;
235     }
236 
237     export uint get_fee(const double amount, double* fees) {
238         TagionCurrency tgn_fees;
239         scope (exit) {
240             *fees = tgn_fees.value;
241         }
242 
243         const can_pay = __wallet_storage.wallet.getFee(TagionCurrency(amount), tgn_fees);
244         return can_pay.value ? 1 : 0;
245     }
246 
247     export uint create_nft_contract(
248             uint32_t* signedContractPtr,
249             uint8_t* nftPtr,
250             const uint32_t nftLen) {
251 
252         immutable nftBuff = cast(immutable)(nftPtr[0 .. nftLen]);
253 
254         if (__wallet_storage.wallet.isLoggedin()) {
255             auto nft = Document(nftBuff);
256 
257             SignedContract signed_contract;
258 
259             const is_created = __wallet_storage.wallet.createNFT(nft, signed_contract);
260             if (is_created) {
261                 const nftDocId = recyclerDoc.create(signed_contract.toDoc);
262                 // Save wallet state to file.
263                 __wallet_storage.write;
264 
265                 *signedContractPtr = nftDocId;
266                 return 1;
267             }
268         }
269         return 0;
270     }
271 
272     export uint create_contract(
273             uint32_t* contractPtr,
274             const uint8_t* invoicePtr,
275             const uint32_t invoiceLen,
276             const double amount,
277             double* fees,
278             uint32_t errorLen,
279             uint8_t* errorPtr,
280         ) {
281 
282         immutable invoiceBuff = cast(immutable)(invoicePtr[0 .. invoiceLen]);
283         TagionCurrency tgn_fees;
284         scope (exit) {
285             *fees = tgn_fees.value;
286 
287         }
288 
289         if (!__wallet_storage.wallet.isLoggedin()) {
290             return NOT_LOGGED_IN;
291         }   
292 
293         auto invoice = Invoice(Document(invoiceBuff));
294         invoice.amount = TagionCurrency(amount);
295 
296         SignedContract signed_contract;
297         const can_pay =
298             __wallet_storage.wallet.payment([invoice], signed_contract, tgn_fees);
299         if (can_pay) {
300             const contract_net = __wallet_storage.wallet.net;
301             const hirpc = HiRPC(contract_net);
302             const contract = hirpc.submit(signed_contract);
303             const contractDocId = recyclerDoc.create(contract.toDoc);
304             // Save wallet state to file.
305             __wallet_storage.write;
306             version(NET_HACK) {
307             __wallet_storage.read;
308             }
309 
310             *contractPtr = contractDocId;
311             return 1;
312         }
313         auto error_result = new HiBON();
314         if (can_pay.msg is null) {
315             error_result["error"] = "error is null?";
316         } else {
317             error_result["error"] = can_pay.msg;
318         }
319         const errorDocId = recyclerDoc.create(Document(error_result));
320         *errorPtr = cast(uint8_t) errorDocId;
321 
322         return PAYMENT_ERROR;
323     }
324 
325     export uint create_invoice(
326             uint8_t* invoicePtr,
327             const double amount,
328             const char* labelPtr,
329             const uint32_t labelLen) {
330 
331         immutable label = cast(immutable)(labelPtr[0 .. labelLen]);
332 
333         if (__wallet_storage.wallet.isLoggedin()) {
334             auto invoice = StdSecureWallet.createInvoice(
335                     label, amount.TGN);
336             __wallet_storage.wallet.registerInvoice(invoice);
337 
338             const invoiceDocId = recyclerDoc.create(invoice.toDoc);
339             // Save wallet state to file.
340             __wallet_storage.write;
341             version(NET_HACK) {
342             __wallet_storage.read;
343             }
344 
345             *invoicePtr = cast(uint8_t) invoiceDocId;
346             return 1;
347         }
348         return 0;
349     }
350 
351     export uint request_update(uint8_t* requestPtr) {
352 
353         if (!__wallet_storage.wallet.isLoggedin()) {
354             return NOT_LOGGED_IN;
355 
356         }
357         const request = __wallet_storage.wallet.getRequestUpdateWallet();
358         const requestDocId = recyclerDoc.create(request.toDoc);
359         *requestPtr = cast(uint8_t) requestDocId;
360         return 1;
361     }
362 
363     export uint update_response(uint8_t* responsePtr, uint32_t responseLen) {
364         import tagion.hibon.HiBONException;
365 
366         immutable response = cast(immutable)(responsePtr[0 .. responseLen]);
367 
368         if (!__wallet_storage.wallet.isLoggedin()) {
369             return NOT_LOGGED_IN;
370         }
371 
372         HiRPC hirpc = HiRPC(__wallet_storage.wallet.net);
373         try {
374             auto receiver = hirpc.receive(Document(response));
375             const result = __wallet_storage.wallet.setResponseUpdateWallet(receiver);
376 
377             if (result) {
378                 // Save wallet state to file.
379                 __wallet_storage.write;
380                 version(NET_HACK) {
381                 __wallet_storage.read;
382                 }
383                 return 1;
384             }
385         }
386         catch (HiBONException e) {
387             return 0;
388         }
389         return 0;
390     }
391 
392     // export void toPretty(uint8_t* docPtr, uint32_t responseLen, char* resultPtr, uint32_t* resultLen) {
393     export void toPretty(uint8_t* docPtr, uint32_t responseLen, uint8_t* resultPtr) {
394         immutable res = cast(immutable)(docPtr[0 .. responseLen]);
395         Document doc = Document(res);
396 
397         import tagion.hibon.HiBONJSON : toPretty;
398 
399         string docToPretty = doc.toPretty;
400 
401         auto result = new HiBON();
402         result["pretty"] = docToPretty;
403 
404         const resultDocId = recyclerDoc.create(Document(result));
405         *resultPtr = cast(uint8_t) resultDocId;
406         // resultPtr = cast(char*) &result[0];
407         // *resultLen = cast(uint32_t) result.length;
408     }
409 
410     @safe
411     export double get_locked_balance() {
412         const balance = __wallet_storage.wallet.locked_balance();
413         return balance.value;
414     }
415 
416     @safe
417     export double get_balance() {
418         const balance = __wallet_storage.wallet.available_balance();
419         return balance.value;
420     }
421 
422     @safe
423     export double get_total_balance() {
424         const balance = __wallet_storage.wallet.total_balance();
425         return balance.value;
426     }
427 
428     export uint get_public_key(uint8_t* pubkeyPtr) {
429         if (__wallet_storage.wallet.isLoggedin()) {
430             const pubkey = __wallet_storage.wallet.getPublicKey();
431 
432             auto result = new HiBON();
433             result["pubkey"] = pubkey;
434 
435             const pubkeyDocId = recyclerDoc.create(Document(result));
436 
437             *pubkeyPtr = cast(uint8_t) pubkeyDocId;
438 
439             return 1;
440         }
441         return 0;
442     }
443 
444     export uint get_derivers_state(uint8_t* deriversStatePtr) {
445         if (__wallet_storage.wallet.isLoggedin()) {
446             const deriversState = __wallet_storage.wallet.getDeriversState();
447 
448             auto result = new HiBON();
449             result["derivers_state"] = deriversState;
450 
451             const deviversStateDocId = recyclerDoc.create(Document(result));
452 
453             *deriversStatePtr = cast(uint8_t) deviversStateDocId;
454 
455             return 1;
456         }
457         return 0;
458     }
459 
460     export uint get_account(uint8_t* accountPtr) {
461         if (__wallet_storage.wallet.isLoggedin()) {
462             
463             const accountDocId = recyclerDoc.create(__wallet_storage.wallet.account.toDoc);
464 
465             *accountPtr = cast(uint8_t) accountDocId;
466 
467             return 1;
468         }
469         return 0;
470     }
471 
472     version (none) export uint set_derivers(const uint8_t* deriversPtr, const uint32_t deriversLen) {
473 
474         immutable encDerivers = cast(immutable)(deriversPtr[0 .. deriversLen]);
475 
476         if (__wallet_storage.wallet.isLoggedin()) {
477             __wallet_storage.wallet.setEncrDerivers(Cipher.CipherDocument(Document(encDerivers)));
478             return 1;
479         }
480         return 0;
481     }
482 
483     export uint get_backup(uint8_t* backupPtr) {
484         if (__wallet_storage.wallet.isLoggedin()) {
485             const encrAccount = __wallet_storage.wallet.getEncrAccount();
486             const backupDocId = recyclerDoc.create(encrAccount.toDoc);
487 
488             *backupPtr = cast(uint8_t) backupDocId;
489 
490             return 1;
491         }
492         return 0;
493     }
494 
495     export uint set_backup(const uint8_t* backupPtr, const uint32_t backupLen) {
496 
497         immutable account = cast(immutable)(backupPtr[0 .. backupLen]);
498 
499         if (__wallet_storage.wallet.isLoggedin()) {
500             __wallet_storage.wallet.setEncrAccount(Cipher.CipherDocument(Document(account)));
501             __wallet_storage.write;
502             version(NET_HACK) {
503             __wallet_storage.read;
504             }
505             return 1;
506         }
507         return 0;
508     }
509 
510     export uint add_bill(const uint8_t* billPtr, const uint32_t billLen) {
511 
512         immutable billBuffer = cast(immutable)(billPtr[0 .. billLen]);
513 
514         if (__wallet_storage.wallet.isLoggedin()) {
515             auto bill = TagionBill(Document(billBuffer));
516             __wallet_storage.wallet.account.add_bill(bill);
517             return 1;
518         }
519         return 0;
520     }
521 
522     export uint remove_bill(uint8_t* pubKeyPtr, uint32_t pubKeyLen) {
523         immutable(ubyte)[] pubKey = cast(immutable(ubyte)[])(pubKeyPtr[0 .. pubKeyLen]);
524 
525         if (__wallet_storage.wallet.isLoggedin()) {
526             const result = __wallet_storage.wallet.account.remove_bill(Pubkey(pubKey));
527             return result;
528         }
529         return 0;
530     }
531 
532     export uint remove_bills_by_contract(const uint8_t* contractPtr, const uint32_t contractLen) {
533         // Collect input and output keys from the contract.
534         // Iterate them and call remove on each.
535 
536         immutable contractBuffer = cast(immutable)(contractPtr[0 .. contractLen]);
537 
538         if (__wallet_storage.wallet.isLoggedin()) {
539 
540             auto contractDoc = Document(contractBuffer);
541 
542             // Contract inputs.
543             const messageTag = "$msg";
544             const paramsTag = "params";
545 
546             auto messageDoc = contractDoc[messageTag].get!Document;
547             auto paramsDoc = messageDoc[paramsTag].get!Document;
548             auto sContract = SignedContract(paramsDoc);
549 
550             import std.algorithm;
551 
552             sContract.contract.inputs.each!(hash => __wallet_storage.wallet.account.remove_bill_by_hash(hash));
553 
554             return 1;
555         }
556         return 0;
557     }
558 
559     export uint ulock_bills_by_contract(const uint8_t* contractPtr, const uint32_t contractLen) {
560 
561         immutable contractBuffer = cast(immutable)(contractPtr[0 .. contractLen]);
562 
563         if (__wallet_storage.wallet.isLoggedin()) {
564 
565             auto contractDoc = Document(contractBuffer);
566 
567             // Contract inputs.
568             const messageTag = "$msg";
569             const paramsTag = "params";
570 
571             auto messageDoc = contractDoc[messageTag].get!Document;
572             auto paramsDoc = messageDoc[paramsTag].get!Document;
573             auto sContract = SignedContract(paramsDoc);
574 
575             import std.algorithm;
576 
577             sContract.contract.inputs.each!(hash => __wallet_storage.wallet.account.unlock_bill_by_hash(hash));
578 
579             return 1;
580         }
581         return 0;
582     }
583 
584     export uint check_contract_payment(const uint8_t* contractPtr, const uint32_t contractLen, uint8_t* statusPtr) {
585         immutable contractBuffer = cast(immutable)(contractPtr[0 .. contractLen]);
586 
587         if (__wallet_storage.wallet.isLoggedin()) {
588 
589             auto contractDoc = Document(contractBuffer);
590 
591             // Contract inputs.
592             const messageTag = "$msg";
593             const paramsTag = "params";
594 
595             auto messageDoc = contractDoc[messageTag].get!Document;
596             auto paramsDoc = messageDoc[paramsTag].get!Document;
597             auto sContract = SignedContract(paramsDoc);
598             const outputs = PayScript(sContract.contract.script).outputs.map!(output => output.toDoc).array;
599 
600             int status = __wallet_storage.wallet.account.check_contract_payment(
601                     sContract.contract.inputs, outputs);
602 
603             *statusPtr = cast(uint8_t) status;
604             return 1;
605         }
606         return 0;
607     }
608 
609     export uint check_invoice_payment(const uint8_t* invoicePtr, const uint32_t invoiceLen, double* amountPtr) {
610         immutable invoiceBuffer = cast(immutable)(invoicePtr[0 .. invoiceLen]);
611 
612         if (__wallet_storage.wallet.isLoggedin()) {
613 
614             auto amount = TagionCurrency(0);
615             auto invoice = Invoice(Document(invoiceBuffer));
616             auto isExist = __wallet_storage.wallet.account.check_invoice_payment(invoice.pkey, amount);
617 
618             if (isExist) {
619                 *amountPtr = amount.value;
620                 return 1;
621             }
622         }
623         return 0;
624     }
625 }
626 
627 unittest {
628     const work_path = new_test_path;
629     scope (success) {
630         work_path.rmdirRecurse;
631     }
632     { // Init storage should fail with empty path.
633         __wallet_storage = null;
634         const char[] path;
635         const pathLen = cast(uint32_t) path.length;
636         const result = wallet_storage_init(path.ptr, pathLen);
637         // Check the result
638         assert(result == 0);
639         assert(__wallet_storage is null);
640     }
641 
642     { // Init storage with correct path.
643         const path = work_path;
644         const uint32_t pathLen = cast(uint32_t) path.length;
645         const uint result = wallet_storage_init(path.ptr, pathLen);
646         // Check the result
647         assert(result != 0, "Expected non-zero result");
648         assert(__wallet_storage !is null);
649     }
650 
651     { // Wallet create.
652         // Create input data
653         const uint8_t[] pincode = cast(uint8_t[]) "1234".dup;
654         const uint32_t pincodeLen = cast(uint32_t) pincode.length;
655         const uint8_t[] mnemonic = cast(uint8_t[]) "some words".dup;
656         const uint32_t mnemonicLen = cast(uint32_t) mnemonic.length;
657         const uint8_t[] salt = cast(uint8_t[]) "salt".dup;
658         const uint32_t saltLen = cast(uint32_t) salt.length;
659 
660         // Call the wallet_create function
661         const uint result = wallet_create(pincode.ptr, pincodeLen, mnemonic.ptr, mnemonicLen, salt.ptr, saltLen);
662 
663         // Check the result
664         assert(result != 0, "Expected non-zero result");
665     }
666 
667     { // Check if wallet was created.
668         const uint result = wallet_check_exist();
669         // Check the result
670         assert(result != 0, "Expected non-zero result");
671     }
672 
673     { // Fail to login to a wallet with an incorrect pincode.
674         const uint8_t[] pincode = cast(uint8_t[]) "5555".dup;
675         const uint32_t pincodeLen = cast(uint32_t) pincode.length;
676         const uint result = wallet_login(pincode.ptr, pincodeLen);
677         // Check the result
678         assert(result == 0);
679     }
680 
681     { // Login to wallet with a correct pincode.
682         const uint8_t[] pincode = cast(uint8_t[]) "1234".dup;
683         const uint32_t pincodeLen = cast(uint32_t) pincode.length;
684         const uint result = wallet_login(pincode.ptr, pincodeLen);
685         // Check the result
686         assert(result != 0, "Expected non-zero result");
687     }
688 
689     { // Check login
690         const uint result = wallet_check_login();
691         // Check the result
692         assert(result != 0, "Expected non-zero result");
693     }
694 
695     { // Logout wallet.
696         const result = wallet_logout();
697         assert(result != 0, "Expected non-zero result");
698         assert(!__wallet_storage.wallet.isLoggedin);
699     }
700 
701     { // Delete wallet.
702 
703         const result = wallet_delete();
704         assert(result != 0, "Expected non-zero result");
705         assert(!__wallet_storage.wallet.isLoggedin);
706         assert(!__wallet_storage.isWalletExist);
707     }
708 
709     { // Validate pin.
710 
711         const uint8_t[] pincode = cast(uint8_t[]) "1234".dup;
712         const uint32_t pincodeLen = cast(uint32_t) pincode.length;
713         const uint8_t[] mnemonic = cast(uint8_t[]) "some words".dup;
714         const uint32_t mnemonicLen = cast(uint32_t) mnemonic.length;
715         uint8_t[] pin_copy;
716         // Call the wallet_create function
717         pin_copy = pincode.dup;
718         wallet_create(pin_copy.ptr, pincodeLen, mnemonic.ptr, mnemonicLen, const(uint8_t*).init, uint32_t.init);
719         pin_copy = pincode.dup;
720         wallet_login(pin_copy.ptr, pincodeLen);
721 
722         const uint result = validate_pin(pincode.ptr, pincodeLen);
723         assert(result != 0, "Expected non-zero result");
724     }
725 
726     { // Validate pin should fail on incorrect pincode.
727         const uint8_t[] pincode = cast(uint8_t[]) "5555".dup;
728         const uint32_t pincodeLen = cast(uint32_t) pincode.length;
729         const uint result = validate_pin(pincode.ptr, pincodeLen);
730         assert(result == 0);
731     }
732 
733     { // Change pincode.
734         const uint8_t[] pincode = cast(uint8_t[]) "1234".dup;
735         const uint32_t pincodeLen = cast(uint32_t) pincode.length;
736         const uint8_t[] newPincode = cast(uint8_t[]) "5555".dup;
737         const uint32_t newPincodeLen = cast(uint32_t) newPincode.length;
738         const uint result = change_pin(pincode.ptr, pincodeLen, newPincode.ptr, newPincodeLen);
739         assert(result != 0, "Expected non-zero result");
740         assert(__wallet_storage.wallet.isLoggedin, "Expected wallet stays logged in");
741     }
742 
743     // Create input data
744     const uint64_t invAmount = 100;
745     const char[] label = "Test Invoice";
746     const uint32_t labelLen = cast(uint32_t) label.length;
747     uint8_t invoiceDocId;
748 
749     { // Create invoice.
750 
751         // Call the create_invoice function
752         const uint result = create_invoice(&invoiceDocId, invAmount, label.ptr, labelLen);
753 
754         // Check the result
755         assert(result == 1, "Expected result to be 1");
756 
757         // Verify that invoiceDocId is non-zero
758         assert(invoiceDocId != 0, "Expected non-zero invoiceDocId");
759     }
760 
761     import std.algorithm : map;
762     import std.range : zip;
763     import std.string : representation;
764 
765     auto bill_amounts = [200, 500, 100].map!(a => a.TGN);
766     [200, 500, 100]
767         .map!(value => value.TGN)
768         .map!(value => __wallet_storage.wallet.requestBill(value))
769         .each!(bill => __wallet_storage.wallet.addBill(bill));
770 
771     const net = new StdHashNet;
772     // Add the bills to the account with the derive keys
773     with (__wallet_storage.wallet.account) {
774 
775         bills = zip(bill_amounts, derivers.byKey)
776             .map!(bill_derive => TagionBill(
777                     bill_derive[0],
778                     currentTime,
779                     bill_derive[1],
780                     Buffer.init)).array;
781     }
782 
783     auto invoiceDoc = recyclerDoc(invoiceDocId);
784 
785     // Contract input data.
786     const uint8_t[] invoice = cast(uint8_t[])(invoiceDoc.serialize);
787     const uint32_t invoiceLen = cast(uint32_t) invoice.length;
788     const uint64_t contAmount = 100;
789     const uint32_t errorLen = 0;
790     uint8_t errorPtr;
791 
792     uint32_t contractDocId;
793 
794     { // Create a contract.
795         double fees;
796         const uint result = create_contract(&contractDocId, invoice.ptr, invoiceLen, contAmount, &fees, errorLen, &errorPtr);
797         // Check the result
798         assert(result == 1, "Expected result to be 1");
799 
800         // Verify that invoiceDocId is non-zero
801         assert(contractDocId != 0, "Expected non-zero contractDocId");
802     }
803 
804     auto contractDoc = recyclerDoc(contractDocId);
805 
806     const uint8_t[] contract = cast(uint8_t[])(contractDoc.serialize);
807     const uint32_t contractLen = cast(uint32_t) contract.length;
808 
809     { // Update request.
810         uint8_t requestDocId;
811         const uint result = request_update(&requestDocId);
812 
813         // Check the result
814         assert(result == 1, "Expected result to be 1");
815 
816         // Verify that invoiceDocId is non-zero
817         assert(requestDocId != 0, "Expected non-zero requestDocId");
818     }
819     { // Get public key.
820         uint8_t pubkeyDocId;
821         uint result = get_public_key(&pubkeyDocId);
822 
823         // Check the result
824         assert(result == 1, "Expected result to be 1");
825 
826         // Verify that invoiceDocId is non-zero
827         assert(pubkeyDocId != 0, "Expected non-zero pubkeyDocId");
828     }
829 
830     { // Get and set derivers.
831         uint8_t backupDocId;
832         uint getBackupResult = get_backup(&backupDocId);
833 
834         // Check the result
835         assert(getBackupResult == 1, "Expected result to be 1");
836 
837         // Verify that invoiceDocId is non-zero
838         assert(backupDocId != 0, "Expected non-zero backupDocId");
839 
840         auto backupDoc = recyclerDoc(backupDocId);
841 
842         // Derivers input data.
843         const uint8_t[] backup = cast(uint8_t[])(backupDoc.serialize);
844         const uint32_t backupLen = cast(uint32_t) backup.length;
845 
846         uint setBackupResult = set_backup(backup.ptr, backupLen);
847 
848         // Check the result
849         assert(setBackupResult == 1, "Expected result to be 1");
850     }
851 
852     { // Get derivers state.
853         uint8_t deriversDocId;
854         uint getDResult = get_derivers_state(&deriversDocId);
855 
856         // Check the result
857         assert(getDResult == 1, "Expected result to be 1");
858 
859         // Verify that invoiceDocId is non-zero
860         assert(deriversDocId != 0, "Expected non-zero deriversDocId");
861     }
862 
863     {   // Get Account
864         uint8_t accountDocId;
865         uint getAResult = get_account(&accountDocId);
866 
867         // Check the result
868         assert(getAResult == 1, "Expected result to be 1");
869 
870         // Verify that invoiceDocId is non-zero
871         assert(accountDocId != 0, "Expected non-zero accountDocId");
872     }
873 
874     { // Ulock bills by contract
875 
876         uint result = ulock_bills_by_contract(contract.ptr, contractLen);
877 
878         // Check the result
879         assert(result == 1, "Expected result to be 1");
880     }
881     { // Check invoice payment
882 
883         double amount;
884         auto result = check_invoice_payment(invoice.ptr, invoiceLen, &amount);
885 
886         // Check the result
887         assert(result == 1, "Expected result to be 1");
888         assert(amount != 0, "Expected amount not to be 0");
889     }
890     { // Check contract payment
891 
892         uint8_t status;
893 
894         uint result = check_contract_payment(contract.ptr, contractLen, &status);
895 
896         // Check the result
897         assert(result == 1, "Expected result to be 1");
898         assert(status == 0, "Expected status to be 0");
899     }
900     { // Remove bills by contract.
901 
902         uint result = remove_bills_by_contract(contract.ptr, contractLen);
903 
904         // Check the result
905         assert(result == 1, "Expected result to be 1");
906     }
907 }
908 
909 alias Store = WalletStorage;
910 struct WalletStorage {
911     StdSecureWallet wallet;
912     enum {
913         accountfile = "account".setExtension(FileExtension.hibon), /// account file name
914         walletfile = "wallet".setExtension(
915                 FileExtension.hibon), /// wallet file name
916         devicefile = "device".setExtension(FileExtension.hibon), /// device file name
917     }
918     string wallet_data_path;
919     this(const char[] walletDataPath) {
920         import std.stdio;
921 
922         wallet_data_path = walletDataPath.idup;
923         writefln("CREATE %s", walletDataPath);
924         import std.file;
925 
926         if (!wallet_data_path.exists) {
927             wallet_data_path.mkdirRecurse;
928         }
929         if (isWalletExist) {
930             read;
931         }
932     }
933 
934     string path(string filename) const pure {
935         return buildPath(wallet_data_path, filename);
936     }
937 
938     bool isWalletExist() const {
939         return only(accountfile, walletfile, devicefile)
940             .map!(file => path(file).exists)
941             .any;
942     }
943 
944     void write() const {
945         // Create a hibon for wallet data.
946 
947         path(devicefile).fwrite(wallet.pin);
948         path(accountfile).fwrite(wallet.account);
949         path(walletfile).fwrite(wallet.wallet);
950 
951     }
952 
953     void read() {
954 
955         version(NET_HACK) {
956             auto _pin = path(devicefile).fread!DevicePIN;
957             auto _wallet = path(walletfile).fread!RecoverGenerator;
958             auto _account = path(accountfile).fread!AccountDetails;
959 
960             if (wallet.net !is null) {
961                 auto __net = cast(shared(StdSecureNet)) wallet.net;
962                 scope(exit) {
963                     __net = null;
964                 }
965                 auto copied_net = new StdSecureNet(__net);
966                 wallet = StdSecureWallet(_pin, _wallet, _account);
967 
968                 wallet.set_net(copied_net);
969             }
970             else {
971                 wallet = StdSecureWallet(_pin, _wallet, _account);
972             }
973         } 
974         else {
975             auto _pin = path(devicefile).fread!DevicePIN;
976             auto _wallet = path(walletfile).fread!RecoverGenerator;
977             auto _account = path(accountfile).fread!AccountDetails;
978             wallet = StdSecureWallet(_pin, _wallet, _account);
979         }
980 
981     }
982 
983     bool remove() const {
984         if (wallet_data_path.exists) {
985 
986             try {
987                 only(accountfile, walletfile, devicefile)
988                     .each!(file => path(file).remove);
989                 //wallet_data_path.rmdir;
990                 return 1;
991             }
992             catch (Exception e) {
993                 last_error = e;
994                 return false;
995             }
996         }
997         return false;
998     }
999 }
1000 
1001 unittest {
1002     import std.exception;
1003     import std.stdio;
1004 
1005     const work_path = new_test_path;
1006     scope (success) {
1007         work_path.rmdirRecurse;
1008     }
1009     scope (failure) {
1010         writefln("failed work_path %s", work_path);
1011     }
1012     { // Write wallet file.
1013 
1014         // Path to stored wallet data.
1015         const walletDataPath = work_path;
1016 
1017         auto strg = new WalletStorage(walletDataPath);
1018 
1019         assertNotThrown(strg.write(), "Expect write result is true");
1020     }
1021 
1022     { // Read wallet file.
1023 
1024         // Path to stored wallet data.
1025         const walletDataPath = work_path;
1026 
1027         auto strg = new WalletStorage(walletDataPath);
1028 
1029         StdSecureWallet secure_wallet;
1030 
1031         assertNotThrown(strg.read(), "Expect read result is true");
1032     }
1033 
1034     { // Check if wallet file is exist.
1035 
1036         // Path to stored wallet data.
1037         const walletDataPath = work_path;
1038 
1039         auto strg = new WalletStorage(walletDataPath);
1040         bool result = strg.isWalletExist();
1041         assert(result, "Expect read result is true");
1042     }
1043 
1044     { // Delete wallet file.
1045 
1046         // Path to stored wallet data.
1047         const walletDataPath = work_path;
1048 
1049         auto strg = new WalletStorage(walletDataPath);
1050         bool result = strg.remove();
1051         assert(result, "Expect read result is true");
1052     }
1053 
1054 }
1055 
1056 version (unittest) {
1057     import std.conv : to;
1058     import std.file;
1059 
1060     string new_test_path(string func = __FUNCTION__, const size_t line = __LINE__) {
1061         const result_path = [deleteme, func, line.to!string].join("_");
1062         result_path.mkdirRecurse;
1063         return result_path;
1064     }
1065 }