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) || (¬_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 }