1 /**
2 * Handles management of key-pair, account-details device-pin
3 */
4 module tagion.wallet.SecureWallet;
5 @safe:
6 import core.time : MonoTime;
7 import std.algorithm;
8 import std.array;
9 import std.format;
10 import std.range;
11 import std.string : representation;
12 import tagion.utils.Miscellaneous;
13 import tagion.utils.Result;
14 import std.exception : assumeWontThrow;
15 
16 //import std.stdio;
17 import tagion.basic.Types : Buffer;
18 import tagion.basic.basic : basename, isinit;
19 import tagion.crypto.Types : Pubkey;
20 import tagion.dart.DARTBasic;
21 import tagion.hibon.Document : Document;
22 import tagion.hibon.HiBON : HiBON;
23 import tagion.hibon.HiBONException : HiBONRecordException;
24 import tagion.hibon.HiBONJSON;
25 import tagion.hibon.HiBONRecord : HiBONRecord;
26 import tagion.utils.StdTime;
27 
28 // import tagion.script.prior.StandardRecords : SignedContract, globals, Script;
29 //import PriorStandardRecords = tagion.script.prior.StandardRecords;
30 
31 import tagion.crypto.SecureInterfaceNet : SecureNet;
32 
33 // import tagion.gossip.GossipNet : StdSecureNet, StdHashNet, scramble;
34 import tagion.Keywords;
35 import tagion.basic.Message;
36 import tagion.basic.tagionexceptions : Check;
37 import tagion.communication.HiRPC;
38 import tagion.crypto.Cipher;
39 import tagion.crypto.random.random;
40 import tagion.script.TagionCurrency;
41 import tagion.script.common;
42 import tagion.utils.Miscellaneous;
43 import tagion.utils.StdTime;
44 import tagion.wallet.AccountDetails;
45 import tagion.wallet.Basic : saltHash;
46 import tagion.wallet.KeyRecover;
47 import tagion.wallet.WalletException : WalletException;
48 import tagion.wallet.WalletRecords : DevicePIN, RecoverGenerator;
49 
50 alias check = Check!(WalletException);
51 alias CiphDoc = Cipher.CipherDocument;
52 
53 import tagion.communication.HiRPC;
54 
55 /// Function and data to recover, sign transaction and hold the account information
56 struct SecureWallet(Net : SecureNet) {
57     protected RecoverGenerator _wallet; /// Information to recover the seed-generator
58     protected DevicePIN _pin; /// Information to check the Pin code
59 
60     AccountDetails account; /// Account-details holding the bills and generator
61     protected SecureNet _net;
62 
63     const(SecureNet) net() const pure nothrow @nogc {
64         return _net;
65     }
66 
67     version (NET_HACK) {
68         void set_net(SecureNet copy_net) {
69             this._net = copy_net;
70         }
71 
72     }
73 
74     /**
75      * 
76      * Params:
77      *   pin = Devices pin code information
78      *   wallet = Infomation to recover the pin-code
79      *   account =  Acount to hold bills and derivers
80      */
81     this(DevicePIN pin,
82             RecoverGenerator wallet = RecoverGenerator.init,
83             AccountDetails account = AccountDetails.init) nothrow {
84         _wallet = wallet;
85         _pin = pin;
86         this.account = account;
87     }
88 
89     this(const Document wallet_doc,
90             const Document pin_doc = Document.init) {
91         auto __wallet = RecoverGenerator(wallet_doc);
92         DevicePIN __pin;
93         if (!pin_doc.empty) {
94             __pin = DevicePIN(pin_doc);
95         }
96         this(__pin, __wallet);
97     }
98 
99     /**
100      * 
101      * Returns: Wallet recovery information
102      */
103     @nogc const(RecoverGenerator) wallet() pure const nothrow {
104         return _wallet;
105     }
106 
107     /**
108      * Retreive the device-pin generation
109      * Returns: Device PIN infomation
110      */
111     @nogc const(DevicePIN) pin() pure const nothrow {
112         return _pin;
113     }
114 
115     /**
116      * 
117      * Returns: The confidence of the answers
118      */
119     @nogc uint confidence() pure const nothrow {
120         return _wallet.confidence;
121     }
122 
123     /**
124      * Creates a wallet from a list for questions and answers
125      * Params:
126      *   questions = List of question
127      *   answers = List of answers
128      *   confidence = Cofindence of the answers
129      *   pincode = Devices pin code
130      * Returns: 
131      *   Create an new wallet accouring with the input
132      */
133     this(
134             scope const(string[]) questions,
135     scope const(char[][]) answers,
136     uint confidence,
137     const(char[]) pincode)
138     in (questions.length is answers.length, "Amount of questions should be same as answers")
139     do {
140         check(questions.length > 3, "Minimal amount of answers is 4");
141         _net = new Net();
142         //        auto hashnet = new StdHashNet;
143         auto recover = KeyRecover(_net);
144 
145         if (confidence == questions.length) {
146             pragma(msg, "fixme(cbr): Due to some bug in KeyRecover");
147             // Due to some bug in KeyRecover
148             confidence--;
149         }
150 
151         recover.createKey(questions, answers, confidence);
152         auto R = new ubyte[_net.hashSize];
153         scope (exit) {
154             R[] = 0;
155         }
156         recover.findSecret(R, questions, answers);
157         _net.createKeyPair(R);
158         _wallet = RecoverGenerator(recover.toDoc);
159         set_pincode(R, pincode);
160     }
161 
162     this(scope const(ubyte)[][] answers, uint confidence, const(char[]) pincode = null) {
163 
164         _net = new Net();
165         auto recover = KeyRecover(_net);
166         if (confidence == answers.length) {
167             confidence--;
168         }
169         recover.createKey(answers, confidence);
170         auto R = new ubyte[_net.hashSize];
171         scope (exit) {
172             R[] = 0;
173         }
174         recover.quizSeed(R, answers, confidence);
175         _net.createKeyPair(R);
176         _wallet = RecoverGenerator(recover.toDoc);
177         if (!pincode.empty) {
178             set_pincode(R, pincode);
179         }
180 
181     }
182 
183     this(
184             scope const(char[]) passphrase,
185     scope const(char[]) pincode,
186     scope const(char[]) salt = null) {
187         _net = new Net;
188         enum size_of_privkey = 32;
189         ubyte[] R;
190         scope (exit) {
191             _wallet.S = _net.saltHash(R);
192             set_pincode(R, pincode);
193             R[] = 0;
194         }
195         _net.generateKeyPair(passphrase, salt,
196                 (scope const(ubyte[]) data) { R = data[0 .. size_of_privkey].dup; });
197     }
198 
199     protected void set_pincode(
200             scope const(ubyte[]) R,
201     scope const(char[]) pincode) scope
202     in (!_net.isinit)
203     do {
204         auto seed = new ubyte[_net.hashSize];
205         getRandom(seed);
206         _pin.setPin(_net, R, pincode.representation, seed.idup);
207     }
208 
209     /**
210      * Checks that the answers to the question is correct
211      * Params:
212      *   questions = List of questions
213      *   answers = List of answers
214      * Returns:
215      *   True of N=confidence number of answers is correct
216      */
217     bool correct(const(string[]) questions, const(char[][]) answers)
218     in (questions.length is answers.length, "Amount of questions should be same as answers")
219     do {
220         _net = new Net;
221         auto recover = KeyRecover(_net, _wallet);
222         scope R = new ubyte[_net.hashSize];
223         return recover.findSecret(R, questions, answers);
224     }
225 
226     /**
227      * Recover the key-pair from the quiz or the device-pincode
228      * Params:
229      *   questions = List of question
230      *   answers = List of answers
231      *   pincode = Devices pin code
232      * Returns:
233      *   True if the key-pair has been recovered for the quiz or the pincode
234      */
235     bool recover(
236             const(string[]) questions,
237     const(char[][]) answers,
238     const(char[]) pincode)
239     in (questions.length is answers.length, "Amount of questions should be same as answers")
240     do {
241         _net = new Net;
242         auto recover = KeyRecover(_net, _wallet);
243         auto R = new ubyte[_net.hashSize];
244         scope (exit) {
245             R[] = 0;
246         }
247         const result = recover.findSecret(R, questions, answers);
248         if (result) {
249             set_pincode(R, pincode);
250             _net.createKeyPair(R);
251             return true;
252         }
253         _net = null;
254         return false;
255     }
256 
257     bool recover(Buffer[] A, const(char[]) pincode = null) {
258         _net = new Net;
259         auto recover = KeyRecover(_net, _wallet);
260         auto R = new ubyte[_net.hashSize];
261         scope (exit) {
262             R[] = 0;
263         }
264         const result = recover.findSecret(R, A);
265         if (result) {
266             if (!pincode.empty) {
267                 set_pincode(R, pincode);
268 
269             }
270             _net.createKeyPair(R);
271             return true;
272         }
273         _net = null;
274         return false;
275     }
276     /**
277      * Checks if the wallet contains a key-pair
278      * Returns: true if the wallet is loggin
279      */
280     @nogc bool isLoggedin() pure const nothrow {
281         return _net !is null;
282     }
283 
284     /**
285      * Throws a WalletExceptions if the wallet is not logged in 
286      */
287     protected void checkLogin() pure const {
288         check(isLoggedin(), "Need login first");
289     }
290 
291     /**
292      * Generates the key-pair from the pin code
293      * Params:
294      *   pincode = device pincode
295      * Returns: true of the pin-code 
296      */
297     bool login(const(char[]) pincode) {
298         if (_pin.D) {
299             logout;
300             auto login_net = new Net;
301             auto R = new ubyte[login_net.hashSize];
302             scope (exit) {
303                 R[] = 0;
304             }
305             const recovered = _pin.recover(login_net, R, pincode.representation);
306             if (recovered) {
307                 login_net.createKeyPair(R);
308                 _net = login_net;
309                 return true;
310             }
311         }
312         return false;
313     }
314 
315     /**
316      * Removes the key-pair 
317      */
318     void logout() pure nothrow {
319         _net = null;
320     }
321 
322     /**
323      * Checks if the pincode is correct
324      * Params:
325      *   pincode = device pincode
326      * Returns: true if the pincode is correct
327      */
328     bool checkPincode(const(char[]) pincode) {
329         const hashnet = (_net.isinit) ? new Net : _net;
330 
331         scope R = new ubyte[hashnet.hashSize];
332         scope (exit) {
333             R[] = 0;
334         }
335         _pin.recover(hashnet, R, pincode.representation);
336         return _pin.S == hashnet.saltHash(R);
337     }
338 
339     /**
340      * Check the pincode 
341      * Params:
342      *   pincode = current device pincode
343      *   new_pincode = new device pincode
344      * Returns: true of the pincode has been change succesfully
345      */
346     bool changePincode(const(char[]) pincode, const(char[]) new_pincode) {
347         check(!_net.isinit, "Key pair has not been created");
348         auto R = new ubyte[_net.hashSize];
349         _pin.recover(_net, R, pincode.representation);
350         if (_pin.S == _net.saltHash(R)) {
351             set_pincode(R, new_pincode);
352             logout;
353             return true;
354         }
355         return false;
356     }
357 
358     /**
359      * Register an invoice to the wallet
360      * Params:
361      *   invoice = invoice to be registered
362      */
363     void registerInvoice(ref Invoice invoice) {
364         account.derive_state = _net.HMAC(account.derive_state ~ _net.pubkey);
365         auto pkey = _net.derivePubkey(account.derive_state);
366         invoice.pkey = derivePubkey;
367         account.derivers[invoice.pkey] = account.derive_state;
368         account.requested_invoices ~= invoice;
369     }
370     /**
371      * Create a new invoice which can be send to a payee 
372      * Params:
373      *   label = Name of the invoice
374      *   amount = Amount 
375      *   info = Invoce information
376      * Returns: The created invoice
377      */
378     static Invoice createInvoice(string label, TagionCurrency amount, Document info = Document.init) {
379         Invoice new_invoice;
380         new_invoice.name = label;
381         new_invoice.amount = amount;
382         new_invoice.info = info;
383         return new_invoice;
384     }
385 
386     Pubkey derivePubkey() {
387         checkLogin;
388         account.derive_state = _net.HMAC(account.derive_state ~ _net.pubkey);
389         return _net.derivePubkey(account.derive_state);
390     }
391 
392     TagionBill[] invoices_to_bills(const(Invoice[]) orders) {
393         Buffer getNonce() {
394             scope nonce = new ubyte[4];
395             getRandom(nonce);
396             return nonce.idup;
397         }
398 
399         return orders.map!((order) => TagionBill(order.amount, currentTime, order.pkey, getNonce)).array;
400     }
401 
402     /**
403      * Create a payment to a list of Invoices and produces a signed-contract
404      * Collect the bill need and sign them in the contract
405      * Params:
406      *   orders = List of invoices
407      *   result = Signed payment
408      * Returns: 
409      */
410     Result!bool payment(const(Invoice[]) orders, ref SignedContract signed_contract, out TagionCurrency fees) nothrow {
411         import tagion.utils.StdTime;
412 
413         try {
414             checkLogin;
415             auto bills = invoices_to_bills(orders);
416             return createPayment(bills, signed_contract, fees);
417         }
418         catch (Exception e) {
419             return Result!bool(e);
420         }
421         return result(true);
422     }
423 
424     /**
425      * Calculates the amount which can be activate
426      * Returns: the amount of available amount
427      */
428     TagionCurrency available_balance() const pure {
429         return account.available;
430     }
431 
432     /**
433      * Calcutales the locked amount in the network
434      * Returns: the locked amount
435      */
436     TagionCurrency locked_balance() const pure {
437         return account.locked;
438     }
439 
440     /**
441      * Calcutales the total amount
442      * Returns: total amount
443      */
444     TagionCurrency total_balance() const pure {
445         return account.total;
446     }
447 
448     /**
449      * Clear the locked bills
450      */
451     @trusted
452     void unlockBills() {
453         account.activated.clear;
454     }
455 
456     /**
457      * Creates HiRPC to request an wallet update
458      * Returns: The command to the the update
459      */
460     const(HiRPC.Sender) getRequestUpdateWallet(HiRPC hirpc = HiRPC(null)) const {
461         auto h = new HiBON;
462         h = account.derivers.byKey.map!(p => cast(Buffer) p);
463         return hirpc.search(h);
464     }
465 
466     const(DARTIndex[]) billIndexes(const(TagionBill)[] bills) const {
467         return bills
468             .map!(bill => net.dartIndex(bill))
469             .array;
470     }
471 
472     const(HiRPC.Sender) getRequestCheckWallet(
473             HiRPC hirpc = HiRPC(null),
474             const(TagionBill)[] to_check = null)
475     const {
476         import tagion.dart.DARTcrud;
477 
478         if (to_check is null) {
479             to_check = account.bills ~ account.requested.values;
480         }
481         return dartCheckRead(billIndexes(to_check), hirpc);
482 
483     }
484 
485     const(SecureNet[]) collectNets(const(TagionBill[]) bills) {
486         return bills
487             .map!(bill => bill.owner in account.derivers)
488             .map!((deriver) => (deriver is null) ? _net.init : _net.derive(*deriver))
489             .array;
490     }
491     /**
492      * Collects the bills for the amount
493      * Params:
494      *   amount = the amount to be collected
495      *   locked_bills = the list of bills
496      * Returns: true if wallet has enough to pay the amount
497      */
498     private bool collect_bills(const TagionCurrency amount, out TagionBill[] locked_bills) {
499         import std.algorithm;
500 
501         // import std.algorithm.sorting : isSorted, sort;
502         // import std.algorithm.iteration : cumulativeFold;
503         import std.range : takeOne, tee;
504         import std.stdio;
505 
506         if (!account.bills.isSorted!q{a.value > b.value}) {
507             account.bills.sort!q{a.value > b.value};
508         }
509 
510         // Select all bills not in use
511         auto none_locked = account.bills.filter!(b => !(b.owner in account.activated)).array;
512 
513         const enough = !none_locked
514             .map!(b => b.value)
515             .cumulativeFold!((a, b) => a + b)
516             .filter!(a => a >= amount)
517             .takeOne
518             .empty;
519         if (enough) {
520             TagionCurrency rest = amount;
521             locked_bills = none_locked
522                 .filter!(b => b.value <= rest) // take all bills smaller than the rest
523                 .until!(b => rest <= 0) // do it until the rest is smaller than or equal to zero
524                 .tee!((b) => rest -= b.value) // subtract their values from the rest
525                 .array;
526 
527             // Check if there is any remaining rest
528             if (rest >= 0) {
529                 TagionBill extra_bill;
530                 // Find an appropriate extra_bill
531                 auto extra_bills = none_locked
532                     .filter!(b => !locked_bills.canFind(b) && b.value >= rest); // Only consider bills with enough value
533                 if (!extra_bills.empty) {
534                     extra_bill = extra_bills.front; // Select the first appropriate bill
535                     locked_bills ~= extra_bill;
536                 }
537             }
538             return true;
539         }
540         return false;
541     }
542 
543     void lock_bills(const(TagionBill[]) locked_bills) {
544         locked_bills.each!(b => account.activated[b.owner] = true);
545     }
546 
547     bool setResponseCheckRead(const(HiRPC.Receiver) receiver) {
548         import tagion.dart.DART;
549 
550         if (!receiver.isResponse) {
551             return false;
552         }
553 
554         auto not_in_dart = receiver.response.result[DART.Params.dart_indices].get!Document[].map!(d => d.get!Buffer);
555 
556         foreach (not_found; not_in_dart) {
557             const bill_index = account.bills
558                 .countUntil!(bill => net.dartIndex(bill) == not_found);
559 
560             if (bill_index >= 0) {
561 
562                 auto used_bill = account.bills[bill_index];
563                 account.used_bills ~= used_bill;
564                 account.bills = account.bills.remove(bill_index);
565                 if (used_bill.owner in account.activated) {
566                     account.activated.remove(used_bill.owner);
567                 }
568             }
569         }
570         foreach (request_bill; account.requested.byValue.array.dup) {
571             if (!not_in_dart.canFind(net.dartIndex(request_bill))) {
572                 account.bills ~= request_bill;
573                 account.requested.remove(request_bill.owner);
574             }
575         }
576         return true;
577     }
578     /**
579      * Update the the wallet for a request update
580      * Params:
581      *   receiver = response to the wallet
582      * Returns: ture if the wallet was updated
583      */
584     @trusted
585     bool setResponseUpdateWallet(const(HiRPC.Receiver) receiver) {
586         import tagion.hibon.HiBONtoText;
587 
588         if (!receiver.isResponse) {
589             return false;
590         }
591 
592         auto found_bills = receiver.response
593             .result[]
594             .map!(e => TagionBill(e.get!Document))
595             .array;
596 
597         foreach (b; found_bills) {
598             if (b.owner !in account.derivers) {
599                 import std.stdio;
600 
601                 writefln("Error, could not pubkey %(%02x%) in derivers", b.owner);
602                 return false;
603             }
604         }
605 
606         foreach (found; found_bills) {
607             if (!account.bills.canFind(found)) {
608                 account.bills ~= found;
609             }
610             account.requested.remove(found.owner);
611 
612             const invoice_index = account.requested_invoices
613                 .countUntil!(invoice => invoice.pkey == found.owner);
614 
615             if (invoice_index >= 0) {
616                 account.requested_invoices = account.requested_invoices.remove(invoice_index);
617             }
618 
619         }
620 
621         auto locked_pkeys = account.activated
622             .byKeyValue
623             .filter!(a => a.value == true)
624             .map!(a => a.key)
625             .array;
626 
627         auto found_owners = found_bills.map!(found => found.owner).array;
628         foreach (pkey; locked_pkeys) {
629             if (!(found_owners.canFind(pkey))) {
630                 account.activated.remove(pkey);
631                 auto bill_index = account.bills.countUntil!(b => b.owner == pkey);
632                 if (bill_index >= 0) {
633                     account.bills = account.bills.remove(bill_index);
634                 }
635             }
636         }
637 
638         // account.activated = new_activated;
639 
640         // go through the locked bills
641 
642         return true;
643     }
644 
645     Result!bool getFee(const(TagionBill)[] to_pay, out TagionCurrency fees, bool print = false) nothrow {
646         import tagion.script.Currency : totalAmount;
647         import tagion.script.execute;
648 
649         try {
650             PayScript pay_script;
651             pay_script.outputs = to_pay;
652             TagionBill[] collected_bills;
653             TagionCurrency amount_remainder = 0.TGN;
654             size_t previous_bill_count = size_t.max;
655 
656             const amount_to_pay = pay_script.outputs
657                 .map!(bill => bill.value)
658                 .totalAmount;
659             const available_wallet_amount = available_balance();
660 
661             do {
662                 collected_bills.length = 0;
663 
664                 const amount_to_collect = amount_to_pay + (-1 * (amount_remainder)) + fees;
665                 check(amount_to_collect < available_wallet_amount, "Amount is too big with fees");
666 
667                 const can_pay = collect_bills(amount_to_collect, collected_bills);
668 
669                 if (collected_bills.length == previous_bill_count) {
670                     return result(false);
671                 }
672 
673                 check(can_pay, format("Is unable to pay the amount %10.6fTGN available %10.6fTGN", amount_to_pay.value, available_balance
674                         .value));
675                 const total_collected_amount = collected_bills
676                     .map!(bill => bill.value)
677                     .totalAmount;
678                 fees = ContractExecution.billFees(
679                         collected_bills.map!(bill => bill.toDoc),
680                         pay_script.outputs.map!(bill => bill.toDoc),
681                         snavs_byte_fee);
682                 amount_remainder = total_collected_amount - amount_to_pay - fees;
683 
684                 previous_bill_count = collected_bills.length;
685             }
686             while (amount_remainder < 0);
687         }
688         catch (Exception e) {
689             return Result!bool(e);
690         }
691         return result(true);
692     }
693 
694     Result!bool getFee(const(Invoice[]) orders, out TagionCurrency fees) nothrow {
695 
696         auto bills = orders.map!((order) => TagionBill(order.amount, assumeWontThrow(currentTime), order.pkey, Buffer
697                 .init))
698             .array;
699         return getFee(bills, fees);
700     }
701 
702     static immutable dummy_pubkey = Pubkey(new ubyte[33]);
703     static immutable dummy_nonce = new ubyte[4];
704 
705     Result!bool getFee(TagionCurrency amount, out TagionCurrency fees) nothrow {
706         auto bill = TagionBill(amount, assumeWontThrow(currentTime), dummy_pubkey, dummy_nonce);
707         return getFee([bill], fees);
708     }
709 
710     // stupid function for testing
711     Result!bool createNFT(
712             Document nft_data,
713             ref SignedContract signed_contract) {
714         import tagion.script.execute;
715         import tagion.script.standardnames;
716 
717         try {
718             HiBON dummy_input = new HiBON;
719             dummy_input[StdNames.owner] = net.pubkey;
720             dummy_input["NFT"] = nft_data;
721             Document[] inputs;
722 
723             inputs ~= Document(dummy_input);
724             SecureNet[] nets;
725             nets ~= (() @trusted => cast(SecureNet) net)();
726 
727             signed_contract = sign(
728                     nets,
729                     inputs,
730                     null,
731                     Document.init);
732         }
733         catch (Exception e) {
734             return Result!bool(e);
735         }
736         return result(true);
737     }
738 
739     enum long snavs_byte_fee = 100;
740     Result!bool createPayment(const(TagionBill)[] to_pay, ref SignedContract signed_contract, out TagionCurrency fees, bool print = false) nothrow {
741         import tagion.hibon.HiBONtoText;
742         import tagion.script.Currency : totalAmount;
743         import tagion.script.execute;
744 
745         try {
746             PayScript pay_script;
747             pay_script.outputs = to_pay;
748             TagionBill[] collected_bills;
749             TagionCurrency amount_remainder = 0.TGN;
750             size_t previous_bill_count = size_t.max;
751 
752             const amount_to_pay = pay_script.outputs
753                 .map!(bill => bill.value)
754                 .totalAmount;
755             check(amount_to_pay < available_balance, "The amount requested for payment should be smaller than the available balance");
756 
757             do {
758                 collected_bills.length = 0;
759                 const amount_to_collect = amount_to_pay + (-1 * (amount_remainder)) + fees;
760                 const can_pay = collect_bills(amount_to_collect, collected_bills);
761                 if (collected_bills.length == previous_bill_count || collected_bills.length == 0) {
762                     return result(false);
763                 }
764                 check(can_pay, format("Is unable to pay the amount %10.6fTGN available %10.6fTGN",
765                         amount_to_pay.value,
766                         available_balance.value));
767                 const total_collected_amount = collected_bills
768                     .map!(bill => bill.value)
769                     .totalAmount;
770 
771                 if (print) {
772                     import std.stdio;
773 
774                     writefln("calculated fee=%s", ContractExecution.billFees(
775                             collected_bills.map!(bill => bill.toDoc),
776                             pay_script.outputs.map!(bill => bill.toDoc),
777                             snavs_byte_fee)
778                     );
779                 }
780                 fees = ContractExecution.billFees(
781                         collected_bills.map!(bill => bill.toDoc),
782                         pay_script.outputs.map!(bill => bill.toDoc),
783                         snavs_byte_fee);
784                 amount_remainder = total_collected_amount - amount_to_pay - fees;
785                 previous_bill_count = collected_bills.length;
786 
787             }
788             while (amount_remainder < 0);
789 
790             const nets = collectNets(collected_bills);
791             check(nets.all!(net => net !is net.init), format("Missing deriver of some of the bills length=%s", collected_bills
792                     .length));
793             if (amount_remainder != 0) {
794                 const bill_remain = requestBill(amount_remainder);
795                 pay_script.outputs ~= bill_remain;
796             }
797             lock_bills(collected_bills);
798             check(nets.length == collected_bills.length, format("number of bills does not match number of signatures nets %s, collected_bills %s", nets
799                     .length, collected_bills.length));
800 
801             signed_contract = sign(
802                     nets,
803                     collected_bills.map!(bill => bill.toDoc)
804                     .array,
805                     null,
806                     pay_script.toDoc);
807         }
808         catch (Exception e) {
809             return Result!bool(e);
810         }
811         return result(true);
812     }
813     /**
814      * Calculates the amount in a list of bills
815      * Params:
816      *   bills = list of bills 
817      * Returns: total amount
818      */
819     static TagionCurrency calcTotal(const(TagionBill[]) bills) pure {
820         return bills.map!(b => b.value).sum;
821     }
822 
823     Buffer getPublicKey() {
824         import std.typecons;
825 
826         const pkey = _net.pubkey;
827         return cast(TypedefType!Pubkey)(pkey);
828     }
829 
830     struct DeriverState {
831         Buffer[Pubkey] derivers;
832         Buffer derive_state;
833         mixin HiBONRecord;
834     }
835 
836     Buffer getDeriversState() {
837         return this.account.derive_state;
838     }
839 
840     TagionBill requestBill(TagionCurrency amount, sdt_t bill_time = currentTime) {
841         check(amount > 0.TGN, format("Requested bill should have a positive value and not %10.6fTGN", amount.value));
842         TagionBill bill;
843         bill.value = amount;
844         bill.time = bill_time;
845         auto nonce = new ubyte[4];
846         getRandom(nonce);
847         bill.nonce = nonce.idup;
848         auto derive = _net.HMAC(bill.toDoc.serialize);
849         bill.owner = _net.derivePubkey(derive);
850         //account.bills ~= bill;
851         account.requestBill(bill, derive);
852         return bill;
853     }
854 
855     TagionBill addBill(const Document doc) {
856         return account.add_bill(doc);
857     }
858 
859     bool addBill(TagionBill bill) {
860         return account.add_bill(bill);
861     }
862 
863     @trusted
864     const(CiphDoc) getEncrDerivers() {
865         DeriverState derive_state;
866         derive_state.derivers = this.account.derivers;
867         derive_state.derive_state = this.account.derive_state;
868         return Cipher.encrypt(this._net, derive_state.toDoc);
869     }
870 
871     void setEncrDerivers(const(CiphDoc) cipher_doc) {
872         Cipher cipher;
873         const derive_state_doc = cipher.decrypt(this._net, cipher_doc);
874         DeriverState derive_state = DeriverState(derive_state_doc);
875         this.account.derivers = derive_state.derivers;
876         this.account.derive_state = derive_state.derive_state;
877     }
878 
879     @trusted
880     const(CiphDoc) getEncrAccount() {
881         return Cipher.encrypt(this._net, this.account.toDoc);
882     }
883 
884     void setEncrAccount(const(CiphDoc) cipher_doc) {
885         Cipher cipher;
886         const account_doc = cipher.decrypt(this._net, cipher_doc);
887         this.account = AccountDetails(account_doc);
888     }
889 
890     unittest {
891         import std.format;
892         import std.range : iota;
893         import std.stdio;
894         import tagion.hibon.HiBONJSON;
895 
896         const good_pin_code = "1234";
897 
898         // Create a new Wallet
899         enum {
900             num_of_questions = 5,
901             confidence = 3
902         }
903         const dummey_questions = num_of_questions.iota.map!(i => format("What %s", i)).array;
904         const dummey_amswers = num_of_questions.iota.map!(i => format("A %s", i)).array;
905         const wallet_doc = SecureWallet(dummey_questions,
906                 dummey_amswers, confidence, good_pin_code).wallet.toDoc;
907 
908         const pin_doc = SecureWallet(
909                 dummey_questions,
910                 dummey_amswers,
911                 confidence,
912                 good_pin_code).pin.toDoc;
913 
914         auto secure_wallet = SecureWallet(wallet_doc, pin_doc);
915         const bad_pin_code = "3434";
916         { // Login test
917             assert(!secure_wallet.isLoggedin);
918             secure_wallet.login(good_pin_code);
919             assert(secure_wallet.checkPincode(good_pin_code));
920             assert(secure_wallet.isLoggedin);
921             secure_wallet.logout;
922             assert(secure_wallet.checkPincode(good_pin_code));
923             assert(!secure_wallet.isLoggedin);
924             secure_wallet.login(bad_pin_code);
925             assert(!secure_wallet.isLoggedin);
926             // Check login pin
927             assert(secure_wallet.checkPincode(good_pin_code));
928             assert(!secure_wallet.isLoggedin);
929             // Login again
930             assert(secure_wallet.login(good_pin_code));
931             assert(secure_wallet.isLoggedin);
932         }
933 
934         const pin_code_2 = "4217";
935         { // Key Recover faild
936             auto test_answers = dummey_amswers.dup;
937             test_answers[0] = "Bad answer 0";
938             test_answers[3] = "Bad answer 1";
939             test_answers[4] = "Bad answer 2";
940 
941             const result = secure_wallet.recover(dummey_questions, test_answers, pin_code_2);
942             assert(!result);
943             assert(!secure_wallet.isLoggedin);
944         }
945 
946         { // Key Recover test
947             auto test_answers = dummey_amswers.dup;
948             test_answers[2] = "Bad answer 0";
949             test_answers[4] = "Bad answer 1";
950 
951             const result = secure_wallet.recover(dummey_questions, test_answers, pin_code_2);
952             assert(result);
953             assert(secure_wallet.isLoggedin);
954         }
955 
956         { // Re-login
957             secure_wallet.logout;
958             assert(secure_wallet.checkPincode(pin_code_2));
959             assert(!secure_wallet.isLoggedin);
960             secure_wallet.login(pin_code_2);
961             assert(secure_wallet.isLoggedin);
962         }
963 
964         const new_pincode = "7851";
965         { // Fail to change pin-code
966             const result = secure_wallet.changePincode(new_pincode, pin_code_2);
967             assert(!result);
968             assert(secure_wallet.isLoggedin);
969         }
970 
971         { // Change pincode
972             const result = secure_wallet.changePincode(pin_code_2, new_pincode);
973             assert(result);
974             assert(!secure_wallet.isLoggedin);
975             secure_wallet.login(new_pincode);
976             assert(secure_wallet.isLoggedin);
977         }
978 
979     }
980 
981     unittest { // Test for account
982         import std.range : zip;
983         import tagion.utils.Miscellaneous;
984 
985         auto sender_wallet = SecureWallet(DevicePIN.init, RecoverGenerator.init);
986         auto _net = new Net;
987 
988         { // Add SecureNet to the wallet
989             immutable very_securet = "Very Secret password";
990             _net.generateKeyPair(very_securet);
991             sender_wallet._net = _net;
992         }
993 
994         { // Create a number of bills in the seneder_wallet
995             auto bill_amounts = [4, 1, 100, 40, 956, 42, 354, 7, 102355].map!(a => a.TGN);
996             const uint epoch = 42;
997 
998             const label = "some_name";
999             auto list_of_invoices = bill_amounts.map!(a => createInvoice(label, a))
1000                 .each!(invoice => sender_wallet.registerInvoice(invoice))();
1001 
1002             // Add the bulls to the account with the derive keys
1003             with (sender_wallet.account) {
1004                 bills = zip(bill_amounts, derivers.byKey).map!(bill_derive => TagionBill(
1005                         bill_derive[0],
1006                         currentTime,
1007                         bill_derive[1],
1008                         Buffer.init)).array;
1009             }
1010 
1011             assert(sender_wallet.available_balance == bill_amounts.sum);
1012             assert(sender_wallet.total_balance == bill_amounts.sum);
1013             assert(sender_wallet.locked_balance == 0.TGN);
1014         }
1015 
1016         auto receiver_wallet = SecureWallet(DevicePIN.init, RecoverGenerator.init);
1017         { // Add securety to the receiver_wallet
1018             auto receiver_net = new Net;
1019             immutable very_securet = "Very Secret password for the receriver";
1020             receiver_net.generateKeyPair(very_securet);
1021             receiver_wallet._net = receiver_net;
1022         }
1023 
1024         pragma(msg, "fixme(cbr): The following test is not finished, Need to transfer to money to receiver");
1025         SignedContract contract_1;
1026         { // The receiver_wallet creates an invoice to the sender_wallet
1027             auto invoice = SecureWallet.createInvoice("To sender 1", 13.TGN);
1028             receiver_wallet.registerInvoice(invoice);
1029             TagionCurrency fees;
1030             // Give the invoice to the sender_wallet and create payment
1031             TagionCurrency expected_fee;
1032             sender_wallet.getFee([invoice], expected_fee);
1033 
1034             sender_wallet.payment([invoice], contract_1, fees);
1035             pragma(msg, "fixme: fix snavs for this to work");
1036             version (none) {
1037                 assert(expected_fee == fees, "fee for get fee and expected fee should be the same");
1038             }
1039         }
1040 
1041         SignedContract contract_2;
1042         { // The receiver_wallet creates an invoice to the sender_wallet
1043             auto invoice = SecureWallet.createInvoice("To sender 2", 53.TGN);
1044             receiver_wallet.registerInvoice(invoice);
1045             TagionCurrency fees;
1046             // Give the invoice to the sender_wallet and create payment
1047             sender_wallet.payment([invoice], contract_2, fees);
1048         }
1049     }
1050 }
1051 
1052 version (unittest) {
1053     import std.exception;
1054     import tagion.crypto.SecureNet;
1055 
1056     //    import std.stdio;
1057     import tagion.script.execute;
1058 
1059     alias StdSecureWallet = SecureWallet!StdSecureNet;
1060 
1061 }
1062 
1063 unittest {
1064     auto wallet = StdSecureWallet("secret", "1234");
1065     const bill1 = wallet.requestBill(1000.TGN);
1066     wallet.addBill(bill1);
1067     assert(wallet.available_balance == 1000.TGN);
1068     const bill_to_pay = wallet.requestBill(1000.TGN);
1069     SignedContract signed_contract;
1070     TagionCurrency fees;
1071     assertThrown(wallet.createPayment([bill_to_pay], signed_contract, fees).get);
1072 
1073     const smaller_bill = wallet.requestBill(999.TGN);
1074 
1075     SignedContract signed_contract1;
1076     TagionCurrency fees1;
1077     assertThrown(wallet.createPayment([bill_to_pay], signed_contract1, fees1).get);
1078 }
1079 
1080 unittest {
1081     import std.range;
1082 
1083     //pay invoice to yourself with multiple bills as outputs
1084     auto wallet = StdSecureWallet("secret", "1234");
1085     const bill1 = wallet.requestBill(1000.TGN);
1086     wallet.addBill(bill1);
1087 
1088     auto invoice_to_pay = wallet.createInvoice("wowo", 10.TGN);
1089     auto invoice_to_pay2 = wallet.createInvoice("wowo2", 10.TGN);
1090     wallet.registerInvoice(invoice_to_pay);
1091     wallet.registerInvoice(invoice_to_pay2);
1092 
1093     SignedContract signed_contract;
1094     TagionCurrency fees;
1095 
1096     TagionBill[] bills = wallet.invoices_to_bills([invoice_to_pay, invoice_to_pay2]);
1097     wallet.createPayment(bills, signed_contract, fees);
1098     HiRPC hirpc = HiRPC(null);
1099 
1100     assert(wallet.account.activated.byValue.filter!(b => b == true).walkLength == 1, "should have one locked bill");
1101     assert(wallet.locked_balance == 1000.TGN);
1102     const req = wallet.getRequestUpdateWallet;
1103     const receiver = hirpc.receive(req.toDoc);
1104 
1105     const number_of_bills = receiver.method.params[].array.length;
1106     assert(number_of_bills == 4, format("should contain three public keys had %s", number_of_bills));
1107 
1108     // create the response containing the two output bills without the original locked bill.
1109     HiBON params = new HiBON;
1110 
1111     import std.stdio;
1112     import tagion.hibon.HiBONJSON;
1113 
1114     TagionBill[] bills_in_dart = bills ~ wallet.account.requested.byValue.array;
1115     foreach (i, bill; bills_in_dart) {
1116         params[i] = bill.toHiBON;
1117     }
1118     auto dart_response = hirpc.result(receiver, Document(params)).toDoc;
1119     const received = hirpc.receive(dart_response);
1120 
1121     wallet.setResponseUpdateWallet(received);
1122 
1123     auto should_have = wallet.calcTotal(bills_in_dart);
1124     assert(should_have == wallet.total_balance, format("should have %s had %s", should_have, wallet.total_balance));
1125 
1126 }
1127 
1128 unittest {
1129     import std.range;
1130 
1131     // pay invoice to yourself.
1132     auto wallet = StdSecureWallet("secret", "1234");
1133     const bill1 = wallet.requestBill(1000.TGN);
1134     wallet.addBill(bill1);
1135 
1136     auto invoice_to_pay = wallet.createInvoice("wowo", 10.TGN);
1137     wallet.registerInvoice(invoice_to_pay);
1138 
1139     SignedContract signed_contract;
1140     TagionCurrency fees;
1141 
1142     TagionBill[] bills = wallet.invoices_to_bills([invoice_to_pay]);
1143     wallet.createPayment(bills, signed_contract, fees);
1144     HiRPC hirpc = HiRPC(null);
1145 
1146     assert(wallet.account.activated.byValue.filter!(b => b == true).walkLength == 1, "should have one locked bill");
1147     assert(wallet.locked_balance == 1000.TGN);
1148     const req = wallet.getRequestUpdateWallet;
1149     const receiver = hirpc.receive(req.toDoc);
1150 
1151     const number_of_bills = receiver.method.params[].array.length;
1152     assert(number_of_bills == 3, format("should contain three public keys had %s", number_of_bills));
1153 
1154     // create the response containing the two output bills without the original locked bill.
1155     HiBON params = new HiBON;
1156 
1157     import std.stdio;
1158     import tagion.hibon.HiBONJSON;
1159 
1160     TagionBill[] bills_in_dart = bills ~ wallet.account.requested.byValue.array;
1161     foreach (i, bill; bills_in_dart) {
1162         params[i] = bill.toHiBON;
1163     }
1164     auto dart_response = hirpc.result(receiver, Document(params)).toDoc;
1165     const received = hirpc.receive(dart_response);
1166 
1167     wallet.setResponseUpdateWallet(received);
1168 
1169     auto should_have = wallet.calcTotal(bills_in_dart);
1170     assert(should_have == wallet.total_balance, format("should have %s had %s", should_have, wallet.total_balance));
1171 
1172 }
1173 
1174 unittest {
1175     // check get fee greater than user amount
1176 
1177     auto wallet1 = StdSecureWallet("some words", "1234");
1178     const bill1 = wallet1.requestBill(1000.TGN);
1179     wallet1.addBill(bill1);
1180 
1181     TagionCurrency fees;
1182     const res = wallet1.getFee(10_000.TGN, fees);
1183 
1184     // should fail
1185     assert(res.value == false);
1186 }
1187 
1188 unittest {
1189 
1190     import std.algorithm;
1191     import std.exception;
1192     import std.stdio;
1193     import tagion.hibon.HiBONJSON;
1194 
1195     auto wallet1 = StdSecureWallet("some words", "1234");
1196     auto wallet2 = StdSecureWallet("some words2", "4321");
1197     const bill1 = wallet1.requestBill(1000.TGN);
1198     const bill2 = wallet1.requestBill(2000.TGN);
1199 
1200     wallet1.addBill(bill1);
1201     wallet1.addBill(bill2);
1202     assert(wallet1.available_balance == 3000.TGN);
1203 
1204     auto payment_request = wallet2.requestBill(1500.TGN);
1205     auto too_big_request = wallet2.requestBill(10000.TGN);
1206 
1207     TagionCurrency expected_fee;
1208     assert(!wallet1.getFee([too_big_request], expected_fee).value, "should throw on too big value");
1209     assert(!wallet1.getFee(10000.TGN, expected_fee).value, "should throw on too big value");
1210     assert(wallet1.getFee(100.TGN, expected_fee).value, "should be able to pay amount");
1211 
1212     assert(wallet1.getFee([payment_request], expected_fee).value, "error in getFee");
1213     assert(wallet1.available_balance == 3000.TGN, "getfee should not change any balances");
1214 
1215     SignedContract signed_contract;
1216     TagionCurrency fee;
1217     assert(wallet1.createPayment([payment_request], signed_contract, fee).value, "error creating payment");
1218 
1219     assert(fee == expected_fee, format("fees not the same %s, %s", fee, expected_fee));
1220 
1221     assert(signed_contract.contract.inputs.uniq.array.length == signed_contract.contract.inputs.length, "signed contract inputs invalid");
1222 }
1223 
1224 unittest {
1225 
1226     import std.algorithm;
1227     import std.exception;
1228     import std.stdio;
1229     import tagion.hibon.HiBONJSON;
1230 
1231     auto wallet1 = StdSecureWallet("some words", "1234");
1232     auto wallet2 = StdSecureWallet("some words2", "4321");
1233     const bill1 = wallet1.requestBill(1000.TGN);
1234     const bill2 = wallet1.requestBill(2000.TGN);
1235 
1236     wallet1.addBill(bill1);
1237     wallet1.addBill(bill2);
1238     assert(wallet1.available_balance == 3000.TGN);
1239 
1240     // create a payment request that is the same size as one bill that the wallet has
1241     auto payment_request = wallet2.requestBill(1000.TGN);
1242 
1243     SignedContract signed_contract;
1244     TagionCurrency fee;
1245     auto p = wallet1.createPayment([payment_request], signed_contract, fee);
1246     assert(p.value, format("ERROR: %s %s", p.value, p.msg));
1247 
1248     assert(signed_contract.contract.inputs.uniq.array.length == signed_contract.contract.inputs.length,
1249             "signed contract inputs invalid");
1250 }
1251 
1252 unittest {
1253     auto wallet1 = StdSecureWallet("some words", "1234");
1254     const bill1 = wallet1.requestBill(1000.TGN);
1255     const bill2 = wallet1.requestBill(2000.TGN);
1256     const bill3 = wallet1.requestBill(3000.TGN);
1257     assert(wallet1.account.requested.length == 3);
1258 
1259     assert(wallet1.account.bills.length == 0);
1260     wallet1.account.add_bill(bill1);
1261     assert(wallet1.account.bills.length == 1);
1262     assert(wallet1.total_balance == 1000.TGN);
1263     assert(wallet1.available_balance == 1000.TGN);
1264     assert(wallet1.locked_balance == 0.TGN);
1265 
1266     {
1267         TagionBill[] locked_bills;
1268         const can_collect = wallet1.collect_bills(1200.TGN, locked_bills);
1269         assert(!can_collect);
1270         assert(locked_bills.length == 0);
1271     }
1272 
1273     {
1274         TagionBill[] locked_bills;
1275         const can_collect = wallet1.collect_bills(500.TGN, locked_bills);
1276         assert(can_collect);
1277         assert(locked_bills.length == 1);
1278         const nets = wallet1.collectNets(locked_bills);
1279 
1280         assert(nets.length == nets.length);
1281         assert(nets.all!(net => net !is net.init));
1282     }
1283 
1284     auto wallet2 = StdSecureWallet("some other words", "4321");
1285     const w2_bill1 = wallet2.requestBill(1500.TGN);
1286     { /// faild not enouch money
1287         SignedContract signed_contract;
1288         TagionCurrency fees;
1289         const result = wallet1.createPayment([w2_bill1], signed_contract, fees);
1290         assert(!result);
1291     }
1292     wallet1.account.add_bill(bill2);
1293     assert(wallet1.available_balance == 3000.TGN);
1294 
1295     pragma(msg, "fixme: remove snavs_byte_fee for this to work");
1296     version (none) { /// succces payment
1297 
1298         import std.stdio;
1299 
1300         SignedContract signed_contract;
1301         TagionCurrency fees;
1302         const bills_to_pay = [w2_bill1];
1303         writefln("PAY ===");
1304         const can_pay = wallet1.createPayment(bills_to_pay, signed_contract, fees);
1305 
1306         const pay_script = PayScript(signed_contract.contract.script);
1307         writefln("----- -----");
1308 
1309         auto input_docs = [bill1, bill2].map!(bill => bill.toDoc);
1310         auto output_docs = pay_script.outputs.map!(bill => bill.toDoc);
1311 
1312         const expected_fees = ContractExecution.billFees(
1313                 input_docs,
1314                 output_docs,
1315                 0
1316         );
1317 
1318         writefln("%s %s", bills_to_pay.map!(bill => bill.toDoc.full_size), pay_script.outputs.map!(bill => bill.toDoc
1319                 .full_size));
1320         writefln("fees=%s expected_fees=%s", fees, expected_fees);
1321         assert(fees == expected_fees);
1322         assert(wallet1.total_balance == 3000.TGN);
1323         assert(wallet1.locked_balance == 3000.TGN);
1324         assert(wallet1.available_balance == 0.TGN);
1325 
1326     }
1327 
1328 }
1329 
1330 // check that the public key is deterministic
1331 unittest {
1332     const words = "long second damp volcano laptop friend noble citizen hip cake safe gown";
1333     const pin = "1234";
1334 
1335     auto wallet1 = StdSecureWallet(words, pin);
1336     auto wallet2 = StdSecureWallet(words, pin);
1337     assert(wallet1.getPublicKey == wallet2.getPublicKey, "should have generated the same publickey");
1338 
1339     auto wallet3 = StdSecureWallet("Some other words", pin);
1340     assert(wallet1.getPublicKey != wallet3.getPublicKey);
1341 
1342     auto wallet4 = StdSecureWallet(words, "5432");
1343     assert(wallet1.getPublicKey == wallet4.getPublicKey, "should have generated the same publickey");
1344 }
1345 
1346 // check pubkey is the same after login/logout
1347 unittest {
1348     import std.stdio;
1349 
1350     const words = "long second damp volcano laptop friend noble citizen hip cake safe gown";
1351     const pin = "1234";
1352 
1353     auto wallet1 = StdSecureWallet(words, pin);
1354     auto pkey_before = wallet1.getPublicKey.idup;
1355     wallet1.logout;
1356     auto pindup = pin.dup;
1357     assert(wallet1.login(pindup));
1358     auto pkey_after = wallet1.getPublicKey;
1359     assert(pkey_before == pkey_after, "public key not the same after login/logout");
1360 }
1361 
1362 // fee amount
1363 unittest {
1364     auto wallet1 = StdSecureWallet("some words", "1234");
1365     const bill1 = wallet1.requestBill(1000.TGN);
1366     const bill2 = wallet1.requestBill(1000.TGN);
1367     const bill3 = wallet1.requestBill(1000.TGN);
1368     assert(wallet1.account.requested.length == 3);
1369     assert(wallet1.account.bills.length == 0);
1370     wallet1.account.add_bill(bill1);
1371     wallet1.account.add_bill(bill2);
1372     wallet1.account.add_bill(bill3);
1373     assert(wallet1.account.bills.length == 3);
1374 
1375     const to_pay = [TagionBill(2000.TGN, sdt_t.init, Pubkey([1, 2, 3, 4]), Buffer.init)];
1376     const to_pay2 = [TagionBill(1999.TGN, sdt_t.init, Pubkey([1, 2, 3, 4]), Buffer.init)];
1377 
1378     TagionCurrency fees;
1379     const res = wallet1.getFee(to_pay, fees);
1380     check(res.value == true, "Wallet should be able to pay 2000 TGN");
1381     const res2 = wallet1.getFee(to_pay2, fees, true);
1382     check(res2.value == true, format("Wallet should be able to pay 1999 TGN fee: %s", fees));
1383 
1384     SignedContract signed_contract;
1385 
1386     const can_pay = wallet1.createPayment(to_pay2, signed_contract, fees);
1387     check(can_pay.value == true, "should be able to create payment");
1388 }
1389 
1390 unittest {
1391     auto wallet1 = StdSecureWallet("some words", "1234");
1392     foreach (i; 0 .. 20) {
1393         const bill = wallet1.requestBill(1000.TGN);
1394         wallet1.account.add_bill(bill);
1395     }
1396     // we pay 10000 should produce negative fee
1397     const to_pay = [TagionBill(10_000.TGN, sdt_t.init, Pubkey([1, 2, 3, 4]), Buffer.init)];
1398 
1399     TagionCurrency get_fees;
1400     const res = wallet1.getFee(to_pay, get_fees);
1401     check(res.value == true, "should be able to pay 10000 tgn");
1402     TagionCurrency actual_fees;
1403 
1404     SignedContract signed_contract;
1405     const can_pay = wallet1.createPayment(to_pay, signed_contract, actual_fees, true);
1406     check(can_pay.value == true, "should be able to create payment");
1407     check(get_fees == actual_fees, "the fee should be the same");
1408     check(actual_fees < 0, "should be a negatvie fee due to the contract being positive");
1409 }
1410 
1411 // amount test
1412 unittest {
1413     import std.stdio;
1414 
1415     auto wallet1 = StdSecureWallet("some words", "1234");
1416     const bill1 = wallet1.requestBill(10000.TGN);
1417     const bill2 = wallet1.requestBill(10000.TGN);
1418     const bill3 = wallet1.requestBill(10000.TGN);
1419     assert(wallet1.account.requested.length == 3);
1420     assert(wallet1.account.bills.length == 0);
1421     wallet1.account.add_bill(bill1);
1422     wallet1.account.add_bill(bill2);
1423     wallet1.account.add_bill(bill3);
1424     assert(wallet1.account.bills.length == 3);
1425 
1426     const to_pay = [TagionBill(19949.TGN, sdt_t.init, Pubkey([1, 2, 3, 4]), Buffer.init)];
1427     const to_pay2 = [TagionBill(30000.TGN, sdt_t.init, Pubkey([1, 2, 3, 4]), Buffer.init)];
1428 
1429     TagionCurrency fees;
1430     const res = wallet1.getFee(to_pay, fees, true);
1431     check(res.value == true, format("Wallet should be able to pay Amount: %s", res.msg));
1432     const res2 = wallet1.getFee(to_pay2, fees, true);
1433     check(res2.value == false, format("Wallet should not be able to pay Amount"));
1434 
1435     SignedContract signed_contract;
1436     const can_pay = wallet1.createPayment(to_pay, signed_contract, fees, true);
1437     check(can_pay.value == true, format("got error: %s", res.msg));
1438 }
1439 
1440 //get fee from amount
1441 unittest {
1442     import tagion.script.execute;
1443 
1444     auto wallet1 = StdSecureWallet("some words", "1234");
1445     const bill1 = wallet1.requestBill(1400.TGN);
1446     const to_pay = wallet1.requestBill(500.TGN);
1447     wallet1.account.add_bill(bill1);
1448 
1449     TagionCurrency fee_amount;
1450     TagionCurrency fee_bill_amount;
1451 
1452     const res = wallet1.getFee(500.TGN, fee_amount);
1453     const res1 = wallet1.getFee([to_pay], fee_bill_amount);
1454     assert(res.value);
1455     assert(res1.value);
1456     assert(fee_amount == fee_bill_amount, format("not the same bill_fee=%s, amount_fee=%s", fee_bill_amount, fee_amount));
1457     // create the payment 
1458     SignedContract contract;
1459     TagionCurrency actual_fee;
1460 
1461     const payment = wallet1.createPayment([to_pay], contract, actual_fee);
1462     assert(payment.value);
1463 
1464     // const pay_script = PayScript(contract.contract.script);
1465     const calc_fees = ContractExecution.billFees(
1466             [bill1.toDoc],
1467             [to_pay.toDoc],
1468             wallet1.snavs_byte_fee,
1469     );
1470     assert(actual_fee == calc_fees, format("fees not the same actualFee=%s, calculatedFee=%s", actual_fee, calc_fees));
1471 
1472     assert(fee_amount == actual_fee, format("fees not the same getFee=%s, actualFee=%s", fee_amount, actual_fee));
1473 }