flocore-lib
Version:
A pure and powerful JavaScript Florincoin library.
946 lines (876 loc) • 101 kB
JavaScript
'use strict';
/* jshint unused: false */
/* jshint latedef: false */
var should = require('chai').should();
var expect = require('chai').expect;
var _ = require('lodash');
var sinon = require('sinon');
var fs = require('fs');
var flocore = require('../..');
var BN = flocore.crypto.BN;
var Transaction = flocore.Transaction;
var Input = flocore.Transaction.Input;
var Output = flocore.Transaction.Output;
var PrivateKey = flocore.PrivateKey;
var Script = flocore.Script;
var Interpreter = flocore.Script.Interpreter;
var Address = flocore.Address;
var Networks = flocore.Networks;
var Opcode = flocore.Opcode;
var errors = flocore.errors;
var transactionVector = require('../data/tx_creation');
// Testnet Flo Transaction
var segwitTXID = "a001bbf5f456e2678d54065385de5082a62d3dd061676540f3f8af774f7560a3";
var segwitWitnessHash = "fc22bf04bd1f49a42675ed1b125fa5a871f5ab70e32dcf3480a38141b748fc46";
var segwitTransactionHex = "0200000000012f0a7ba7b4bcecab54734a87aba325f2258f822804bc3cf55d59d4e6cce876d1dc010000006b4830450221008b9d036483e2bd0117eb172e5fc29185eaa458a6a4f0e27f97c2d5666761b6af02207301c36df9408f605c805495f3107c05bbac16e978ce53a80fda34ce88004f77012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff10ffa93808647ba0ebb5cf6c489acf86ab74d81ff7c25438591209d4e6463e83030000006b483045022100bf838a458a5e0a878a792c28fa9378e1b77f6a0b1499d8b864018bbdf8b440d40220129164c5a857245bad2dda25e28b25b386acd0ea64d1548b48984319fff26d46012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff1a4bc4e151ea5c34fe7ca509986cb12f890b572efaf447d8721191c967a06c70010000006a47304402204c26d201ece317005998ec129314418a363ebbb51446714ca9b692879b2a1cb6022015570dbf0b576ce8f3ff8a0422a9c2f91619fa3ca1e5f3cbb236368efa4a6278012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff1af1166c31bcc86767021e226323c3d3f4676f016dfbbe6efc759421034ea327010000006b483045022100c1b47b442ae19421d813179921694981bc21174c203276ef4d9f682e28547570022077b3f366621f9e2eba44118dd769f072ded98b60630f772c486a13c07827d515012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff1c8cc1cf0ffe8a961c771eb671df1404b08b5d092dfc18d391e46f08b4b1e2ce020000006a4730440220285b1a1920275c389f8bd538360464a83d02992782a9fc5473a88d69d4f3f276022039523994f96adb0379458fc75cac16ca43e09443ab0fa259d1318d24e1a71ac9012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff34c3d8bb721fd4b094f73964c77eacab6c8cf712c88584cb2e16d0c2379e9430020000006a4730440220032f24c3a82f1caf566bffb2ed6caf2b8a126e60291f9482a1accf9422ae3105022034edcbae9ed5975cd678c61515942e5ca4eac1db91d1198f91b96f214f03674e012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff44d033d5b7e74d6da2199656c7c281baf11e8a245a5f6182ee7fc5789fdad96b030000006a4730440220567fdf527445b231ec5827155abcfa609d34159f7d96bc4da73c7401cf428f7f02202094abaa27040bd01518bfc2e5c4527edeb72d10b74de0adf620b43467ba22a4012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff4de1169a6bc11044604ed2ccef1b53a8b3889c7dff0f87977014dc5f59db9301020000006a4730440220205b719ae63124e4189f967acfb9620d39fef3a35824b718adfa793fbf09b1b602203462619e416e7c6c7aa5781f27e2ed20078698f04574d6a12d6224cfd69eecca012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff52c8cc2667f711591e5c0931d508314c0152cbc7227dcb53bea5a155683e2f95020000006a4730440220234e3ea302f7b79afe75b0c402cc92ef464231488417eb47090aa143b729221c022046f5eaf0ef095d8cf0955941e0dd864dbaafb14f0d931be1625f92cc4380318d012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff57fd25b88882d83870a2e4728717d6a1ffbc5d6dde3af9de2ccfc80b1ecc939b010000006a47304402200f0d6f30ed48826afa1c1e90f37a748c94bfa9c23acd97614cbfaba997eb0f5d02205d60ece6901b145ac74404308c6e8b7990a1e097d4f3ae78a30f7315122418db01210208ca8f9233ada056b65b8dc23641e2fa5968571ac582a5f869d00d3bf8a84541feffffff6149ebbab2c932c0cae14170235e06f8c216208279c1a209733482e877ff930a010000006a473044022007be5b9b28dff5617c64d51239e7ac39ed8acc8a06731fd474302947a894c66c02203d51edb433e2747123de7e0ef6175335a879100514c599979374fac4a69cb944012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff666b74ed41f1df24a4fa0868058f5d4c55dd3bee8b2131d0b2cd1f71dbc7366c020000006a473044022029915a3218cd249ded5691ec4ec68c544fbd72abf38ad0561d48e65a45b984370220074ddf61bdbf00a39d4d2237c6ec2c5faaf2756e9cc7a6630052d44e29b40f67012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff6df5260159a2367caa41a23e1fd49a59c197839a7d602c782c6174d7de1ac12e030000006b483045022100d5711eb8c24a654eb1eaa3199d3e16723c14be92b96c0687f6945c1a132e4ced022034971c74793b7d262c5481f0c9798a629df43df52cc93b5983896818c2f16323012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff72c12e260417b87e7ddec5458e789847c76d0320e42082b9af544395c496b2c7020000006b483045022100c163fa8df0edf21ab5b8f49d172b8b07b8115b6ea5f4bfc6c03a2d7661e587ef022014a785a590253db283a02df842f7749080be3faef1549222bd05af6b13f28b7e012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff78b0e4620398dce034d704b7f3db06c1453edd61cf0d7a3703b05607d86483f1030000006b483045022100e59e0b09d8f580898c8a34e1d7c6c956537fe16f1da0a421ee8dea72e4366f9c02200f2950f913f481e81d4a916709c118d6f5bf3501d391fbba57abc59b9c1edc74012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff8a03d7647874b6f845965ef2036f47d6bbddbb62a896370feba8c62983b0cb4f000000006b483045022100fd2bcaf55f237c208f0490c936231a5b14ebeac63e4c8769e074754d00231353022062006093ab7c2c799b8245fa4de8f5c164926da35e1cd32b6a4e5949f1b6edff012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff91631c42763b570d7f7aa1b7d27f60e693710892fabe9198c82804a953fe70dc020000006b4830450221009cc144b1ce8c6c25f1287c169cad086996e6e1595d087c0808eb534a081598a90220474b49cf16757ef035550460a95024e3622072f7ed7b9cdbbf4157a8d7652724012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff93e980535669f571bf43e6f42cce912ff7e70b56265cdc062726a6f2cb3dc084020000006a47304402202d445a066c7feff2f825bb7b326b9a09cee8e6590ff7fcfc8136f7cb409fa86f02204c82178a12fbd588daab8b13a4051adff9948f51bea8b09d4a38e64ad69b15f4012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff9aa74db1df731a5bdc35c2ed176303d80f4be1c88e89e68aacbd454870d77d51030000006a4730440220283111fe0dcf871ae939aa5ac806daf0537295ba542aa38d000dc70d1a82279902202f3120b3232b74514b0bc4cdc396b9a85e4fb5fb834babef7f30a7a542304f0d012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff9cc365224f84bc0eb0797c09b5f16e6140e18413a4d69b72c7050e9806cd5e0a020000006a4730440220433810e3c2fc4567b54daf71b117f583d968d34c495d57958e785ad5a55f8d050220066c83169a417657fc3addbbb0db81611fa9cd9fc6f36c005195d0fc2a34b94e012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffff9d3d9a7eb2a874b79fb522ee5707cc653faa6e04b806fba172aa0dda9e807785020000006a47304402201382a9f32825098768fac5bbf13a99b507fcf85e2da78a9f2b2a3b212fa61f210220543fff140a8b3f4c6f60d852cb61a63bab133751d03c044af8322a9cdece9b2d012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffa1a1f2f4685dfd2d74c7215d2ac825b7c5660a19775a0bbcf65ebbab7b73768d020000006b4830450221008f53f3f141c87e260fc8b201330af09aaed86fd683104e2cf10ee70d64d4c2ac02205ad175506358ae6d615fd87934d40cee88282ecb009e4c1ccad92e95c959e003012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffa49ee406b2202beb8bcdfacb17d3d244065b0dfc276ee1f2fb5d254d26af3b770000000017160014f3aae9630c650c54e82c539b792a895a9df1ccdbfeffffffa78384a3babf5a6632641ec35a73ff17373bafd4a87396fa406aef4be8558380030000006b483045022100bc8b9c02a707790cf01efae8d9f0f2cc36f1d6661208ecd29b8fb0f4e96b66b202202b547fba674cc4460c41512b3bfea6b760cd091d05175dd092eacef41565f77e012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffa8b1cb744a5b9e59d708046ee5c3f41bdb2f22bd314df4637f72cb6237454ac3030000006a47304402205bd3443e20dcbc9ae0b3c0c1399d2f6378a360729698674cf55f46d5f6696c0e0220297c7a9917847f380f75ca595394ba72d49bb8afb9e574adce86e5683e96dce3012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffabc536792b6c313baec01f8a10167c45cf5b5377054c30eac07c1dbbd3052e17030000006b483045022100e11090f7f39d5ecd3a9338cb48155ab43d6abcc16d04ca6c1cb88a675be2c0d602205e2c88196cb1b66a5e6604b181ef7540d113a3a292435782336bf50a4d3bfe49012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffb174f4bde7a7219278c8f7e20af307b352535f78834acd7f13927a4cbf434a9d030000006b483045022100d15bc603d4e43b6d25d43dcd4393566a101735ab2d5128065e92bee77429162f02200f0c4e4ed70a6626e312e3ad1849669e95ef93c6db3c8fad50ae5103ffa0548a012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffb1f8cd44e462a07bd060eb933065ed9914bb5e3c9c14e8ebfa7ca4b141a9b09f030000006a47304402202a3117f82d124160cb184a02a713856b37a08f7b21581380571204b935eb8211022062b2cf9cb593131ecca756d6e64d19603eb031e5b61ae2bba79ce049077db182012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffb2d037a667c95a308358842a40617b26694c550073a0e76513b2f85e27bb2101010000006a47304402202b80561d473bbbd07210bdf6c6a59b4076ab032bd22c5ebd8299e8f687b9ce6f02201b0404c1411b0c7f2f30a4a5f26aadb0b4084e98d9dce907334f9255a74eaa4f012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffb83f1cb0dec621e041a8df9d023bb0b1abf1186f5be83ada43af05bf6c1b1e10010000006a47304402203df9e73f0d02eb7389fabc194cd18181586d96d331de92cd90562652c0e3c79102202214f108badb47f21426ac0c3544dabcfe84e59310fa7196fc1ed10094bcbf94012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffbb07d5153c6c76dd58add91f3d6a8ef44689fb7caedbedc48175c73d13297fa7030000006b483045022100e532ace6422be5d7fcba55e4c7953fbbea2901e2c4fd1bf8000fe93010141cb50220079f77b1e1ba4066246819a0758e21b984dc48bedc9cb46b184ecb17570d6622012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffc0664186cf0365a26c72c81e7645115749e35114b5d1a0d4d38b24f733dbc97e030000006b483045022100f1b14ef60c0eae0c07d3ba91741a9145fbed192371cfc2d240f8c2d96489840102207d06ec6eed9b41791e810078ec27b534e1d7cb76eca3bfa0f3f3fc9ae8a28243012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffc155e20a588a06162d2621d5da4e11295f07bf0602f423d70517531817fd998c030000006b483045022100f04df0a562b7e6cfb6fbb69e9391361256870b0074edb4f0f4c2058b8db04c4c0220286770dd104ca532ea22e9e3d7d3208e04e2a3a676a07fca18a1bfe566e7093a012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffc20264c340fed0688706a33a07c11a5db15a0c17421472723da56239bc5bc368010000006a4730440220287d3eac747df632bd7ad7b341e44058a11550f5bce94fa4f0b9b7da3b8399fd022068ac78a38f845446270be182e7319492f93a30aea80b815dd02f334ad5f38d0f012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffcf5cac84ef07520c391d19dd7b5faddba32a09f1fc1290a5d4da2c4ca06726e9020000006b483045022100f79aac33d70368a4b16925568aa6a9e648ec59792f34a9eb5b1b53cc0f85a93f0220641e4bdd3e7592ecc966d82e596b3f28a11851b0caf91688e31858ce7fcf9e79012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffd62abab7c61549818aacff0a16797f01bfcc3c9fadcfb80a77b325e41da30c2b010000006a473044022047642203baa9e3b45f3679998b7489304a8b5da5908519fbc160cd8e998da7bb02200420e0f03d2ade718be661c9e28f299fa72d6dd6e21b506b6df1acea459b7be1012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffd90808ca51027440dbc7438788f824c65488aae0544034386f4053e19d77d4b1010000006a473044022077552ae0512a06bcbbbecb0a5af2692f1c8c4d812993fd756c5dc6418df49fad02204e14ff51bbe914608bdb924d898d476863fe480e707626ca164bef33a2985eae012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffda233186bf4c4f43262f918f6b52f538d7aaba3317607e10b87e8957ea9bf883020000006a473044022049dbc8b8fba8c461a74b10d99cb793fb6b660cf2d6c271d99a961410e50ed26d0220457e9d40f83e56a9440472ceea04a191b38e666ea615bba86fae3ed62f8ddcd6012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffdb7062d78170fdc669b46eb325a680923e0008ff83f32592f5c3dcd47b427601030000006a473044022065faa5c120e8e3d78d6e79d37ea72890096819a426e2b04508a1dc52935c8b1c0220246fe63b7c5d8a7b8eb4c6f422709a1fa977c3b00bb724bd699e0137e8b3cd85012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffe03aca08fcc1b108b6dad891632aeb8cd045961f4aa68152eb7ecc1aedc201ab000000006a47304402206db7242b08f02d65a8b2b2bf0fff2963f1b14ca72274118bef9c416bc5b4a70f0220342323154080c540485c70124da6258d8aef9a363fc0f3bc34916c59ed12ea8c0121038f1ef341be736541e59fb19d42219a7eae96ac14cac282bd27cfe9631aa9b08cfeffffffe46f327e312879073016bb2b29f077c1bd7ec83545ba5267ae2ff3466aab7cb9020000006b483045022100bb3791a4815b0cb09a836030685feb9ca10597685cebf6d5a81d0cfbfc8b574002202900c99adfdd12a19dc21ab7a28a72ed06e87af93ddfd38af94ec71d9f4d4eaf012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffe8a399ebeb841744480678c24d805edc314f927a94af60b31b1f3e9e1aa07b52020000006a47304402202002c939cc0099ab23553673a56519e7164b894b500b1825ad8821af6416a0f0022041b26590575924b830bea0b851db17614282a3444cd1febf0852b80a34e83238012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56feffffffeefdd139ab656d5822d93ec8304693618f42eed82cbb3f8803745988b4988538020000006b483045022100976efe634bbf5771fc817766c4e3c9d7a5b9e7fa463ca6f550c143cc4a7efa9a02200a52ef2c29a9efa9af7929485f5ccd656924be3883f3fc43c701edcae84dd97a012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56fefffffff04b6e6ee4e15728be511f5bce03fe42964d4c053271b4528a5368f01c5dfc5b010000006a47304402200850873d023e6ac0d8b0138cde597088061bcdb3816c6cc1286ad94e3928aaf102204e9aec6a3664f582275ed33846a59896bf7247ef5df0306d0e7ed5194ce457c7012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56fefffffff517d99ffdbd3bd448103feffcf6a9b9990e0b3d8ab3d30662555367602fcf06010000006a47304402207d5799c70a602a5925a17737ba9c09dd8f09bf640e2e634060336bffd936f94e0220230aff2a3ebe7cca658df4d7c28a46fc809aad50fcb832ae35ccfcbb475087c1012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56fefffffff9d6e5dc3d24f0cb8095d67bb8dd99ead644daeb39b9558b2fdcaf8fd0f69268020000006a473044022063905b5659d910032d42e129c26c80c8846fe0d1cf373d37880a2f4dadc9402c022013934744935d313c276d624d24a9ddff652b72f840456b4203a79e1f3d5c014a012102d9d0dfbb89bbb7ef1b02be7bfcfcc92b4599a65fea79d7598007aa1261a98c56fefffffffc8e6e83c94cb3572780bdd6ca7600064eced923053be5401ab416e43d6aa9b5000000006a47304402203b6edfa764998485614ff1762c946baf35ae9c05bde24dd10bd362131a2d183102204f3d0e2622bc8f5e40a940d78b4ee86fc1d530975864b195af9190653342ec40012103a0915d65b5cd997e95f669af096d6ce6e3a1ac9001bdced574fd637d86be0cfefeffffff026a640d00000000001976a9142408f30fa7a95a0b3d7e1d9cfc1351d9197271df88ac005039278c0400001976a914ef0ffd96cfbf83354760f399e5a45160baeb27f788ac0000000000000000000000000000000000000000000002483045022100e63491bc48fb7d3ab6c9377c94650014031d41145f4d0b9cb3da653916dcb43602203e8afbb9c3a624851b6b224f602be4327dd0af3325c926b90f20ec42acaf54b3012103b0c4aaa3eb2fcd3ffb3361d8a1d9ea7ffba5fd83915f8835261d160513cf61bb00000000000000000000000000000000000000000000000080aa000000";
describe('Transaction', function() {
it('should serialize and deserialize correctly a given transaction', function() {
var transaction = new Transaction(tx_1_hex);
transaction.uncheckedSerialize().should.equal(tx_1_hex);
});
it('should parse the version as a signed integer', function () {
var transaction = Transaction('ffffffff0000ffffffff')
transaction.version.should.equal(-1);
transaction.nLockTime.should.equal(0xffffffff);
});
it('fails if an invalid parameter is passed to constructor', function() {
expect(function() {
return new Transaction(1);
}).to.throw(errors.InvalidArgument);
});
var testScript = 'OP_DUP OP_HASH160 7dcd23de5408d28edf0f45da2b8f7c57a398df31 OP_EQUALVERIFY OP_CHECKSIG';
var testScriptHex = '76a9147dcd23de5408d28edf0f45da2b8f7c57a398df3188ac';
var testPrevTx = 'ed3fa66cbaef9333135a42d6deb7c8a5eb3675aac028b6a943ab4574618a70d1';
var testAmount = 1 * 1e8;
var testTransaction = new Transaction()
.from({
'txId': testPrevTx,
'outputIndex': 0,
'script': testScript,
'satoshis': testAmount
})
.to('oYVNuMx5VUrVkkZJv6jPWxoTD3r4w7bnMf', testAmount - 10000);
it('can serialize to a plain javascript object', function() {
var object = testTransaction.toObject();
object.inputs[0].output.satoshis.should.equal(testAmount);
object.inputs[0].output.script.should.equal(testScriptHex);
object.inputs[0].prevTxId.should.equal(testPrevTx);
object.inputs[0].outputIndex.should.equal(0);
object.outputs[0].satoshis.should.equal(testAmount - 10000);
});
it('will not accept NaN as an amount', function() {
(function() {
var stringTx = new Transaction().to('oYVNuMx5VUrVkkZJv6jPWxoTD3r4w7bnMf', NaN);
}).should.throw('Amount is expected to be a positive integer');
});
it('returns the fee correctly', function() {
testTransaction.getFee().should.equal(10000);
});
it('will return zero as the fee for a coinbase', function() {
// block #2: 0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098
var coinbaseTransaction = new Transaction('01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000');
coinbaseTransaction.getFee().should.equal(0);
});
it('serialize to Object roundtrip', function() {
var a = testTransaction.toObject();
var newTransaction = new Transaction(a);
var b = newTransaction.toObject();
a.should.deep.equal(b);
});
it('toObject/fromObject with signatures and custom fee', function() {
var tx = new Transaction()
.from(simpleUtxoWith100000Satoshis)
.to([{address: toAddress, satoshis: 50000}])
.fee(15000)
.change(changeAddress)
.sign(privateKey);
var txData = JSON.stringify(tx);
var tx2 = new Transaction(JSON.parse(txData));
var txData2 = JSON.stringify(tx2);
txData.should.equal(txData2);
});
it('toObject/fromObject with p2sh signatures and custom fee', function() {
var tx = new Transaction()
.from(p2shUtxoWith1FLO, [p2shPublicKey1, p2shPublicKey2, p2shPublicKey3], 2)
.to([{address: toAddress, satoshis: 50000}])
.fee(15000)
.change(changeAddress)
.sign(p2shPrivateKey1)
.sign(p2shPrivateKey2);
var txData = JSON.stringify(tx);
var tx2 = new Transaction(JSON.parse(txData));
var tx2Data = JSON.stringify(tx2);
txData.should.equal(tx2Data);
});
it('fromObject with pay-to-public-key previous outputs', function() {
var tx = flocore.Transaction({
hash: '132856bf03d6415562a556437d22ac63c37a4595fd986c796eb8e02dc031aa25',
version: 1,
inputs: [
{
prevTxId: 'e30ac3db24ef28500f023775d8eb06ad8a26241690080260308208a4020012a4',
outputIndex: 0,
sequenceNumber: 4294967294,
script: '473044022024dbcf41ccd4f3fe325bebb7a87d0bf359eefa03826482008e0fe7795586ad440220676f5f211ebbc311cfa631f14a8223a343cbadc6fa97d6d17f8d2531308b533201',
scriptString: '71 0x3044022024dbcf41ccd4f3fe325bebb7a87d0bf359eefa03826482008e0fe7795586ad440220676f5f211ebbc311cfa631f14a8223a343cbadc6fa97d6d17f8d2531308b533201',
output: {
satoshis: 5000000000,
script: '2103b1c65d65f1ff3fe145a4ede692460ae0606671d04e8449e99dd11c66ab55a7feac'
}
}
],
outputs: [
{
satoshis: 3999999040,
script: '76a914fa1e0abfb8d26e494375f47e04b4883c44dd44d988ac'
},
{
satoshis: 1000000000,
script: '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac'
}
],
nLockTime: 139
});
tx.inputs[0].should.be.instanceof(flocore.Transaction.Input.PublicKey);
tx.inputs[0].output.satoshis.should.equal(5000000000);
tx.inputs[0].output.script.toHex().should.equal('2103b1c65d65f1ff3fe145a4ede692460ae0606671d04e8449e99dd11c66ab55a7feac');
});
it('constructor returns a shallow copy of another transaction', function() {
var transaction = new Transaction(tx_1_hex);
var copy = new Transaction(transaction);
copy.uncheckedSerialize().should.equal(transaction.uncheckedSerialize());
});
it('should display correctly in console', function() {
var transaction = new Transaction(tx_1_hex);
transaction.inspect().should.equal('<Transaction: ' + tx_1_hex + '>');
});
it('standard hash of transaction should be decoded correctly', function() {
var transaction = new Transaction(tx_1_hex);
transaction.id.should.equal(tx_1_id);
});
it('serializes an empty transaction', function() {
var transaction = new Transaction();
transaction.uncheckedSerialize().should.equal(tx_empty_hex);
});
it('serializes and deserializes correctly', function() {
var transaction = new Transaction(tx_1_hex);
transaction.uncheckedSerialize().should.equal(tx_1_hex);
});
describe('transaction creation test vector', function() {
this.timeout(5000);
var index = 0;
transactionVector.forEach(function(vector) {
index++;
it('case ' + index, function() {
var i = 0;
var transaction = new Transaction();
while (i < vector.length) {
var command = vector[i];
var args = vector[i + 1];
if (command === 'serialize') {
transaction.serialize().should.equal(args);
} else {
transaction[command].apply(transaction, args);
}
i += 2;
}
});
});
});
// TODO: Migrate this into a test for inputs
var fromAddress = 'oPoBiZJxdHtNYVNhNTspRE8a4id6ytMKzY';
var simpleUtxoWith100000Satoshis = {
address: fromAddress,
txId: '0fa147b287dacf753fd5f0e9aaf342464555b78960352ec043b9f7289e82e60f',
outputIndex: 0,
script: Script.buildPublicKeyHashOut(fromAddress).toString(),
satoshis: 100000
};
var simpleUtxoWith1000000Satoshis = {
address: fromAddress,
txId: '0fa147b287dacf753fd5f0e9aaf342464555b78960352ec043b9f7289e82e60f',
outputIndex: 0,
script: Script.buildPublicKeyHashOut(fromAddress).toString(),
satoshis: 1000000
};
var anyoneCanSpendUTXO = JSON.parse(JSON.stringify(simpleUtxoWith100000Satoshis));
anyoneCanSpendUTXO.script = new Script().add('OP_TRUE');
var toAddress = 'oYVNuMx5VUrVkkZJv6jPWxoTD3r4w7bnMf';
var changeAddress = 'oPoBiZJxdHtNYVNhNTspRE8a4id6ytMKzY';
var changeAddressP2SH = 'oPoBiZJxdHtNYVNhNTspRE8a4id6ytMKzY';
var privateKey = 'cSY2jbTDyPhmEx94TD72NwuHQ4J4pxzMEjj8G1Nc7c1eptCnevza';
var private1 = '6ce7e97e317d2af16c33db0b9270ec047a91bff3eff8558afb5014afb2bb5976';
var private2 = 'c9b26b0f771a0d2dad88a44de90f05f416b3b385ff1d989343005546a0032890';
var public1 = new PrivateKey(private1).publicKey;
var public2 = new PrivateKey(private2).publicKey;
var simpleUtxoWith1FLO = {
address: fromAddress,
txId: '0fa147b287dacf753fd5f0e9aaf342464555b78960352ec043b9f7289e82e60f',
outputIndex: 1,
script: Script.buildPublicKeyHashOut(fromAddress).toString(),
satoshis: 1e8
};
var tenth = 1e7;
var fourth = 25e6;
var half = 5e7;
var p2shPrivateKey1 = PrivateKey.fromWIF('cWA4VXpGArqr2Y2Z4KvhGx8nNEjAgzycaqS6ZGrWFKCYBdtL2FV8');
var p2shPublicKey1 = p2shPrivateKey1.toPublicKey();
var p2shPrivateKey2 = PrivateKey.fromWIF('cRRfp177BL8fjhSsYBDymSjb5rG1p8F1AD51vrwhoRiCNU6ur59r');
var p2shPublicKey2 = p2shPrivateKey2.toPublicKey();
var p2shPrivateKey3 = PrivateKey.fromWIF('cTKLF5W5SLaGTTMs7bWQ73nR4Zs118W1HZA57CWzW16A7KPqoZTv');
var p2shPublicKey3 = p2shPrivateKey3.toPublicKey();
var p2shAddress = Address.createMultisig([
p2shPublicKey1,
p2shPublicKey2,
p2shPublicKey3
], 2, 'testnet');
var p2shUtxoWith1FLO = {
address: p2shAddress.toString(),
txId: '0fa147b287dacf753fd5f0e9aaf342464555b78960352ec043b9f7289e82e60f',
outputIndex: 0,
script: Script(p2shAddress).toString(),
satoshis: 1e8
};
describe('adding inputs', function() {
it('adds just once one utxo', function() {
var tx = new Transaction();
tx.from(simpleUtxoWith1FLO);
tx.from(simpleUtxoWith1FLO);
tx.inputs.length.should.equal(1);
});
describe('isFullySigned', function() {
it('works for normal p2pkh', function() {
var transaction = new Transaction()
.from(simpleUtxoWith100000Satoshis)
.to([{address: toAddress, satoshis: 50000}])
.change(changeAddress)
.sign(privateKey);
transaction.isFullySigned().should.equal(true);
});
it('fails when Inputs are not subclassed and isFullySigned is called', function() {
var tx = new Transaction(tx_1_hex);
expect(function() {
return tx.isFullySigned();
}).to.throw(errors.Transaction.UnableToVerifySignature);
});
it('fails when Inputs are not subclassed and verifySignature is called', function() {
var tx = new Transaction(tx_1_hex);
expect(function() {
return tx.isValidSignature({
inputIndex: 0
});
}).to.throw(errors.Transaction.UnableToVerifySignature);
});
it('passes result of input.isValidSignature', function() {
var tx = new Transaction(tx_1_hex);
tx.from(simpleUtxoWith1FLO);
tx.inputs[0].isValidSignature = sinon.stub().returns(true);
var sig = {
inputIndex: 0
};
tx.isValidSignature(sig).should.equal(true);
});
});
});
describe('change address', function() {
it('can calculate simply the output amount', function() {
var transaction = new Transaction()
.from(simpleUtxoWith1000000Satoshis)
.to(toAddress, 500000)
.change(changeAddress)
.sign(privateKey);
transaction.outputs.length.should.equal(2);
transaction.outputs[1].satoshis.should.equal(400000);
transaction.outputs[1].script.toString()
.should.equal(Script.fromAddress(changeAddress).toString());
var actual = transaction.getChangeOutput().script.toString();
var expected = Script.fromAddress(changeAddress).toString();
actual.should.equal(expected);
});
it('accepts a P2SH address for change', function() {
var transaction = new Transaction()
.from(simpleUtxoWith1000000Satoshis)
.to(toAddress, 500000)
.change(changeAddressP2SH)
.sign(privateKey);
transaction.outputs.length.should.equal(2);
transaction.outputs[1].script.isScriptHashOut().should.equal(true);
});
it('can recalculate the change amount', function() {
var transaction = new Transaction()
.from(simpleUtxoWith100000Satoshis)
.to(toAddress, 50000)
.change(changeAddress)
.fee(0)
.sign(privateKey);
transaction.getChangeOutput().satoshis.should.equal(50000);
transaction = transaction
.to(toAddress, 20000)
.sign(privateKey);
transaction.outputs.length.should.equal(3);
transaction.outputs[2].satoshis.should.equal(30000);
transaction.outputs[2].script.toString()
.should.equal(Script.fromAddress(changeAddress).toString());
});
it('adds no fee if no change is available', function() {
var transaction = new Transaction()
.from(simpleUtxoWith100000Satoshis)
.to(toAddress, 99000)
.sign(privateKey);
transaction.outputs.length.should.equal(1);
});
it('adds no fee if no money is available', function() {
var transaction = new Transaction()
.from(simpleUtxoWith100000Satoshis)
.to(toAddress, 100000)
.change(changeAddress)
.sign(privateKey);
transaction.outputs.length.should.equal(1);
});
it('fee can be set up manually', function() {
var transaction = new Transaction()
.from(simpleUtxoWith100000Satoshis)
.to(toAddress, 80000)
.fee(10000)
.change(changeAddress)
.sign(privateKey);
transaction.outputs.length.should.equal(2);
transaction.outputs[1].satoshis.should.equal(10000);
});
it('fee per kb can be set up manually', function() {
var inputs = _.map(_.range(10), function(i) {
var utxo = _.clone(simpleUtxoWith100000Satoshis);
utxo.outputIndex = i;
return utxo;
});
var transaction = new Transaction()
.from(inputs)
.to(toAddress, 950000)
.feePerKb(8000)
.change(changeAddress)
.sign(privateKey);
transaction._estimateSize().should.be.within(1000, 1999);
transaction.outputs.length.should.equal(2);
transaction.outputs[1].satoshis.should.equal(34000);
});
it('if satoshis are invalid', function() {
var transaction = new Transaction()
.from(simpleUtxoWith100000Satoshis)
.to(toAddress, 99999)
.change(changeAddress)
.sign(privateKey);
transaction.outputs[0]._satoshis = 100;
transaction.outputs[0]._satoshisBN = new BN(101, 10);
expect(function() {
return transaction.serialize();
}).to.throw(errors.Transaction.InvalidSatoshis);
});
it('if fee is too small, fail serialization', function() {
var transaction = new Transaction()
.from(simpleUtxoWith100000Satoshis)
.to(toAddress, 99999)
.change(changeAddress)
.sign(privateKey);
expect(function() {
return transaction.serialize();
}).to.throw(errors.Transaction.FeeError.TooSmall);
});
it('on second call to sign, change is not recalculated', function() {
var transaction = new Transaction()
.from(simpleUtxoWith100000Satoshis)
.to(toAddress, 100000)
.change(changeAddress)
.sign(privateKey)
.sign(privateKey);
transaction.outputs.length.should.equal(1);
});
it('getFee() returns the difference between inputs and outputs if no change address set', function() {
var transaction = new Transaction()
.from(simpleUtxoWith100000Satoshis)
.to(toAddress, 1000);
transaction.getFee().should.equal(99000);
});
});
describe('serialization', function() {
it('stores the change address correctly', function() {
var serialized = new Transaction()
.change(changeAddress)
.toObject();
var deserialized = new Transaction(serialized);
expect(deserialized._changeScript.toString()).to.equal(Script.fromAddress(changeAddress).toString());
expect(deserialized.getChangeOutput()).to.equal(null);
});
it('can avoid checked serialize', function() {
var transaction = new Transaction()
.from(simpleUtxoWith1FLO)
.to(fromAddress, 1);
expect(function() {
return transaction.serialize();
}).to.throw();
expect(function() {
return transaction.serialize(true);
}).to.not.throw();
});
it('stores the fee set by the user', function() {
var fee = 1000000;
var serialized = new Transaction()
.fee(fee)
.toObject();
var deserialized = new Transaction(serialized);
expect(deserialized._fee).to.equal(fee);
});
});
describe('checked serialize', function() {
it('fails if no change address was set', function() {
var transaction = new Transaction()
.from(simpleUtxoWith1FLO)
.to(toAddress, 1);
expect(function() {
return transaction.serialize();
}).to.throw(errors.Transaction.ChangeAddressMissing);
});
it('fails if a high fee was set', function() {
var transaction = new Transaction()
.from(simpleUtxoWith1FLO)
.change(changeAddress)
.fee(50000000)
.to(toAddress, 40000000);
expect(function() {
return transaction.serialize();
}).to.throw(errors.Transaction.FeeError.TooLarge);
});
it('fails if a dust output is created', function() {
var transaction = new Transaction()
.from(simpleUtxoWith1FLO)
.to(toAddress, 545)
.change(changeAddress)
.sign(privateKey);
expect(function() {
return transaction.serialize();
}).to.throw(errors.Transaction.DustOutputs);
});
it('doesn\'t fail if a dust output is not dust', function() {
var transaction = new Transaction()
.from(simpleUtxoWith1FLO)
.to(toAddress, 546)
.change(changeAddress)
.sign(privateKey);
expect(function() {
return transaction.serialize();
}).to.not.throw(errors.Transaction.DustOutputs);
});
it('doesn\'t fail if a dust output is an op_return', function() {
var transaction = new Transaction()
.from(simpleUtxoWith1FLO)
.addData('not dust!')
.change(changeAddress)
.sign(privateKey);
expect(function() {
return transaction.serialize();
}).to.not.throw(errors.Transaction.DustOutputs);
});
it('fails when outputs and fee don\'t add to total input', function() {
var transaction = new Transaction()
.from(simpleUtxoWith1FLO)
.to(toAddress, 99900000)
.fee(99999)
.sign(privateKey);
expect(function() {
return transaction.serialize();
}).to.throw(errors.Transaction.FeeError.Different);
});
it('checks output amount before fee errors', function() {
var transaction = new Transaction();
transaction.from(simpleUtxoWith1FLO);
transaction
.to(toAddress, 10000000000000)
.change(changeAddress)
.fee(5);
expect(function() {
return transaction.serialize();
}).to.throw(errors.Transaction.InvalidOutputAmountSum);
});
it('will throw fee error with disableMoreOutputThanInput enabled (but not triggered)', function() {
var transaction = new Transaction();
transaction.from(simpleUtxoWith1FLO);
transaction
.to(toAddress, 84000000)
.change(changeAddress)
.fee(16000000);
expect(function() {
return transaction.serialize({
disableMoreOutputThanInput: true
});
}).to.throw(errors.Transaction.FeeError.TooLarge);
});
describe('skipping checks', function() {
var buildSkipTest = function(builder, check, expectedError) {
return function() {
var transaction = new Transaction();
transaction.from(simpleUtxoWith1FLO);
builder(transaction);
var options = {};
options[check] = true;
expect(function() {
return transaction.serialize(options);
}).not.to.throw();
expect(function() {
return transaction.serialize();
}).to.throw(expectedError);
};
};
it('can skip the check for too much fee', buildSkipTest(
function(transaction) {
return transaction
.fee(50000000)
.change(changeAddress)
.sign(privateKey);
}, 'disableLargeFees', errors.Transaction.FeeError.TooLarge
));
it('can skip the check for a fee that is too small', buildSkipTest(
function(transaction) {
return transaction
.fee(1)
.change(changeAddress)
.sign(privateKey);
}, 'disableSmallFees', errors.Transaction.FeeError.TooSmall
));
it('can skip the check that prevents dust outputs', buildSkipTest(
function(transaction) {
return transaction
.to(toAddress, 100)
.change(changeAddress)
.sign(privateKey);
}, 'disableDustOutputs', errors.Transaction.DustOutputs
));
it('can skip the check that prevents unsigned outputs', buildSkipTest(
function(transaction) {
return transaction
.to(toAddress, 10000)
.change(changeAddress);
}, 'disableIsFullySigned', errors.Transaction.MissingSignatures
));
it('can skip the check that avoids spending more florincoins than the inputs for a transaction', buildSkipTest(
function(transaction) {
return transaction
.to(toAddress, 10000000000000)
.change(changeAddress)
.sign(privateKey);
}, 'disableMoreOutputThanInput', errors.Transaction.InvalidOutputAmountSum
));
});
});
describe('#verify', function() {
it('not if _satoshis and _satoshisBN have different values', function() {
var tx = new Transaction()
.from({
'txId': testPrevTx,
'outputIndex': 0,
'script': testScript,
'satoshis': testAmount
})
.to('mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc', testAmount - 10000);
tx.outputs[0]._satoshis = 100;
tx.outputs[0]._satoshisBN = new BN('fffffffffffffff', 16);
var verify = tx.verify();
verify.should.equal('transaction txout 0 satoshis is invalid');
});
it('not if _satoshis is negative', function() {
var tx = new Transaction()
.from({
'txId': testPrevTx,
'outputIndex': 0,
'script': testScript,
'satoshis': testAmount
})
.to('mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc', testAmount - 10000);
tx.outputs[0]._satoshis = -100;
tx.outputs[0]._satoshisBN = new BN(-100, 10);
var verify = tx.verify();
verify.should.equal('transaction txout 0 satoshis is invalid');
});
it('not if transaction is greater than max block size', function() {
var tx = new Transaction()
.from({
'txId': testPrevTx,
'outputIndex': 0,
'script': testScript,
'satoshis': testAmount
})
.to('mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc', testAmount - 10000);
tx.toBuffer = sinon.stub().returns({
length: 10000000
});
var verify = tx.verify();
verify.should.equal('transaction over the maximum block size');
});
it('not if has null input (and not coinbase)', function() {
var tx = new Transaction()
.from({
'txId': testPrevTx,
'outputIndex': 0,
'script': testScript,
'satoshis': testAmount
})
.to('mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc', testAmount - 10000);
tx.isCoinbase = sinon.stub().returns(false);
tx.inputs[0].isNull = sinon.stub().returns(true);
var verify = tx.verify();
verify.should.equal('transaction input 0 has null input');
});
});
describe('to and from JSON', function() {
it('takes a string that is a valid JSON and deserializes from it', function() {
var simple = new Transaction();
expect(new Transaction(simple.toJSON()).uncheckedSerialize()).to.equal(simple.uncheckedSerialize());
var complex = new Transaction()
.from(simpleUtxoWith100000Satoshis)
.to(toAddress, 50000)
.change(changeAddress)
.sign(privateKey);
var cj = complex.toJSON();
var ctx = new Transaction(cj);
expect(ctx.uncheckedSerialize()).to.equal(complex.uncheckedSerialize());
});
it('serializes the `change` information', function() {
var transaction = new Transaction();
transaction.change(changeAddress);
expect(transaction.toJSON().changeScript).to.equal(Script.fromAddress(changeAddress).toString());
expect(new Transaction(transaction.toJSON()).uncheckedSerialize()).to.equal(transaction.uncheckedSerialize());
});
it('serializes correctly p2sh multisig signed tx', function() {
var t = new Transaction(tx2hex);
expect(t.toString()).to.equal(tx2hex);
var r = new Transaction(t);
expect(r.toString()).to.equal(tx2hex);
var j = new Transaction(t.toObject());
expect(j.toString()).to.equal(tx2hex);
});
});
describe('serialization of inputs', function() {
it('can serialize and deserialize a P2PKH input', function() {
var transaction = new Transaction()
.from(simpleUtxoWith1FLO);
var deserialized = new Transaction(transaction.toObject());
expect(deserialized.inputs[0] instanceof Transaction.Input.PublicKeyHash).to.equal(true);
});
it('can serialize and deserialize a P2SH input', function() {
var transaction = new Transaction()
.from({
txId: '0000', // Not relevant
outputIndex: 0,
script: Script.buildMultisigOut([public1, public2], 2).toScriptHashOut(),
satoshis: 10000
}, [public1, public2], 2);
var deserialized = new Transaction(transaction.toObject());
expect(deserialized.inputs[0] instanceof Transaction.Input.MultiSigScriptHash).to.equal(true);
});
});
describe('checks on adding inputs', function() {
var transaction = new Transaction();
it('fails if no output script is provided', function() {
expect(function() {
transaction.addInput(new Transaction.Input());
}).to.throw(errors.Transaction.NeedMoreInfo);
});
it('fails if no satoshi amount is provided', function() {
var input = new Transaction.Input();
expect(function() {
transaction.addInput(input);
}).to.throw(errors.Transaction.NeedMoreInfo);
expect(function() {
transaction.addInput(new Transaction.Input(), Script.empty());
}).to.throw(errors.Transaction.NeedMoreInfo);
});
it('allows output and transaction to be feed as arguments', function() {
expect(function() {
transaction.addInput(new Transaction.Input(), Script.empty(), 0);
}).to.not.throw();
});
it('does not allow a threshold number greater than the amount of public keys', function() {
expect(function() {
transaction = new Transaction();
return transaction.from({
txId: '0000000000000000000000000000000000000000000000000000000000000000',
outputIndex: 0,
script: Script(),
satoshis: 10000
}, [], 1);
}).to.throw('Number of required signatures must be greater than the number of public keys');
});
it('will add an empty script if not supplied', function() {
transaction = new Transaction();
var outputScriptString = 'OP_2 21 0x038282263212c609d9ea2a6e3e172de238d8c39' +
'cabd5ac1ca10646e23fd5f51508 21 0x038282263212c609d9ea2a6e3e172de23' +
'8d8c39cabd5ac1ca10646e23fd5f51508 OP_2 OP_CHECKMULTISIG OP_EQUAL';
transaction.addInput(new Transaction.Input({
prevTxId: '0000000000000000000000000000000000000000000000000000000000000000',
outputIndex: 0,
script: new Script()
}), outputScriptString, 10000);
transaction.inputs[0].output.script.should.be.instanceof(flocore.Script);
transaction.inputs[0].output.script.toString().should.equal(outputScriptString);
});
});
describe('removeInput and removeOutput', function() {
it('can remove an input by index', function() {
var transaction = new Transaction()
.from(simpleUtxoWith1FLO);
transaction.inputs.length.should.equal(1);
transaction.inputAmount.should.equal(simpleUtxoWith1FLO.satoshis);
transaction.removeInput(0);
transaction.inputs.length.should.equal(0);
transaction.inputAmount.should.equal(0);
});
it('can remove an input by transaction id', function() {
var transaction = new Transaction()
.from(simpleUtxoWith1FLO);
transaction.inputs.length.should.equal(1);
transaction.inputAmount.should.equal(simpleUtxoWith1FLO.satoshis);
transaction.removeInput(simpleUtxoWith1FLO.txId, simpleUtxoWith1FLO.outputIndex);
transaction.inputs.length.should.equal(0);
transaction.inputAmount.should.equal(0);
});
it('fails if the index provided is invalid', function() {
var transaction = new Transaction()
.from(simpleUtxoWith1FLO);
expect(function() {
transaction.removeInput(2);
}).to.throw(errors.Transaction.InvalidIndex);
});
it('an output can be removed by index', function() {
var transaction = new Transaction()
.to([
{address: toAddress, satoshis: 40000000},
{address: toAddress, satoshis: 40000000}
])
transaction.outputs.length.should.equal(2);
transaction.outputAmount.should.equal(80000000);
transaction.removeOutput(0);
transaction.outputs.length.should.equal(1);
transaction.outputAmount.should.equal(40000000);
});
});
describe('handling the nLockTime', function() {
var MILLIS_IN_SECOND = 1000;
var timestamp = 1423504946;
var blockHeight = 342734;
var date = new Date(timestamp * MILLIS_IN_SECOND);
it('handles a null locktime', function() {
var transaction = new Transaction();
expect(transaction.getLockTime()).to.equal(null);
});
it('handles a simple example', function() {
var future = new Date(2025, 10, 30); // Sun Nov 30 2025
var transaction = new Transaction()
.lockUntilDate(future);
transaction.nLockTime.should.equal(future.getTime() / 1000);
transaction.getLockTime().should.deep.equal(future);
});
it('accepts a date instance', function() {
var transaction = new Transaction()
.lockUntilDate(date);
transaction.nLockTime.should.equal(timestamp);
transaction.getLockTime().should.deep.equal(date);
});
it('accepts a number instance with a timestamp', function() {
var transaction = new Transaction()
.lockUntilDate(timestamp);
transaction.nLockTime.should.equal(timestamp);
transaction.getLockTime().should.deep.equal(new Date(timestamp * 1000));
});
it('accepts a block height', function() {
var transaction = new Transaction()
.lockUntilBlockHeight(blockHeight);
transaction.nLockTime.should.equal(blockHeight);
transaction.getLockTime().should.deep.equal(blockHeight);
});
it('fails if the block height is too high', function() {
expect(function() {
return new Transaction().lockUntilBlockHeight(5e8);
}).to.throw(errors.Transaction.BlockHeightTooHigh);
});
it('fails if the date is too early', function() {
expect(function() {
return new Transaction().lockUntilDate(1);
}).to.throw(errors.Transaction.LockTimeTooEarly);
expect(function() {
return new Transaction().lockUntilDate(499999999);
}).to.throw(errors.Transaction.LockTimeTooEarly);
});
it('fails if the block height is negative', function() {
expect(function() {
return new Transaction().lockUntilBlockHeight(-1);
}).to.throw(errors.Transaction.NLockTimeOutOfRange);
});
it('has a non-max sequenceNumber for effective date locktime tx', function() {
var transaction = new Transaction()
.from(simpleUtxoWith1FLO)
.lockUntilDate(date);
transaction.inputs[0].sequenceNumber
.should.equal(Transaction.Input.DEFAULT_LOCKTIME_SEQNUMBER);
});
it('has a non-max sequenceNumber for effective blockheight locktime tx', function() {
var transaction = new Transaction()
.from(simpleUtxoWith1FLO)
.lockUntilBlockHeight(blockHeight);
transaction.inputs[0].sequenceNumber
.should.equal(Transaction.Input.DEFAULT_LOCKTIME_SEQNUMBER);
});
it('should serialize correctly for date locktime ', function() {
var transaction= new Transaction()
.from(simpleUtxoWith1FLO)
.lockUntilDate(date);
var serialized_tx = transaction.uncheckedSerialize();
var copy = new Transaction(serialized_tx);
serialized_tx.should.equal(copy.uncheckedSerialize());
copy.inputs[0].sequenceNumber
.should.equal(Transaction.Input.DEFAULT_LOCKTIME_SEQNUMBER)
});
it('should serialize correctly for a block height locktime', function() {
var transaction= new Transaction()
.from(simpleUtxoWith1FLO)
.lockUntilBlockHeight(blockHeight);
var serialized_tx = transaction.uncheckedSerialize();
var copy = new Transaction(serialized_tx);
serialized_tx.should.equal(copy.uncheckedSerialize());
copy.inputs[0].sequenceNumber
.should.equal(Transaction.Input.DEFAULT_LOCKTIME_SEQNUMBER)
});
});
it('handles anyone-can-spend utxo', function() {
var transaction = new Transaction()
.from(anyoneCanSpendUTXO)
.to(toAddress, 50000);
should.exist(transaction);
});
it('handles unsupported utxo in tx object', function() {
var transaction = new Transaction();
transaction.fromObject.bind(transaction, JSON.parse(unsupportedTxObj))
.should.throw('Unsupported input script type: OP_1 OP_ADD OP_2 OP_EQUAL');
});
it('will error if object hash does not match transaction hash', function() {
var tx = new Transaction(tx_1_hex);
var txObj = tx.toObject();
txObj.hash = 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458';
(function() {
var tx2 = new Transaction(txObj);
}).should.throw('Hash in object does not match transaction hash');
});
describe('inputAmount + outputAmount', function() {
it('returns correct values for simple transaction', function() {
var transaction = new Transaction()
.from(simpleUtxoWith1FLO)
.to(toAddress, 40000000);
transaction.inputAmount.should.equal(100000000);
transaction.outputAmount.should.equal(40000000);
});
it('returns correct values for transaction with change', function() {
var transaction = new Transaction()
.from(simpleUtxoWith1FLO)
.change(changeAddress)
.to(toAddress, 1000);
transaction.inputAmount.should.equal(100000000);
transaction.outputAmount.should.equal(99900000);
});
it('returns correct values for coinjoin tr