1 module tagion.tools.wallet.WalletInterface;
2 import core.thread;
3 import std.algorithm;
4 import std.conv : to;
5 import std.exception : ifThrown;
6 import std.file : exists, mkdir;
7 import std.format;
8 import std.path;
9 import std.range;
10 import std.stdio;
11 import std.string : representation;
12 import tagion.basic.Message;
13 import tagion.basic.Types : Buffer, FileExtension, hasExtension;
14 import tagion.basic.basic : isinit;
15 import tagion.basic.range : doFront;
16 import tagion.crypto.SecureInterfaceNet;
17 import tagion.crypto.SecureNet;
18 import tagion.hibon.HiBONFile : fread, fwrite;
19 import tagion.hibon.HiBONRecord : isHiBONRecord, isRecord;
20 import tagion.script.TagionCurrency;
21 import tagion.tools.wallet.WalletOptions;
22 import tagion.utils.Term;
23 import tagion.wallet.AccountDetails;
24 import tagion.wallet.KeyRecover;
25 import tagion.wallet.SecureWallet;
26 import tagion.wallet.WalletRecords;
27 
28 //import tagion.basic.tagionexceptions : check;
29 import std.range;
30 import std.typecons;
31 import tagion.communication.HiRPC;
32 import tagion.crypto.Types : Pubkey, Fingerprint;
33 import tagion.dart.DARTBasic;
34 import tagion.dart.DARTcrud;
35 import tagion.hibon.Document;
36 import tagion.hibon.HiBONJSON : toPretty;
37 import tagion.hibon.HiBONtoText;
38 import tagion.script.common;
39 import tagion.script.execute : ContractExecution;
40 import tagion.script.standardnames;
41 import tagion.tools.Basic;
42 import tagion.wallet.SecureWallet : check;
43 import tagion.wallet.WalletException;
44 import tagion.tools.secretinput;
45 
46 //import tagion.wallet.WalletException : check;
47 /**
48  * @brief strip white spaces in begin/end of text
49  * @param word - input parameter with out
50  * return dublicate out parameter
51  */
52 const(char[]) trim(return scope const(char)[] word) pure nothrow @safe @nogc {
53     import std.ascii : isWhite;
54 
55     while (word.length && word[0].isWhite) {
56         word = word[1 .. $];
57     }
58     while (word.length && word[$ - 1].isWhite) {
59         word = word[0 .. $ - 1];
60     }
61     return word;
62 }
63 
64 /**
65  * @brief Write in console warning message
66  */
67 void warning() {
68     writefln("%sWARNING%s: This wallet should only be used for the Tagion Dev-net%s", RED, BLUE, RESET);
69 }
70 
71 void word_strip(scope ref char[] word_strip) pure nothrow @safe @nogc {
72     import std.ascii : isWhite;
73 
74     scope not_change = word_strip;
75     scope (exit) {
76         assert((word_strip.length is 0) || (&not_change[0] is &word_strip[0]), "The pincode should not be reallocated");
77     }
78     size_t current_i;
79     foreach (c; word_strip) {
80         if (!c.isWhite) {
81             word_strip[current_i++] = c;
82         }
83     }
84     word_strip = word_strip[0 .. current_i];
85 }
86 
87 enum MAX_PINCODE_SIZE = 128;
88 enum LINE = "------------------------------------------------------";
89 
90 pragma(msg, "Fixme(lr)Remove trusted when nng is safe");
91 HiRPC.Receiver sendSubmitHiRPC(string address, HiRPC.Sender contract, const(SecureNet) net) @trusted {
92     import nngd;
93     import std.exception;
94     import tagion.hibon.Document;
95     import tagion.hibon.HiBONtoText;
96 
97     int rc;
98     NNGSocket sock = NNGSocket(nng_socket_type.NNG_SOCKET_REQ);
99     sock.sendtimeout = 1000.msecs;
100     sock.sendbuf = 0x4000;
101     sock.recvtimeout = 3000.msecs;
102 
103     rc = sock.dial(address);
104     if (rc != 0) {
105         throw new WalletException(format("Could not dial address %s: %s", address, nng_errstr(rc)));
106     }
107 
108     rc = sock.send(contract.toDoc.serialize);
109     check(sock.m_errno == nng_errno.NNG_OK, format("NNG_ERRNO %d", cast(int) sock.m_errno));
110     check(rc == 0, format("Could not send bill to network %s", nng_errstr(rc)));
111 
112     auto response_data = sock.receive!Buffer;
113     auto response_doc = Document(response_data);
114     // We should probably change these exceptions so it always returns a HiRPC.Response error instead?
115     check(response_doc.isRecord!(HiRPC.Receiver), format("Error in response when sending bill %s", response_doc.toPretty));
116 
117     HiRPC hirpc = HiRPC(net);
118     return hirpc.receive(response_doc);
119 }
120 
121 HiRPC.Receiver sendShellSubmitHiRPC(string address, HiRPC.Sender contract, const(SecureNet) net) {
122     import nngd;
123 
124     pragma(msg, "Change Web Post and reply so it takes immutable stream");
125     WebData rep = WebClient.post(address, cast(ubyte[]) contract.toDoc.serialize, [
126         "Content-type": "application/octet-stream"
127     ]);
128 
129     if (rep.status != http_status.NNG_HTTP_STATUS_OK) {
130         throw new WalletException(format("Send shell submit error(%d): %s", rep.status, rep.msg));
131     }
132 
133     Document response_doc = Document(cast(immutable) rep.rawdata);
134     HiRPC hirpc = HiRPC(net);
135     return hirpc.receive(response_doc);
136 }
137 
138 HiRPC.Receiver sendShellHiRPC(string address, HiRPC.Sender dart_req, HiRPC hirpc) {
139     import nngd;
140 
141     WebData rep = WebClient.post(address, cast(ubyte[]) dart_req.toDoc.serialize, [
142         "Content-type": "application/octet-stream"
143     ]);
144 
145     if (rep.status != http_status.NNG_HTTP_STATUS_OK) {
146         throw new WalletException(format("send shell submit error(%d): %s", rep.status, rep.msg));
147     }
148 
149     Document response_doc = Document(cast(immutable) rep.rawdata);
150 
151     return hirpc.receive(response_doc);
152 }
153 
154 HiRPC.Receiver sendShellHiRPC(string address, Document dart_req, HiRPC hirpc) {
155     import nngd;
156 
157     WebData rep = WebClient.post(address, cast(ubyte[]) dart_req.serialize, ["Content-type": "application/octet-stream"]);
158 
159     Document response_doc = Document(cast(immutable) rep.rawdata);
160     return hirpc.receive(response_doc);
161 }
162 
163 pragma(msg, "Fixme(lr)Remove trusted when nng is safe");
164 HiRPC.Receiver sendDARTHiRPC(string address, HiRPC.Sender dart_req, HiRPC hirpc, Duration recv_duration = 15_000.msecs) @trusted {
165     import nngd;
166     import std.exception;
167     import tagion.hibon.HiBONException;
168 
169     int rc;
170     NNGSocket s = NNGSocket(nng_socket_type.NNG_SOCKET_REQ);
171     scope (exit) {
172         s.close();
173     }
174     s.recvtimeout = recv_duration;
175 
176     while (1) {
177         writefln("REQ to dial... %s", address);
178         rc = s.dial(address);
179         if (rc == 0) {
180             break;
181         }
182         if (rc == nng_errno.NNG_ECONNREFUSED) {
183             nng_sleep(100.msecs);
184         }
185         if (rc != 0) {
186             throw new WalletException(format("Could not dial kernel %s, %s", address, nng_errstr(rc)));
187         }
188     }
189     rc = s.send(dart_req.toDoc.serialize);
190     if (s.errno != 0) {
191         throw new WalletException(format("error in send of darthirpc: %s", s.errno));
192     }
193     Document received_doc = s.receive!Buffer;
194     if (s.errno != 0) {
195         throw new WalletException(format("REQ Socket error after receive: %s", s.errno));
196     }
197 
198     try {
199         hirpc.receive(received_doc);
200     }
201     catch (HiBONException e) {
202         writefln("::error::ERROR in hirpc receive: %s %s", e, received_doc.toPretty);
203     }
204 
205     return hirpc.receive(received_doc);
206 }
207 
208 struct WalletInterface {
209     const(WalletOptions) options;
210     alias StdSecureWallet = SecureWallet!StdSecureNet;
211     StdSecureWallet secure_wallet;
212     Invoices payment_requests;
213     Quiz quiz;
214     this(const WalletOptions options) {
215         this.options = options;
216     }
217 
218     bool load() {
219         if (options.walletfile.exists) {
220             const wallet_doc = options.walletfile.fread;
221             const pin_doc = options.devicefile.exists ? options.devicefile.fread : Document.init;
222             if (wallet_doc.isInorder && pin_doc.isInorder) {
223                 secure_wallet = WalletInterface.StdSecureWallet(wallet_doc, pin_doc);
224             }
225             if (options.quizfile.exists) {
226                 quiz = options.quizfile.fread!Quiz;
227             }
228             if (options.accountfile.exists) {
229                 secure_wallet.account = options.accountfile.fread!AccountDetails;
230             }
231             return true;
232         }
233         quiz.questions = options.questions.dup;
234         return false;
235     }
236 
237     void save(const bool recover_flag) {
238         if (secure_wallet.isLoggedin && !dry_switch) {
239             verbose("Write %s", options.walletfile);
240 
241             options.walletfile.fwrite(secure_wallet.wallet);
242             verbose("Write %s", options.devicefile);
243             options.devicefile.fwrite(secure_wallet.pin);
244             if (!recover_flag) {
245                 verbose("Write %s", options.quizfile);
246                 options.quizfile.fwrite(quiz);
247             }
248             if (secure_wallet.account !is AccountDetails.init) {
249                 verbose("Write %s", options.accountfile);
250                 options.accountfile.fwrite(secure_wallet.account);
251             }
252         }
253     }
254 
255     enum FKEY = YELLOW;
256     /**
257     * @brief console UI waiting cursor
258     */
259     static void pressKey() {
260         writefln("Press %1$sEnter%2$s", YELLOW, RESET);
261         readln;
262     }
263 
264     enum retry = 4;
265     /**
266     * @brief change pin code interface
267     */
268     bool loginPincode(const bool changepin) {
269         //CLEARSCREEN.write;
270         char[] old_pincode;
271         char[] new_pincode1;
272         char[] new_pincode2;
273         scope (exit) {
274             // Scramble the code to prevent memory leaks
275             old_pincode[] = 0;
276             new_pincode1[] = 0;
277             new_pincode2[] = 0;
278         }
279         foreach (i; 0 .. retry) {
280             //HOME.write;
281             writefln("%1$sAccess code required%2$s", GREEN, RESET);
282             writefln("%1$sEnter empty pincode to proceed recovery%2$s", YELLOW, RESET);
283             //writefln("pincode:");
284             scope (exit) {
285                 old_pincode[] = 0;
286             }
287             info("Press ctrl-C to break");
288             info("Press ctrl-A to show the pincode");
289             getSecret("pincode: ", old_pincode);
290             old_pincode.word_strip;
291             if (old_pincode.length) {
292                 secure_wallet.login(old_pincode);
293                 if (secure_wallet.isLoggedin) {
294                     if (!changepin) {
295                         return true;
296                     }
297                     break;
298                 }
299                 error("Wrong pincode");
300             }
301         }
302         //CLEARSCREEN.write;
303         if (changepin && secure_wallet.isLoggedin) {
304             foreach (i; 0 .. retry) {
305                 //HOME.write;
306                 //CLEARSCREEN.write;
307                 scope (success) {
308                     CLEARSCREEN.write;
309                 }
310                 LINE.writeln;
311                 info("Change you pin code");
312                 LINE.writeln;
313                 if (secure_wallet.pin.D) {
314                     bool ok;
315                     do {
316                         info("New pincode:%s", CLEARDOWN);
317                         getSecret("pincode: ", new_pincode1);
318                         new_pincode1.word_strip;
319                         info("Repeaté:");
320                         getSecret("pincode: ", new_pincode2);
321                         new_pincode2.word_strip;
322                         ok = (new_pincode1.length >= 4);
323                         if (ok && (ok = (new_pincode1 == new_pincode2)) is true) {
324                             secure_wallet.changePincode(old_pincode, new_pincode1);
325                             secure_wallet.login(new_pincode1);
326                             options.devicefile.fwrite(secure_wallet.pin);
327                             return true;
328                         }
329                         else {
330                             writefln("%1$sPincode to short or does not match%2$s", RED, RESET);
331                         }
332                     }
333                     while (!ok);
334                     return true;
335                 }
336                 writefln("%1$sPin code is missing. You need to recover you keys%2$s", RED, RESET);
337             }
338         }
339         return false;
340     }
341 
342     bool generateSeedFromPassphrase(const(char[]) passphrase, const(char[]) pincode, const(char[]) salt = null) {
343         auto tmp_secure_wallet = StdSecureWallet(passphrase, pincode, salt);
344         if (!secure_wallet.wallet.isinit && secure_wallet.wallet.S != tmp_secure_wallet.wallet.S) {
345             return false;
346         }
347         secure_wallet = tmp_secure_wallet;
348         return true;
349     }
350 
351     /**
352     * @brief generate q/a pair keys
353     * @param questions - string array
354     * @param recover_flag - recover/create flag (true mean creating)
355     */
356     void generateSeed(const(string[]) questions, const bool recover_flag) {
357         auto answers = new char[][questions.length];
358         auto translated_questions = questions.map!(s => message(s));
359         CLEARSCREEN.write;
360         scope (success) {
361             CLEARSCREEN.write;
362         }
363         int ch;
364         KeyStroke key;
365         uint select_index = 0;
366         uint confidence;
367         if (recover_flag) {
368             confidence = secure_wallet.confidence;
369         }
370         while (ch != 'q') {
371             //    import core.stdc.stdio : getc, stdin;
372             HOME.write;
373             // warning();
374             if (recover_flag) {
375                 writefln("%sRecover account%s", YELLOW, RESET);
376                 writefln("Answers %d to more of the questions below", confidence);
377             }
378             else {
379                 writefln("%sCreate a new account%s", BLUE, RESET);
380                 writefln("Answers two to more of the questions below");
381             }
382             LINE.writeln;
383             uint number_of_answers;
384             foreach (i, question, answer; lockstep(translated_questions, answers)) {
385                 string select_code;
386                 string chosen_code;
387                 if (select_index == i) {
388                     select_code = BLUE ~ BACKGOUND_WHITE;
389                 }
390                 if (answer.length) {
391                     chosen_code = GREEN;
392                     number_of_answers++;
393                 }
394                 writefln("%2d %s%s%s%s %s%s", i, select_code, chosen_code, question, RESET, answer.trim, CLEAREOL);
395             }
396             writefln("recover_flag=%s", recover_flag);
397             if (!recover_flag) {
398                 confidence = min(confidence, number_of_answers);
399             }
400             writefln("Confidence %d", confidence);
401 
402             LINE.writefln;
403             if (recover_flag) {
404                 writefln("%1$sq%2$s:quit %1$sEnter%2$s:select %1$sUp/Down%2$s:move %1$sc%2$s:recover%3$s",
405                         FKEY, RESET, CLEARDOWN);
406             }
407             else {
408                 writefln("%1$sq%2$s:quit %1$sEnter%2$s:select %1$sUp/Down%2$s:move %1$sLeft/Right%2$s:confidence %1$sc%2$s:create%3$s",
409                         FKEY, RESET, CLEARDOWN);
410             }
411             const keycode = key.getKey(ch);
412             with (KeyStroke.KeyCode) {
413                 switch (keycode) {
414                 case UP:
415                     select_index = (select_index - 1) % questions.length;
416                     break;
417                 case DOWN:
418                     select_index = (select_index + 1) % questions.length;
419                     break;
420                 case LEFT:
421                     if (!recover_flag && confidence > 2) {
422                         confidence--;
423                     }
424                     break;
425                 case RIGHT:
426                     if (!recover_flag) {
427                         confidence = max(confidence + 1, number_of_answers);
428                     }
429                     break;
430                 case ENTER:
431                     writefln("%s%s%s", questions[select_index], CLEAREOL, CLEARDOWN);
432                     char[] answer;
433                     answer.length = MAX_PINCODE_SIZE;
434                     readln(answer);
435                     //answer.word_strip;
436                     answers[select_index] = answer;
437                     if (!recover_flag) {
438                         confidence++;
439                     }
440                     break;
441                 case NONE:
442                     switch (ch) {
443                     case 'c': // Create Wallet
444                         scope (exit) {
445                             // Erase the answer from memory
446                             answers
447                                 .filter!(a => a.length > 0)
448                                 .each!((ref a) { a[] = 0; a = null; });
449                             pressKey;
450                         }
451                         auto quiz_list = zip(questions, answers)
452                             .filter!(q => q[1].length > 0);
453                         quiz.questions = quiz_list.map!(q => q[0]).array.dup;
454                         auto selected_answers = quiz_list.map!(q => q[1]).array;
455                         if (selected_answers.length < 3) {
456                             writefln("%1$sThen number of answers must be more than %4$d%2$s%3$s",
457                                     RED, RESET, CLEAREOL, selected_answers.length);
458                         }
459                         else {
460                             if (recover_flag) {
461                                 writefln("RECOVER_FLAG");
462                                 stdout.flush;
463                                 const ok = secure_wallet.correct(quiz.questions, selected_answers);
464                                 writefln("RECOVER %s", ok);
465                                 if (ok) {
466                                     writefln("%1$s%3$d or more answers was correct%2$s", GREEN, RESET, confidence);
467                                 }
468                                 else {
469                                     writefln("%1$sSome wrong answers. The account has not been recovered%2$s", RED, RESET);
470                                     secure_wallet.logout;
471                                     continue;
472                                 }
473                             }
474                             do {
475                                 char[] pincode1;
476                                 pincode1.length = MAX_PINCODE_SIZE;
477                                 char[] pincode2;
478                                 pincode2.length = MAX_PINCODE_SIZE;
479                                 scope (exit) {
480                                     pincode1[] = 0;
481                                     pincode2[] = 0;
482                                 }
483                                 writefln("Pincode:%s", CLEARDOWN);
484                                 readln(pincode1);
485                                 pincode1.word_strip;
486                                 writefln("Repeate:");
487                                 readln(pincode2);
488                                 pincode2.word_strip;
489 
490                                 if (pincode1 != pincode2) {
491                                     writefln("%sPincode is not the same%s", RED, RESET);
492                                 }
493                                 else if (pincode1.length < 4) {
494                                     writefln("%sPincode must be at least 4 chars%s", RED, RESET);
495                                 }
496                                 else if (pincode1.length > MAX_PINCODE_SIZE) {
497                                     writefln("%1$sPincode must be less than %3$d chars%2$s",
498                                             RED, RESET, pincode1.length);
499                                 }
500                                 else {
501                                     if (recover_flag) {
502                                         const ok = secure_wallet.recover(quiz.questions, selected_answers, pincode1);
503                                         if (ok) {
504                                             writefln("%1$sWallet recovered%2$s", GREEN, RESET);
505                                             save(recover_flag);
506                                         }
507                                         else {
508                                             writefln("%1$sWallet NOT recovered%2$s", RED, RESET);
509                                         }
510                                     }
511                                     else {
512                                         secure_wallet = StdSecureWallet(
513                                                 quiz.questions, selected_answers, confidence, pincode1);
514                                         save(recover_flag);
515                                     }
516                                 }
517                             }
518                             while (!secure_wallet.isLoggedin);
519                             return;
520                         }
521                         break;
522                     default:
523                         //writefln("Ignore %s '%s'", keycode, cast(char) ch);
524                         // ignore
525                     }
526                     break;
527                 default:
528                     // ignore
529                 }
530             }
531 
532         }
533     }
534 
535     import tagion.script.common : TagionBill;
536 
537     string show(in TagionBill bill) {
538         const index = secure_wallet.net.dartIndex(bill);
539         const deriver = secure_wallet.account.derivers.get(bill.owner, Buffer.init);
540         return format("dartIndex %s\nDeriver   %s\n%s",
541                 index.encodeBase64, deriver.encodeBase64, bill.toPretty);
542     }
543 
544     string show(const Document doc) {
545         const index = secure_wallet.net.calcHash(doc);
546         const deriver = secure_wallet.account.derivers.get(Pubkey(doc[StdNames.owner].get!Buffer), Buffer.init);
547         return format("fingerprint %s\nDeriver   %s\n%s",
548                 index.encodeBase64, deriver.encodeBase64, doc.toPretty);
549     }
550 
551     string show(T)(T rec) if (isHiBONRecord!T) {
552         return show(rec.toDoc);
553     }
554 
555     static string toText(
556             const(HashNet) hash_net,
557             const TagionBill bill,
558             string mark = null) {
559         import std.format;
560         import tagion.hibon.HiBONtoText;
561         import tagion.utils.StdTime : toText;
562 
563         return format("%2$s%3$27-s %4$s %5$17.6fTGN%1$s",
564                 RESET, mark,
565                 bill.time.toText,
566                 hash_net.calcHash(bill)
567                 .encodeBase64,
568                 bill.value.value);
569     }
570 
571     void listAccount(File fout, const(HashNet) hash_net = null) {
572         void innerAccount(const(HashNet) _hash_net) {
573             const line = format("%-(%s%)", "- ".repeat(40));
574             fout.writefln("%-5s %-27s %-45s %-40s", "No", "Date", "Fingerprint", "Value");
575             fout.writeln(line);
576             auto bills = secure_wallet.account.bills ~ secure_wallet.account.requested.values;
577             bills.sort!(q{a.time < b.time});
578             foreach (i, bill; bills) {
579                 string mark = GREEN;
580                 if (bill.owner in secure_wallet.account.requested) {
581                     mark = RED;
582                 }
583                 else if (bill.owner in secure_wallet.account.activated) {
584                     mark = YELLOW;
585                 }
586                 writefln("%4s] %s", i, toText(_hash_net, bill, mark));
587                 verbose("%s", show(bill));
588             }
589             fout.writeln(line);
590         }
591 
592         if (hash_net) {
593             innerAccount(hash_net);
594             return;
595         }
596         innerAccount(secure_wallet.net);
597     }
598 
599     void listInvoices(File fout) {
600         const invoices = secure_wallet.account.requested_invoices;
601         if (invoices.empty) {
602             return;
603         }
604 
605         fout.writeln("Outstanding invoice requests");
606         const line = format("%-(%s%)", "- ".repeat(40));
607         fout.writefln("%-5s %-10s %-45s", "No", "Label", "Deriver");
608         foreach (i, invoice; invoices) {
609             fout.writefln("%4s] %-10s %s", i, invoice.name, invoice.pkey.encodeBase64);
610         }
611         fout.writeln(line);
612     }
613 
614     void sumAccount(File fout) {
615         with (secure_wallet.account) {
616             fout.writefln("Available : %17.6fTGN", available.value);
617             fout.writefln("Locked    : %17.6fTGN", locked.value);
618             fout.writefln("Total     : %17.6fTGN", total.value);
619 
620         }
621     }
622 
623     struct Switch {
624         bool force;
625         //        bool list;
626         //        bool sum;
627         bool send;
628         bool sendkernel;
629         bool pay;
630         bool request;
631         bool update;
632         bool trt_update;
633         double amount;
634         bool faucet;
635         bool save_wallet;
636         string invoice;
637         string output_filename;
638     }
639 
640     enum update_tag = "update";
641     void operate(Switch wallet_switch, const(string[]) args) {
642         if (secure_wallet.isLoggedin) {
643             with (wallet_switch) {
644                 scope (success) {
645                     if (save_wallet) {
646                         save(false);
647                     }
648                 }
649                 if (amount !is amount.init || invoice !is invoice.init) {
650                     Document request;
651                     if (invoice !is invoice.init) {
652                         scope invoice_args = invoice.splitter(":");
653                         import tagion.basic.range : eatOne;
654 
655                         auto new_invoice = secure_wallet.createInvoice(
656                                 invoice_args.eatOne,
657                                 invoice_args.eatOne.to!double.TGN
658                         );
659                         check(new_invoice.name !is string.init, "Invalid name on invoice");
660                         check(new_invoice.amount > 0, "Invoice amount not valid");
661                         secure_wallet.registerInvoice(new_invoice);
662                         request = new_invoice.toDoc;
663                         if (faucet) {
664                             sendShellHiRPC(options.addr ~ options.faucet_shell_endpoint, request, HiRPC(secure_wallet.net));
665                         }
666                     }
667                     else {
668                         auto bill = secure_wallet.requestBill(amount.TGN);
669                         request = bill.toDoc;
670                     }
671                     const default_name = invoice ? "invoice" : "bill";
672                     output_filename = (output_filename.empty) ? default_name.setExtension(FileExtension.hibon) : output_filename;
673                     output_filename.fwrite(request);
674                     writefln("%1$sCreated %3$s%2$s", GREEN, RESET, output_filename);
675                     save_wallet = true;
676                     return;
677                 }
678                 if (force) {
679                     foreach (arg; args[1 .. $]) {
680                         TagionBill bill;
681                         if (arg.hasExtension(FileExtension.hibon)) {
682                             bill = arg.fread!TagionBill;
683                         }
684                         if ((arg.extension.empty) && (bill is TagionBill.init)) {
685                             verbose("index %s", arg);
686                             const index = arg.decode.ifThrown(Buffer.init);
687                             check(index !is Buffer.init, format("Illegal fingerprint %s", arg));
688                             bill = secure_wallet.account.requested.get(Pubkey(index), TagionBill.init); /// Find bill by owner key     
689 
690                             if (bill is TagionBill.init) {
691                                 // Find bill by fingerprint 
692                                 bill = secure_wallet.account.requested.byValue
693                                     .filter!(b => index == secure_wallet.net.dartIndex(b))
694                                     .doFront;
695 
696                             }
697 
698                         }
699                         if (bill !is TagionBill.init) {
700                             writefln("%s", toText(secure_wallet.net, bill));
701                             verbose("%s", show(bill));
702                             secure_wallet.addBill(bill);
703                             save_wallet = true;
704                         }
705                     }
706                 }
707                 if (request) {
708                     secure_wallet.account.requested.byValue
709                         .each!(bill => secure_wallet.net.dartIndex(bill)
710                                 .encodeBase64.setExtension(FileExtension.hibon).fwrite(bill));
711                 }
712                 if (update || trt_update) {
713                     const update_net = secure_wallet.net.derive(
714                             secure_wallet.net.calcHash(
715                             update_tag.representation));
716                     const hirpc = HiRPC(update_net);
717 
718                     const(HiRPC.Sender) getRequest() {
719                         if (trt_update) {
720                             return secure_wallet.getRequestUpdateWallet(hirpc);
721                         }
722                         return secure_wallet.getRequestCheckWallet(hirpc);
723                     }
724 
725                     const req = getRequest();
726 
727                     if (output_filename !is string.init) {
728                         output_filename.fwrite(req);
729                     }
730                     if (send || sendkernel) {
731                         HiRPC.Receiver received = sendkernel ?
732                             sendDARTHiRPC(options.dart_address, req, hirpc) : sendShellHiRPC(
733                                     options.addr ~ options.dart_shell_endpoint, req, hirpc);
734 
735                         verbose("Received response", received.toPretty);
736 
737                         auto res = trt_update ? secure_wallet.setResponseUpdateWallet(
738                                 received) : secure_wallet.setResponseCheckRead(received);
739                         writeln(res ? "wallet updated succesfully" : "wallet not updated succesfully");
740                         save_wallet = true;
741                     }
742 
743                 }
744 
745                 if (pay) {
746 
747                     Document[] requests_to_pay = args[1 .. $]
748                         .filter!(file => file.hasExtension(FileExtension.hibon))
749                         .map!(file => file.fread)
750                         .array;
751 
752                     TagionBill[] to_pay;
753                     foreach (doc; requests_to_pay) {
754                         if (doc.isRecord!TagionBill) {
755                             to_pay ~= TagionBill(doc);
756                         }
757                         else if (doc.isRecord!Invoice) {
758                             import tagion.utils.StdTime : currentTime;
759 
760                             auto read_invoice = Invoice(doc);
761                             to_pay ~= TagionBill(read_invoice.amount, currentTime, read_invoice.pkey, Buffer.init);
762                         }
763                         else {
764                             check(0, "File supplied not TagionBill or Invoice");
765                         }
766                     }
767 
768                     SignedContract signed_contract;
769                     TagionCurrency fees;
770                     secure_wallet.createPayment(to_pay, signed_contract, fees).get;
771 
772                     //   check(created_payment, "payment was not successful");
773 
774                     output_filename = (output_filename.empty) ? "submit".setExtension(FileExtension.hibon) : output_filename;
775                     const message = secure_wallet.net.calcHash(signed_contract);
776                     const contract_net = secure_wallet.net.derive(message);
777                     const hirpc = HiRPC(contract_net);
778                     const hirpc_submit = hirpc.submit(signed_contract);
779                     if (!output_filename.empty) {
780                         output_filename.fwrite(hirpc_submit);
781                     }
782                     verbose("submit\n%s", show(hirpc_submit));
783                     secure_wallet.account.hirpcs ~= hirpc_submit.toDoc;
784                     save_wallet = true;
785                     if (send) {
786                         sendShellSubmitHiRPC(options.addr ~ options.contract_shell_endpoint, hirpc_submit, contract_net);
787                     }
788                     else if (sendkernel) {
789                         sendSubmitHiRPC(options.contract_address, hirpc_submit, contract_net);
790                     }
791                 }
792             }
793         }
794     }
795 }