diff --git a/fixtures/chains/for_wallet_c922713eac.json b/fixtures/chains/for_wallet_c922713eac.json new file mode 100644 index 000000000..fba5af71c --- /dev/null +++ b/fixtures/chains/for_wallet_c922713eac.json @@ -0,0 +1,1087 @@ +{ + "network": "testnet", + "state": { + "fees": { + "minRelay": 1000 + }, + "blockHeight": 623235, + "blockHeaders": { + "000000b0e2931614db83139686c4b95b30f4351fc1c8265081da235d1ef1ed07": "0000002014b19c3f28e3ea5b8a3764cd76ad322ef1a3e7ca12cf7e72f9625f9d080000004a46458c0f1e647a4c5c0cde93c1349d846c33d143eb5e439ee7e9650da5b9ed4138a761d127011e26e70000", + "000001810cf3b49ed94ae033f007923d3c243077f5f9e24b559b536087e8b960": "000000206c529bed8f7d56831dbfbe2c87a3c6188bbed1841002926c018993ddf60000008e4b077f4978057981ab7ced5fa3749b8559b627efcdfc7513b7892520cca9c55f4a9761d0dd011e31a50000", + "00000141c85d1b1b5a1313a139c6ebc9e748f1677d3a5a72e5c6c0635c0c07df": "0000002060b9e88760539b554be2f9f57730243c3d9207f033e04ad99eb4f30c810100004aeddaefb9b9a124e9cc154102dd189a3db7ef6bab61d78bb5013d1907798151404b9761160f021e967a0000", + "000002092c76130d20419ca56ea02cc111a121ceb0996715b37e213e00bed874": "000000206b7c87ccac5d5b836c3f3c951ef34b231d51d214ce7fbb68e1ce5474dc00000050f9e99a67a7bceca14f1c20379a7531a01d7aa8a9142ac5e400c2cf4fbddc22bf4b97618b38021eb89b0c00", + "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1": "000000200e44bcf1de3f37892c24004d1835f31689c341d411c92750a4f91626490000005581a10aa3891b2e1c163b6372c172c299b037f7443d97e2ba90c85ee8dc76f4d04c9761f08d011e55560000", + "000000d672c7929c0f570e4793670f7695c848f8bb98d910aade929b41b38ce2": "00000020cd8db43289d1574b2938b0b87bfef9c9cf1d78a7f5626159e506e079490100009cbfb07f675787ebcd3cf8329344212919ee495b471a7af6c944dd3fc07bdad0904f97617c5d011e090e0c00", + "00000105e03b663923fa2b2da60404113cdffa1ca7a19df32188f4add3308452": "00000020e28cb3419b92deaa10d998bbf848c895760f6793470e570f9c92c772d60000000b6ceb13eccadbdb1d4d53b4503dc17b5b02a595b7ef87c5866d25150718acb5365097613a5c011e40c40400", + "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554": "00000020d11c62bf5e2d31c9caf71dec9095aa956bddb9cdff7d8a8364c8afea3e00000079e6a9a6c22641abf7f2e2a49a4819327be5a83cf11ec44d51468ddba47f9987f2519761ed51011e981c0500" + }, + "transactions": { + "a43f20bcb46fef22926745a27ca38f63c773f2c7c4cbb55aaf184b58b3755965": { + "transaction": "0300000001481116b70180090112d22598c4962e2034ea269c755a8445410ecdd0299a388a010000006a47304402202be55c1f32da1dd6f408e9a945d38670f64258716044da4314ffda738dbcefd2022050555557347a78209556b1399443ab4685aac8f9c579f209038f470db434c8f50121035cf01fcf7ab699115a5ace61cc818cedd6287a3ff3d2178b44bf93bdcea09c3cffffffff0199e1610d000000001976a914b0b7c8c3637ec294f583738a46d1aa07b0f6ed8c88ac00000000", + "metadata": { + "blockHash": "000001810cf3b49ed94ae033f007923d3c243077f5f9e24b559b536087e8b960", + "height": 615751, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "c8f0d780e6cedb9c37724f98cc3fecd6d5ad314db28e3cd439184bf25196ceb4": { + "transaction": "0300000001655975b3584b18af5ab5cbc4c7f273c7638fa37ca245679222ef6fb4bc203fa4000000006b48304502210080185b616b2f8cb013e264b66146954cdc1597053ea8238467b896413b836039022071e71147dc45fc2dbde666c2774912c5bfd00b761268a3a4f8951375eb895c310121029671ae86f7eeb5c127568fddc977a1cce1f76ad64efa83c8ec9fcaef08ea9738ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac92b9610d000000001976a9147f6f4280f91e00126d927466cd48629439b763fa88ac00000000", + "metadata": { + "blockHash": "000001810cf3b49ed94ae033f007923d3c243077f5f9e24b559b536087e8b960", + "height": 615751, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "ca2afbe06b0187777f7e1aa8ae90c16ea094fbf1ed1b2ae8372e34c31a2cf67d": { + "transaction": "0300000001b4ce9651f24b1839d43c8eb24d31add5d6ec3fcc984f72379cdbcee680d7f0c8010000006a47304402206c93144ccd1816503fa13e36c5cc7aa4bc834dafb7543d306b0ac122902e3b55022076c756c3ab6ce341f55674f77a21a995264028f5891c1e257691bd743260f7fb01210208ef0d91b0377fd629082192d55ac831b6c9ff0603ed6dc19cbb3d6e21913917ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac8b91610d000000001976a9144b99b817a9caa57fc141cbfa68e2dc86d54ca5ee88ac00000000", + "metadata": { + "blockHash": "000001810cf3b49ed94ae033f007923d3c243077f5f9e24b559b536087e8b960", + "height": 615751, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "3096b29ee16f6ec1b8e8b5f3d01beb5a231d87b51b6fa4e637e0f45b6b8b83b6": { + "transaction": "03000000017df62c1ac3342e37e82a1bedf1fb94a06ec190aea81a7e7f7787016be0fb2aca010000006a47304402200122dda1789ce203582718e77afac10cb4e33acffe728d57b54263e4c007655f0220633b18a0260c731e59bbf69e353ab824d5693d5ea7dd863093adf5916de7bbe901210381e52ecbce2dbd36931346483a2a90b48fcd6d077504ff6ef172e16b1b1104e1ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac8469610d000000001976a914cc7406fdd5c28fbea82d60e2ab1307179c474cd088ac00000000", + "metadata": { + "blockHash": "000001810cf3b49ed94ae033f007923d3c243077f5f9e24b559b536087e8b960", + "height": 615751, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "95fe8be7b264aa71a1a55478abce7233311b7f3a22135a6a2543988294ddbc68": { + "transaction": "0300000001b6838b6b5bf4e037e6a46f1bb5871d235aeb1bd0f3b5e8b8c16e6fe19eb29630010000006b483045022100f03d0e357ac3253f4799061814fe1fe4dad95784240b7aa4b83dadc6d06657c102205e1f81d5e9fe12fb6bd73bc4cdc23468fb9f5ea76c7a37d93281850f2f9f3bc2012103d8505ef3e6bdb5c5cfdadbce5b512979cfaac497642d8406c05bbe9d8dc1f245ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac7d41610d000000001976a914bc6604d7be005be0ed9106d923fdeb13ecd8da3288ac00000000", + "metadata": { + "blockHash": "00000141c85d1b1b5a1313a139c6ebc9e748f1677d3a5a72e5c6c0635c0c07df", + "height": 615752, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "267c4191e3b32241bf00cf15ff7e02460e4d6cc892afaaf3a28f88601b5b6a38": { + "transaction": "030000000168bcdd94829843256a5a13223a7f1b313372ceab7854a5a171aa64b2e78bfe95010000006b483045022100ce097043895c2c32f34be8010790f6c1c86b392be0368c52ece87c7233c8b1cd0220040d2ba2647d337f34ffc422b2d2edb24c9395bd0c19c680afa0fd3013493eee0121035d2cd2b5154fe849c3a9856397c26064c52b033a382b1054635ccb96290cbaa3ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac7619610d000000001976a914744e5f4f8661dcc51e88f4615fe64a1d6b26e6dd88ac00000000", + "metadata": { + "blockHash": "00000141c85d1b1b5a1313a139c6ebc9e748f1677d3a5a72e5c6c0635c0c07df", + "height": 615752, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "eb39c1c9e0e5dd8df5ec1f822a4fd96469749f56443377262d933f888568047d": { + "transaction": "0300000001386a5b1b60888fa2f3aaaf92c86c4d0e46027eff15cf00bf4122b3e391417c26010000006b483045022100cd1ff3e404263423fcea33895ee550d5fd3ae848082402748b9f31b2a3e2071902202e98c8b1c73a84a3f3b03854842aa2590d95881d5af3697b47c36f2df040e6120121031dc2909f3f80373b7b6383e61adfc8a312954b7d420ee8f9a17bddf271999d95ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac6ff1600d000000001976a914a1cd6944da97ed00333c224679234a799d7de43f88ac00000000", + "metadata": { + "blockHash": "000002092c76130d20419ca56ea02cc111a121ceb0996715b37e213e00bed874", + "height": 615754, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "d066ac08dc743bfe5a29fd541802f9269b3da17c71f6b2f64aabd6c68a671566": { + "transaction": "03000000017d046885883f932d26773344569f746964d94f2a821fecf58ddde5e0c9c139eb010000006b48304502210082037c36c8acde9120e5b5234f2b1625698b599966ab65f24498145e7076821a022057b8b2ac8056e93a5753b3c1ad95eff8b9ac0cf6135425d349f765b8db0bd501012103eae3d568169c0c7944c27623a7295ef1f3a563890123c3c33450d8ce4e60117dffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac68c9600d000000001976a914a1ecca0e966e04bc0e6d26466eedc56237eeebc188ac00000000", + "metadata": { + "blockHash": "000002092c76130d20419ca56ea02cc111a121ceb0996715b37e213e00bed874", + "height": 615754, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "a3f4b8c9c9f49434e0437ad1aa0fa9f9c621d3a8742cee174402863a5b23fe4a": { + "transaction": "03000000016615678ac6d6ab4af6b2f6717ca13d9b26f9021854fd295afe3b74dc08ac66d0010000006b483045022100db5523499661badbec84dca3ee03f701de09982497e34fd8df284532ed0e09c902202a5da05c4378f1d878558103bdabcd1801dd2aac9d782c7f7eaa31348d6d00a7012103310974f905740f426e2c21af561d442d198f474344ad79b7071fdafd92fceee4ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac61a1600d000000001976a914c6be1292e6c212259bbf760e509c7cf3e8ce15a888ac00000000", + "metadata": { + "blockHash": "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1", + "height": 615757, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "eee88d2a03a0452dfe0abc9a84915f33d552110088c4382f0c459960494bf2ab": { + "transaction": "03000000014afe235b3a86024417ee2c74a8d321c6f9a90faad17a43e03494f4c9c9b8f4a3010000006b483045022100c222770356e4157a7926454701483d26f8ad2a1595d4fe0898eb9ce1ab2b216202202994cfb2563f53f28ab21959289a890fcd2cfdcaf7cc576be8c5930b76ade1d90121036ef5809736958928e5e369a805a7f3236369dcda8cb0ad8b41c02ec12de92b36ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac5a79600d000000001976a914526a5726b41e7f178f9c6ef94e63a321b61db0c688ac00000000", + "metadata": { + "blockHash": "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1", + "height": 615757, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "0fd66f212549dbb84d42a745e9e0bef5e6772acfaf8b6d06579cffa15ac9fa1d": { + "transaction": "0300000001abf24b496099450c2f38c488001152d5335f91849abc0afe2d45a0032a8de8ee010000006a47304402203ff02fcd8551e4525522a37d6cc7202daea3be57b7d2a34dbb1d2cc06a38a6cc0220456f04bd6fa328e53f66167ea1c2c18f27d80838f4306e17e633410afa0aefed0121037be066d359ad86dba34a084754a9a79a63626e7ab9df7dd185505c6465969d1affffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac5351600d000000001976a914c96c81e8a992e95eade60a4e3ab967de672c4e6288ac00000000", + "metadata": { + "blockHash": "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1", + "height": 615757, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "92be5095443f506771d6f145716ab33a12b20239b5d363c9b09d5e5cd7fb3f96": { + "transaction": "03000000011dfac95aa1ff9c57066d8bafcf2a77e6f5bee0e945a7424db8db4925216fd60f010000006a47304402210097facc4725c831d0afc70828d18c128868fddcdec91479cb3d242658bfbee887021f361e45fe35cfada131d376e290ac4ab0a9710839d49f96690b1a3a5dc160a0012102df67e08e44684da52bd8b648344a00ee6ffae73a7efdfcfb5503af799c3d069cffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac4c29600d000000001976a914c0c6b5c4f8f775acbd6de28c5aafcdb785d6d08588ac00000000", + "metadata": { + "blockHash": "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1", + "height": 615757, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "ea704ab4f38d786116875270c141b628f182684fd39dcd57c969237cf92f5589": { + "transaction": "0300000001963ffbd75c5e9db0c963d3b53902b2123ab36a7145f1d67167503f449550be92010000006a473044022065fdcd2e579ae58af2634386d1f92da1617a0ab16bea34e5a5900b9454a528920220496e59ef90d5d79e0dcaf9b97f962e63e41a71cf336e8fdf0bdbabb72deffae801210273e7e7d1fe3d3cb5038f058bb1d5076b0781d61085f5c658ffd6246632b03f9affffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac4501600d000000001976a9145c6bb6833b27ca6252e64faa48b8a29e783df3e788ac00000000", + "metadata": { + "blockHash": "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1", + "height": 615757, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "f86445d20beccda3749058d90a605f4179a51a04b09f1262a9aefb382fe21bfd": { + "transaction": "030000000189552ff97c2369c957cd9dd34f6882f128b641c17052871661788df3b44a70ea010000006b483045022100b671fc3bc69567298f61f5d033b7746985fe517b83e30f38dac0f1306aba0a5902201a9e509d96ab9f57c74cca218171f49b7a3d56a66afde0bf53481ebd68892e19012103108df8bbcd37ace008ec407ca8cdc95ebdb220db3df8527418a0d86c95c1af3bffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac3ed95f0d000000001976a91444fcf081778ce018b167d007d910f3c8993c4c7f88ac00000000", + "metadata": { + "blockHash": "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1", + "height": 615757, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "b2420b554dad82bffb22f10bda0124876fc6d02b4892d01833b413cad275c351": { + "transaction": "0300000001fd1be22f38fbaea962129fb0041aa579415f600ad9589074a3cdec0bd24564f8010000006b483045022100c19616212adbade24c4322520e20218a6518947cb420025b6cda0560194b3c1c0220208e1132a95674160069cd02007200acfa7568d7b4e7af5c63bbbdbe1add2f3b012102cd31f5a37c8032f34f6babb216a48d8ee5de54bc212e86a4d76bd49d10f18e91ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac37b15f0d000000001976a9148d1adb237d7f960b5d6912e21169ca9f15a2ebe988ac00000000", + "metadata": { + "blockHash": "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1", + "height": 615757, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "174bbb78437049150cfe8c00ec53cd9f0ee038d0e9451e85eef25b5256a2e95e": { + "transaction": "030000000151c375d2ca13b43318d092482bd0c66f872401da0bf122fbbf82ad4d550b42b2010000006b483045022100c41c6a7c30c3479bccc020cf468b5c8be4188caef840b4a65f9a3bf16e8c3738022008458d62093d180bbee74e5839820189fa7fbe3160364ae69ddffe42ace22e3b01210234c8bb381c51a9de5253d77b7f5758dc73c832c2750332e62882b131c73e4955ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac30895f0d000000001976a914ea923f5fcaba8406dbbc2074262923653f2b9da588ac00000000", + "metadata": { + "blockHash": "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1", + "height": 615757, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "fec6553adbe93a1702b43a9a871979ea0294bcd8d776ae684ff7aca0d22fe0e9": { + "transaction": "03000000015ee9a256525bf2ee851e45e9d038e00e9fcd53ec008cfe0c1549704378bb4b17010000006a47304402203abb41d61f4eaecf5ac9ffe350bd6fef1e7e6ff5733c5f3399f09d191e1b8cc602206623acc6bf4ea2219e20183fd952f42c21efc64daf53049983509dddbd633c74012103cb4ce869ba12c894772aa8300dd362af3b8eee63ef394b3d86210b4d3044a70bffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac29615f0d000000001976a91493e02288f0c39d5927425ee77428696592678f5588ac00000000", + "metadata": { + "blockHash": "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1", + "height": 615757, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "fc9863caf91f51da5ae67cd8bdada46435db0e940164bee87248026c79ccebd9": { + "transaction": "0300000001e9e02fd2a0acf74f68ae76d7d8bc9402ea7919879a3ab402173ae9db3a55c6fe010000006a4730440220273eee107fc493e599ce014f7087371e9ec28199a8a4676a588e0e7a96efaa2f02202c55628295bd3d3b1b895dc60dd7b4b98d9558393fcdb22106878721f554b5e7012102db98cb80d13a6e575589c641f22cfb75278e2b664ce1b7765fca4f16f3be8bb8ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac22395f0d000000001976a9148f8dea282edef6cdfb5c03cfe80cb139d8380d2688ac00000000", + "metadata": { + "blockHash": "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1", + "height": 615757, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "74789930fe5d4bab8e485f1432a79704d3f111eec959aed55584c04b7f7da2dc": { + "transaction": "0300000001d9ebcc796c024872e8be6401940edb3564a4adbdd87ce65ada511ff9ca6398fc010000006a473044022009f48d0032c8190278d37cb3374edf2681cba880d4fabdeeeb041d8414c2b6dd02205b26b9ac50262c1f5d0b4369264383014a209293e5f0a26acc0322736f29d60f0121038cd3c39953611f8273ad74bbbd3e919a71c5e7481f4841f6ddc9ac0427d8297bffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac1b115f0d000000001976a914299b3526d93433676cd3aaaf28839a8f83db97f988ac00000000", + "metadata": { + "blockHash": "0000011021296590288241b6d23eba1276c50dcada8dc69f2a237fc458b17dc1", + "height": 615757, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "4b1ec1877f9c2eaa4fbfeb034b38b21156aab4955d597fc328b6c30be1acd10e": { + "transaction": "0300000001dca27d7f4bc08455d5ae59c9ee11f1d30497a732145f488eab4b5dfe30997874010000006a473044022057fb09fa6301445cd72ec36c9bdbc1748eada3098f41227d10d258c152bc06eb02207ca0e41d447bbea34af8d3ee1ea879e03e5daf0241777e942e08a8d3450cfdb10121035a0a22c2a6d893fbdbe7c2cc7ad2d09292eb0323b78e6a64503b9f09adc7b888ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac14e95e0d000000001976a9149c7f9e354bf6e4b3e8fcff6ebeebed179464397f88ac00000000", + "metadata": { + "blockHash": "000000d672c7929c0f570e4793670f7695c848f8bb98d910aade929b41b38ce2", + "height": 615766, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "c0cbf437c0f8e980b8d64a3148a258645a821dc51631c4dfdb3dcd8324d6819e": { + "transaction": "03000000010ed1ace10bc3b628c37f595d95b4aa5611b2384b03ebbf4faa2e9c7f87c11e4b010000006b483045022100d4968053278a5e0d7d177de6aacb8d5b955b155e8a7fcf0b263c990b4058be0102201702447e79c98812aa34588d8f5f5cf955f2a92ce0c22b1eafa0abafdf95b6e0012103c921989753f9a5c4b656aa3e64da167eb4c44b20848db06f8dd0857b2468f5cbffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac0dc15e0d000000001976a914ff4c2b2c85b85a2e4ae8078587cbba81f3880c1988ac00000000", + "metadata": { + "blockHash": "00000105e03b663923fa2b2da60404113cdffa1ca7a19df32188f4add3308452", + "height": 615767, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "5ec24edb2394c13db2d2b3fb1502cd5a4db8006b283eb6868cc44828df30148c": { + "transaction": "03000000019e81d62483cd3ddbdfc43116c51d825a6458a248314ad6b880e9f8c037f4cbc0010000006a4730440220624a0ca69ff3730cf2107945ffb629209fc8cefbe32abf5eecff76e8efa110f2022059f3b834178bbb360c9a473f896d7eda2ad9fff1af2c7f478ee3364824f4163d012102b997bc2dec7eebe4c504f95e0373bbd25e8c9970d4d9a84a36c701e4d1b50bcaffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac06995e0d000000001976a914315c783a94c3cb762a141cd949b631111caad00488ac00000000", + "metadata": { + "blockHash": "00000105e03b663923fa2b2da60404113cdffa1ca7a19df32188f4add3308452", + "height": 615767, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "f95dbbc93352dd0cfe0350ce7407eabc5ead0cd5b3265873c5544447047eddf1": { + "transaction": "03000000018c1430df2848c48c86b63e286b00b84d5acd0215fbb3d2b23dc19423db4ec25e010000006a4730440220508d1d7cc4a3d961186913c3e17df787a8338342f8e47354c98a28b7091fec01022003aa09800685591628478d62eeee34b45d6f5dd3138f6455fd0decc83a0780a0012102bb5fbf33eed8fd6fe472584bce8551e8082ecf4d445f562b5b20b60b018e8043ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088acff705e0d000000001976a9141a34cc7c74c833c38a10c390b77f7fcced77af0d88ac00000000", + "metadata": { + "blockHash": "00000105e03b663923fa2b2da60404113cdffa1ca7a19df32188f4add3308452", + "height": 615767, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "5cfbb3fd2dfdd75f590278a1749fe2dae2d5395eef5e87690756adb44cb5745d": { + "transaction": "0300000001f1dd7e04474454c5735826b3d50cad5ebcea0774ce5003fe0cdd5233c9bb5df9010000006b483045022100f39957205230341508c414b17d4961f52c5dc0a426280381a407c6c46954ab09022006c8ea46b28f076211dc8d00c7fd16863285711102d2b073946323a61264a1080121023e6748994e6df1a568a0bc42fe903f7abedbe4b9161bbb4e60e0c30996df1401ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088acf8485e0d000000001976a91463da38cac685abf6975de7b8d54415f9cb0f1e3088ac00000000", + "metadata": { + "blockHash": "00000105e03b663923fa2b2da60404113cdffa1ca7a19df32188f4add3308452", + "height": 615767, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "34ba3e041c3026bab4f2c2c0ec1e1bb496ec72042aba9b03a324b74d1ec1d1af": { + "transaction": "03000000015d74b54cb4ad560769875eef5e39d5e2dae29f74a17802595fd7fd2dfdb3fb5c010000006a473044022000cf20af647df385691ffee7c20de78b05cd264645f784b3bd94bfa847bcd34402204ab594edbc06d4f7bc883eec99ff554a014e7bc9ae1d821e48ed75bd41ba176201210299f14e8558141cd4b58eff7da5d62bc5d12fc35ec7ca62fb577361b7b39323d5ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088acf1205e0d000000001976a914cd5ff7587af9ce7762fb73ee132b392a7734691688ac00000000", + "metadata": { + "blockHash": "00000105e03b663923fa2b2da60404113cdffa1ca7a19df32188f4add3308452", + "height": 615767, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "0d61420bb114d9d16b424e2822ab8412f3dec63e0b8da8df8f0fd5786782abd0": { + "transaction": "0300000001afd1c11e4db724a3039bba2a0472ec96b41b1eecc0c2f2b4ba26301c043eba34010000006a473044022046f78fed1260cc82d6c2c8be5b724d2a7c85687009c893cb464506106c40ce7c0220406ecc013482039460cd724bdabd39e6e407129b0f42367a8b8a5f6a0ac853a801210298dd86b2cef1adc0011c67fd29c314bb1b57c3f1be4d815f604d598ae22e55dcffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088aceaf85d0d000000001976a9145246323c87caa68e41f818bd2a78ffa969b8ecd288ac00000000", + "metadata": { + "blockHash": "00000105e03b663923fa2b2da60404113cdffa1ca7a19df32188f4add3308452", + "height": 615767, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "0e62d7c53e01bd3a61065ca279bd77a6e1354c0c6a373502d7cc9328be3812a8": { + "transaction": "0300000001d0ab826778d50f8fdfa88d0b3ec6def31284ab22284e426bd1d914b10b42610d010000006b483045022100b7dec3519f1a1fbdd57f4cc4625a522b2cbd0980b577a79285d0e3995230129b022060af9411ce03cf9e17d30fc544ab8f31f068fd10a6b27def605e3d03c75c66d7012102c877e9a908ff2532da285c49e4f5fbb4b4fe9890167e8f53926f8f0405893ce9ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ace3d05d0d000000001976a914fd17b92a259f10b059e3b961e2245fced0ffeaf088ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "49c214eab55a17f80ec6e136861cbc56d507ea0c491864706b3cfa0a7f175634": { + "transaction": "0300000001a81238be2893ccd70235376a0c4c35e1a677bd79a25c06613abd013ec5d7620e010000006a47304402203f21c9573085b88848d22b894e5cfad783e7bb94bdd073c677b8a49495aa8f40022036cc89a2b492b1bde71d5fbc9c9a5825314669c9b4af635e34ba0d807edaf5e70121034bce811ced77c6a27f970af6ac6e5ee6a2efd713273774f3039490bc26f19f49ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088acdca85d0d000000001976a914dfe1744ed3efe1c41ce02d92d6f4b3c789f3594e88ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "d17f579a4ead6a798618d3b567c2f130cac22a0830219d03404d68d9b6a5d8ce": { + "transaction": "03000000013456177f0afa3c6b706418490cea07d556bc1c8636e1c60ef8175ab5ea14c249010000006b483045022100853ad2f6f1fb290020e0c703dfea5d8ad476b671631ee3ff3b123045eca5a4d802206f81f7dc3ecd86b7ca2dd51243ec18562a9d935813b25c766839317b1b178b9b0121022a56bb1ffad84cd344cf40b5c968cb5879c1a2eff24035604a21e9acb18db570ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088acd5805d0d000000001976a9145707912cce7499bd9f594536d9ef6a9442161ce388ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "9594a1c5a05fa24a78e9b93f03ee62b56d9c90e62e62298a3cf54629a355750a": { + "transaction": "0300000001ced8a5b6d9684d40039d2130082ac2ca30f1c267b5d31886796aad4e9a577fd1010000006b483045022100a321cac2bb16cbc30682700ef402c98d0c603b948d38e8d5a3b3368021bfc91e02202fb7cc27a5f701f9f15fa2a790b351e462dcd56bb3e33c959a2f78e53a5f9e400121030132d760b2488626feba05f7317a4ce255cfb5ed4bd23a78366bf0846bf666b1ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088acce585d0d000000001976a914f8f4d2ba9d26ceb5b237ab220c5fa09e12495ca488ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "d522f8a9406dc497893ec3e99c69a05a1b88e0a010e3f12495628e412b85663a": { + "transaction": "03000000010a7555a32946f53c8a29622ee6909c6db562ee033fb9e9784aa25fa0c5a19495010000006a4730440220025799d9fde964000894e13249a7a856aa4f7402392e29a90a8560093f4d179402200db0ac7f4cbfeb1ec1e8ffa2eddf00cabcd53a6d51a9c7e2af90c6cd9e3d805401210372c3b4139803029186ffdaace2acc48bb3131b24b19643c7919e4c994eacd4d7ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088acc7305d0d000000001976a914aefcf82febe1bd937263b53bc5f93708616665f988ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "3ee6bec9dbe57574305a25d72362434b8f3d02a79c6228d12f0a957891f5e52d": { + "transaction": "03000000013a66852b418e629524f1e310a0e0881b5aa0699ce9c33e8997c46d40a9f822d5010000006b483045022100fc74a5f633c25a152195b7ffc2e59309af554cbf26ac176f35b8607742f01854022031d4edc7ba4851e1fa98c761f2389251532943632ac6ed0fd88f23d5781535b9012102bfa11d27e6cb225d549ec6677c49c4ca84dc463648bc541f91d1f1fdd3b11a37ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088acc0085d0d000000001976a914f9dfb4ac40a2d2aff83d58d78d5eeb98eaed432588ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "89181c1b627d32ea7dceda330c4c44d4992e98f7cacc392691220bba800ec6a7": { + "transaction": "03000000012de5f59178950a2fd128629ca7023d8f4b436223d7255a307475e5dbc9bee63e010000006b483045022100d9cb2fa10a5f04e17d170509732408a2026712908b296f79f545923c1c80deec022012b08498ff70108cf48be3633b539f59a572ecc121d2cdf69782f34570c71ce2012102c82f336f78045e7ba70295560bd674b5edf004a80a7ea6aef3c5fd110fc2fabbffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088acb9e05c0d000000001976a914adbd1ff2c75b70c94865cb4bb0afc78c1e36d78688ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "5b02921dda2f33def0951615466578b3cac8fd19ae9a9e75ee0176bf3c8e4821": { + "transaction": "0300000001a7c60e80ba0b22912639cccaf7982e99d4444c0c33dace7dea327d621b1c1889010000006b4830450221008d15bc527302c6b330f89c34add456d702e67dfa3b633e605e434332305e308c02205a7860004ec9a7c5703febccef584b39d5a29c770b19b0bb74072514013eb1b501210351ab77c8b2b4cf0430b9d8324e46d0d672117f174bab7487e3e601e60cbc731cffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088acb2b85c0d000000001976a91427d236621e716361c0253902e192b3c1ee2bd79388ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "3d2e84e85560f835084d6e6514563a16453ded77d8474244b1a5dc95c8891faf": { + "transaction": "030000000121488e3cbf7601ee759e9aae19fdc8cab3786546151695f0de332fda1d92025b010000006a47304402206b8006112d2ccf477e23673d3decfa4314af671e03fed8eda958fe6a78317b8e022014aa1dfd05e43fca8fdc233aabee6701b5bd32ee9af983497cab93bb960ca9c70121023d72e96a3287e997680e62a16120a395e3f68bd96d56291aa58eed08a5121dbfffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088acab905c0d000000001976a9140c5d1982e36bd9b5f4aa08d8e4c51a5535e2a8ad88ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "2a035c18d1a51c7164beac80f374bef6b49d084681e8035071fc0adc903b606c": { + "transaction": "0300000001af1f89c895dca5b1444247d877ed3d45163a5614656e4d0835f86055e8842e3d010000006a473044022027389c256710a7b70fe3cf0b8d04c486f1f6f2e3b94662f68dae7be2e8ec741d022071d3514613d9bcef688339addd86a0103667ae997f170c82f8eceba5b7c0aae201210306fa7efecfe3e7e4bc0d71e333ad9b330ba5027f0bcbcf9209dc6dc6d1d214a6ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088aca4685c0d000000001976a914681ebc28972f2e23f88a6b8f71a30f45fe08b45f88ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "11d14e8be169305b49e5a080f0570d2be6783c4e8bbe0c32451b97e35b188ee9": { + "transaction": "03000000016c603b90dc0afc715003e88146089db4f6be74f380acbe64711ca5d1185c032a010000006a47304402203f685c0d8dc11f74c153d93d895e0bf0a0b0ec31120345a9b98baa8e28808c02022011530b45fb4a53b33fc4201fd4b2834b672931c4e5f7097f852c126146d9e6140121038977b564fcf729b468572015dcd11f2475e28fc2f86dd1d3699a6873134ceabfffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac9d405c0d000000001976a91454eee81f9d2650a8162dea7cf0f444b270a685b688ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "b8c506c24e66aabc59b409b2ab37cd97893dae1ef671c5e839dd8649a65586a0": { + "transaction": "0300000001e98e185be3971b45320cbe8b4e3c78e62b0d57f080a0e5495b3069e18b4ed111010000006b483045022100a12b68d57f6c591ffa5001987c8b2b246e396965cbb24452a2931d3837d6dd710220554d1a231a29aac6cb68b0278b1039521ab3f08ebb53ed78e4319648fcb00538012103cbb5e729a4dfbb2607e4c2ec0afae068b21b76d907b859e0980b98d5e0212b0bffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac96185c0d000000001976a914f050e5615a87152533df496a5fdc7867c327e77888ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "1b33390deac7e4c58761f048ee6d04ad2770277d78f2111951347c318088f4bd": { + "transaction": "0300000001a08655a64986dd39e8c571f61eae3d8997cd37abb209b459bcaa664ec206c5b8010000006b483045022100e11f02c0571a2beac3993191d31936d9fbd4f7b4e71ebefc195763ad66c0ff680220565006889d022b790f57a6a9e8fbd404b353365e73e29b8152cf1a7f7bacf3bf012103d55102102e4a446c4f76fd26a1052ca304b1523fe8fefdc81a21bdb37a593d26ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac8ff05b0d000000001976a91404ee78127bf986f1abf07b35e30613949811410588ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "1219e136566a370d2b302059a03b77f0ad0cfd98d6767e8a877157cbf6ffdccc": { + "transaction": "0300000001bdf48880317c34511911f2787d277027ad046dee48f06187c5e4c7ea0d39331b010000006b4830450221009cf93733d4ea0de4bb5b2b7c15a146ca7798bdc9c03e305c0a30fff9d9b86c5a0220653ea274a79540674ca92d08f89543a6a0e15dc2ba53bc7e9d29a694f5088dbb0121024f2a07e71f7e37ff5bcce315b2af83b32f1730c63a1de8f3bc2a3f624e15d6e4ffffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac88c85b0d000000001976a91430fcee94aa13b13de63fd0b52e384cb5590c973588ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + }, + "33b14c6bc960c5717d734d5a15dc86b2060bf6e746cc509863344204d356cee4": { + "transaction": "0300000001ccdcfff6cb5771878a7e76d698fd0cadf0773ba05920302b0d376a5636e11912010000006b483045022100e3a3d166cdb9cab262a80b8c85a3ca82f837997669ff7b3eef5be3573a1c06e202207330cfaed20b37fd63e88e1e5395383e7242c4ad91e71493ccc8a51504a8a9fd0121038d955c7bd1805614e5a7d1641433915a0833b96d96ba25375da4f217cc9fdb7affffffff0210270000000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac81a05b0d000000001976a9140a163cfcba43b87e58b1996f61376d7bd8d9805288ac00000000", + "metadata": { + "blockHash": "0000005546b84ccf1a71cbd58e2bc0f76b14f98034729c0d3095fe51cb4b4554", + "height": 615769, + "isInstantLocked": null, + "isChainLocked": null + } + } + }, + "instantLocks": {}, + "addresses": { + "ycRqwWy2pNzwNumX79MmRyvQYVpy6w3AeX": { + "address": "ycRqwWy2pNzwNumX79MmRyvQYVpy6w3AeX", + "transactions": [ + "a43f20bcb46fef22926745a27ca38f63c773f2c7c4cbb55aaf184b58b3755965", + "c8f0d780e6cedb9c37724f98cc3fecd6d5ad314db28e3cd439184bf25196ceb4" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ycuRYGdNudwRxKNDQBqHDW7aGbJU6uqhXo": { + "address": "ycuRYGdNudwRxKNDQBqHDW7aGbJU6uqhXo", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yfNFUv1vfx3EVyVGtfZNXqvFR7HLP5MGRq": { + "address": "yfNFUv1vfx3EVyVGtfZNXqvFR7HLP5MGRq", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yjDWQWhNixjKLMjzmXxWqLdJUEcgyjQh1o": { + "address": "yjDWQWhNixjKLMjzmXxWqLdJUEcgyjQh1o", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ygCCNzQM2Uj7Dfyi6sxPGBmRY6hqHGEFP9": { + "address": "ygCCNzQM2Uj7Dfyi6sxPGBmRY6hqHGEFP9", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yX5c33WWgLacu7Z1hnBTLKPSXbbGt4BFNt": { + "address": "yX5c33WWgLacu7Z1hnBTLKPSXbbGt4BFNt", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yYeyVQqiejQsFmkQjtbcigLLPVGJKHxQBL": { + "address": "yYeyVQqiejQsFmkQjtbcigLLPVGJKHxQBL", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yTFXnvNARXGPNL1ZKFHyVah3xY6JxzQaPD": { + "address": "yTFXnvNARXGPNL1ZKFHyVah3xY6JxzQaPD", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yNGpfTzZkJNhEQUk6Wj1VdS8RUVpq6qA2g": { + "address": "yNGpfTzZkJNhEQUk6Wj1VdS8RUVpq6qA2g", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ySW43fHX7uTEjJWjozu3EF3bsXywFoo1gf": { + "address": "ySW43fHX7uTEjJWjozu3EF3bsXywFoo1gf", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yM3LdfhFcMQBQoECRrh2WZwy6c8xnsA1rq": { + "address": "yM3LdfhFcMQBQoECRrh2WZwy6c8xnsA1rq", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ybDdvzUVYvqSPnvdzqgQo1ictZxTjoNTeY": { + "address": "ybDdvzUVYvqSPnvdzqgQo1ictZxTjoNTeY", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yZLvsg1LuC1q9xa1FqNoKWKHZJTLb1Rs5U": { + "address": "yZLvsg1LuC1q9xa1FqNoKWKHZJTLb1Rs5U", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yeGtNDLgTEfSYtYK84kXwvHXwRu3AQprku": { + "address": "yeGtNDLgTEfSYtYK84kXwvHXwRu3AQprku", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yPBq6vmEXnz2FtHVztLHD9HPQf8FMbnRPr": { + "address": "yPBq6vmEXnz2FtHVztLHD9HPQf8FMbnRPr", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yjcBeyf1r8EkjNFXGgX4NmNQEwk4NNH4Dp": { + "address": "yjcBeyf1r8EkjNFXGgX4NmNQEwk4NNH4Dp", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ySCxSmKAfjo8fk15TCM9chiS1dAQfJ54z2": { + "address": "ySCxSmKAfjo8fk15TCM9chiS1dAQfJ54z2", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ySBWPfBDnN2iWKt4uRoFaVPGDoRDQpo5i1": { + "address": "ySBWPfBDnN2iWKt4uRoFaVPGDoRDQpo5i1", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yXMhEgMKAknAUTNR31nYr4uSP7GMpmCgsm": { + "address": "yXMhEgMKAknAUTNR31nYr4uSP7GMpmCgsm", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yf9Zudcaof9c3KrdPVz3r7YPZJ1U2ECbXE": { + "address": "yf9Zudcaof9c3KrdPVz3r7YPZJ1U2ECbXE", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yXwFx2jAMxxntn421z9PsdzytmHRJGjyU4": { + "address": "yXwFx2jAMxxntn421z9PsdzytmHRJGjyU4", + "transactions": [ + "c8f0d780e6cedb9c37724f98cc3fecd6d5ad314db28e3cd439184bf25196ceb4", + "ca2afbe06b0187777f7e1aa8ae90c16ea094fbf1ed1b2ae8372e34c31a2cf67d" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yTDBgBUYbPq2XWkrnEWXTZGDcvbPxH1chY": { + "address": "yTDBgBUYbPq2XWkrnEWXTZGDcvbPxH1chY", + "transactions": [ + "ca2afbe06b0187777f7e1aa8ae90c16ea094fbf1ed1b2ae8372e34c31a2cf67d", + "3096b29ee16f6ec1b8e8b5f3d01beb5a231d87b51b6fa4e637e0f45b6b8b83b6" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yexVh4ARXbi7ZCUEEGEDde63t91N5e6kfC": { + "address": "yexVh4ARXbi7ZCUEEGEDde63t91N5e6kfC", + "transactions": [ + "3096b29ee16f6ec1b8e8b5f3d01beb5a231d87b51b6fa4e637e0f45b6b8b83b6", + "95fe8be7b264aa71a1a55478abce7233311b7f3a22135a6a2543988294ddbc68" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ydVc6fZz46csMbAea6xtd1AmW3Zohk9DX5": { + "address": "ydVc6fZz46csMbAea6xtd1AmW3Zohk9DX5", + "transactions": [ + "95fe8be7b264aa71a1a55478abce7233311b7f3a22135a6a2543988294ddbc68", + "267c4191e3b32241bf00cf15ff7e02460e4d6cc892afaaf3a28f88601b5b6a38" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yWvR7rm6s28SePpVi24AX1pNcBoTubsEcC": { + "address": "yWvR7rm6s28SePpVi24AX1pNcBoTubsEcC", + "transactions": [ + "267c4191e3b32241bf00cf15ff7e02460e4d6cc892afaaf3a28f88601b5b6a38", + "eb39c1c9e0e5dd8df5ec1f822a4fd96469749f56443377262d933f888568047d" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yb4yiJrSTstkTrm4Ah9cfkyYmbC3H2TPoS": { + "address": "yb4yiJrSTstkTrm4Ah9cfkyYmbC3H2TPoS", + "transactions": [ + "eb39c1c9e0e5dd8df5ec1f822a4fd96469749f56443377262d933f888568047d", + "d066ac08dc743bfe5a29fd541802f9269b3da17c71f6b2f64aabd6c68a671566" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yb5dJVhq2pi68Qm1ZfzUsux9fCqPdw5GuM": { + "address": "yb5dJVhq2pi68Qm1ZfzUsux9fCqPdw5GuM", + "transactions": [ + "d066ac08dc743bfe5a29fd541802f9269b3da17c71f6b2f64aabd6c68a671566", + "a3f4b8c9c9f49434e0437ad1aa0fa9f9c621d3a8742cee174402863a5b23fe4a" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yeSJLf7xsfenJwmwAb28suXpHLwd8bgc2K": { + "address": "yeSJLf7xsfenJwmwAb28suXpHLwd8bgc2K", + "transactions": [ + "a3f4b8c9c9f49434e0437ad1aa0fa9f9c621d3a8742cee174402863a5b23fe4a", + "eee88d2a03a0452dfe0abc9a84915f33d552110088c4382f0c459960494bf2ab" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yTqDebXPzENywmQiciNjn6U5Hkv7uBYvgm": { + "address": "yTqDebXPzENywmQiciNjn6U5Hkv7uBYvgm", + "transactions": [ + "eee88d2a03a0452dfe0abc9a84915f33d552110088c4382f0c459960494bf2ab", + "0fd66f212549dbb84d42a745e9e0bef5e6772acfaf8b6d06579cffa15ac9fa1d" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yegUezShAWzyZfM8WVSj5yzaKzVTRQC9n7": { + "address": "yegUezShAWzyZfM8WVSj5yzaKzVTRQC9n7", + "transactions": [ + "0fd66f212549dbb84d42a745e9e0bef5e6772acfaf8b6d06579cffa15ac9fa1d", + "92be5095443f506771d6f145716ab33a12b20239b5d363c9b09d5e5cd7fb3f96" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ydtkdeeX8NfAR7thLnzDPwrsiZ7c3a7bVL": { + "address": "ydtkdeeX8NfAR7thLnzDPwrsiZ7c3a7bVL", + "transactions": [ + "92be5095443f506771d6f145716ab33a12b20239b5d363c9b09d5e5cd7fb3f96", + "ea704ab4f38d786116875270c141b628f182684fd39dcd57c969237cf92f5589" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yUk83srvLCbpjkiYTpcPpftjGroSeYdHTF": { + "address": "yUk83srvLCbpjkiYTpcPpftjGroSeYdHTF", + "transactions": [ + "ea704ab4f38d786116875270c141b628f182684fd39dcd57c969237cf92f5589", + "f86445d20beccda3749058d90a605f4179a51a04b09f1262a9aefb382fe21bfd" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yScDono7QjZPqNJo27eCsrNwUAXFdTLK9P": { + "address": "yScDono7QjZPqNJo27eCsrNwUAXFdTLK9P", + "transactions": [ + "f86445d20beccda3749058d90a605f4179a51a04b09f1262a9aefb382fe21bfd", + "b2420b554dad82bffb22f10bda0124876fc6d02b4892d01833b413cad275c351" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yZBYJEqmt8rE57unfDRseXBLkdCyfDF9VX": { + "address": "yZBYJEqmt8rE57unfDRseXBLkdCyfDF9VX", + "transactions": [ + "b2420b554dad82bffb22f10bda0124876fc6d02b4892d01833b413cad275c351", + "174bbb78437049150cfe8c00ec53cd9f0ee038d0e9451e85eef25b5256a2e95e" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yhhkAYqLNJQyu5XNAT61b2b8Qo7k5Uib1b": { + "address": "yhhkAYqLNJQyu5XNAT61b2b8Qo7k5Uib1b", + "transactions": [ + "174bbb78437049150cfe8c00ec53cd9f0ee038d0e9451e85eef25b5256a2e95e", + "fec6553adbe93a1702b43a9a871979ea0294bcd8d776ae684ff7aca0d22fe0e9" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yZoLgZuUEmFANWFA8PE2zNVH1Yaz9fciGT": { + "address": "yZoLgZuUEmFANWFA8PE2zNVH1Yaz9fciGT", + "transactions": [ + "fec6553adbe93a1702b43a9a871979ea0294bcd8d776ae684ff7aca0d22fe0e9", + "fc9863caf91f51da5ae67cd8bdada46435db0e940164bee87248026c79ccebd9" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yZQVV3EaLpAehKMpPiGy7w3NRmgKD7xJX1": { + "address": "yZQVV3EaLpAehKMpPiGy7w3NRmgKD7xJX1", + "transactions": [ + "fc9863caf91f51da5ae67cd8bdada46435db0e940164bee87248026c79ccebd9", + "74789930fe5d4bab8e485f1432a79704d3f111eec959aed55584c04b7f7da2dc" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yQ7SV5m46ck1fVQrJinvA982aBAUv3TCq2": { + "address": "yQ7SV5m46ck1fVQrJinvA982aBAUv3TCq2", + "transactions": [ + "74789930fe5d4bab8e485f1432a79704d3f111eec959aed55584c04b7f7da2dc", + "4b1ec1877f9c2eaa4fbfeb034b38b21156aab4955d597fc328b6c30be1acd10e" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yaaw9BZ16eoXv4RAGBA9qbSrjvTTbqbUKq": { + "address": "yaaw9BZ16eoXv4RAGBA9qbSrjvTTbqbUKq", + "transactions": [ + "4b1ec1877f9c2eaa4fbfeb034b38b21156aab4955d597fc328b6c30be1acd10e", + "c0cbf437c0f8e980b8d64a3148a258645a821dc51631c4dfdb3dcd8324d6819e" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yjbLQQkP5tZ3aZ734vFXscFG14vDPnE1rx": { + "address": "yjbLQQkP5tZ3aZ734vFXscFG14vDPnE1rx", + "transactions": [ + "c0cbf437c0f8e980b8d64a3148a258645a821dc51631c4dfdb3dcd8324d6819e", + "5ec24edb2394c13db2d2b3fb1502cd5a4db8006b283eb6868cc44828df30148c" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yYqmE6MCitNpgyL7iRdNheG2ZP4bNCxoMV": { + "address": "yYqmE6MCitNpgyL7iRdNheG2ZP4bNCxoMV", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yQpSjWKF6HabXAMizRvMETDd6EbQixXQZ7": { + "address": "yQpSjWKF6HabXAMizRvMETDd6EbQixXQZ7", + "transactions": [ + "5ec24edb2394c13db2d2b3fb1502cd5a4db8006b283eb6868cc44828df30148c", + "f95dbbc93352dd0cfe0350ce7407eabc5ead0cd5b3265873c5544447047eddf1" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yNi1fiMR554w6MehxFoESXwUHk3v6AJELy": { + "address": "yNi1fiMR554w6MehxFoESXwUHk3v6AJELy", + "transactions": [ + "f95dbbc93352dd0cfe0350ce7407eabc5ead0cd5b3265873c5544447047eddf1", + "5cfbb3fd2dfdd75f590278a1749fe2dae2d5395eef5e87690756adb44cb5745d" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yVRRAWki91bieh6eAYjwZZpJLcgmY14D79": { + "address": "yVRRAWki91bieh6eAYjwZZpJLcgmY14D79", + "transactions": [ + "5cfbb3fd2dfdd75f590278a1749fe2dae2d5395eef5e87690756adb44cb5745d", + "34ba3e041c3026bab4f2c2c0ec1e1bb496ec72042aba9b03a324b74d1ec1d1af" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yf3NLNnFSnZQuQEFHrAU3Kzdip3AfNXBy3": { + "address": "yf3NLNnFSnZQuQEFHrAU3Kzdip3AfNXBy3", + "transactions": [ + "34ba3e041c3026bab4f2c2c0ec1e1bb496ec72042aba9b03a324b74d1ec1d1af", + "0d61420bb114d9d16b424e2822ab8412f3dec63e0b8da8df8f0fd5786782abd0" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yTpUMFdpe2WnMz9cjJp3jcDR4G6gXjW5jh": { + "address": "yTpUMFdpe2WnMz9cjJp3jcDR4G6gXjW5jh", + "transactions": [ + "0d61420bb114d9d16b424e2822ab8412f3dec63e0b8da8df8f0fd5786782abd0", + "0e62d7c53e01bd3a61065ca279bd77a6e1354c0c6a373502d7cc9328be3812a8" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yjPgE4RimwUGZFJpDZjANyioWd1SbZpcaS": { + "address": "yjPgE4RimwUGZFJpDZjANyioWd1SbZpcaS", + "transactions": [ + "0e62d7c53e01bd3a61065ca279bd77a6e1354c0c6a373502d7cc9328be3812a8", + "49c214eab55a17f80ec6e136861cbc56d507ea0c491864706b3cfa0a7f175634" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ygjDcpfLxzs8pweCQ6Ty8GioNDTgfQX3GQ": { + "address": "ygjDcpfLxzs8pweCQ6Ty8GioNDTgfQX3GQ", + "transactions": [ + "49c214eab55a17f80ec6e136861cbc56d507ea0c491864706b3cfa0a7f175634", + "d17f579a4ead6a798618d3b567c2f130cac22a0830219d03404d68d9b6a5d8ce" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yUFchfQMACPyoy6HtqNRv8gjEYKvjd3Wd6": { + "address": "yUFchfQMACPyoy6HtqNRv8gjEYKvjd3Wd6", + "transactions": [ + "d17f579a4ead6a798618d3b567c2f130cac22a0830219d03404d68d9b6a5d8ce", + "9594a1c5a05fa24a78e9b93f03ee62b56d9c90e62e62298a3cf54629a355750a" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yj1oiPUjyo2RLvpbyigfb7pLiX1piritz1": { + "address": "yj1oiPUjyo2RLvpbyigfb7pLiX1piritz1", + "transactions": [ + "9594a1c5a05fa24a78e9b93f03ee62b56d9c90e62e62298a3cf54629a355750a", + "d522f8a9406dc497893ec3e99c69a05a1b88e0a010e3f12495628e412b85663a" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ycGhUD69dS2V18LFy4qrfYhG1qJ8JnVvX7": { + "address": "ycGhUD69dS2V18LFy4qrfYhG1qJ8JnVvX7", + "transactions": [ + "d522f8a9406dc497893ec3e99c69a05a1b88e0a010e3f12495628e412b85663a", + "3ee6bec9dbe57574305a25d72362434b8f3d02a79c6228d12f0a957891f5e52d" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yj6f6KLsQHFoVCUmfbpjSGmdqnB3j9NouZ": { + "address": "yj6f6KLsQHFoVCUmfbpjSGmdqnB3j9NouZ", + "transactions": [ + "3ee6bec9dbe57574305a25d72362434b8f3d02a79c6228d12f0a957891f5e52d", + "89181c1b627d32ea7dceda330c4c44d4992e98f7cacc392691220bba800ec6a7" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ycA6K1bW4nxYH8UBvYaXpxmdnMoXPQxp8L": { + "address": "ycA6K1bW4nxYH8UBvYaXpxmdnMoXPQxp8L", + "transactions": [ + "89181c1b627d32ea7dceda330c4c44d4992e98f7cacc392691220bba800ec6a7", + "5b02921dda2f33def0951615466578b3cac8fd19ae9a9e75ee0176bf3c8e4821" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yPx12VyoeVLhMCVDiwK7Js7zDWNtNbq8JV": { + "address": "yPx12VyoeVLhMCVDiwK7Js7zDWNtNbq8JV", + "transactions": [ + "5b02921dda2f33def0951615466578b3cac8fd19ae9a9e75ee0176bf3c8e4821", + "3d2e84e85560f835084d6e6514563a16453ded77d8474244b1a5dc95c8891faf" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yMSpVBXff6XoLMwmP7N4nX4Mjo21ewkYkE": { + "address": "yMSpVBXff6XoLMwmP7N4nX4Mjo21ewkYkE", + "transactions": [ + "3d2e84e85560f835084d6e6514563a16453ded77d8474244b1a5dc95c8891faf", + "2a035c18d1a51c7164beac80f374bef6b49d084681e8035071fc0adc903b606c" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yVoywf8fK8VtPEiLyx6zknqWBjE4izUXvD": { + "address": "yVoywf8fK8VtPEiLyx6zknqWBjE4izUXvD", + "transactions": [ + "2a035c18d1a51c7164beac80f374bef6b49d084681e8035071fc0adc903b606c", + "11d14e8be169305b49e5a080f0570d2be6783c4e8bbe0c32451b97e35b188ee9" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yU4Xor7Y8XjXFNYmVebqKeaCPqDmKNxbYd": { + "address": "yU4Xor7Y8XjXFNYmVebqKeaCPqDmKNxbYd", + "transactions": [ + "11d14e8be169305b49e5a080f0570d2be6783c4e8bbe0c32451b97e35b188ee9", + "b8c506c24e66aabc59b409b2ab37cd97893dae1ef671c5e839dd8649a65586a0" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yiE7vzjGNseTtEwtXZTmjcf99hfwLziJPq": { + "address": "yiE7vzjGNseTtEwtXZTmjcf99hfwLziJPq", + "transactions": [ + "b8c506c24e66aabc59b409b2ab37cd97893dae1ef671c5e839dd8649a65586a0", + "1b33390deac7e4c58761f048ee6d04ad2770277d78f2111951347c318088f4bd" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yLmXE68Kq8UrK42QjyNWi8gFqf1ihd3UiW": { + "address": "yLmXE68Kq8UrK42QjyNWi8gFqf1ihd3UiW", + "transactions": [ + "1b33390deac7e4c58761f048ee6d04ad2770277d78f2111951347c318088f4bd", + "1219e136566a370d2b302059a03b77f0ad0cfd98d6767e8a877157cbf6ffdccc" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yQnUHRyB768tawz3iLDs5VVLYg5dGEJYQi": { + "address": "yQnUHRyB768tawz3iLDs5VVLYg5dGEJYQi", + "transactions": [ + "1219e136566a370d2b302059a03b77f0ad0cfd98d6767e8a877157cbf6ffdccc", + "33b14c6bc960c5717d734d5a15dc86b2060bf6e746cc509863344204d356cee4" + ], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yMEnFG5TBqEZXYXTg3PhENtZgGbwhw6qbX": { + "address": "yMEnFG5TBqEZXYXTg3PhENtZgGbwhw6qbX", + "transactions": [ + "33b14c6bc960c5717d734d5a15dc86b2060bf6e746cc509863344204d356cee4" + ], + "utxos": { + "33b14c6bc960c5717d734d5a15dc86b2060bf6e746cc509863344204d356cee4-1": { + "satoshis": 224108673, + "script": "76a9140a163cfcba43b87e58b1996f61376d7bd8d9805288ac" + } + }, + "balanceSat": 224108673, + "unconfirmedBalanceSat": 0 + }, + "ybPPRHGDK6HUjAapixJaHjpFrBP7p1eNHX": { + "address": "ybPPRHGDK6HUjAapixJaHjpFrBP7p1eNHX", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yeKckmFWSPq4ZgxrKtqgWLfspT5YSuinAw": { + "address": "yeKckmFWSPq4ZgxrKtqgWLfspT5YSuinAw", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yX5fXtxaF5Umi3wkCvFCCbYjQniex8YfZb": { + "address": "yX5fXtxaF5Umi3wkCvFCCbYjQniex8YfZb", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yYXYNLd3pwDnZMo8VvFZWtuTaW97yMBtvh": { + "address": "yYXYNLd3pwDnZMo8VvFZWtuTaW97yMBtvh", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yPxjUsH2X9SFnpXt4ke7boWdVJb1jLRU2U": { + "address": "yPxjUsH2X9SFnpXt4ke7boWdVJb1jLRU2U", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yPaJs6aVAxsSsjsaeoWEEf1KWNJHA4osTX": { + "address": "yPaJs6aVAxsSsjsaeoWEEf1KWNJHA4osTX", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yRtTcfySvuZHmnyTGnSVFJiuXQLAMtA8Zp": { + "address": "yRtTcfySvuZHmnyTGnSVFJiuXQLAMtA8Zp", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yWniGBz9g5WzC8SNw2Yz6rTiw8gXCwviHH": { + "address": "yWniGBz9g5WzC8SNw2Yz6rTiw8gXCwviHH", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yTBBLUse991FY17xVQxTW6gELhDPLTbrfg": { + "address": "yTBBLUse991FY17xVQxTW6gELhDPLTbrfg", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yW8ZcyB43XEaNb1rC3vU8B787pCFd5A92G": { + "address": "yW8ZcyB43XEaNb1rC3vU8B787pCFd5A92G", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yZFMjHZLcw8VLZSfBDamfaowiespUADi8x": { + "address": "yZFMjHZLcw8VLZSfBDamfaowiespUADi8x", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yS4CoPF6Bb6vZAorsTQGddhdqzUNA4NQkq": { + "address": "yS4CoPF6Bb6vZAorsTQGddhdqzUNA4NQkq", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yeKFhYBcoDB5BFQVatGEY6xAWWgyAH2znL": { + "address": "yeKFhYBcoDB5BFQVatGEY6xAWWgyAH2znL", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yVtwa9msBFy3s5YW4zpKaRFEAS3CK5h1sd": { + "address": "yVtwa9msBFy3s5YW4zpKaRFEAS3CK5h1sd", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ySdqTcWTqRHJBwsymifjg4j6hBTUtrhjCo": { + "address": "ySdqTcWTqRHJBwsymifjg4j6hBTUtrhjCo", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yXgzDeQ6Fa3gzthByHYeiwyVKYzDov7EtR": { + "address": "yXgzDeQ6Fa3gzthByHYeiwyVKYzDov7EtR", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yZVUpv1RfnH4gTiTtuarXVpAcK1h9irJ7q": { + "address": "yZVUpv1RfnH4gTiTtuarXVpAcK1h9irJ7q", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "yS4osisWi5dpSmUZFav56Uxj4Uhc3i7RaL": { + "address": "yS4osisWi5dpSmUZFav56Uxj4Uhc3i7RaL", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ygAb2f6gztLoPdZrma8BRjm3MtNGZFrUbX": { + "address": "ygAb2f6gztLoPdZrma8BRjm3MtNGZFrUbX", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + }, + "ygukA9r4bM3CDS1gkFfTfVNozC7c5FvnGj": { + "address": "ygukA9r4bM3CDS1gkFfTfVNozC7c5FvnGj", + "transactions": [], + "utxos": {}, + "balanceSat": 0, + "unconfirmedBalanceSat": 0 + } + } + } +} diff --git a/fixtures/chains/testnet/blockheaders.json b/fixtures/chains/testnet/blockheaders.json new file mode 100644 index 000000000..d58e0885f --- /dev/null +++ b/fixtures/chains/testnet/blockheaders.json @@ -0,0 +1,12 @@ +[ + { + "height": 600000, + "blockheader": "00000020a69faca490012f4371e4bd40a40167809e6fe1b03c9c5dcb7df247f2fa000000b4b8c2d4a78fb35e50a4eda0bd3a5b9b32f4d739b28a4266f24775b3b79d5b61c73275610e04021ea74d0000", + "hash":"000000de786e659950e0f27681faf1a91871d15de264d0b769cb5941c1d807c3" + }, + { + "height": 610000, + "blockheader": "00000020af5155e1f8d5b60fb247ac9bb8badbdce1fa72302336c7daab53585caf0000006d495b9629989ac3699d528956b1f617fb51a199ef4277c3f45f4606aba8e461d1c78a61210b011e94ef0000", + "hash":"00000094d124cfb68d6d59ffaec9f7d63965cb894855684e23a586274b49708f" + } +] \ No newline at end of file diff --git a/fixtures/chains/testnet/transactions.json b/fixtures/chains/testnet/transactions.json new file mode 100644 index 000000000..5fd376a7a --- /dev/null +++ b/fixtures/chains/testnet/transactions.json @@ -0,0 +1,14 @@ +[ + { + "blockHash": "000000b2537d9b3468e02dc6f49fb8bbb5c9d8b77895df1e76816fbad5555d7d", + "transaction": "02000000019583d226737edfad682cc1f0297f688bfca4c0046062ed3e88fa47dd47eab2b2010000006a473044022021550232cc659fa412deb2ea98956faf9ff0efea3a04029bb63022f6809105ce0220230a3f955a3ee7563f5b006342d8db940f3b29f4e312701bf51720575c7a7a8a012103f3d2b2ddfe6ffad2b8fc1708f2eafadfed36bf31a05dfa04b1fc176526475b0ffeffffff02a003d806000000001976a91464220a1c12690ec26d837b3be0a2e3588bb4b79188ac4c5bc92b250000001976a9143e89746b9aa52703ab784bc0df467b160406ffb988acb56b0900", + "height": 617398, + "isInstantLocked": true, + "isChainLocked": true + }, + { + "transaction": "0200000001c7d66bb85e0069c221b44b07f49f52cc4f2e54f70e14430b94888327763a66a9010000006b483045022100da5b319f73e6adfee751f33308f5a8c1fceeab2683e15e132d79053b3118639602204262022fb85f88d9802649a289a1134b678efcf708faaeae8f101e8eab785054012102bc626898b49f31f5194de7bc68004401639a20cfa82e4c2eac9684a91fc47a57feffffff0270f2f605000000001976a91464220a1c12690ec26d837b3be0a2e3588bb4b79188ac912e250c250000001976a91415e1edb5c5d9e67d0e36f94343b3eff26bb76d1088ac266e0900", + "blockHash":"0000005c81a683007e86e75c76b4b2feca229f806702ca92953562f2ae628ce7", + "height": 618023 + } +] \ No newline at end of file diff --git a/fixtures/wallets/c922713eac.json b/fixtures/wallets/c922713eac.json new file mode 100644 index 000000000..3fac82777 --- /dev/null +++ b/fixtures/wallets/c922713eac.json @@ -0,0 +1,97 @@ +{ + "walletId": "c922713eac", + "state": { + "mnemonic": null, + "paths": { + "m/44'/1'/0'": { + "path": "m/44'/1'/0'", + "addresses": { + "m/0/0": "ycRqwWy2pNzwNumX79MmRyvQYVpy6w3AeX", + "m/0/1": "ycuRYGdNudwRxKNDQBqHDW7aGbJU6uqhXo", + "m/0/2": "yfNFUv1vfx3EVyVGtfZNXqvFR7HLP5MGRq", + "m/0/3": "yjDWQWhNixjKLMjzmXxWqLdJUEcgyjQh1o", + "m/0/4": "ygCCNzQM2Uj7Dfyi6sxPGBmRY6hqHGEFP9", + "m/0/5": "yX5c33WWgLacu7Z1hnBTLKPSXbbGt4BFNt", + "m/0/6": "yYeyVQqiejQsFmkQjtbcigLLPVGJKHxQBL", + "m/0/7": "yTFXnvNARXGPNL1ZKFHyVah3xY6JxzQaPD", + "m/0/8": "yNGpfTzZkJNhEQUk6Wj1VdS8RUVpq6qA2g", + "m/0/9": "ySW43fHX7uTEjJWjozu3EF3bsXywFoo1gf", + "m/0/10": "yM3LdfhFcMQBQoECRrh2WZwy6c8xnsA1rq", + "m/0/11": "ybDdvzUVYvqSPnvdzqgQo1ictZxTjoNTeY", + "m/0/12": "yZLvsg1LuC1q9xa1FqNoKWKHZJTLb1Rs5U", + "m/0/13": "yeGtNDLgTEfSYtYK84kXwvHXwRu3AQprku", + "m/0/14": "yPBq6vmEXnz2FtHVztLHD9HPQf8FMbnRPr", + "m/0/15": "yjcBeyf1r8EkjNFXGgX4NmNQEwk4NNH4Dp", + "m/0/16": "ySCxSmKAfjo8fk15TCM9chiS1dAQfJ54z2", + "m/0/17": "ySBWPfBDnN2iWKt4uRoFaVPGDoRDQpo5i1", + "m/0/18": "yXMhEgMKAknAUTNR31nYr4uSP7GMpmCgsm", + "m/0/19": "yf9Zudcaof9c3KrdPVz3r7YPZJ1U2ECbXE", + "m/1/0": "yXwFx2jAMxxntn421z9PsdzytmHRJGjyU4", + "m/1/1": "yTDBgBUYbPq2XWkrnEWXTZGDcvbPxH1chY", + "m/1/2": "yexVh4ARXbi7ZCUEEGEDde63t91N5e6kfC", + "m/1/3": "ydVc6fZz46csMbAea6xtd1AmW3Zohk9DX5", + "m/1/4": "yWvR7rm6s28SePpVi24AX1pNcBoTubsEcC", + "m/1/5": "yb4yiJrSTstkTrm4Ah9cfkyYmbC3H2TPoS", + "m/1/6": "yb5dJVhq2pi68Qm1ZfzUsux9fCqPdw5GuM", + "m/1/7": "yeSJLf7xsfenJwmwAb28suXpHLwd8bgc2K", + "m/1/8": "yTqDebXPzENywmQiciNjn6U5Hkv7uBYvgm", + "m/1/9": "yegUezShAWzyZfM8WVSj5yzaKzVTRQC9n7", + "m/1/10": "ydtkdeeX8NfAR7thLnzDPwrsiZ7c3a7bVL", + "m/1/11": "yUk83srvLCbpjkiYTpcPpftjGroSeYdHTF", + "m/1/12": "yScDono7QjZPqNJo27eCsrNwUAXFdTLK9P", + "m/1/13": "yZBYJEqmt8rE57unfDRseXBLkdCyfDF9VX", + "m/1/14": "yhhkAYqLNJQyu5XNAT61b2b8Qo7k5Uib1b", + "m/1/15": "yZoLgZuUEmFANWFA8PE2zNVH1Yaz9fciGT", + "m/1/16": "yZQVV3EaLpAehKMpPiGy7w3NRmgKD7xJX1", + "m/1/17": "yQ7SV5m46ck1fVQrJinvA982aBAUv3TCq2", + "m/1/18": "yaaw9BZ16eoXv4RAGBA9qbSrjvTTbqbUKq", + "m/1/19": "yjbLQQkP5tZ3aZ734vFXscFG14vDPnE1rx", + "m/0/20": "yYqmE6MCitNpgyL7iRdNheG2ZP4bNCxoMV", + "m/1/20": "yQpSjWKF6HabXAMizRvMETDd6EbQixXQZ7", + "m/1/21": "yNi1fiMR554w6MehxFoESXwUHk3v6AJELy", + "m/1/22": "yVRRAWki91bieh6eAYjwZZpJLcgmY14D79", + "m/1/23": "yf3NLNnFSnZQuQEFHrAU3Kzdip3AfNXBy3", + "m/1/24": "yTpUMFdpe2WnMz9cjJp3jcDR4G6gXjW5jh", + "m/1/25": "yjPgE4RimwUGZFJpDZjANyioWd1SbZpcaS", + "m/1/26": "ygjDcpfLxzs8pweCQ6Ty8GioNDTgfQX3GQ", + "m/1/27": "yUFchfQMACPyoy6HtqNRv8gjEYKvjd3Wd6", + "m/1/28": "yj1oiPUjyo2RLvpbyigfb7pLiX1piritz1", + "m/1/29": "ycGhUD69dS2V18LFy4qrfYhG1qJ8JnVvX7", + "m/1/30": "yj6f6KLsQHFoVCUmfbpjSGmdqnB3j9NouZ", + "m/1/31": "ycA6K1bW4nxYH8UBvYaXpxmdnMoXPQxp8L", + "m/1/32": "yPx12VyoeVLhMCVDiwK7Js7zDWNtNbq8JV", + "m/1/33": "yMSpVBXff6XoLMwmP7N4nX4Mjo21ewkYkE", + "m/1/34": "yVoywf8fK8VtPEiLyx6zknqWBjE4izUXvD", + "m/1/35": "yU4Xor7Y8XjXFNYmVebqKeaCPqDmKNxbYd", + "m/1/36": "yiE7vzjGNseTtEwtXZTmjcf99hfwLziJPq", + "m/1/37": "yLmXE68Kq8UrK42QjyNWi8gFqf1ihd3UiW", + "m/1/38": "yQnUHRyB768tawz3iLDs5VVLYg5dGEJYQi", + "m/1/39": "yMEnFG5TBqEZXYXTg3PhENtZgGbwhw6qbX", + "m/1/40": "ybPPRHGDK6HUjAapixJaHjpFrBP7p1eNHX", + "m/1/41": "yeKckmFWSPq4ZgxrKtqgWLfspT5YSuinAw", + "m/1/42": "yX5fXtxaF5Umi3wkCvFCCbYjQniex8YfZb", + "m/1/43": "yYXYNLd3pwDnZMo8VvFZWtuTaW97yMBtvh", + "m/1/44": "yPxjUsH2X9SFnpXt4ke7boWdVJb1jLRU2U", + "m/1/45": "yPaJs6aVAxsSsjsaeoWEEf1KWNJHA4osTX", + "m/1/46": "yRtTcfySvuZHmnyTGnSVFJiuXQLAMtA8Zp", + "m/1/47": "yWniGBz9g5WzC8SNw2Yz6rTiw8gXCwviHH", + "m/1/48": "yTBBLUse991FY17xVQxTW6gELhDPLTbrfg", + "m/1/49": "yW8ZcyB43XEaNb1rC3vU8B787pCFd5A92G", + "m/1/50": "yZFMjHZLcw8VLZSfBDamfaowiespUADi8x", + "m/1/51": "yS4CoPF6Bb6vZAorsTQGddhdqzUNA4NQkq", + "m/1/52": "yeKFhYBcoDB5BFQVatGEY6xAWWgyAH2znL", + "m/1/53": "yVtwa9msBFy3s5YW4zpKaRFEAS3CK5h1sd", + "m/1/54": "ySdqTcWTqRHJBwsymifjg4j6hBTUtrhjCo", + "m/1/55": "yXgzDeQ6Fa3gzthByHYeiwyVKYzDov7EtR", + "m/1/56": "yZVUpv1RfnH4gTiTtuarXVpAcK1h9irJ7q", + "m/1/57": "yS4osisWi5dpSmUZFav56Uxj4Uhc3i7RaL", + "m/1/58": "ygAb2f6gztLoPdZrma8BRjm3MtNGZFrUbX", + "m/1/59": "ygukA9r4bM3CDS1gkFfTfVNozC7c5FvnGj" + } + } + }, + "identities": { + "0": "D8oTFRQqiKcLEYXZBUVLsM9jBctq7YcM7MAXXT9GJesK" + } + } +} diff --git a/src/CONSTANTS.js b/src/CONSTANTS.js index 87edf80f4..838efb8ef 100644 --- a/src/CONSTANTS.js +++ b/src/CONSTANTS.js @@ -46,7 +46,9 @@ const CONSTANTS = { PRIVATEKEY: 'privateKey', // TODO: DEPRECATE. SINGLE_ADDRESS: 'single_address', + // TODO: DEPRECATE. HDWALLET: 'hdwallet', + HDPRIVATE: 'hdprivate', HDPUBLIC: 'hdpublic', }, // List of account function and properties that can be injected in a plugin @@ -79,7 +81,6 @@ const CONSTANTS = { ], UNSAFE_PROPERTIES: [ 'storage', - 'keyChain', 'identities', ], SAFE_PROPERTIES: [ diff --git a/src/EVENTS.js b/src/EVENTS.js index 7be5a5edd..1f6448ffc 100644 --- a/src/EVENTS.js +++ b/src/EVENTS.js @@ -10,6 +10,7 @@ module.exports = { TRANSACTION: 'transaction', BLOCKHEADER: 'blockheader', FETCHED_ADDRESS: 'FETCHED/ADDRESS', + UPDATED_ADDRESS: 'UPDATED/ADDRESS', ERROR_UPDATE_ADDRESS: 'ERROR/UPDATE_ADDRESS', FETCHED_TRANSACTION: 'FETCHED/TRANSACTION', FETCHED_UNCONFIRMED_TRANSACTION: 'FETCHED/UNCONFIRMED_TRANSACTION', diff --git a/src/errors/InjectionToPluginUnallowed.js b/src/errors/InjectionToPluginUnallowed.js index 3d7e57f67..6d6767f0d 100644 --- a/src/errors/InjectionToPluginUnallowed.js +++ b/src/errors/InjectionToPluginUnallowed.js @@ -1,8 +1,8 @@ const WalletLibError = require('./WalletLibError'); class InjectionToPluginUnallowed extends WalletLibError { - constructor(pluginName) { - super(`Injection of plugin : ${pluginName} Unallowed`); + constructor(currentPluginName, injectingPluginName) { + super(`Injection of plugin : ${injectingPluginName} into ${currentPluginName} not allowed`); } } diff --git a/src/index.d.ts b/src/index.d.ts index 9ea8f7c74..20ab7dd08 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -4,7 +4,11 @@ import { Account } from "./types/Account/Account"; import { Wallet } from "./types/Wallet/Wallet"; import { Identities } from "./types/Identities/Identities"; +import { IdentitiesStore } from "./types/IdentitiesStore/IdentitiesStore"; import { KeyChain } from "./types/KeyChain/KeyChain"; +import { KeyChainStore } from "./types/KeyChainStore/KeyChainStore"; +import { Storage } from "./types/Storage/Storage"; +import { ChainStore } from "./types/ChainStore/ChainStore"; import CONSTANTS from "./CONSTANTS"; import EVENTS from "./EVENTS"; import utils from "./utils"; @@ -13,8 +17,11 @@ import plugins from "./plugins"; export { Account, Wallet, + ChainStore, KeyChain, + KeyChainStore, Identities, + IdentitiesStore, EVENTS, CONSTANTS, utils, diff --git a/src/index.js b/src/index.js index 2fa9d8a9e..c5284a72f 100644 --- a/src/index.js +++ b/src/index.js @@ -2,20 +2,28 @@ // polyfill included here. Making it work with webpack is rather tricky, so it is used as per // documentation: https://github.com/YuzuJS/setImmediate#usage require('setimmediate'); -const Wallet = require('./types/Wallet/Wallet'); const Account = require('./types/Account/Account'); +const ChainStore = require('./types/ChainStore/ChainStore'); const Identities = require('./types/Identities/Identities'); const KeyChain = require('./types/KeyChain/KeyChain'); +const KeyChainStore = require('./types/KeyChainStore/KeyChainStore'); +const Storage = require('./types/Storage/Storage'); +const Wallet = require('./types/Wallet/Wallet'); +const WalletStore = require('./types/WalletStore/WalletStore'); const EVENTS = require('./EVENTS'); const CONSTANTS = require('./CONSTANTS'); const utils = require('./utils'); const plugins = require('./plugins'); module.exports = { - Wallet, Account, + ChainStore, Identities, KeyChain, + KeyChainStore, + Storage, + Wallet, + WalletStore, EVENTS, CONSTANTS, utils, diff --git a/src/plugins/Plugins/ChainPlugin.js b/src/plugins/Plugins/ChainPlugin.js index af0d1c45d..9c659c375 100644 --- a/src/plugins/Plugins/ChainPlugin.js +++ b/src/plugins/Plugins/ChainPlugin.js @@ -33,7 +33,10 @@ class ChainPlugin extends StandardPlugin { */ async execBlockListener() { const self = this; - const { network } = this.storage.store.wallets[this.walletId]; + const { network } = this.storage.application; + const chainStore = this.storage.getChainStore(network); + + // const { network } = this.storage.store.wallets[this.walletId]; if (!this.isSubscribedToBlocks) { self.transport.on(EVENTS.BLOCK, async (ev) => { @@ -41,7 +44,7 @@ class ChainPlugin extends StandardPlugin { const { payload: block } = ev; this.parentEvents.emit(EVENTS.BLOCK, { type: EVENTS.BLOCK, payload: block }); // We do not announce BLOCKHEADER as this is done by Storage - await self.storage.importBlockHeader(block.header); + await chainStore.importBlockHeader(block.header); }); self.transport.on(EVENTS.BLOCKHEIGHT_CHANGED, async (ev) => { const { payload: blockheight } = ev; @@ -50,7 +53,7 @@ class ChainPlugin extends StandardPlugin { type: EVENTS.BLOCKHEIGHT_CHANGED, payload: blockheight, }); - this.storage.store.chains[network.toString()].blockHeight = blockheight; + chainStore.state.blockHeight = blockheight; logger.debug(`ChainPlugin - setting chain blockheight ${blockheight}`); }); await self.transport.subscribeToBlocks(); @@ -69,20 +72,20 @@ class ChainPlugin extends StandardPlugin { return false; } + const { network } = this.storage.application; + const chainStore = this.storage.getChainStore(network); const { chain: { blocksCount: blocks }, network: { fee: { relay } } } = res; - const { network } = this.storage.store.wallets[this.walletId]; - logger.debug('ChainPlugin - Setting up starting blockHeight', blocks); - this.storage.store.chains[network.toString()].blockHeight = blocks; + chainStore.state.blockHeight = blocks; if (relay) { - this.storage.store.chains[network.toString()].fees.minRelay = dashToDuffs(relay); + chainStore.state.fees.minRelay = dashToDuffs(relay); } const bestBlock = await this.transport.getBlockHeaderByHeight(blocks); - await this.storage.importBlockHeader(bestBlock); + await chainStore.importBlockHeader(bestBlock); return true; } diff --git a/src/plugins/StandardPlugin.js b/src/plugins/StandardPlugin.js index 4b2ec8fdf..6a5c5a22b 100644 --- a/src/plugins/StandardPlugin.js +++ b/src/plugins/StandardPlugin.js @@ -63,7 +63,7 @@ class StandardPlugin extends EventEmitter { // this.parentEvents = {on:obj.on, emit:obj.emit}; this.parentEvents = obj; } else { - this.emit('error', new InjectionToPluginUnallowed(name), { + this.emit('error', new InjectionToPluginUnallowed(this.name, name), { type: 'plugin', pluginType: 'plugin', pluginName: this.name, diff --git a/src/plugins/Workers/IdentitySyncWorker.js b/src/plugins/Workers/IdentitySyncWorker.js index c11cdd4e4..99b5f3db9 100644 --- a/src/plugins/Workers/IdentitySyncWorker.js +++ b/src/plugins/Workers/IdentitySyncWorker.js @@ -26,7 +26,8 @@ class IdentitySyncWorker extends Worker { } async execute() { - const indexedIds = await this.storage.getIndexedIdentityIds(this.walletId); + const walletStore = this.storage.getWalletStore(this.walletId); + const indexedIds = await walletStore.getIndexedIdentityIds(); // Add gaps to empty indices const unusedIndices = []; @@ -98,11 +99,12 @@ class IdentitySyncWorker extends Worker { logger.silly(`IdentitySyncWorker - got ${Identifier.from(fetchedId)} at ${index}`); // eslint-disable-next-line no-await-in-loop - await this.storage.insertIdentityIdAtIndex( - this.walletId, - Identifier.from(fetchedId).toString(), - index, - ); + await this.storage + .getWalletStore(this.walletId) + .insertIdentityIdAtIndex( + Identifier.from(fetchedId).toString(), + index, + ); } logger.silly('IdentitySyncWorker - sync finished'); diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js b/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js index 73d4bfda6..db518ac96 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js @@ -25,6 +25,7 @@ class TransactionSyncStreamWorker extends Worker { 'importBlockHeader', 'importInstantLock', 'storage', + 'keyChainStore', 'transport', 'walletId', 'getAddress', @@ -49,12 +50,11 @@ class TransactionSyncStreamWorker extends Worker { * @param {string[]} addressList * @param {string} network */ - static filterWalletTransactions(transactions, addressList, network) { + static filterAddressesTransactions(transactions, addressList, network) { const spentOutputs = []; const unspentOutputs = []; const filteredTransactions = transactions.filter((tx) => { let isWalletTransaction = false; - tx.inputs.forEach((input) => { if (input.script) { const addr = input.script.toAddress(network).toString(); @@ -124,7 +124,6 @@ class TransactionSyncStreamWorker extends Worker { .getMessagesList() .map((instantSendLock) => new InstantLock(Buffer.from(instantSendLock))); } - return walletTransactions; } @@ -135,11 +134,11 @@ class TransactionSyncStreamWorker extends Worker { const { skipSynchronizationBeforeHeight, skipSynchronization, - } = (this.storage.store.syncOptions || {}); + } = (this.storage.application.syncOptions || {}); if (skipSynchronization) { logger.debug('TransactionSyncStreamWorker - Wallet created from a new mnemonic. Sync from the best block height.'); - const bestBlockHeight = this.storage.store.chains[this.network.toString()].blockHeight; + const bestBlockHeight = this.storage.getChainStore(this.network.toString()).blockHeight; this.setLastSyncedBlockHeight(bestBlockHeight); return; } @@ -238,25 +237,15 @@ class TransactionSyncStreamWorker extends Worker { } setLastSyncedBlockHash(hash) { - const { walletId } = this; - const accountsStore = this.storage.store.wallets[walletId].accounts; + const applicationStore = this.storage.application; - const accountStore = ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(this.walletType)) - ? accountsStore[this.BIP44PATH.toString()] - : accountsStore[this.index.toString()]; + applicationStore.blockHash = hash; - accountStore.blockHash = hash; - - return accountStore.blockHash; + return applicationStore.blockHash; } getLastSyncedBlockHash() { - const { walletId } = this; - const accountsStore = this.storage.store.wallets[walletId].accounts; - - const { blockHash } = ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(this.walletType)) - ? accountsStore[this.BIP44PATH.toString()] - : accountsStore[this.index.toString()]; + const { blockHash } = this.storage.application; return blockHash; } diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/handlers/onStreamError.js b/src/plugins/Workers/TransactionSyncStreamWorker/handlers/onStreamError.js index fb3577c98..624c58210 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/handlers/onStreamError.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/handlers/onStreamError.js @@ -2,6 +2,7 @@ const logger = require('../../../../logger'); function onStreamError(error, reject) { logger.silly('TransactionSyncStreamWorker - end stream on error'); + logger.silly(error.message); reject(error); } module.exports = onStreamError; diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js b/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js index 7e3f57e53..e87d86e14 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js @@ -1,15 +1,5 @@ -const { WALLET_TYPES } = require('../../../../CONSTANTS'); - module.exports = function getAddressesToSync() { - const { BIP44PATH, walletId, walletType } = this; - const { addresses } = this.storage.getStore().wallets[walletId]; - - const isHDWallet = [WALLET_TYPES.HDPUBLIC, WALLET_TYPES.HDWALLET].includes(walletType); - // We have two cases, for privateKey based wallet, we return all address in store. - // But for HDWallet, addresses in store can be of another account, that we filter out - return Object.keys(addresses) - .map((addressType) => Object.values(addresses[addressType])) - .flatMap((addressList) => addressList) - .filter((accountAddress) => !isHDWallet || accountAddress.path.startsWith(BIP44PATH)) - .map((filteredAccountAddress) => filteredAccountAddress.address); + return this.keyChainStore.getKeyChains() + .map((keychain) => keychain.getWatchedAddresses()) + .reduce((pre, cur) => pre.concat(cur)); }; diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.spec.js b/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.spec.js index 16597f841..4a6f9e6cc 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.spec.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.spec.js @@ -1,5 +1,27 @@ const { expect } = require('chai'); const getAddressesToSync = require('./getAddressesToSync'); +const KeyChainStore = require("../../../../types/KeyChainStore/KeyChainStore"); +const KeyChain = require("../../../../types/KeyChain/KeyChain"); +const { HDPrivateKey, HDPublicKey, PrivateKey } = require("@dashevo/dashcore-lib"); + + +const privateKey = new PrivateKey('ee56be968a42e58fda23b83da17f90e002cafbe35a702c2f5598b13fdaa238db', 'testnet') +const hdprivateKey1 = new HDPrivateKey("xprv9s21ZrQH143K39R9Ux28kCBUHcQFdBeVE2CXFVz6GnA2a6pqTsPhHR5QHtMP5ZTRpYkKqc9ifjkJ2V1h318qWsYgyxCBUurRdTNthjgwKMw", 'testnet'); +const hdpublicKey1 = new HDPublicKey("xpub661MyMwAqRbcFhaucFQun3ivEyA5gy5NKnjr1xMUVkyqdF3VNNy3TLinwnYMSUye5FF5pDSrn2SPX3zvKRQGrpZ44VVUBeuxuzov7enWpkf",'testnet'); + +const keychainPrivate1 = new KeyChain({privateKey}); +const keychainHDPrivate1 = new KeyChain({HDPrivateKey: hdprivateKey1}); +const keychainHDPublic1 = new KeyChain({HDPublicKey: hdpublicKey1}); + +const keychainStorePrivateKeyWallet = new KeyChainStore(); +keychainStorePrivateKeyWallet.addKeyChain(keychainPrivate1, { isMasterKeyChain: true}); +keychainPrivate1.getForPath(0, { isWatched: true }) + +const keychainStoreHDPrivateKeyWallet = new KeyChainStore(); +keychainStoreHDPrivateKeyWallet.addKeyChain(keychainHDPrivate1, { isMasterKeyChain: true}); +keychainHDPrivate1.getForPath(`m/0/0`, { isWatched: true }) +keychainHDPrivate1.getForPath(`m/0/1`, { isWatched: true }) + const mockedStore1 = { wallets: { @@ -107,31 +129,30 @@ const mockedStore2 = { const mockSelfPrivateKeyType = { storage: { getStore:()=>mockedStore1 }, + keyChainStore: keychainStorePrivateKeyWallet, walletId: '123456789', walletType: 'privateKey', } + const mockSelfIndex0 = { storage: { getStore:()=>mockedStore2 }, + keyChainStore: keychainStoreHDPrivateKeyWallet, walletId: '123456789', walletType: 'hdwallet', BIP44PATH: `m/44'/1'/0'` } -const mockSelfIndex1 = { - ...mockSelfIndex0, - BIP44PATH: `m/44'/1'/1'` -} describe('TransactionSyncStreamWorker#getAddressesToSync', function suite() { it('should correctly fetch addresses to sync', async () => { - const addressesIndex0 = getAddressesToSync.call(mockSelfIndex0 ); - expect(addressesIndex0).to.deep.equal(['yizmJb63ygipuJaRgYtpWCV2erQodmaZt8', 'yizmJb63ygipuJaRgYtpWCV2erQodmaZt9']) - - const addressesIndex1 = getAddressesToSync.call(mockSelfIndex1 ); - expect(addressesIndex1).to.deep.equal(['yQ5TfKcj3NHM4V4K5VBgoFJj9Q4LKX13gn']) + const addressesIndex0 = getAddressesToSync.call(mockSelfIndex0); + expect(addressesIndex0).to.deep.equal([ + 'Xpkr9M3DP8RgcWw4SHUW75PYtmU1Lh5Ss2', + 'Xp1kwhXoUVHKRKmoXt3dB4i4KhryHSYjtW' + ]) const addressesIndex2 = getAddressesToSync.call(mockSelfPrivateKeyType ); - expect(addressesIndex2).to.deep.equal(['yizmJb63ygipuJaRgYtpWCV2erQodmaZt1']) + expect(addressesIndex2).to.deep.equal(['yZprpQkn7FYUHjqm3dY4sCs9SorMCi4oyR']) }); }); diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/methods/getLastSyncedBlockHeight.js b/src/plugins/Workers/TransactionSyncStreamWorker/methods/getLastSyncedBlockHeight.js index ef9ca277d..85e0a4aea 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/methods/getLastSyncedBlockHeight.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/methods/getLastSyncedBlockHeight.js @@ -1,15 +1,9 @@ -const { WALLET_TYPES } = require('../../../../CONSTANTS'); /** * Return last synced block height * @return {number} */ module.exports = function getLastSyncedBlockHeight() { - const { walletId } = this; - const accountsStore = this.storage.store.wallets[walletId].accounts; - - let { blockHeight } = ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(this.walletType)) - ? accountsStore[this.BIP44PATH.toString()] - : accountsStore[this.index.toString()]; + let { blockHeight } = this.storage.application; // Fix Genesis issue on DCore if (blockHeight === 0) blockHeight = 1; diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/methods/handleTransactionFromStream.js b/src/plugins/Workers/TransactionSyncStreamWorker/methods/handleTransactionFromStream.js index 299e4c48c..eca6ee66b 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/methods/handleTransactionFromStream.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/methods/handleTransactionFromStream.js @@ -7,7 +7,6 @@ async function handleTransactionFromStream(transaction) { // eslint-disable-next-line no-restricted-syntax // eslint-disable-next-line no-underscore-dangle const transactionHash = transaction.hash; - this.pendingRequest[transactionHash] = { isProcessing: true, type: 'transaction' }; // eslint-disable-next-line no-await-in-loop const getTransactionResponse = await this.transport.getTransaction(transactionHash); @@ -62,8 +61,8 @@ async function handleTransactionFromStream(transaction) { const metadata = { blockHash: getTransactionResponse.blockHash, height: getTransactionResponse.height, - instantLocked: getTransactionResponse.instantLocked, - chainLocked: getTransactionResponse.chainLocked, + instantLocked: getTransactionResponse.isInstantLocked, + chainLocked: getTransactionResponse.isChainLocked, }; delete this.pendingRequest[transactionHash]; diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/methods/processChunks.js b/src/plugins/Workers/TransactionSyncStreamWorker/methods/processChunks.js index 19e51bff8..ce586c8fd 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/methods/processChunks.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/methods/processChunks.js @@ -3,6 +3,7 @@ const GrpcError = require('@dashevo/grpc-common/lib/server/error/GrpcError'); const GrpcErrorCodes = require('@dashevo/grpc-common/lib/server/error/GrpcErrorCodes'); const logger = require('../../../../logger'); const isBrowser = require('../../../../utils/isBrowser'); +const {chain} = require("lodash/seq"); function isAnyIntersection(arrayA, arrayB) { const intersection = arrayA.filter((e) => arrayB.indexOf(e) > -1); @@ -23,20 +24,22 @@ async function processChunks(dataChunk) { const transactionsFromResponse = this.constructor .getTransactionListFromStreamResponse(dataChunk); - const walletTransactions = this.constructor - .filterWalletTransactions(transactionsFromResponse, addresses, network); - if (walletTransactions.transactions.length) { + const addressesTransaction = this.constructor + .filterAddressesTransactions(transactionsFromResponse, addresses, network); + + if (addressesTransaction.transactions.length) { + // Normalizing format of transaction for account.importTransactions + const addressesTransactionsWithoutMetadata = addressesTransaction.transactions.map((tx) => [tx]); // When a transaction exist, there is multiple things we need to do : // 1) The transaction itself needs to be imported const addressesGeneratedCount = await self - .importTransactions(walletTransactions.transactions); - + .importTransactions(addressesTransactionsWithoutMetadata); // 2) Transaction metadata need to be fetched and imported as well. // as such event might happen in the future // As we require height information, we fetch transaction using client - const awaitingMetadataPromises = walletTransactions.transactions + const awaitingMetadataPromises = addressesTransaction.transactions .map((transaction) => self.handleTransactionFromStream(transaction) .then(({ transactionResponse, @@ -46,6 +49,7 @@ async function processChunks(dataChunk) { Promise .all(awaitingMetadataPromises) .then(async (transactionsWithMetadata) => { + // Import into account await self.importTransactions(transactionsWithMetadata); }); @@ -80,10 +84,10 @@ async function processChunks(dataChunk) { // Wrapping `cancel` in `setImmediate` due to bug with double-free // explained here (https://github.com/grpc/grpc-node/issues/1652) // and here (https://github.com/nodejs/node/issues/38964) - await new Promise((resolveCancel) => setImmediate(() => { - self.stream.cancel(); - resolveCancel(); - })); + // await new Promise((resolveCancel) => setImmediate(() => { + // self.stream.cancel(); + // resolveCancel(); + // })); } } } @@ -95,7 +99,7 @@ async function processChunks(dataChunk) { if (merkleBlockFromResponse) { // Reverse hashes, as they're little endian in the header const transactionsInHeader = merkleBlockFromResponse.hashes.map((hashHex) => Buffer.from(hashHex, 'hex').reverse().toString('hex')); - const transactionsInWallet = Object.keys(self.storage.getStore().transactions); + const transactionsInWallet = [...self.storage.getChainStore(self.network).state.transactions.keys()]; const isTruePositive = isAnyIntersection(transactionsInHeader, transactionsInWallet); if (isTruePositive) { self.importBlockHeader(merkleBlockFromResponse.header); diff --git a/src/plugins/Workers/TransactionSyncStreamWorker/methods/setLastSyncedBlockHeight.js b/src/plugins/Workers/TransactionSyncStreamWorker/methods/setLastSyncedBlockHeight.js index 19bd654ac..05579ea35 100644 --- a/src/plugins/Workers/TransactionSyncStreamWorker/methods/setLastSyncedBlockHeight.js +++ b/src/plugins/Workers/TransactionSyncStreamWorker/methods/setLastSyncedBlockHeight.js @@ -7,14 +7,8 @@ const { WALLET_TYPES } = require('../../../../CONSTANTS'); * @return {number} */ module.exports = function setLastSyncedBlockHeight(blockHeight) { - const { walletId } = this; - const accountsStore = this.storage.store.wallets[walletId].accounts; + const applicationStore = this.storage.application; + applicationStore.blockHeight = blockHeight; - const accountStore = ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(this.walletType)) - ? accountsStore[this.BIP44PATH.toString()] - : accountsStore[this.index.toString()]; - - accountStore.blockHeight = blockHeight; - - return accountStore.blockHeight; + return applicationStore.blockHeight; }; diff --git a/src/test/mocks/mockAccountWithStorage.js b/src/test/mocks/mockAccountWithStorage.js new file mode 100644 index 000000000..549067ae9 --- /dev/null +++ b/src/test/mocks/mockAccountWithStorage.js @@ -0,0 +1,32 @@ +const walletStoreMock = require('../../../fixtures/wallets/c922713eac.json'); +const chainStoreMock = require('../../../fixtures/chains/for_wallet_c922713eac.json'); +const Storage = require('../../types/Storage/Storage'); +const { KeyChainStore, KeyChain } = require('../../index'); + +module.exports = (opts = {}) => { + const { walletId } = walletStoreMock; + + const mockedAccount = { + walletId, + index: 0, + storage: new Storage(), + accountPath: "m/44'/1'/0'", + network: 'testnet', + ...opts, + }; + + mockedAccount.keyChainStore = new KeyChainStore(); + mockedAccount.keyChainStore.addKeyChain(new KeyChain({ + HDPrivateKey: 'tprv8gpcZgdXPzdXKBjSzieMyfwr6KidKucLiiA9VbCLCx1spyJNd38a5KdjtVuc9bVUNpFM2LdFCrYSyUXHx1RCTdr6qQen1HTECwAZ1p8yqiB', + lookAheadOpts: { + 'm/0': 40, + 'm/1': 40, + }, + }), { isMasterKeyChain: true }); + mockedAccount.storage.createWalletStore(walletId); + mockedAccount.storage.createChainStore('testnet'); + mockedAccount.storage.getWalletStore(walletId).importState(walletStoreMock); + mockedAccount.storage.getChainStore('testnet').importState(chainStoreMock); + + return mockedAccount; +}; diff --git a/src/transport/DAPIClientTransport/methods/getTransaction.js b/src/transport/DAPIClientTransport/methods/getTransaction.js index 936076c91..9f9194a3d 100644 --- a/src/transport/DAPIClientTransport/methods/getTransaction.js +++ b/src/transport/DAPIClientTransport/methods/getTransaction.js @@ -24,8 +24,8 @@ module.exports = async function getTransaction(txid) { transaction: new Transaction(response.getTransaction()), blockHash: response.getBlockHash().toString('hex'), height, - instantLocked, - chainLocked, + isInstantLocked: instantLocked, + isChainLocked: chainLocked, }; } catch (e) { if (e instanceof NotFoundError) { diff --git a/src/transport/DAPIClientTransport/methods/getTransaction.spec.js b/src/transport/DAPIClientTransport/methods/getTransaction.spec.js index bb2dea976..63fad293c 100644 --- a/src/transport/DAPIClientTransport/methods/getTransaction.spec.js +++ b/src/transport/DAPIClientTransport/methods/getTransaction.spec.js @@ -39,8 +39,8 @@ describe('transports - DAPIClientTransport .getTransaction', function suite() { expect(res.transaction.hash).to.equal('2c0ee853b91b23d881f96f0128bbb5ebb90c9ef7e7bdb4eda360b0e5abf97239'); expect(res.blockHash).to.equal('4f46066bd50cc2684484407696b7949e82bd906ea92c040f59a97cba47ed8176'); expect(res.height).to.equal(42); - expect(res.instantLocked).to.equal(true); - expect(res.chainLocked).to.equal(false); + expect(res.isInstantLocked).to.equal(true); + expect(res.isChainLocked).to.equal(false); }); it('should return null if transaction if not found', async () => { diff --git a/src/types/Account/Account.d.ts b/src/types/Account/Account.d.ts index 5310566bc..00049f84d 100644 --- a/src/types/Account/Account.d.ts +++ b/src/types/Account/Account.d.ts @@ -58,7 +58,7 @@ export declare class Account { getAddresses(_type: AddressType): [AddressObj]; getBlockHeader(identifier: string|number):Promise getConfirmedBalance(displayDuffs?: boolean): number; - getPlugin(name: string): Object; + getPlugin(name: string): any; getPrivateKeys(addressList: [PublicAddress]): [PrivateKey]; getTotalBalance(displayDuffs?: boolean): number; getTransaction(txid: transactionId): Transaction; diff --git a/src/types/Account/Account.js b/src/types/Account/Account.js index 914f372a9..d4519d397 100644 --- a/src/types/Account/Account.js +++ b/src/types/Account/Account.js @@ -1,7 +1,7 @@ const _ = require('lodash'); const EventEmitter = require('events'); const logger = require('../../logger'); -const { WALLET_TYPES } = require('../../CONSTANTS'); +const { WALLET_TYPES, BIP44_ADDRESS_GAP } = require('../../CONSTANTS'); const { is } = require('../../utils'); const EVENTS = require('../../EVENTS'); const Wallet = require('../Wallet/Wallet'); @@ -94,6 +94,7 @@ class Account extends EventEmitter { this.storage = wallet.storage; // Forward all storage event + this.storage.on(EVENTS.UPDATED_ADDRESS, (ev) => this.emit(ev.type, ev)); this.storage.on(EVENTS.CONFIGURED, (ev) => this.emit(ev.type, ev)); this.storage.on(EVENTS.REHYDRATE_STATE_FAILED, (ev) => this.emit(ev.type, ev)); this.storage.on(EVENTS.REHYDRATE_STATE_SUCCESS, (ev) => this.emit(ev.type, ev)); @@ -114,29 +115,76 @@ class Account extends EventEmitter { } switch (this.walletType) { case WALLET_TYPES.HDWALLET: - case WALLET_TYPES.HDPUBLIC: - this.storage.createAccount( - this.walletId, - this.BIP44PATH, - this.network, - this.label, - ); + this.accountPath = getBIP44Path(this.network, this.index); + // this.storage + // .getWalletStore(this.walletId) + // .createPathState(this.BIP44PATH); + // this.storage.createAccount( + // this.walletId, + // this.BIP44PATH, + // this.network, + // this.label, + // ); break; + case WALLET_TYPES.HDPUBLIC: case WALLET_TYPES.PRIVATEKEY: case WALLET_TYPES.PUBLICKEY: case WALLET_TYPES.ADDRESS: case WALLET_TYPES.SINGLE_ADDRESS: - this.storage.createSingleAddress( - this.walletId, - this.network, - this.label, - ); + this.accountPath = 'm/0'; + // this.storage + // .getWalletStore(this.walletId) + // .createPathState(this); + // this.storage.createSingleAddress( + // this.walletId, + // this.network, + // this.label, + // ); break; default: throw new Error(`Invalid wallet type ${this.walletType}`); } - this.keyChain = wallet.keyChain; + this.storage + .getWalletStore(this.walletId) + .createPathState(this.accountPath); + + let keyChainStorePath = this.index; + const keyChainStoreOpts = {}; + + switch (this.walletType) { + case WALLET_TYPES.HDPUBLIC: + keyChainStorePath = this.accountPath; + keyChainStoreOpts.lookAheadOpts = { + paths: { + 'm/0': BIP44_ADDRESS_GAP, + }, + }; + break; + case WALLET_TYPES.HDWALLET: + case WALLET_TYPES.HDPRIVATE: + keyChainStorePath = this.BIP44PATH; + keyChainStoreOpts.lookAheadOpts = { + paths: { + 'm/0': BIP44_ADDRESS_GAP, + 'm/1': BIP44_ADDRESS_GAP, + }, + }; + break; + default: + break; + } + + this.keyChainStore = wallet + .keyChainStore + .makeChildKeyChainStore(keyChainStorePath, keyChainStoreOpts); + + // This forces keychainStore to set to issued key what is already its masterkey + if ([WALLET_TYPES.PUBLICKEY, WALLET_TYPES.PRIVATEKEY].includes(this.walletType)) { + this.keyChainStore + .getMasterKeyChain() + .getForPath('0', { isWatched: true }); + } this.cacheTx = (opts.cacheTx) ? opts.cacheTx : defaultOptions.cacheTx; this.cacheBlockHeaders = (opts.cacheBlockHeaders) @@ -149,25 +197,25 @@ class Account extends EventEmitter { watchers: {}, }; - // Handle import of cache - if (opts.cache) { - if (opts.cache.addresses) { - try { - this.storage.importAddresses(opts.cache.addresses, this.walletId); - } catch (e) { - this.disconnect(); - throw e; - } - } - if (opts.cache.transactions) { - try { - this.storage.importTransactions(opts.cache.transactions); - } catch (e) { - this.disconnect(); - throw e; - } - } - } + // // Handle import of cache + // if (opts.cache) { + // if (opts.cache.addresses) { + // try { + // this.storage.importAddresses(opts.cache.addresses, this.walletId); + // } catch (e) { + // this.disconnect(); + // throw e; + // } + // } + // if (opts.cache.transactions) { + // try { + // this.storage.importTransactions(opts.cache.transactions); + // } catch (e) { + // this.disconnect(); + // throw e; + // } + // } + // } this.emit(EVENTS.CREATED, { type: EVENTS.CREATED, payload: null }); } @@ -205,7 +253,8 @@ class Account extends EventEmitter { * @param {InstantLock} instantLock */ importInstantLock(instantLock) { - this.storage.importInstantLock(instantLock); + const chainStore = this.storage.getChainStore(this.network); + chainStore.importInstantLock(instantLock); this.emit(Account.getInstantLockTopicName(instantLock.txid), instantLock); } @@ -225,10 +274,10 @@ class Account extends EventEmitter { */ waitForInstantLock(transactionHash, timeout = this.waitForInstantLockTimeout) { let rejectTimeout; - + const chainStore = this.storage.getChainStore(this.network); return Promise.race([ new Promise((resolve) => { - const instantLock = this.storage.getInstantLock(transactionHash); + const instantLock = chainStore.getInstantLock(transactionHash); if (instantLock != null) { clearTimeout(rejectTimeout); resolve(instantLock); diff --git a/src/types/Account/Account.spec.js b/src/types/Account/Account.spec.js index a7b01b6ff..63dde6b2a 100644 --- a/src/types/Account/Account.spec.js +++ b/src/types/Account/Account.spec.js @@ -7,6 +7,10 @@ const { WALLET_TYPES } = require('../../CONSTANTS'); const { Account, EVENTS } = require('../../index'); const EventEmitter = require('events'); const inMem = require('../../adapters/InMem'); +const Storage = require('../Storage/Storage'); +const {mock} = require("sinon"); +const KeyChainStore = require("../KeyChainStore/KeyChainStore"); +const KeyChain = require("../KeyChain/KeyChain"); const blockHeader = new Dashcore.BlockHeader.fromObject({ hash: '00000ac3a0c9df709260e41290d6902e5a4a073099f11fe8c1ce80aadc4bb331', version: 2, @@ -29,10 +33,9 @@ describe('Account - class', function suite() { const mockStorage = { on: emitter.on, emit: emitter.emit, - store: {}, + storage: new Storage(), getStore: () => {}, saveState: () => {}, - stopWorker: () => {}, createAccount: () => {}, importBlockHeader: (blockheader)=>{ mockStorage.emit(EVENTS.BLOCKHEADER, {type: EVENTS.BLOCKHEADER, payload:blockheader}); @@ -43,8 +46,13 @@ describe('Account - class', function suite() { this.walletType = WALLET_TYPES.HDWALLET; this.accounts = []; this.network = Dashcore.Networks.testnet; - this.storage = mockStorage; + this.storage = new Storage(); })()); + mocks.wallet.storage.application.network = mocks.wallet.network; + mocks.wallet.storage.createWalletStore(mocks.wallet.walletId); + mocks.wallet.storage.createChainStore(mocks.wallet.network); + mocks.wallet.keyChainStore = new KeyChainStore() + mocks.wallet.keyChainStore.addKeyChain(new KeyChain({mnemonic: fluidMnemonic.mnemonic}), { isMasterKeyChain: true }) }); it('should be specify on missing params', () => { const expectedException1 = 'Expected wallet to be passed as param'; @@ -96,7 +104,7 @@ describe('Account - class', function suite() { await account.on(EVENTS.BLOCKHEADER, ()=>{ done(); }); - account.storage.importBlockHeader(blockHeader); + account.storage.getChainStore(account.network).importBlockHeader(blockHeader); }) }); diff --git a/src/types/Account/_initializeAccount.js b/src/types/Account/_initializeAccount.js index 858e74883..263cf3d3a 100644 --- a/src/types/Account/_initializeAccount.js +++ b/src/types/Account/_initializeAccount.js @@ -3,10 +3,38 @@ const EVENTS = require('../../EVENTS'); const { WALLET_TYPES } = require('../../CONSTANTS'); const preparePlugins = require('./_preparePlugins'); const ensureAddressesToGapLimit = require('../../utils/bip44/ensureAddressesToGapLimit'); +const { UPDATED_ADDRESS } = require('../../EVENTS'); +const { is } = require('../../utils'); // eslint-disable-next-line no-underscore-dangle async function _initializeAccount(account, userUnsafePlugins) { const self = account; + + function markAddressAsUsed(props) { + const { address } = props.payload; + // This works if the TX cames from our main address, but not in all cases... + self.keyChainStore + .getMasterKeyChain() + .markAddressAsUsed(address); + } + + self.on(UPDATED_ADDRESS, markAddressAsUsed); + + const accountStore = account.storage + .getWalletStore(account.walletId) + .getPathState(account.accountPath); + + const chainStore = account.storage.getChainStore(account.network); + + const issuedPaths = account.keyChainStore + .getMasterKeyChain() + .getIssuedPaths(); + + issuedPaths.forEach((issuedPath) => { + accountStore.addresses[issuedPath.path] = issuedPath.address.toString(); + chainStore.importAddress(issuedPath.address.toString()); + }); + // We run faster in offlineMode to speed up the process when less happens. const readinessIntervalTime = (account.offlineMode) ? 50 : 200; // TODO: perform rejection with a timeout @@ -14,16 +42,16 @@ async function _initializeAccount(account, userUnsafePlugins) { return new Promise(async (resolve, reject) => { try { if (account.injectDefaultPlugins) { - if ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(account.walletType)) { - ensureAddressesToGapLimit( - account.store.wallets[account.walletId], - account.walletType, - account.index, - account.getAddress.bind(account), - ); - } else { - await account.getAddress('0'); // We force what is usually done by the BIP44Worker. - } + // if ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(account.walletType)) { + // ensureAddressesToGapLimit( + // account.store.wallets[account.walletId], + // account.walletType, + // account.index, + // account.getAddress.bind(account), + // ); + // } else { + // await account.getAddress('0'); // We force what is usually done by the BIP44Worker. + // } } // Will sort and inject plugins. @@ -68,35 +96,11 @@ async function _initializeAccount(account, userUnsafePlugins) { // while SyncWorker fetch'em on network clearInterval(self.readinessInterval); - switch (account.walletType) { - case WALLET_TYPES.PRIVATEKEY: - case WALLET_TYPES.SINGLE_ADDRESS: - account.generateAddress(0); - sendReady(); - return resolve(true); - case WALLET_TYPES.PUBLICKEY: - case WALLET_TYPES.ADDRESS: - account.generateAddress(0); - sendReady(); - return resolve(true); - default: - break; - } - if (!account.injectDefaultPlugins) { sendReady(); return resolve(true); } - if ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(account.walletType)) { - ensureAddressesToGapLimit( - account.store.wallets[account.walletId], - account.walletType, - account.index, - account.getAddress.bind(account), - ); - } - sendReady(); return resolve(true); } diff --git a/src/types/Account/methods/broadcastTransaction.js b/src/types/Account/methods/broadcastTransaction.js index ed1b3cf47..0d11eba63 100644 --- a/src/types/Account/methods/broadcastTransaction.js +++ b/src/types/Account/methods/broadcastTransaction.js @@ -6,30 +6,63 @@ const { InvalidDashcoreTransaction, } = require('../../../errors'); -function impactAffectedInputs({ inputs }) { +function impactAffectedInputs({ transaction }) { const { - storage, walletId, + storage, network, } = this; + const { inputs, changeIndex } = transaction.toObject(); + const txid = transaction.hash; + + const addresses = storage.getChainStore(network).getAddresses(); // We iterate out input to substract their balance. inputs.forEach((input) => { - const potentiallySelectedAddresses = storage.searchAddressesWithTx(input.prevTxId); - // Fixme : If you want this check, you will need to modify fixtures of our tests. - // if (!potentiallySelectedAddresses.found) { - // throw new Error('Input is not part of that Wallet.'); - // } - potentiallySelectedAddresses.results.forEach((potentiallySelectedAddress) => { - const { type, path } = potentiallySelectedAddress; - if (potentiallySelectedAddress.utxos[`${input.prevTxId}-${input.outputIndex}`]) { - const inputUTXO = potentiallySelectedAddress.utxos[`${input.prevTxId}-${input.outputIndex}`]; - const address = storage.store.wallets[walletId].addresses[type][path]; + const potentiallySelectedAddresses = [...addresses] + .reduce((acc, [address, { transactions }]) => { + if (transactions.includes(input.prevTxId)) acc.push(address); + return acc; + }, []); + + potentiallySelectedAddresses.forEach((potentiallySelectedAddress) => { + // console.log(addresses.get(pot)); + const addressData = addresses.get(potentiallySelectedAddress); + if (addressData.utxos[`${input.prevTxId}-${input.outputIndex}`]) { + const inputUTXO = addressData.utxos[`${input.prevTxId}-${input.outputIndex}`]; + // const address = storage.store.wallets[walletId].addresses[type][path]; // Todo: This modify the balance of an address, we need a std method to do that instead. - address.balanceSat -= inputUTXO.satoshis; - delete address.utxos[`${input.prevTxId}-${input.outputIndex}`]; + addressData.balanceSat -= inputUTXO.satoshis; + delete addressData.utxos[`${input.prevTxId}-${input.outputIndex}`]; } }); }); + const changeOutput = transaction.getChangeOutput(); + if (changeOutput) { + const addressString = changeOutput.script.toAddress(network).toString(); + + const address = addresses.get(addressString); + const utxoKey = `${txid}-${changeIndex}`; + + /** + * In some cases, `Storage#importTransaction` function gets called before the + * `impactAffectedInputs`and this utxo being written as a confirmed one. + * Skip creation of the unconfirmed UTXOs for such cases. + */ + if (!address.utxos[utxoKey]) { + address.utxos[utxoKey] = new Dashcore.Transaction.UnspentOutput( + { + txId: txid, + vout: changeIndex, + script: changeOutput.script, + satoshis: changeOutput.satoshis, + address: addressString, + }, + ); + address.unconfirmedBalanceSat = changeOutput.satoshis; + address.used = true; + } + } + return true; } @@ -42,7 +75,6 @@ function impactAffectedInputs({ inputs }) { */ async function broadcastTransaction(transaction, options = {}) { const { network, storage } = this; - const { chains } = storage.getStore(); if (!this.transport) throw new ValidTransportLayerRequired('broadcast'); // We still support having in rawtransaction, if this is the case @@ -61,8 +93,7 @@ async function broadcastTransaction(transaction, options = {}) { throw new Error('Transaction not signed.'); } - const { inputs } = transaction.toObject(); - const { minRelay: minRelayFeeRate } = chains[network.toString()].fees; + const { minRelay: minRelayFeeRate } = storage.getChainStore(network).state.fees; // eslint-disable-next-line no-underscore-dangle const estimateKbSize = transaction._estimateSize() / 1000; @@ -78,9 +109,8 @@ async function broadcastTransaction(transaction, options = {}) { // We now need to impact/update our affected inputs // so we clear them out from UTXOset. impactAffectedInputs.call(this, { - inputs, + transaction, }); - return txid; } diff --git a/src/types/Account/methods/broadcastTransaction.spec.js b/src/types/Account/methods/broadcastTransaction.spec.js index 0613a8e3a..df89a2cad 100644 --- a/src/types/Account/methods/broadcastTransaction.spec.js +++ b/src/types/Account/methods/broadcastTransaction.spec.js @@ -4,7 +4,7 @@ const broadcastTransaction = require('./broadcastTransaction'); const validRawTxs = require('../../../../fixtures/rawtx').valid; const invalidRawTxs = require('../../../../fixtures/rawtx').invalid; const expectThrowsAsync = require('../../../utils/expectThrowsAsync'); - +const ChainStore = require('../../ChainStore/ChainStore'); const { PrivateKey } = Dashcore; describe('Account - broadcastTransaction', function suite() { @@ -14,12 +14,14 @@ describe('Account - broadcastTransaction', function suite() { let keysToSign; let oneToOneTx; let fee; + // const storage = new Storage(); + // storage.createChainStore('testnet') + // storage.getChainStore('testnet').state.fees.minRelay = 888; + const chainStore = new ChainStore('testnet'); + chainStore.state.fees.minRelay = 888; + chainStore.importAddress('yTBXsrcGw74yMUsK34fBKAWJx3RNCq97Aq'); const storage = { - getStore: ()=>({ - chains:{ - "testnet": { fees: { minRelay: 888 }} - } - }) + getChainStore:()=> chainStore } beforeEach(() => { utxos = [ @@ -87,11 +89,7 @@ describe('Account - broadcastTransaction', function suite() { sendTransaction: () => sendCalled = +1, }, network: 'testnet', - storage: { - getStore: storage.getStore, - searchAddress: () => { searchCalled = +1; return { found: false }; }, - searchAddressesWithTx: () => { searchCalled = +1; return { results: [] }; }, - }, + storage }; const tx = oneToOneTx; @@ -109,11 +107,7 @@ describe('Account - broadcastTransaction', function suite() { sendTransaction: () => sendCalled = +1, }, network: 'testnet', - storage: { - getStore: storage.getStore, - searchAddress: () => { searchCalled = +1; return { found: false }; }, - searchAddressesWithTx: (affectedTxId) => { searchCalled = +1; return { results: [] }; }, - }, + storage }; return broadcastTransaction @@ -132,11 +126,7 @@ describe('Account - broadcastTransaction', function suite() { sendTransaction: () => sendCalled = +1, }, network: 'testnet', - storage: { - getStore: storage.getStore, - searchAddress: () => { searchCalled = +1; return { found: false }; }, - searchAddressesWithTx: () => { searchCalled = +1; return { results: [] }; }, - }, + storage }; const tx = oneToOneTx; @@ -151,11 +141,7 @@ describe('Account - broadcastTransaction', function suite() { sendTransaction: () => sendCalled = +1, }, network: 'testnet', - storage: { - getStore: storage.getStore, - searchAddress: () => { searchCalled = +1; return { found: false }; }, - searchAddressesWithTx: () => { searchCalled = +1; return { results: [] }; }, - }, + storage }; const tx = oneToOneTx; diff --git a/src/types/Account/methods/createTransaction.js b/src/types/Account/methods/createTransaction.js index 5aca76c07..71334ed4b 100644 --- a/src/types/Account/methods/createTransaction.js +++ b/src/types/Account/methods/createTransaction.js @@ -117,7 +117,7 @@ function createTransaction(opts = {}) { } }); try { - const signedTx = this.keyChain.sign( + const signedTx = this.keyChainStore.getMasterKeyChain().sign( tx, transformedPrivateKeys, crypto.Signature.SIGHASH_ALL, diff --git a/src/types/Account/methods/createTransaction.spec.js b/src/types/Account/methods/createTransaction.spec.js index 7dedef8c9..646e75461 100644 --- a/src/types/Account/methods/createTransaction.spec.js +++ b/src/types/Account/methods/createTransaction.spec.js @@ -7,6 +7,8 @@ const { mnemonic } = require('../../../../fixtures/wallets/mnemonics/during-deve const FixtureTransport = require('../../../transport/FixtureTransport/FixtureTransport'); const getUTXOS = require('./getUTXOS'); +const getPrivateKeys = require('./getPrivateKeys'); +const getUnusedAddress = require('./getUnusedAddress'); const { simpleDescendingAccumulator } = require('../../../utils/coinSelections/strategies'); const addressesFixtures = require('../../../../fixtures/addresses.json'); @@ -14,18 +16,17 @@ const fixtureUTXOS = require('../../../transport/FixtureTransport/data/utxos/yQ1 const validStore = require('../../../../fixtures/walletStore').valid.orange.store; const craftedGenerousMinerStrategy = require('../../../../fixtures/strategies/craftedGenerousMinerStrategy'); +const mockAccountWithStorage = require("../../../test/mocks/mockAccountWithStorage"); describe('Account - createTransaction', function suite() { this.timeout(10000); let mockWallet; it('sould warn on missing inputs', function () { - const self = { - store: validStore, - walletId: 'a3771aaf93', - getUTXOS, - network: 'testnet' - }; + const self = mockAccountWithStorage() + self.getUTXOS = getUTXOS; + self.getUnusedAddress = getUnusedAddress; + self.getPrivateKeys = getPrivateKeys; const mockOpts1 = {}; const mockOpts2 = { @@ -62,8 +63,12 @@ describe('Account - createTransaction', function suite() { return [new HDPrivateKey('tprv8jG3ctd1DEVADnLP3hwS1Gfzjxf5E4WL2UutfJkhAQs7rVu2b3Ryv4WQ46mddZyMbGaSUYnY9wFeuFRAejapjoB1LGzTfM55mxMhZ1X4eGX')] } }, - keyChain: { - sign: (tx, privateKeys) => tx.sign(privateKeys), + keyChainStore: { + getMasterKeyChain:() => { + return { + sign: (tx, privateKeys) => tx.sign(privateKeys), + } + } }, storage: { searchTransaction: (txId) => { diff --git a/src/types/Account/methods/disconnect.js b/src/types/Account/methods/disconnect.js index c60edef6a..317dc02a2 100644 --- a/src/types/Account/methods/disconnect.js +++ b/src/types/Account/methods/disconnect.js @@ -20,7 +20,6 @@ module.exports = async function disconnect() { } if (this.storage) { await this.storage.saveState(); - await this.storage.stopWorker(); } if (this.removeAllListeners) this.removeAllListeners(); if (this.storage.removeAllListeners) this.storage.removeAllListeners(); diff --git a/src/types/Account/methods/disconnect.spec.js b/src/types/Account/methods/disconnect.spec.js index 0e2036fb3..aa0fcfe3c 100644 --- a/src/types/Account/methods/disconnect.spec.js +++ b/src/types/Account/methods/disconnect.spec.js @@ -33,7 +33,6 @@ describe('Account - disconnect', function suite() { it('should disconnect to stream and worker', async () => { expect(transportConnected).to.equal(true); await disconnect.call(self); - // console.log(self, transportConnected, emitted); expect(transportConnected).to.equal(false); expect(emitted).to.deep.equal([ 'WORKER/DUMMYWORKER/STARTING', diff --git a/src/types/Account/methods/encrypt.spec.js b/src/types/Account/methods/encrypt.spec.js index 89653f84b..facc317ea 100644 --- a/src/types/Account/methods/encrypt.spec.js +++ b/src/types/Account/methods/encrypt.spec.js @@ -29,7 +29,7 @@ describe('Account - encrypt', function suite() { const secret = 'secret'; it('should encrypt extPubKey with aes', () => { - const extPubKey = account.keyChain.getKeyForPath(derivationPath, 'HDPublicKey').toString(); + const extPubKey = account.keyChainStore.getMasterKeyChain().getForPath(derivationPath).key.toString(); const encryptedExtPubKey = account.encrypt('aes', extPubKey, secret).toString(); const bytes = CryptoJS.AES.decrypt(encryptedExtPubKey, secret); const decrypted = bytes.toString(CryptoJS.enc.Utf8); diff --git a/src/types/Account/methods/generateAddress.js b/src/types/Account/methods/generateAddress.js index 450f582fe..c24169b00 100644 --- a/src/types/Account/methods/generateAddress.js +++ b/src/types/Account/methods/generateAddress.js @@ -1,58 +1,94 @@ -const { PublicKey } = require('@dashevo/dashcore-lib'); const EVENTS = require('../../../EVENTS'); const { WALLET_TYPES } = require('../../../CONSTANTS'); const { is } = require('../../../utils'); + /** * Generate an address from a path and import it to the store * @param {string} path - * @return {AddressObj} Address information + * @param {boolean} [isWatchedAddress=true] - if the address will be watched + * @return {AddressInfo} Address information * */ -function generateAddress(path) { +function generateAddress(path, isWatchedAddress = true) { if (is.undefOrNull(path)) throw new Error('Expected path to generate an address'); let index = 0; - let privateKey; let address; + let keyPathData; const { network } = this; switch (this.walletType) { case WALLET_TYPES.ADDRESS: - address = this.keyChain.address; + address = this.keyChainStore.getMasterKeyChain().rootKey; + if (isWatchedAddress) { + this.keyChainStore.issuedPaths.set(0, { + path: 0, + address, + isUsed: false, + isWatched: true, + }); + } break; case WALLET_TYPES.PUBLICKEY: - address = new PublicKey(this.keyChain.publicKey.toString()).toAddress(network).toString(); + // eslint-disable-next-line no-case-declarations + const { rootKey } = this.keyChainStore.getMasterKeyChain(); + address = rootKey.toAddress(network).toString(); + if (isWatchedAddress) { + this.keyChainStore.issuedPaths.set(0, { + key: rootKey, + path: 0, + address, + isUsed: false, + isWatched: true, + }); + } break; + case WALLET_TYPES.HDPRIVATE: case WALLET_TYPES.HDWALLET: // eslint-disable-next-line prefer-destructuring - index = parseInt(path.toString().split('/')[5], 10); - privateKey = this.keyChain.getKeyForPath(path); - address = privateKey.publicKey.toAddress(network).toString(); + index = parseInt(path.toString().split('/')[2], 10); + keyPathData = this.keyChainStore + .getMasterKeyChain() + .getForPath(path, { isWatched: isWatchedAddress }); + address = keyPathData.address.toString(); break; case WALLET_TYPES.HDPUBLIC: index = parseInt(path.toString().split('/')[5], 10); - privateKey = this.keyChain.getKeyForChild(index); - address = privateKey.publicKey.toAddress(network).toString(); + // eslint-disable-next-line no-case-declarations + keyPathData = this.keyChainStore + .getMasterKeyChain() + .getForPath(path, { isWatched: isWatchedAddress }); + address = keyPathData.address.toString(); break; + // TODO: DEPRECATE USAGE OF SINGLE_ADDRESS in favor or PRIVATEKEY + case WALLET_TYPES.PRIVATEKEY: case WALLET_TYPES.SINGLE_ADDRESS: default: - privateKey = this.keyChain.getKeyForPath(path.toString()); - address = privateKey.publicKey.toAddress(network).toString(); + keyPathData = this.keyChainStore + .getMasterKeyChain() + .getForPath(path, { isWatched: isWatchedAddress }); + address = keyPathData.address.toString(); + break; } const addressData = { path: path.toString(), index, address, - // privateKey, transactions: [], + utxos: {}, balanceSat: 0, unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false, }; - this.storage.importAddresses(addressData, this.walletId); + const accountStore = this.storage + .getWalletStore(this.walletId) + .getPathState(this.accountPath); + + const chainStore = this.storage.getChainStore(this.network); + + accountStore.addresses[addressData.path] = addressData.address.toString(); + chainStore.importAddress(addressData.address.toString()); this.emit(EVENTS.GENERATED_ADDRESS, { type: EVENTS.GENERATED_ADDRESS, payload: addressData }); return addressData; } + module.exports = generateAddress; diff --git a/src/types/Account/methods/getAddress.js b/src/types/Account/methods/getAddress.js index d4771dc77..86efad073 100644 --- a/src/types/Account/methods/getAddress.js +++ b/src/types/Account/methods/getAddress.js @@ -1,40 +1,26 @@ const { WALLET_TYPES } = require('../../../CONSTANTS'); -const getTypePathFromWalletType = (walletType, addressType = 'external', index, BIP44PATH) => { - let type; - let path; - - const addressTypeIndex = (addressType === 'external') ? 0 : 1; - switch (walletType) { - case WALLET_TYPES.HDWALLET: - type = addressType; - path = `${BIP44PATH}/${addressTypeIndex}/${index}`; - break; - case WALLET_TYPES.HDPUBLIC: - type = 'external'; - path = `${BIP44PATH}/${addressTypeIndex}/${index}`; - break; - case WALLET_TYPES.PUBLICKEY: - case WALLET_TYPES.ADDRESS: - case WALLET_TYPES.PRIVATEKEY: - case WALLET_TYPES.SINGLE_ADDRESS: - default: - type = 'misc'; - path = '0'; - } - return { type, path }; -}; /** * Get a specific addresss based on the index and type of address. * @param {number} index - The index on the type - * @param {AddressType} [_type="external"] - Type of the address (external, internal, misc) + * @param {AddressType} [addressType="external"] - Type of the address (external, internal, misc) * @return */ -function getAddress(index = 0, _type = 'external') { - const { type, path } = getTypePathFromWalletType(this.walletType, _type, index, this.BIP44PATH); +function getAddress(addressIndex = 0, addressType = 'external') { + const addressTypeIndex = (addressType === 'external') ? 0 : 1; + + const { addresses } = this.storage.getWalletStore(this.walletId).getPathState(this.accountPath); + const addressPath = ([WALLET_TYPES.HDPUBLIC, WALLET_TYPES.HDWALLET].includes(this.walletType)) + ? `m/${addressTypeIndex}/${addressIndex}` : '0'; + + const address = addresses[addressPath]; + if (!address) return this.generateAddress(addressPath); - const { wallets } = this.storage.getStore(); - const matchingTypeAddresses = wallets[this.walletId].addresses[type]; - return (matchingTypeAddresses[path]) ? matchingTypeAddresses[path] : this.generateAddress(path); + const chainStore = this.storage.getChainStore(this.network); + return { + index: addressIndex, + path: addressPath, + ...chainStore.getAddress(address), + }; } module.exports = getAddress; diff --git a/src/types/Account/methods/getAddresses.js b/src/types/Account/methods/getAddresses.js index 92a64e653..8ea2d9c58 100644 --- a/src/types/Account/methods/getAddresses.js +++ b/src/types/Account/methods/getAddresses.js @@ -2,20 +2,47 @@ const { WALLET_TYPES } = require('../../../CONSTANTS'); /** * Get all the addresses from the store from a given type - * @param {AddressType} [_type="external"] - Type of the address (external, internal, misc) + * @param {AddressType} [addressType="external"] - Type of the address (external, internal, misc) * @return {[AddressObj]} address - All address matching the type */ -function getAddresses(_type = 'external') { - const miscTypes = [ - WALLET_TYPES.SINGLE_ADDRESS, - WALLET_TYPES.PUBLICKEY, - WALLET_TYPES.PRIVATEKEY, - WALLET_TYPES.ADDRESS, - ]; - const walletType = (miscTypes.includes(this.walletType)) - ? 'misc' - : ((_type) || 'external'); - const store = this.storage.getStore(); - return store.wallets[this.walletId].addresses[walletType]; +function getAddresses(addressType = 'external') { + // const miscTypes = [ + // WALLET_TYPES.SINGLE_ADDRESS, + // WALLET_TYPES.PUBLICKEY, + // WALLET_TYPES.PRIVATEKEY, + // WALLET_TYPES.ADDRESS, + // ]; + // const walletType = (miscTypes.includes(this.walletType)) + // ? 'misc' + // : ((_type) || 'external'); + // const store = this.storage.getStore(); + // return store.wallets[this.walletId].addresses[walletType]; + const addressTypeIndex = (addressType === 'external') ? 0 : 1; + + const { addresses } = this.storage + .getWalletStore(this.walletId) + .getPathState(this.accountPath); + + const chainStore = this.storage.getChainStore(this.network); + + const baseAddressPath = ([WALLET_TYPES.HDPUBLIC, WALLET_TYPES.HDWALLET].includes(this.walletType)) + ? `m/${addressTypeIndex}` : '0'; + + const typedAddresses = {}; + + Object + .entries(addresses) + .forEach(([path, address]) => { + if (path.startsWith(baseAddressPath)) { + const index = parseInt(path.split('/').slice(-1)[0], 10); + typedAddresses[path] = { + index, + path, + ...chainStore.getAddress(address), + }; + } + }); + + return typedAddresses; } module.exports = getAddresses; diff --git a/src/types/Account/methods/getBalance.spec.js b/src/types/Account/methods/getBalance.spec.js deleted file mode 100644 index 9ade6080a..000000000 --- a/src/types/Account/methods/getBalance.spec.js +++ /dev/null @@ -1,42 +0,0 @@ -const { expect } = require('chai'); -const mockedStore = require('../../../../fixtures/sirentonight-fullstore-snapshot-1562711703'); -const getTotalBalance = require('./getTotalBalance'); -const getConfirmedBalance = require('./getConfirmedBalance'); -const getUnconfirmedBalance = require('./getUnconfirmedBalance'); -const calculateDuffBalance = require('../../Storage/methods/calculateDuffBalance'); - -let mockedWallet; -describe('Account - getTotalBalance', function suite() { - this.timeout(10000); - before(() => { - const storageHDW = { - store: mockedStore, - calculateDuffBalance, - getStore: () => mockedStore, - mappedAddress: {}, - }; - const walletId = Object.keys(mockedStore.wallets)[0]; - mockedWallet = { - walletId, - index: 0, - storage: storageHDW, - }; - }); - it('should correctly get the balance', async () => { - const balance = await getTotalBalance.call(mockedWallet); - expect(balance).to.equal(184499999506); - }); - it('should correctly get the balance confirmed only', async () => { - const balance = await getConfirmedBalance.call(mockedWallet); - expect(balance).to.equal(184499999506); - }); - it('should correctly get the balance dash value instead of duff', async () => { - const balanceTotalDash = await getTotalBalance.call(mockedWallet, false); - const balanceUnconfDash = await getUnconfirmedBalance.call(mockedWallet, false); - const balanceConfDash = await getConfirmedBalance.call(mockedWallet, false); - - expect(balanceTotalDash).to.equal(1844.99999506); - expect(balanceUnconfDash).to.equal(0); - expect(balanceConfDash).to.equal(1844.99999506); - }); -}); diff --git a/src/types/Account/methods/getConfirmedBalance.js b/src/types/Account/methods/getConfirmedBalance.js index c84a71c48..158ca50f0 100644 --- a/src/types/Account/methods/getConfirmedBalance.js +++ b/src/types/Account/methods/getConfirmedBalance.js @@ -1,4 +1,4 @@ -const { duffsToDash } = require('../../../utils'); +const { duffsToDash, calculateDuffBalance } = require('../../../utils'); /** * Return the confirmed balance of an account. @@ -7,10 +7,13 @@ const { duffsToDash } = require('../../../utils'); */ function getConfirmedBalance(displayDuffs = true) { const { - walletId, storage, + walletId, storage, accountPath, network, } = this; - const accountIndex = this.index; - const totalSat = storage.calculateDuffBalance(walletId, accountIndex, 'confirmed'); + + const { addresses } = storage.getWalletStore(walletId).getPathState(accountPath); + + const chainStore = storage.getChainStore(network); + const totalSat = (calculateDuffBalance(Object.values(addresses), chainStore, 'confirmed')); return (displayDuffs) ? totalSat : duffsToDash(totalSat); } diff --git a/src/types/Account/methods/getConfirmedBalance.spec.js b/src/types/Account/methods/getConfirmedBalance.spec.js new file mode 100644 index 000000000..d8c6f4b65 --- /dev/null +++ b/src/types/Account/methods/getConfirmedBalance.spec.js @@ -0,0 +1,36 @@ +const { expect } = require('chai'); +const getTotalBalance = require('./getTotalBalance'); +const getConfirmedBalance = require('./getConfirmedBalance'); +const getUnconfirmedBalance = require('./getUnconfirmedBalance'); +const mockAccountWithStorage = require("../../../test/mocks/mockAccountWithStorage"); + + +let mockedAccount; +describe('Account - getTotalBalance', function suite() { + this.timeout(10000); + before(() => { + mockedAccount = mockAccountWithStorage() + }); + + it('should correctly get the balance', () => { + const balance = getTotalBalance.call(mockedAccount); + expect(balance).to.equal(224108673); + }); + + it('should correctly get the balance confirmed only', () => { + const balance = getConfirmedBalance.call(mockedAccount); + expect(balance).to.equal(224108673); + }); + + // TODO: file looks like a complete duplicate of the getTotalBalance.spec.js + // Should we actually mock and test confirmed balance? + it('should correctly get the balance dash value instead of duff', () => { + const balanceTotalDash = getTotalBalance.call(mockedAccount, false); + const balanceUnconfDash = getUnconfirmedBalance.call(mockedAccount, false); + const balanceConfDash = getConfirmedBalance.call(mockedAccount, false); + + expect(balanceTotalDash).to.equal(2.24108673); + expect(balanceUnconfDash).to.equal(0); + expect(balanceConfDash).to.equal(2.24108673); + }); +}); diff --git a/src/types/Account/methods/getPrivateKeys.js b/src/types/Account/methods/getPrivateKeys.js index 9c8392af5..f7a068f8e 100644 --- a/src/types/Account/methods/getPrivateKeys.js +++ b/src/types/Account/methods/getPrivateKeys.js @@ -5,23 +5,20 @@ */ function getPrivateKeys(addressList) { let addresses = []; - let privKeys = []; + const privKeys = []; if (addressList.constructor.name === Object.name) { addresses = [addressList]; } else { addresses = addressList; } - const { walletId } = this; - const self = this; - const subwallets = Object.keys(this.store.wallets[walletId].addresses); - subwallets.forEach((subwallet) => { - const paths = Object.keys(self.store.wallets[walletId].addresses[subwallet]); - paths.forEach((path) => { - const address = self.store.wallets[walletId].addresses[subwallet][path]; - if (addresses.includes(address.address)) { - const privateKey = self.keyChain.getKeyForPath(path); - privKeys = privKeys.concat([privateKey]); - } - }); + const { keyChainStore } = this; + + const keyChain = keyChainStore.getMasterKeyChain(); + + addresses.forEach((address) => { + const addressData = keyChain.getForAddress(address); + if (addressData) { + privKeys.push(addressData.key); + } }); return privKeys; diff --git a/src/types/Account/methods/getTotalBalance.js b/src/types/Account/methods/getTotalBalance.js index 8b490ac8f..9c0161549 100644 --- a/src/types/Account/methods/getTotalBalance.js +++ b/src/types/Account/methods/getTotalBalance.js @@ -1,4 +1,4 @@ -const { duffsToDash } = require('../../../utils'); +const { duffsToDash, calculateDuffBalance } = require('../../../utils'); /** * Return the total balance of an account (confirmed + unconfirmed). @@ -7,9 +7,14 @@ const { duffsToDash } = require('../../../utils'); */ function getTotalBalance(displayDuffs = true) { const { - walletId, storage, index, + walletId, storage, accountPath, network, } = this; - const totalSat = storage.calculateDuffBalance(walletId, index, 'total'); + + const { addresses } = storage.getWalletStore(walletId).getPathState(accountPath); + + const chainStore = storage.getChainStore(network); + + const totalSat = (calculateDuffBalance(Object.values(addresses), chainStore, 'total')); return (displayDuffs) ? totalSat : duffsToDash(totalSat); } diff --git a/src/types/Account/methods/getTotalBalance.spec.js b/src/types/Account/methods/getTotalBalance.spec.js new file mode 100644 index 000000000..0368595db --- /dev/null +++ b/src/types/Account/methods/getTotalBalance.spec.js @@ -0,0 +1,30 @@ +const { expect } = require('chai'); +const getTotalBalance = require('./getTotalBalance'); +const getConfirmedBalance = require('./getConfirmedBalance'); +const getUnconfirmedBalance = require('./getUnconfirmedBalance'); +const mockAccountWithStorage = require("../../../test/mocks/mockAccountWithStorage"); + +let mockedAccount; +describe('Account - getTotalBalance', function suite() { + this.timeout(10000); + before(() => { + mockedAccount = mockAccountWithStorage() + }); + it('should correctly get the balance',() => { + const balance = getTotalBalance.call(mockedAccount); + expect(balance).to.equal(224108673); + }); + it('should correctly get the balance confirmed only', () => { + const balance = getConfirmedBalance.call(mockedAccount); + expect(balance).to.equal(224108673); + }); + it('should correctly get the balance dash value instead of duff', () => { + const balanceTotalDash = getTotalBalance.call(mockedAccount, false); + const balanceUnconfDash = getUnconfirmedBalance.call(mockedAccount, false); + const balanceConfDash = getConfirmedBalance.call(mockedAccount, false); + + expect(balanceTotalDash).to.equal(2.24108673); + expect(balanceUnconfDash).to.equal(0); + expect(balanceConfDash).to.equal(2.24108673); + }); +}); diff --git a/src/types/Account/methods/getTransaction.js b/src/types/Account/methods/getTransaction.js index 4c7dce06a..da7a09705 100644 --- a/src/types/Account/methods/getTransaction.js +++ b/src/types/Account/methods/getTransaction.js @@ -6,15 +6,14 @@ const EVENTS = require('../../../EVENTS'); * @return {Promise<{metadata: TransactionMetaData|null, transaction: Transaction}>} */ async function getTransaction(txid = null) { - const searchTransaction = await this.storage.searchTransaction(txid); - const searchTransactionMetadata = await this.storage.searchTransactionMetadata(txid); - if (searchTransaction.found) { - const searchResult = { transaction: searchTransaction.result, metadata: null }; - if (searchTransactionMetadata.found) { - searchResult.metadata = searchTransactionMetadata.result; - } - return searchResult; + const { storage, network } = this; + const chainStore = storage.getChainStore(network); + const searchedTransaction = chainStore.getTransaction(txid); + + if (searchedTransaction) { + return searchedTransaction; } + const getTransactionResponse = await this.transport.getTransaction(txid); if (!getTransactionResponse) return null; const { diff --git a/src/types/Account/methods/getTransaction.spec.js b/src/types/Account/methods/getTransaction.spec.js index 2a4f80cd0..ea65ce935 100644 --- a/src/types/Account/methods/getTransaction.spec.js +++ b/src/types/Account/methods/getTransaction.spec.js @@ -1,123 +1,64 @@ const { expect } = require('chai'); -const mockedStore = require('../../../../fixtures/sirentonight-fullstore-snapshot-1562711703'); const getTransaction = require('./getTransaction'); -const searchTransaction = require('../../Storage/methods/searchTransaction'); -const searchTransactionMetadata = require('../../Storage/methods/searchTransactionMetadata'); +const mockAccountWithStorage = require("../../../test/mocks/mockAccountWithStorage"); -let mockedWallet; +let mockedAccount; let fetchTransactionInfoCalledNb = 0; describe('Account - getTransaction', function suite() { this.timeout(10000); before(() => { - const storageHDW = { - store: mockedStore, - getStore: () => mockedStore, - mappedAddress: {}, - searchTransaction, - searchTransactionMetadata, - importTransactions: () => null, - }; - const walletId = Object.keys(mockedStore.wallets)[0]; - mockedWallet = { - walletId, - index: 0, - storage: storageHDW, + mockedAccount = mockAccountWithStorage({ transport: { - getTransaction: () => {fetchTransactionInfoCalledNb += 1; return null}, - }, - }; + getTransaction: () => { + fetchTransactionInfoCalledNb += 1; + return null + }, + } + }); }); it('should correctly get a existing transaction', async () => { - const tx = await getTransaction.call(mockedWallet, '92150f239013c961db15bc91d904404d2ae0520929969b59b69b17493569d0d5'); - expect(tx.transaction).to.deep.equal(expectedTx); + const tx = await getTransaction.call(mockedAccount, 'c8f0d780e6cedb9c37724f98cc3fecd6d5ad314db28e3cd439184bf25196ceb4'); + + expect(tx.transaction.toObject()).to.deep.equal(expectedTx); + expect(tx.metadata).to.deep.equal({ - hash: "92150f239013c961db15bc91d904404d2ae0520929969b59b69b17493569d0d5", - blockHash: '000000c5d6ca463ebbfddffe9a0a135312b6d8fc4eae2787b82b0fca9de7a554', - height: 29197, - instantLocked: false, - chainLocked: false + blockHash: '000001810cf3b49ed94ae033f007923d3c243077f5f9e24b559b536087e8b960', + height: 615751, + isInstantLocked: null, + isChainLocked: null }); }); + it('should correctly try to fetch un unexisting transaction', async () => { expect(fetchTransactionInfoCalledNb).to.equal(0); - const tx = await getTransaction.call(mockedWallet, '92151f239013c961db15bc91d904404d2ae0520929969b59b69b17493569d0d5'); + const tx = await getTransaction.call(mockedAccount, '92151f239013c961db15bc91d904404d2ae0520929969b59b69b17493569d0d5'); expect(fetchTransactionInfoCalledNb).to.equal(1); expect(tx).to.equal(null); }); }); const expectedTx = { - hash: '92150f239013c961db15bc91d904404d2ae0520929969b59b69b17493569d0d5', - blockhash: '000000c5d6ca463ebbfddffe9a0a135312b6d8fc4eae2787b82b0fca9de7a554', - blockheight: 29197, - blocktime: 1562060795, - fees: 522, - size: 521, - vout: [{ - value: '0.99990990', - n: 0, - scriptPubKey: { - hex: '76a914ba84943e63925288d2972cd5d0c2e1e06873c7c688ac', - asm: 'OP_DUP OP_HASH160 ba84943e63925288d2972cd5d0c2e1e06873c7c6 OP_EQUALVERIFY OP_CHECKSIG', - addresses: ['ydKfMe2n4vWsrzvgfSieQsFFxM9XMoWBff'], - type: 'pubkeyhash', - }, - spentTxId: 'eabe39ada39b58d70c03e0e79b7d2c767ed1239dda436bbc5a58954285421acc', - spentIndex: 1, - spentHeight: 30969, - }, { - value: '1000.00000000', - n: 1, - scriptPubKey: { - hex: '76a91485ada58442067249829d52ddd6c99c97a112749188ac', - asm: 'OP_DUP OP_HASH160 85ada58442067249829d52ddd6c99c97a1127491 OP_EQUALVERIFY OP_CHECKSIG', - addresses: ['yYWGjtb7XJqbXsUPfkaTWQKzcPYfmMp1Co'], - type: 'pubkeyhash', + hash: 'c8f0d780e6cedb9c37724f98cc3fecd6d5ad314db28e3cd439184bf25196ceb4', + version: 3, + inputs: [ + { + prevTxId: 'a43f20bcb46fef22926745a27ca38f63c773f2c7c4cbb55aaf184b58b3755965', + outputIndex: 0, + sequenceNumber: 4294967295, + script: '48304502210080185b616b2f8cb013e264b66146954cdc1597053ea8238467b896413b836039022071e71147dc45fc2dbde666c2774912c5bfd00b761268a3a4f8951375eb895c310121029671ae86f7eeb5c127568fddc977a1cce1f76ad64efa83c8ec9fcaef08ea9738', + scriptString: '72 0x304502210080185b616b2f8cb013e264b66146954cdc1597053ea8238467b896413b836039022071e71147dc45fc2dbde666c2774912c5bfd00b761268a3a4f8951375eb895c3101 33 0x029671ae86f7eeb5c127568fddc977a1cce1f76ad64efa83c8ec9fcaef08ea9738' + } + ], + outputs: [ + { + satoshis: 10000, + script: '76a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac' }, - spentTxId: '5a5626c59f3830d5d9e7261bed5ced2694a343100c18d4a730b26639f5832944', - spentIndex: 0, - spentHeight: 29197, - }], - vin: [{ - hash: '0c25c534aeef8a151e8ce325882f80af647621b9f0a54f995f75c0d2994966ad', - vout: 0, - sequence: 4294967294, - n: 0, - scriptSig: { - hex: '483045022100b24c95914f666ecb3ac41048110d7732b890b0d3fac9a9ff05560913e530430a022006660d72df91158f4d4710b75b9b05502a1332b5afca1ab5f98d012119f62553012103a6592040a30bf9254306a9d1086803cd450ae817ed5b4ba34e3e1b43d48bb783', - asm: '3045022100b24c95914f666ecb3ac41048110d7732b890b0d3fac9a9ff05560913e530430a022006660d72df91158f4d4710b75b9b05502a1332b5afca1ab5f98d012119f62553[ALL] 03a6592040a30bf9254306a9d1086803cd450ae817ed5b4ba34e3e1b43d48bb783', - }, - addr: 'yXzZsVfpPxjewfVd7oa2D6tBMHW7JbonBr', - valueSat: 99991512, - value: 0.99991512, - doubleSpentTxID: null, - }, { - hash: '92056b727a3e37f5946dc18aa4f497ba9c0e3a328105e743175629bf7c8f3d37', - vout: 0, - sequence: 4294967294, - n: 1, - scriptSig: { - hex: '483045022100fe69fdb70c0550b900960e9fbfd7254726a237c8b5688e5c9a7fba15947638fa02206ba0463b51922b56d0064c06b55c21328a39aa81312cf25ec982fa5c1eed9214012103353b4deb77923b026278d116e2007d6f97a058e42d35f1fd39efd5314705f844', - asm: '3045022100fe69fdb70c0550b900960e9fbfd7254726a237c8b5688e5c9a7fba15947638fa02206ba0463b51922b56d0064c06b55c21328a39aa81312cf25ec982fa5c1eed9214[ALL] 03353b4deb77923b026278d116e2007d6f97a058e42d35f1fd39efd5314705f844', - }, - addr: 'yhvXpqQjfN9S4j5mBKbxeGxiETJrrLETg5', - valueSat: 50000000000, - value: 500, - doubleSpentTxID: null, - }, { - hash: 'be27a3dae2742aaca103fea0967edd9a6d0ef5cf90159af39f80ad5a7a50b7d6', - vout: 0, - sequence: 4294967294, - n: 2, - scriptSig: { - hex: '47304402205d30afd97e5efbec984faae5be922a487d7adce1518a3214966198a5423c150d02204ea0e15a5fcf5b8034294c9d2b9d6fc638f75506fcac3530c91b960eaf2e6859012103353b4deb77923b026278d116e2007d6f97a058e42d35f1fd39efd5314705f844', - asm: '304402205d30afd97e5efbec984faae5be922a487d7adce1518a3214966198a5423c150d02204ea0e15a5fcf5b8034294c9d2b9d6fc638f75506fcac3530c91b960eaf2e6859[ALL] 03353b4deb77923b026278d116e2007d6f97a058e42d35f1fd39efd5314705f844', - }, - addr: 'yhvXpqQjfN9S4j5mBKbxeGxiETJrrLETg5', - valueSat: 50000000000, - value: 500, - doubleSpentTxID: null, - }], - txlock: false, - spendable: false, + { + satoshis: 224508306, + script: '76a9147f6f4280f91e00126d927466cd48629439b763fa88ac' + } + ], + nLockTime: 0 }; + diff --git a/src/types/Account/methods/getTransactionHistory.js b/src/types/Account/methods/getTransactionHistory.js index 3118f363a..5b7e3f4d7 100644 --- a/src/types/Account/methods/getTransactionHistory.js +++ b/src/types/Account/methods/getTransactionHistory.js @@ -1,8 +1,6 @@ const { each } = require('lodash'); const { - filterTransactions, categorizeTransactions, - extendTransactionsWithMetadata, // calculateTransactionFees, } = require('../../../utils'); @@ -24,36 +22,13 @@ function getTransactionHistory() { network, } = this; - const transactions = this.getTransactions(); - const store = storage.getStore(); + const transactionsWithMetadata = this.getTransactions(); - const chainStore = store.chains[network.toString()]; - const { blockHeaders } = chainStore; - - const { wallets: walletStore, transactionsMetadata } = store; - - const accountStore = walletStore[walletId]; - - // In store, not all transaction are specific to this account, we filter our transactions. - const filteredTransactions = filterTransactions( - accountStore, - walletType, - accountIndex, - transactions, - ); - const filteredTransactionsWithMetadata = extendTransactionsWithMetadata( - filteredTransactions, - transactionsMetadata, - ); - - const categorizedTransactions = categorizeTransactions( - filteredTransactionsWithMetadata, - accountStore, - accountIndex, - walletType, - network, - ); + const walletStore = storage.getWalletStore(walletId); + const chainStore = storage.getChainStore(network); + const { blockHeaders } = chainStore.state; + const categorizedTransactions = categorizeTransactions(transactionsWithMetadata, walletStore, accountIndex, walletType, network); const sortedCategorizedTransactions = categorizedTransactions.sort(sortByHeightDescending); each(sortedCategorizedTransactions, (categorizedTransaction) => { @@ -65,19 +40,16 @@ function getTransactionHistory() { isChainLocked, isInstantLocked, } = categorizedTransaction; - const blockHash = categorizedTransaction.blockHash !== '' ? categorizedTransaction.blockHash : null; - // To get time of block, let's find the blockheader. - const blockHeader = blockHeaders[blockHash]; - + const blockHeader = blockHeaders.get(blockHash); // If it's unconfirmed, we won't have a blockHeader nor it's time. const time = blockHeader ? blockHeader.time : -1; const normalizedTransactionHistory = { - // Would require knowing the vout of this vin to determinate inputAmount. - // This information could be fetched, but the necessity vs the cost is questionable. - // fees: calculateTransactionFees(categorizedTransaction.transaction), + // Would require knowing the vout of this vin to determinate inputAmount. + // This information could be fetched, but the necessity vs the cost is questionable. + // fees: calculateTransactionFees(categorizedTransaction.transaction), from, to, type, @@ -90,6 +62,7 @@ function getTransactionHistory() { transactionHistory.push(normalizedTransactionHistory); }); + // Sort by decreasing time. return transactionHistory.sort(sortbyTimeDescending); } diff --git a/src/types/Account/methods/getTransactionHistory.spec.js b/src/types/Account/methods/getTransactionHistory.spec.js index 6dd869b2a..c34aaaae8 100644 --- a/src/types/Account/methods/getTransactionHistory.spec.js +++ b/src/types/Account/methods/getTransactionHistory.spec.js @@ -3,11 +3,6 @@ const {Transaction, BlockHeader} = require('@dashevo/dashcore-lib'); const {WALLET_TYPES} = require('../../../CONSTANTS'); const getTransactions = require('./getTransactions'); const getTransactionHistory = require('./getTransactionHistory'); -const searchTransaction = require('../../Storage/methods/searchTransaction'); -const getTransaction = require('../../Storage/methods/getTransaction'); -const getBlockHeader = require('../../Storage/methods/getBlockHeader'); -const searchBlockHeader = require('../../Storage/methods/searchBlockHeader'); -const searchAddress = require('../../Storage/methods/searchAddress'); const mockedStoreHDWallet = require('../../../../fixtures/duringdevelop-fullstore-snapshot-1548538361'); const mockedStoreSingleAddress = require('../../../../fixtures/da07-fullstore-snapshot-1548533266'); diff --git a/src/types/Account/methods/getTransactions.js b/src/types/Account/methods/getTransactions.js index 453fe3884..77778716c 100644 --- a/src/types/Account/methods/getTransactions.js +++ b/src/types/Account/methods/getTransactions.js @@ -3,6 +3,17 @@ * @return {[Transaction]} transactions - All transaction in the store */ module.exports = function getTransactions() { - const store = this.storage.getStore(); - return store.transactions; + const chainStore = this.storage.getChainStore(this.network); + const walletStore = this.storage.getWalletStore(this.walletId); + const transactions = []; + const { addresses } = walletStore.getPathState(this.accountPath); + + Object.values(addresses).forEach((address) => { + const transactionIds = chainStore.getAddress(address).transactions; + transactionIds.forEach((transactionId) => { + const tx = chainStore.getTransaction(transactionId); + transactions.push([tx.transaction, tx.metadata]); + }); + }); + return transactions; }; diff --git a/src/types/Account/methods/getUTXOS.js b/src/types/Account/methods/getUTXOS.js index d0dcfcd6b..eab227c02 100644 --- a/src/types/Account/methods/getUTXOS.js +++ b/src/types/Account/methods/getUTXOS.js @@ -11,44 +11,32 @@ const { WALLET_TYPES, COINBASE_MATURITY } = require('../../../CONSTANTS'); function getUTXOS(options = { coinbaseMaturity: COINBASE_MATURITY, }) { - const self = this; const { walletId, network, - BIP44PATH, - walletType, } = this; const utxos = []; - const isHDWallet = [WALLET_TYPES.HDPUBLIC, WALLET_TYPES.HDWALLET].includes(walletType); - const currentBlockHeight = this.store.chains[network].blockHeight; + const chainStore = this.storage.getChainStore(network); + const accountState = this.storage.getWalletStore(walletId).getPathState(this.accountPath); + const currentBlockHeight = chainStore.blockHeight; - for (const addressType in this.store.wallets[walletId].addresses) { - if (!addressType || !['external', 'internal', 'misc'].includes(addressType)) { - continue; - } - for (const path in self.store.wallets[walletId].addresses[addressType]) { - if (!path) continue; - const address = self.store.wallets[walletId].addresses[addressType][path]; - - if (isHDWallet && !path.startsWith(BIP44PATH)) continue; - - for (const identifier in address.utxos) { - if (!identifier) continue; - const [txid, outputIndex] = identifier.split('-'); - const transaction = new Transaction(this.store.transactions[txid]); - - if (transaction.isCoinbase()) { - // If the transaction is not a special transaction, we can't check its - // maturity at the moment of writing this comment. - // The wallet library doesn't maintain the header chain and thus we can - // figure out the height only from the payload, but old coinbase transactions - // doesn't have a payload. - if (!transaction.isSpecialTransaction()) { - continue; - } + Object.values(accountState.addresses).forEach((address) => { + const addressData = chainStore.getAddress(address); + const utxosKeys = Object.keys(addressData.utxos); + utxosKeys.forEach((utxoIdentifier) => { + let skipUtxo = false; + const [txid, outputIndex] = utxoIdentifier.split('-'); + const { transaction } = chainStore.getTransaction(txid); + if (transaction.isCoinbase()) { + // If the transaction is not a special transaction, we can't check its + // maturity at the moment of writing this comment. + // The wallet library doesn't maintain the header chain and thus we can + // figure out the height only from the payload, but old coinbase transactions + // doesn't have a payload. + if (transaction.isSpecialTransaction()) { const transactionHeight = (this.store.transactionsMetadata[txid]) ? this.store.transactionsMetadata[txid].height : transaction.extraPayload.height; @@ -56,22 +44,24 @@ function getUTXOS(options = { // We check maturity is at least 100 blocks. // another way is to just read _scriptBuffer height value. if (transactionHeight + options.coinbaseMaturity > currentBlockHeight) { - continue; + skipUtxo = true; } } + } + if (!skipUtxo) { utxos.push(new Transaction.UnspentOutput( { txId: txid, vout: parseInt(outputIndex, 10), - script: address.utxos[identifier].script, - satoshis: address.utxos[identifier].satoshis, - address: new Address(address.address, network), + script: addressData.utxos[utxoIdentifier].script, + satoshis: addressData.utxos[utxoIdentifier].satoshis, + address: new Address(addressData.address, network), }, )); } - } - } + }); + }); return utxos.sort((a, b) => b.satoshis - a.satoshis); } diff --git a/src/types/Account/methods/getUTXOS.spec.js b/src/types/Account/methods/getUTXOS.spec.js index eafae7efc..0e2d8fbb7 100644 --- a/src/types/Account/methods/getUTXOS.spec.js +++ b/src/types/Account/methods/getUTXOS.spec.js @@ -1,167 +1,38 @@ const { expect } = require('chai'); const Dashcore = require('@dashevo/dashcore-lib'); const getUTXOS = require('./getUTXOS'); -const { Transaction } = Dashcore; +const mockAccountWithStorage = require("../../../test/mocks/mockAccountWithStorage"); -const mockedStoreEmpty = { - wallets: { - 123456789: { - addresses: {}, - }, - }, - chains: { - testnet: { - blockHeight: 10000 - } - } -}; +describe('Account - getUTXOS', function suite() { + this.timeout(10000); -const mockedStore2 = { - wallets: { - 123456789: { - addresses: { - external: { - "m/44'/1'/0'/0/0": { - address: 'yizmJb63ygipuJaRgYtpWCV2erQodmaZt8', - balanceSat: 100000000, - fetchedLast: 0, - path: "m/44'/1'/0'/0/0", - transactions: [ - 'dd7afaadedb5f022cec6e33f1c8520aac897df152bd9f876842f3723ab9614bc', - '1d8f924bef2e24d945d7de2ac66e98c8625e4cefeee4e07db2ea334ce17f9c35', - '7ae825f4ecccd1e04e6c123e0c55d236c79cd04c6ab64e839aed2ae0af3003e6', - ], - index: 0, - unconfirmedBalanceSat: 0, - used: true, - utxos: { - "dd7afaadedb5f022cec6e33f1c8520aac897df152bd9f876842f3723ab9614bc-0": - { - address: 'yizmJb63ygipuJaRgYtpWCV2erQodmaZt8', - txId: 'dd7afaadedb5f022cec6e33f1c8520aac897df152bd9f876842f3723ab9614bc', - outputIndex: 0, - script: '76a914f8c2652847720ab6d401291e5a48e2c8fe5d3c9f88ac', - satoshis: 100000000, - }, - } - }, - "m/44'/1'/1'/0/0":{ - address: 'yQ5TfKcj3NHM4V4K5VBgoFJj9Q4LKX13gn', - balanceSat: 14419880000, - fetchedLast: 0, - path: "m/44'/1'/1'/0/0", - transactions: [ - 'b8838022a663ae486192cf2499f9ae657e8c3a7e823a447b8b7e3d348d3916ba', - ], - index: 0, - unconfirmedBalanceSat: 0, - used: true, - utxos: { - "b8838022a663ae486192cf2499f9ae657e8c3a7e823a447b8b7e3d348d3916ba-0": - { - address: 'yQ5TfKcj3NHM4V4K5VBgoFJj9Q4LKX13gn', - txId: 'b8838022a663ae486192cf2499f9ae657e8c3a7e823a447b8b7e3d348d3916ba', - outputIndex: 0, - script: '76a914293b5b9a2154a0e4543027d694276cd5fdcb74cd88ac', - satoshis: 14419880000, - }, - } - } - }, - }, - }, - }, - chains: { - testnet: { - blockHeight: 10000 - } - }, - transactions: { - dd7afaadedb5f022cec6e33f1c8520aac897df152bd9f876842f3723ab9614bc: new Transaction({ - "hash": "f13a95fc3a9b6146590b12fbe48749738a1b3ffe30e42de5b1898f8f9d76b879", - "version": 3, - "inputs": [ - { - "prevTxId": "0000000000000000000000000000000000000000000000000000000000000000", - "outputIndex": 4294967295, - "sequenceNumber": 4294967295, - "script": "0264080101" - } - ], - "outputs": [ - { - "satoshis": 7972544484, - "script": "76a9144c1b05387342497e3c8fbe0b80754ae4b33134c488ac" - }, - { - "satoshis": 7972544480, - "script": "76a914214035c10a2d2cef9992ca715a0115366edd229e88ac" - } - ], - "nLockTime": 0, - "type": 5, - "extraPayload": "020064080000aa254fcb634bf1962b67bb64ce178a954353c71d0b6119361390a9fd1a71bd2c0000000000000000000000000000000000000000000000000000000000000000" - }), - b8838022a663ae486192cf2499f9ae657e8c3a7e823a447b8b7e3d348d3916ba: new Transaction({ - "hash": "f13a95fc3a9b6146590b12fbe48749738a1b3ffe30e42de5b1898f8f9d76b879", - "version": 3, - "inputs": [ - { - "prevTxId": "0000000000000000000000000000000000000000000000000000000000000000", - "outputIndex": 4294967295, - "sequenceNumber": 4294967295, - "script": "0264080101" - } - ], - "outputs": [ - { - "satoshis": 7972544484, - "script": "76a9144c1b05387342497e3c8fbe0b80754ae4b33134c488ac" - }, - { - "satoshis": 7972544480, - "script": "76a914214035c10a2d2cef9992ca715a0115366edd229e88ac" - } - ], - "nLockTime": 0, - "type": 5, - "extraPayload": "020064080000aa254fcb634bf1962b67bb64ce178a954353c71d0b6119361390a9fd1a71bd2c0000000000000000000000000000000000000000000000000000000000000000" - }), - }, - transactionsMetadata:{ + it('should return empty UTXOs list for new account', () => { + const mockedAccount = mockAccountWithStorage(); + const { walletId, accountPath, network } = mockedAccount; - } -}; + // Wipe transactions and addresses from the storage to simulate empty UTXOs + mockedAccount.storage.getWalletStore(walletId).state.paths.get(accountPath).addresses = {} + const chainStore = mockedAccount.storage.getChainStore(network); + chainStore.state.blockHeaders = {}; + chainStore.state.transactions = {}; + chainStore.state.addresses = {}; + + const utxos = getUTXOS.call(mockedAccount); -describe('Account - getUTXOS', function suite() { - this.timeout(10000); - it('should get the proper UTXOS list', () => { - const utxos = getUTXOS.call({ - store: mockedStoreEmpty, - getStore: mockedStoreEmpty, - walletId: '123456789', - network: 'testnet', - walletType: 'hdwallet', - BIP44PATH: "m/44'/1'/0'" - }); expect(utxos).to.be.deep.equal([]); + }) - const utxos2 = getUTXOS.call({ - store: mockedStore2, - getStore: mockedStore2, - walletId: '123456789', - network: 'testnet', - walletType: 'hdwallet', - BIP44PATH: "m/44'/1'/0'" - }); + it('should get the proper UTXOS list', () => { + const mockedAccount = mockAccountWithStorage(); + const utxos = getUTXOS.call(mockedAccount); - expect(utxos2).to.be.deep.equal([new Dashcore.Transaction.UnspentOutput( + expect(utxos).to.be.deep.equal([new Dashcore.Transaction.UnspentOutput( { - address: new Dashcore.Address('yizmJb63ygipuJaRgYtpWCV2erQodmaZt8'), - txId: 'dd7afaadedb5f022cec6e33f1c8520aac897df152bd9f876842f3723ab9614bc', - outputIndex: 0, - script: new Dashcore.Script('76a914f8c2652847720ab6d401291e5a48e2c8fe5d3c9f88ac'), - satoshis: 100000000, + address: new Dashcore.Address('yMEnFG5TBqEZXYXTg3PhENtZgGbwhw6qbX'), + txId: '33b14c6bc960c5717d734d5a15dc86b2060bf6e746cc509863344204d356cee4', + outputIndex: 1, + script: new Dashcore.Script('76a9140a163cfcba43b87e58b1996f61376d7bd8d9805288ac'), + satoshis: 224108673, }, )]); }); diff --git a/src/types/Account/methods/getUnconfirmedBalance.js b/src/types/Account/methods/getUnconfirmedBalance.js index 6b320a0ff..9833525b4 100644 --- a/src/types/Account/methods/getUnconfirmedBalance.js +++ b/src/types/Account/methods/getUnconfirmedBalance.js @@ -1,4 +1,4 @@ -const { duffsToDash } = require('../../../utils'); +const { duffsToDash, calculateDuffBalance } = require('../../../utils'); /** * Return the total balance of unconfirmed utxo @@ -7,11 +7,13 @@ const { duffsToDash } = require('../../../utils'); */ function getUnconfirmedBalance(displayDuffs = true) { const { - walletId, storage, + walletId, storage, accountPath, network, } = this; - const accountIndex = this.index; - const totalSat = storage.calculateDuffBalance(walletId, accountIndex, 'unconfirmed'); + const { addresses } = storage.getWalletStore(walletId).getPathState(accountPath); + + const chainStore = storage.getChainStore(network); + const totalSat = (calculateDuffBalance(Object.values(addresses), chainStore, 'unconfirmed')); return (displayDuffs) ? totalSat : duffsToDash(totalSat); } diff --git a/src/types/Account/methods/getUnconfirmedBalance.spec.js b/src/types/Account/methods/getUnconfirmedBalance.spec.js new file mode 100644 index 000000000..ad562c6bb --- /dev/null +++ b/src/types/Account/methods/getUnconfirmedBalance.spec.js @@ -0,0 +1,35 @@ +const { expect } = require('chai'); +const getTotalBalance = require('./getTotalBalance'); +const getConfirmedBalance = require('./getConfirmedBalance'); +const getUnconfirmedBalance = require('./getUnconfirmedBalance'); +const mockAccountWithStorage = require("../../../test/mocks/mockAccountWithStorage"); + +let mockedAccount; +describe('Account - getUnconfirmedBalance', function suite() { + this.timeout(10000); + before(() => { + mockedAccount = mockAccountWithStorage() + }); + + it('should correctly get the balance', () => { + const balance = getTotalBalance.call(mockedAccount); + expect(balance).to.equal(224108673); + }); + + it('should correctly get the balance confirmed only', () => { + const balance = getConfirmedBalance.call(mockedAccount); + expect(balance).to.equal(224108673); + }); + + // TODO: file looks like a complete duplicate of the getTotalBalance.spec.js + // Should we actually mock and test unconfirmed balance? + it('should correctly get the balance dash value instead of duff', () => { + const balanceTotalDash = getTotalBalance.call(mockedAccount, false); + const balanceUnconfDash = getUnconfirmedBalance.call(mockedAccount, false); + const balanceConfDash = getConfirmedBalance.call(mockedAccount, false); + + expect(balanceTotalDash).to.equal(2.24108673); + expect(balanceUnconfDash).to.equal(0); + expect(balanceConfDash).to.equal(2.24108673); + }); +}); diff --git a/src/types/Account/methods/getUnusedAddress.js b/src/types/Account/methods/getUnusedAddress.js index cdd6334c3..9a5447199 100644 --- a/src/types/Account/methods/getUnusedAddress.js +++ b/src/types/Account/methods/getUnusedAddress.js @@ -10,27 +10,44 @@ function getUnusedAddress(type = 'external', skip = 0) { let unused = { address: '', }; - let skipped = 0; + const skipped = 0; const { walletId } = this; const accountIndex = this.index; - const keys = Object.keys(this.store.wallets[walletId].addresses[type]) - // We filter out other potential account - .filter((el) => parseInt(el.split('/')[3], 10) === accountIndex); - for (let i = 0; i < keys.length; i += 1) { - const key = keys[i]; - const el = (this.store.wallets[walletId].addresses[type][key]); + const { addresses } = this.storage.getWalletStore(walletId).getPathState(this.accountPath); - if (!el || !el.address || el.address === '') { - logger.warn('getUnusedAddress received an empty one.', el, i, skipped); - } - unused = el; - if (el.used === false) { - if (skipped < skip) { - skipped += 1; - } else { - break; + const chainStore = this.storage.getChainStore(this.network); + + // We sort by type + const sortedAddresses = { + external: {}, + internal: {}, + }; + Object + .keys(addresses) + .forEach((path) => { + const splittedPath = path.split('/'); + let pathType = 'external'; + if (splittedPath.length > 1) { + pathType = (splittedPath[splittedPath.length - 2] === '0') ? 'external' : 'internal'; } + sortedAddresses[pathType][path] = addresses[path]; + }); + + const keys = Object.keys(sortedAddresses[type]); + + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + const address = (sortedAddresses[type][key]); + const addressState = chainStore.getAddress(address); + if (!addressState || addressState.transactions.length === 0) { + const keychainData = this.keyChainStore.getMasterKeyChain().getForPath(key); + unused = { + address: keychainData.address.toString(), + path: key, + index: parseInt(key.split('/').splice(-1)[0], 10), + }; + break; } } diff --git a/src/types/Account/methods/getUnusedAddress.spec.js b/src/types/Account/methods/getUnusedAddress.spec.js index 91d0f4400..109e1e8e3 100644 --- a/src/types/Account/methods/getUnusedAddress.spec.js +++ b/src/types/Account/methods/getUnusedAddress.spec.js @@ -4,55 +4,51 @@ const getUnusedAddress = require('./getUnusedAddress'); const getAddress = require('./getAddress'); const generateAddress = require('./generateAddress'); const KeyChain = require('../../KeyChain/KeyChain'); +const KeyChainStore = require("../../KeyChainStore/KeyChainStore"); +const mockAccountWithStorage = require("../../../test/mocks/mockAccountWithStorage"); -const mockedStore = require('../../../../fixtures/duringdevelop-fullstore-snapshot-1548538361'); - -const HDRootKeyMockedStore = 'tprv8ZgxMBicQKsPfEan1JB7NF4STbvnjGvP9318CN7FPGZp5nsUTBqmerxtDVpsJjFufyfkTgoe6QfHcDhMqjN3ZoFKtb8SnXFeubNjQreZSq6'; +const HDRootKeyMockedStore = 'tprv8gpcZgdXPzdXKBjSzieMyfwr6KidKucLiiA9VbCLCx1spyJNd38a5KdjtVuc9bVUNpFM2LdFCrYSyUXHx1RCTdr6qQen1HTECwAZ1p8yqiB'; describe('Account - getUnusedAddress', function suite() { this.timeout(10000); + let mockedAccount; + + before(() => { + mockedAccount = mockAccountWithStorage({ + keyChainStore: new KeyChainStore() + }) + + const keyChain = new KeyChain({ + type: 'HDPrivateKey', + HDPrivateKey: Dashcore.HDPrivateKey(HDRootKeyMockedStore), + lookAheadOpts: { + paths: { + 'm/0': 20, + 'm/1': 60, + }, + } + }); + + mockedAccount.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); + + mockedAccount.getAddress = getAddress.bind(mockedAccount); + mockedAccount.generateAddress = generateAddress.bind(mockedAccount); + }) + it('should get the proper unused address', () => { - const self = { - store: mockedStore, - storage: { - getStore: () => mockedStore, - importAddresses: (_) => (_), - }, - emit: (_) => (_), - keyChain: new KeyChain({ - type: 'HDPrivateKey', - HDPrivateKey: Dashcore.HDPrivateKey(HDRootKeyMockedStore), - }), - BIP44PATH: 'm/44\'/1\'/0\'', - walletId: '5061b8276c', - index: 0, - }; - self.getAddress = getAddress.bind(self); - self.generateAddress = generateAddress.bind(self); - - const unusedAddressExternal = getUnusedAddress.call(self); - const unusedAddressInternal = getUnusedAddress.call(self, 'internal'); - - // console.log(mockedStore.wallets[self.walletId].addresses.internal) + const unusedAddressExternal = getUnusedAddress.call(mockedAccount); + const unusedAddressInternal = getUnusedAddress.call(mockedAccount, 'internal'); + expect(unusedAddressExternal).to.be.deep.equal({ - address: 'yaVrJ5dgELFkYwv6AydDyGPAJQ5kTJXyAN', - balanceSat: 0, - fetchedLast: 1548538385006, - path: 'm/44\'/1\'/0\'/0/5', - transactions: [], - unconfirmedBalanceSat: 0, - utxos: {}, - used: false, + address: 'ycuRYGdNudwRxKNDQBqHDW7aGbJU6uqhXo', + index: 1, + path: 'm/0/1' }); + expect(unusedAddressInternal).to.be.deep.equal({ - address: 'yaZFt1VnAbi72mtyjDNV4AwTECqdg5Bv95', - balanceSat: 0, - fetchedLast: 1548538385164, - path: 'm/44\'/1\'/0\'/1/8', - transactions: [], - unconfirmedBalanceSat: 0, - utxos: {}, - used: false, + address: 'ybPPRHGDK6HUjAapixJaHjpFrBP7p1eNHX', + path: 'm/1/40', + index: 40 }); }); }); diff --git a/src/types/Account/methods/getUnusedIdentityIndex.js b/src/types/Account/methods/getUnusedIdentityIndex.js index 7cf353d07..0e32b274e 100644 --- a/src/types/Account/methods/getUnusedIdentityIndex.js +++ b/src/types/Account/methods/getUnusedIdentityIndex.js @@ -6,7 +6,7 @@ async function getUnusedIdentityIndex() { // Force identities sync before return unused index await this.getWorker('IdentitySyncWorker').execWorker(); - const identityIds = this.storage.getIndexedIdentityIds(this.walletId); + const identityIds = this.storage.getWalletStore(this.walletId).getIndexedIdentityIds(); const firstMissingIndex = identityIds.findIndex((identityId) => !identityId); diff --git a/src/types/Account/methods/importBlockHeader.js b/src/types/Account/methods/importBlockHeader.js index 01e4517a4..79c38b4fd 100644 --- a/src/types/Account/methods/importBlockHeader.js +++ b/src/types/Account/methods/importBlockHeader.js @@ -15,16 +15,14 @@ module.exports = async function importBlockHeader(blockHeader) { // knowing the following blockHeight blockheader's prevHash value // const previousHash = blockHeader.prevHash.reverse().toString('hex'); const { - walletId, BIP44PATH, index, store, storage, walletType, + walletId, BIP44PATH, index, store, storage, network, walletType, } = this; - const localWalletStore = store.wallets[walletId]; - const localAccountStore = ([WALLET_TYPES.HDPUBLIC, WALLET_TYPES.HDWALLET].includes(walletType)) - ? localWalletStore.accounts[BIP44PATH.toString()] - : localWalletStore.accounts[index.toString()]; + const applicationStore = storage.application; + const chainStore = storage.getChainStore(network); - localAccountStore.blockHash = blockHeader.id; + applicationStore.blockHash = blockHeader.id; - storage.importBlockHeader(blockHeader); + chainStore.importBlockHeader(blockHeader); logger.silly(`Account.importBlockHeader(${blockHeader.id})`); }; diff --git a/src/types/Account/methods/importTransactions.js b/src/types/Account/methods/importTransactions.js index 2e3bb0dd1..68e1f3ded 100644 --- a/src/types/Account/methods/importTransactions.js +++ b/src/types/Account/methods/importTransactions.js @@ -1,6 +1,7 @@ const logger = require('../../../logger'); const { WALLET_TYPES } = require('../../../CONSTANTS'); const ensureAddressesToGapLimit = require('../../../utils/bip44/ensureAddressesToGapLimit'); +const {chain} = require("lodash/seq"); /** * Import transactions and always keep a number of unused addresses up to gap @@ -8,30 +9,48 @@ const ensureAddressesToGapLimit = require('../../../utils/bip44/ensureAddressesT * @param transactions * @returns {Promise} */ -module.exports = async function importTransactions(transactions) { +module.exports = async function importTransactions(transactionsWithMayBeMetadata) { const { - walletType, - walletId, - index, - store, storage, - getAddress, + network, + walletId, + accountPath, + keyChainStore, } = this; - const localWalletStore = store.wallets[walletId]; + const chainStore = storage.getChainStore(network); + const accountStore = storage + .getWalletStore(walletId) + .getPathState(accountPath); - storage.importTransactions(transactions); - logger.silly(`Account.importTransactions(len: ${transactions.length})`); + const masterKeyChain = keyChainStore.getMasterKeyChain(); + const keyChains = keyChainStore.getKeyChains(); + transactionsWithMayBeMetadata.forEach((transactionWithMetadata) => { + if (!Array.isArray(transactionWithMetadata)) { + throw new Error('Expecting transactions to be an array of transaction and metadata elements'); + } + const [transaction, metadata] = transactionWithMetadata; + // Affected addresses might not be from our master keychain (account) + const affectedAddressesData = chainStore.importTransaction(transaction, metadata); + const affectedAddresses = Object.keys(affectedAddressesData); + logger.silly(`Account.importTransactions - Import ${transaction.hash} to chainStore. ${affectedAddresses.length} addresses affected.`); - if ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(walletType)) { - // After each imports, we will need to ensure we keep our gap of 20 unused addresses - return ensureAddressesToGapLimit( - localWalletStore, - walletType, - index, - getAddress.bind(this), - ); - } + affectedAddresses.forEach((address) => { + keyChains.forEach((keyChain)=>{ + const issuedPaths = keyChain.markAddressAsUsed(address); + if(issuedPaths){ + issuedPaths.forEach((issuedPath)=>{ + if(keyChain.keyChainId === masterKeyChain.keyChainId){ + logger.silly(`Account.importTransactions - newly issued paths ${issuedPath.length}`); + accountStore.addresses[issuedPath.path] = issuedPath.address.toString(); + } + chainStore.importAddress(issuedPath.address.toString()); + }) + } + }) + }); + }); + logger.silly(`Account.importTransactions(len: ${transactionsWithMayBeMetadata.length})`); return 0; }; diff --git a/src/types/Account/methods/injectPlugin.spec.js b/src/types/Account/methods/injectPlugin.spec.js index 68bd7c9b8..6849f6913 100644 --- a/src/types/Account/methods/injectPlugin.spec.js +++ b/src/types/Account/methods/injectPlugin.spec.js @@ -22,7 +22,7 @@ describe('Account - injectPlugin', function suite() { emit: emitter.emit, } it('should prevent sensible access', async function () { - const expectedException1 = 'Injection of plugin : storage Unallowed'; + const expectedException1 = 'Injection of plugin : storage into WorkingWorker not allowed'; await expectThrowsAsync(async () => await injectPlugin.call(mockedSelf, WorkingWorker), expectedException1); }); it('should work', function (done) { diff --git a/src/types/Account/methods/sign.js b/src/types/Account/methods/sign.js index 33509703a..33756ea29 100644 --- a/src/types/Account/methods/sign.js +++ b/src/types/Account/methods/sign.js @@ -30,5 +30,5 @@ module.exports = function sign(object, privateKeys = [], sigType) { }); } - return this.keyChain.sign(object, privateKeys, sigType); + return this.keyChainStore.getMasterKeyChain().sign(object, privateKeys, sigType); }; diff --git a/src/types/Account/methods/sign.spec.js b/src/types/Account/methods/sign.spec.js index 6e3b6e8e4..e2de5251e 100644 --- a/src/types/Account/methods/sign.spec.js +++ b/src/types/Account/methods/sign.spec.js @@ -18,7 +18,7 @@ describe('Account - sign', function suite() { wallet.disconnect(); }); it('should sign a transaction with a message', function () { - account.importTransactions(transactions); + account.importTransactions([[new Dashcore.Transaction(transactions["4e2a8b05a805fcee959b8ecfd5557e196a9b8490dd280d6f599b391d650407c8"])]]); const transaction = account.createTransaction({ recipient: 'yNPbcFfabtNmmxKdGwhHomdYfVs6gikbPf', // Evonet faucet satoshis: 1000000, // 1 Dash diff --git a/src/types/ChainStore/ChainStore.js b/src/types/ChainStore/ChainStore.js new file mode 100644 index 000000000..a153188ff --- /dev/null +++ b/src/types/ChainStore/ChainStore.js @@ -0,0 +1,44 @@ +/** + * Hold information relatives to a specific chains such as + * - blockheaders + * - transactions + * - instantlocks + * + * It also keep a state for watched addresses with their current balance and utxos. + */ + +class ChainStore { + constructor(networkIdentifier) { + this.network = networkIdentifier; + + this.state = { + fees: { + minRelay: -1, + }, + blockHeight: 0, + blockHeaders: new Map(), + transactions: new Map(), + instantLocks: new Map(), + addresses: new Map(), + }; + } +} + +ChainStore.prototype.considerTransaction = require('./methods/considerTransaction'); + +ChainStore.prototype.exportState = require('./methods/exportState'); +ChainStore.prototype.importState = require('./methods/importState'); + +ChainStore.prototype.getAddress = require('./methods/getAddress'); +ChainStore.prototype.getAddresses = require('./methods/getAddresses'); + +ChainStore.prototype.getBlockHeader = require('./methods/getBlockHeader'); +ChainStore.prototype.getInstantLock = require('./methods/getInstantLock'); +ChainStore.prototype.getTransaction = require('./methods/getTransaction'); + +ChainStore.prototype.importAddress = require('./methods/importAddress'); +ChainStore.prototype.importBlockHeader = require('./methods/importBlockHeader'); +ChainStore.prototype.importInstantLock = require('./methods/importInstantLock'); +ChainStore.prototype.importTransaction = require('./methods/importTransaction'); + +module.exports = ChainStore; diff --git a/src/types/ChainStore/methods/considerTransaction.js b/src/types/ChainStore/methods/considerTransaction.js new file mode 100644 index 000000000..d0b722040 --- /dev/null +++ b/src/types/ChainStore/methods/considerTransaction.js @@ -0,0 +1,65 @@ +const { Transaction } = require('@dashevo/dashcore-lib'); +const { is } = require('../../../utils'); +const { FETCHED_CONFIRMED_TRANSACTION, UPDATED_ADDRESS } = require('../../../EVENTS'); +const logger = require('../../../logger'); + +const { Output } = Transaction; + +function considerTransaction(transactionHash) { + logger.silly(`ChainStore - Considering transaction ${transactionHash}`); + const { transaction } = this.getTransaction(transactionHash); + + const { inputs, outputs } = transaction; + let outputIndex = -1; + + const processedAddressesForTx = {}; + + [...inputs, ...outputs].forEach((element) => { + const isOutput = (element instanceof Output); + if (isOutput) outputIndex += 1; + + if (element.script) { + const address = element.script.toAddress(this.network).toString(); + const watchedAddress = this.getAddress(address); + if (watchedAddress) { + // If the transactions has already been processed in a previous insertion, + // we can skip the processing now, it's important to do so as we might consider + // the same transaction multiple times (e.g: on address import) + if (watchedAddress.transactions.includes(transactionHash)) { + return; + } + + // We mark our address as affected so we update the tx later on + if (!processedAddressesForTx[watchedAddress.address]) { + processedAddressesForTx[watchedAddress.address] = watchedAddress; + } + + if (!isOutput) { + const vin = element; + const utxoKey = `${vin.prevTxId.toString('hex')}-${vin.outputIndex}`; + if (watchedAddress.utxos[utxoKey]) { + const previousOutput = watchedAddress.utxos[utxoKey]; + watchedAddress.balanceSat -= previousOutput.satoshis; + delete watchedAddress.utxos[utxoKey]; + } + } else { + const vout = element; + + const utxoKey = `${transaction.hash}-${outputIndex}`; + if (!watchedAddress.utxos[utxoKey]) { + watchedAddress.utxos[utxoKey] = vout.toJSON(); + watchedAddress.balanceSat += vout.satoshis; + } + } + } + } + }); + + // As the same address can have one or more inputs and one or more outputs in the same tx + // we update it's transactions array as last step of importing + Object.values(processedAddressesForTx).forEach((addressObject) => { + addressObject.transactions.push(transaction.hash); + }); + return processedAddressesForTx; +} +module.exports = considerTransaction; diff --git a/src/types/ChainStore/methods/exportState.js b/src/types/ChainStore/methods/exportState.js new file mode 100644 index 000000000..e82e91880 --- /dev/null +++ b/src/types/ChainStore/methods/exportState.js @@ -0,0 +1,46 @@ +function exportState() { + const { network, state } = this; + const { + fees, + blockHeight, + blockHeaders, + transactions, + instantLocks, + addresses, + } = state; + + const serializedState = { + network, + state: { + fees, + blockHeight, + blockHeaders: {}, + transactions: {}, + instantLocks: {}, + addresses: {}, + }, + }; + + [...blockHeaders.entries()].forEach(([blockHeaderHash, blockHeader]) => { + serializedState.state.blockHeaders[blockHeaderHash] = blockHeader.toString(); + }); + + [...transactions.entries()].forEach(([transactionHash, { transaction, metadata }]) => { + serializedState.state.transactions[transactionHash] = { + transaction: transaction.toString(), + metadata, + }; + }); + + [...instantLocks.entries()].forEach(([transactionHash, instantLock]) => { + serializedState.state.instantLocks[transactionHash] = instantLock.toString(); + }); + + [...addresses.entries()].forEach(([address, addressObj]) => { + serializedState.state.addresses[address] = addressObj; + }); + + return serializedState; +} + +module.exports = exportState; diff --git a/src/types/ChainStore/methods/getAddress.js b/src/types/ChainStore/methods/getAddress.js new file mode 100644 index 000000000..1076c9ed2 --- /dev/null +++ b/src/types/ChainStore/methods/getAddress.js @@ -0,0 +1,5 @@ +function getAddress(address) { + return this.state.addresses.get(address.toString()); +} + +module.exports = getAddress; diff --git a/src/types/ChainStore/methods/getAddresses.js b/src/types/ChainStore/methods/getAddresses.js new file mode 100644 index 000000000..6d6b94bcf --- /dev/null +++ b/src/types/ChainStore/methods/getAddresses.js @@ -0,0 +1,5 @@ +function getAddresses() { + return this.state.addresses; +} + +module.exports = getAddresses; diff --git a/src/types/ChainStore/methods/getBlockHeader.js b/src/types/ChainStore/methods/getBlockHeader.js new file mode 100644 index 000000000..be7d9c579 --- /dev/null +++ b/src/types/ChainStore/methods/getBlockHeader.js @@ -0,0 +1,5 @@ +function getBlockHeader(blockHeaderHash) { + return this.state.blockHeaders.get(blockHeaderHash); +} + +module.exports = getBlockHeader; diff --git a/src/types/ChainStore/methods/getInstantLock.js b/src/types/ChainStore/methods/getInstantLock.js new file mode 100644 index 000000000..523380658 --- /dev/null +++ b/src/types/ChainStore/methods/getInstantLock.js @@ -0,0 +1,5 @@ +function getInstantLock(transactionHash) { + return this.state.instantLocks.get(transactionHash); +} + +module.exports = getInstantLock; diff --git a/src/types/ChainStore/methods/getTransaction.js b/src/types/ChainStore/methods/getTransaction.js new file mode 100644 index 000000000..7f97e8713 --- /dev/null +++ b/src/types/ChainStore/methods/getTransaction.js @@ -0,0 +1,5 @@ +function getTransaction(transactionHash) { + return this.state.transactions.get(transactionHash); +} + +module.exports = getTransaction; diff --git a/src/types/ChainStore/methods/importAddress.js b/src/types/ChainStore/methods/importAddress.js new file mode 100644 index 000000000..11f6e48bb --- /dev/null +++ b/src/types/ChainStore/methods/importAddress.js @@ -0,0 +1,20 @@ +const logger = require('../../../logger'); + +function importAddress(address) { + logger.silly(`ChainStore - import address ${address}`); + if (this.state.addresses.has(address.toString())) throw new Error('Address is already inserted'); + this.state.addresses.set(address.toString(), { + address: address.toString(), + transactions: [], + utxos: {}, + balanceSat: 0, + unconfirmedBalanceSat: 0, + }); + + // We need to consider all previous transactions + [...this.state.transactions].forEach(([transactionHash]) => { + this.considerTransaction(transactionHash); + }); +} + +module.exports = importAddress; diff --git a/src/types/ChainStore/methods/importBlockHeader.js b/src/types/ChainStore/methods/importBlockHeader.js new file mode 100644 index 000000000..9d13c0642 --- /dev/null +++ b/src/types/ChainStore/methods/importBlockHeader.js @@ -0,0 +1,5 @@ +function importBlockHeader(blockHeader) { + this.state.blockHeaders.set(blockHeader.hash, blockHeader); +} + +module.exports = importBlockHeader; diff --git a/src/types/ChainStore/methods/importInstantLock.js b/src/types/ChainStore/methods/importInstantLock.js new file mode 100644 index 000000000..c39d5d493 --- /dev/null +++ b/src/types/ChainStore/methods/importInstantLock.js @@ -0,0 +1,5 @@ +function importInstantLock(instantLock) { + this.state.instantLocks.set(instantLock.txid, instantLock); +} + +module.exports = importInstantLock; diff --git a/src/types/ChainStore/methods/importInstantLock.spec.js b/src/types/ChainStore/methods/importInstantLock.spec.js new file mode 100644 index 000000000..86cfd3443 --- /dev/null +++ b/src/types/ChainStore/methods/importInstantLock.spec.js @@ -0,0 +1 @@ +const is = '01424b1c86a93794b4cc8bf9391494faf6994fe6794747ba2bc415e7bcd353204301000000c518facee06d0dc8d5b71028ce949dc8ba99d95c023af2752421a3647f6ce668827fbd5503f9a48a73ab9e30e564cd8b95f6c603caa93deaf4c3836c89d19cb71edc6edab0eccafe42d415e5598fa92e15ff71d5e8a81a34cac04c2fea031cc2f9e653a2d2a7fa2ef1994dee8df60e031c29ab7481ccb1bd3a89e7576cb79880'; \ No newline at end of file diff --git a/src/types/ChainStore/methods/importState.js b/src/types/ChainStore/methods/importState.js new file mode 100644 index 000000000..ab0feb9a2 --- /dev/null +++ b/src/types/ChainStore/methods/importState.js @@ -0,0 +1,42 @@ +const { + BlockHeader, + Transaction, + InstantLock, +} = require('@dashevo/dashcore-lib'); + +function importState(state) { + const { + network, + state: { + fees, + blockHeight, + blockHeaders, + transactions, + instantLocks, + addresses, + }, + } = state; + + this.network = network; + + this.state.fees = fees; + this.state.blockHeight = blockHeight; + + // We actually do not import address state, but import the address itself + // Which will force processing tx for each added address, therefore we might want to do + // import address as the first process done + Object.values(addresses).forEach(({ address }) => { + this.importAddress(address); + }); + + Object.values(blockHeaders).forEach((serializedBlockHeader) => { + this.importBlockHeader(new BlockHeader(Buffer.from(serializedBlockHeader, 'hex'))); + }); + Object.values(transactions).forEach(({ transaction: serializedTransaction, metadata }) => { + this.importTransaction(new Transaction(Buffer.from(serializedTransaction, 'hex')), metadata); + }); + Object.values(instantLocks).forEach((serializedInstantLock) => { + this.importTransaction(new InstantLock(Buffer.from(serializedInstantLock, 'hex'))); + }); +} +module.exports = importState; diff --git a/src/types/ChainStore/methods/importTransaction.js b/src/types/ChainStore/methods/importTransaction.js new file mode 100644 index 000000000..9bde724bf --- /dev/null +++ b/src/types/ChainStore/methods/importTransaction.js @@ -0,0 +1,19 @@ +const { Transaction } = require('@dashevo/dashcore-lib'); + +function importTransaction(transaction, metadata = {}) { + // Even if transaction is a transaction object, if manglized, + // it might end up not being a correct instanceof internally. + const normalizedTransaction = new Transaction(transaction); + this.state.transactions.set(normalizedTransaction.hash, { + transaction: normalizedTransaction, + metadata: { + blockHash: metadata.blockHash || null, + height: metadata.height || null, + isInstantLocked: metadata.isInstantLocked || null, + isChainLocked: metadata.isChainLocked || null, + }, + }); + return this.considerTransaction(normalizedTransaction.hash); +} + +module.exports = importTransaction; diff --git a/src/types/Identities/Identities.js b/src/types/Identities/Identities.js index f721356dc..f457fe9fc 100644 --- a/src/types/Identities/Identities.js +++ b/src/types/Identities/Identities.js @@ -10,7 +10,7 @@ class Identities { this.storage = wallet.storage; - this.keyChain = wallet.keyChain; + this.keyChain = wallet.keyChainStore.getMasterKeyChain(); } } diff --git a/src/types/Identities/methods/getIdentityHDKeyById.js b/src/types/Identities/methods/getIdentityHDKeyById.js index 374925a14..79cd6eedc 100644 --- a/src/types/Identities/methods/getIdentityHDKeyById.js +++ b/src/types/Identities/methods/getIdentityHDKeyById.js @@ -5,7 +5,10 @@ * @return {HDPrivateKey} */ function getIdentityHDKeyById(identityId, keyIndex) { - const identityIndex = this.storage.getIndexedIdentityIds(this.walletId).indexOf(identityId); + const identityIndex = this.storage + .getWalletStore(this.walletId) + .getIndexedIdentityIds() + .indexOf(identityId); if (identityIndex === -1) { throw new Error(`Identity with ID ${identityId} is not associated with wallet, or it's not synced`); diff --git a/src/types/Identities/methods/getIdentityHDKeyById.spec.js b/src/types/Identities/methods/getIdentityHDKeyById.spec.js index e13dc0875..2d8f3a2b8 100644 --- a/src/types/Identities/methods/getIdentityHDKeyById.spec.js +++ b/src/types/Identities/methods/getIdentityHDKeyById.spec.js @@ -1,7 +1,6 @@ const { expect } = require('chai'); const mockedStore = require('../../../../fixtures/sirentonight-fullstore-snapshot-1562711703'); const getIdentityHDKeyById = require('./getIdentityHDKeyById'); -const searchTransaction = require('../../Storage/methods/searchTransaction'); let walletMock; let fetchTransactionInfoCalledNb = 0; @@ -10,12 +9,15 @@ describe('Wallet#getIdentityHDKeyById', function suite() { this.timeout(10000); before(() => { expectedKeyMock = "123"; + const walletStoreMock = { + getIndexedIdentityIds: () => mockedStore.wallets[Object.keys(mockedStore.wallets)].identityIds + } + const storageMock = { store: mockedStore, getStore: () => mockedStore, mappedAddress: {}, - searchTransaction, - getIndexedIdentityIds: () => mockedStore.wallets[Object.keys(mockedStore.wallets)].identityIds, + getWalletStore: () => walletStoreMock, }; const walletId = Object.keys(mockedStore.wallets)[0]; walletMock = { diff --git a/src/types/Identities/methods/getIdentityIds.spec.js b/src/types/Identities/methods/getIdentityIds.spec.js index df90868e4..df2e88717 100644 --- a/src/types/Identities/methods/getIdentityIds.spec.js +++ b/src/types/Identities/methods/getIdentityIds.spec.js @@ -1,7 +1,6 @@ const { expect } = require('chai'); const mockedStore = require('../../../../fixtures/sirentonight-fullstore-snapshot-1562711703'); const getIdentityIds = require('./getIdentityIds'); -const searchTransaction = require('../../Storage/methods/searchTransaction'); let mockedWallet; let fetchTransactionInfoCalledNb = 0; @@ -12,7 +11,6 @@ describe('Wallet#getIdentityIds', function suite() { store: mockedStore, getStore: () => mockedStore, mappedAddress: {}, - searchTransaction, getIndexedIdentityIds: () => mockedStore.wallets[Object.keys(mockedStore.wallets)].identityIds, }; const walletId = Object.keys(mockedStore.wallets)[0]; diff --git a/src/types/KeyChain/KeyChain.js b/src/types/KeyChain/KeyChain.js index 643dcc627..749317787 100644 --- a/src/types/KeyChain/KeyChain.js +++ b/src/types/KeyChain/KeyChain.js @@ -1,52 +1,107 @@ const { Networks, HDPrivateKey, HDPublicKey } = require('@dashevo/dashcore-lib'); -const { has } = require('lodash'); +const { PrivateKey, PublicKey } = require('@dashevo/dashcore-lib'); +const { doubleSha256 } = require('../../utils/crypto'); +const { mnemonicToHDPrivateKey } = require('../../utils/mnemonic'); -// eslint-disable-next-line no-underscore-dangle -const _defaultOpts = { - network: Networks.testnet.toString(), - keys: {}, -}; +function generateKeyChainId(key) { + const keyChainIdSuffix = doubleSha256(key.toString()).toString('hex').slice(0, 10); + return `kc${keyChainIdSuffix}`; +} -class KeyChain { - constructor(opts = JSON.parse(JSON.stringify(_defaultOpts))) { - const defaultOpts = JSON.parse(JSON.stringify(_defaultOpts)); - this.network = defaultOpts.network; - this.keys = { ...defaultOpts.keys }; - - if (has(opts, 'HDPrivateKey')) { - this.type = 'HDPrivateKey'; - this.HDPrivateKey = (typeof opts.HDPrivateKey === 'string') ? HDPrivateKey(opts.HDPrivateKey) : opts.HDPrivateKey; - this.network = this.HDPrivateKey.network; - } else if (has(opts, 'HDPublicKey')) { - this.type = 'HDPublicKey'; - this.HDPublicKey = (typeof opts.HDPublicKey === 'string') ? HDPublicKey(opts.HDPublicKey) : opts.HDPublicKey; - this.network = this.HDPublicKey.network; - } else if (has(opts, 'privateKey')) { - this.type = 'privateKey'; - this.privateKey = opts.privateKey; - } else if (has(opts, 'publicKey')) { - this.type = 'publicKey'; - this.publicKey = opts.publicKey; - } else if (has(opts, 'address')) { - this.type = 'address'; - this.address = opts.address.toString(); - } else { - throw new Error('Expect privateKey, publicKey, HDPublicKey, HDPrivateKey or Address'); +function fromOptions(opts) { + let rootKey; + let rootKeyType; + let network = Networks.testnet.toString(); + let passphrase = ''; + + if (opts) { + if (opts.passphrase) { + passphrase = opts.passphrase; + } + if (opts.mnemonic) { + rootKeyType = 'HDPrivateKey'; + rootKey = (typeof opts.mnemonic === 'string') ? HDPrivateKey(opts.HDPrivateKey) : opts.HDPrivateKey; + } + if (opts.network) { + network = opts.network; + } + if (opts.HDPrivateKey) { + rootKeyType = 'HDPrivateKey'; + rootKey = (typeof opts.HDPrivateKey === 'string') ? HDPrivateKey(opts.HDPrivateKey) : opts.HDPrivateKey; + network = rootKey.network.toString(); + } else if (opts.HDPublicKey) { + rootKeyType = 'HDPublicKey'; + rootKey = (typeof opts.HDPublicKey === 'string') ? HDPublicKey(opts.HDPublicKey) : opts.HDPublicKey; + network = rootKey.network.toString(); + } else if (opts.privateKey) { + rootKeyType = 'privateKey'; + rootKey = (typeof opts.privateKey === 'string') ? new PrivateKey(opts.privateKey, opts.network) : opts.privateKey; + network = rootKey.network.toString(); + } else if (opts.publicKey) { + rootKeyType = 'publicKey'; + rootKey = (typeof opts.publicKey === 'string') ? new PublicKey(opts.publicKey, opts.network) : opts.publicKey; + network = rootKey.network.toString(); + } else if (opts.address) { + rootKeyType = 'address'; + rootKey = opts.address.toString(); + } else if (opts.mnemonic) { + return fromOptions({ + ...opts, + HDPrivateKey: mnemonicToHDPrivateKey(opts.mnemonic, network, passphrase), + }); } - if (opts.network) this.network = opts.network; - if (opts.keys) this.keys = { ...opts.keys }; } + + const lookAheadOpts = { + isWatched: true, + paths: {}, + ...opts.lookAheadOpts, + }; + + return { + rootKeyType, + rootKey, + network, + passphrase, + lookAheadOpts, + }; } -KeyChain.prototype.generateKeyForChild = require('./methods/generateKeyForChild'); -KeyChain.prototype.generateKeyForPath = require('./methods/generateKeyForPath'); +class KeyChain { + constructor(opts = {}) { + const { + rootKey, + rootKeyType, + network, + lookAheadOpts, + } = fromOptions(opts); + if (!rootKeyType || !rootKey) { + throw new Error('Expect one of [mnemonic, HDPrivateKey, HDPublicKey, privateKey, publicKey, address] to be provided.'); + } + this.keyChainId = generateKeyChainId(rootKey); + + this.rootKey = rootKey; + this.network = network; + this.rootKeyType = rootKeyType; + this.lookAheadOpts = { isWatched: true, ...lookAheadOpts }; + + this.issuedPaths = new Map(); + + this.maybeLookAhead(); + } +} +KeyChain.prototype.getForPath = require('./methods/getForPath'); +KeyChain.prototype.getForAddress = require('./methods/getForAddress'); KeyChain.prototype.getDIP15ExtendedKey = require('./methods/getDIP15ExtendedKey'); +KeyChain.prototype.getFirstUnusedAddress = require('./methods/getFirstUnusedAddress'); KeyChain.prototype.getHardenedBIP44HDKey = require('./methods/getHardenedBIP44HDKey'); KeyChain.prototype.getHardenedDIP9FeatureHDKey = require('./methods/getHardenedDIP9FeatureHDKey'); KeyChain.prototype.getHardenedDIP15AccountKey = require('./methods/getHardenedDIP15AccountKey'); -KeyChain.prototype.getKeyForChild = require('./methods/getKeyForChild'); -KeyChain.prototype.getKeyForPath = require('./methods/getKeyForPath'); -KeyChain.prototype.getPrivateKey = require('./methods/getPrivateKey'); +KeyChain.prototype.getRootKey = require('./methods/getRootKey'); +KeyChain.prototype.getWatchedAddresses = require('./methods/getWatchedAddresses'); +KeyChain.prototype.getIssuedPaths = require('./methods/getIssuedPaths'); +KeyChain.prototype.maybeLookAhead = require('./methods/maybeLookAhead'); +KeyChain.prototype.markAddressAsUsed = require('./methods/markAddressAsUsed'); KeyChain.prototype.sign = require('./methods/sign'); module.exports = KeyChain; diff --git a/src/types/KeyChain/KeyChain.spec.js b/src/types/KeyChain/KeyChain.spec.js index 3cf9bb0b5..b804d18f6 100644 --- a/src/types/KeyChain/KeyChain.spec.js +++ b/src/types/KeyChain/KeyChain.spec.js @@ -8,36 +8,33 @@ let keychain2; const mnemonic = 'during develop before curtain hazard rare job language become verb message travel'; const mnemonic2 = 'birth kingdom trash renew flavor utility donkey gasp regular alert pave layer'; const pk = '4226d5e2fe8cbfe6f5beb7adf5a5b08b310f6c4a67fc27826779073be6f5699e'; - +const hdPublicKey = 'xpub661MyMwAqRbcFGB6XSWBsD725rJDUbFUpy4zWe2u22nJ2BxpoHFxtVDfKnTnvVQHohnY7AsVpRTHDv6PyPQTYu1KxFPKw29MAVXPEpz1G7V'; const expectedRootDIP15AccountKey_0 = 'tprv8hRzmheQujhJN5XP2dj955nAFCKeEoSifJRWuutdbwWRtusdDQ426jbp75EqErUSuTxmPyxYmP1TpcF5qdxGhXLNXRLMGsRLG6NFCv1WnaQ'; const expectedRootDIP15AccountKey_1 = 'tprv8hRzmheQujhJQyCtFTuUFHxB3Ag5VLB994zhH4CfxbA41cq73HT2mpYq5M33V54oJyn6g514saxxVJB886G55eYX56J6D6x87UNNT6iQHkR'; const expectedKeyForChild_0 = 'tprv8d4podc2Tg459CH2bwLHXj3vdJFBT2rdsk5Nr1djH7hzHdt5LRdvN6QyFwMiDy7ffRdik7fEVRKKgsHB4F18sh8xF6jFXpKq4sUgGBoSbKw'; describe('Keychain', function suite() { - this.timeout(10000); + this.timeout(1000); it('should create a keychain', () => { - const expectedException1 = 'Expect privateKey, publicKey, HDPublicKey, HDPrivateKey or Address'; + const expectedException1 = 'Expect one of [mnemonic, HDPrivateKey, HDPublicKey, privateKey, publicKey, address] to be provided.'; expect(() => new KeyChain()).to.throw(expectedException1); - expect(() => new KeyChain(mnemonic)).to.throw(expectedException1); - keychain = new KeyChain({ HDPrivateKey: mnemonicToHDPrivateKey(mnemonic, 'testnet') }); - expect(keychain.type).to.equal('HDPrivateKey'); + keychain = new KeyChain({ mnemonic: mnemonic, network: 'testnet' }); + expect(keychain.rootKeyType).to.equal('HDPrivateKey'); expect(keychain.network.toString()).to.equal('testnet'); - expect(keychain.keys).to.deep.equal({}); + expect(keychain.rootKey.network.toString()).to.equal('testnet'); - keychain2 = new KeyChain({ HDPrivateKey: mnemonicToHDPrivateKey(mnemonic2, 'mainnet') }); - }); - it('should get private key', () => { - expect(keychain.getPrivateKey().toString()).to.equal(pk); + keychain2 = new KeyChain({ mnemonic: mnemonic2, network: 'livenet' }); }); + it('should generate key for full path', () => { const path = 'm/44\'/1\'/0\'/0/0'; - const pk2 = keychain.getKeyForPath(path); + const pk2 = keychain.getForPath(path).key; const address = new Dashcore.Address(pk2.publicKey.toAddress()).toString(); expect(address).to.equal('yNfUebksUc5HoSfg8gv98ruC3jUNJUM8pT'); }); it('should get hardened feature path', () => { const hardenedPk = keychain.getHardenedBIP44HDKey(); - const pk2 = keychain.getKeyForPath('m/44\'/1\''); + const pk2 = keychain.getForPath('m/44\'/1\'').key; expect(pk2.toString()).to.equal(hardenedPk.toString()); }); it('should get DIP15 account key', function () { @@ -69,7 +66,7 @@ describe('Keychain', function suite() { }); it('should derive from hardened feature path', () => { const hardenedHDKey = keychain.getHardenedBIP44HDKey(); - const pk2 = keychain.getKeyForPath(`m/44'/1'`); + const pk2 = keychain.getForPath(`m/44'/1'`).key; expect(pk2.toString()).to.equal(hardenedHDKey.toString()); expect(hardenedHDKey.toString()).to.deep.equal('tprv8dtrJNytYHRiZY585hmHGbguS6VjGpK49puSB7oXZjLHcQfrAzQkF4ZCxM2DkEbyY85J4EYcZ8EjT5ZCU8ozB727TDdodbfXet5GkGau2RQ'); const derivedPk = hardenedHDKey.deriveChild(0, true).deriveChild(0).deriveChild(0); @@ -78,29 +75,86 @@ describe('Keychain', function suite() { }); it('should get hardened DIP9FeatureHDKey', function () { const hardenedHDKey = keychain.getHardenedDIP9FeatureHDKey(); - const pk2 = keychain.getKeyForPath(`m/9'/1'`); + const pk2 = keychain.getForPath(`m/9'/1'`).key; expect(pk2.toString()).to.equal(hardenedHDKey.toString()); expect(hardenedHDKey.toString()).to.deep.equal('tprv8fBJjWoGgCpGRCbyzE9RUA59rmoN1RUijhLnXGL4VHnLxvSe523yVg4GrGzbR6TyXtdynAEh5z8UX55EXt2Cb3xjvrsx2PgTY9BHxzFVkWn'); }); - it('should generate key for child', () => { + it('should get key for path', () => { const keychain2 = new KeyChain({ HDPrivateKey: mnemonicToHDPrivateKey(mnemonic, 'testnet') }); - const keyForChild = keychain2.generateKeyForChild(0); + const keyForChild = keychain2.getForPath(0).key; expect(keyForChild.toString()).to.equal(expectedKeyForChild_0); }); + it('should mark address watched and get watched addresses', function () { + const key0 = keychain.getForPath(0); + key0.isWatched = true; + const key1 = keychain.getForPath(1, { isWatched: true }); + const key2 = keychain.getForPath(2, { isWatched: true }); + const watchedAddresses = keychain.getWatchedAddresses(); + let expectedWatchedAddresses = [ + keychain.getForPath(0).address.toString(), + keychain.getForPath(1).address.toString(), + keychain.getForPath(2).address.toString() + ]; + expect(watchedAddresses).to.deep.equal(expectedWatchedAddresses); + }); + it('should get watched addresses', function () { + const watchedAddresses = keychain.getWatchedAddresses(); + const expectedWatchedAddresses = [ + 'ybQDfNwiDjk8ZH5UUmHQzAMEmjbrbK5dAj', + 'yhFX5rseJPitV45HUCaa9haeGHtLuooBaq', + 'yhqxsmYk6jfoGWf1hJKq7d4U2cGHCgzpFU' + ] + expect(watchedAddresses).to.deep.equal(expectedWatchedAddresses); + }); + // it('should get watched public keys', function () { + // const watchedPubKeys = keychain.getWatchedPublicKeys(); + // const expectedWatchedPubKeys = [ + // '03e6ab8177a7ca2699da4f83ca3c27768fb88b70ae9d6bde1cba8de88355ccf199', + // '0246a870b65153b98e453ff08f7198d06bce2a790286f44d90929aceafafa0673f', + // '025ad16f78f67801e52abe5b512d66e3896d4c2fa3ca3150349437a5dd13519967' + // ] + // expect(watchedPubKeys).to.deep.equal(expectedWatchedPubKeys) + // }); + it('should remove an address from watched addresses', function () { + const data0 = keychain.getForPath(0, { isWatched: false }); + const data1 = keychain.getForPath(1); + const data2 = keychain.getForPath(2); + data2.isWatched = false; - it('should sign', () => { - + expect(keychain.getWatchedAddresses().length).to.equal(1); }); -}); -describe('Keychain - clone', function suite() { - this.timeout(10000); - it('should clone', () => { - const keychain2 = new KeyChain(keychain); - expect(keychain2).to.deep.equal(keychain); - expect(keychain2.keys).to.deep.equal(keychain.keys); + it('should get address for path', function (){ + const address0_1 = keychain.getForPath(1).address; + expect(address0_1.toString()).to.equal('yhFX5rseJPitV45HUCaa9haeGHtLuooBaq') + }) + it('should mark address as used', function () { + const address0_0 = keychain.getForPath(0).address; + keychain.markAddressAsUsed(address0_0); + expect(keychain.issuedPaths.get(0).isUsed).to.equal(true) }); }); +describe('Keychain - HDPublicKey', function suite(){ + let hdpubKeyChain; + it('should initiate from a HDPublicKey', function () { + hdpubKeyChain = new KeyChain({ + HDPublicKey: new Dashcore.HDPublicKey(hdPublicKey), + network: 'testnet' + }); + // As the HDPublicKey starts with xpub, it's livenet and should take priority over our network being set. + expect(hdpubKeyChain.network.toString()).to.equal('livenet'); + expect(hdpubKeyChain.keyChainId).to.equal('kc5059442d66'); + expect(hdpubKeyChain.getRootKey().toString()).to.equal(hdPublicKey); + }); + it('should derivate', function () { + const key0_1 = hdpubKeyChain.getForPath(1).key; + expect(key0_1.publicKey.toAddress(hdpubKeyChain.network).toString()).to.equal('XoL5LcBiDWcj6L7fFwytsFoX5Vz7BVXw9w') + }); + it('should get address for path', function (){ + const address0_1 = hdpubKeyChain.getForPath(2).address; + expect(address0_1.toString()).to.equal('XwAzpxQKbgebaLiadq1c6rDeFJ4FKPUufy') + }) +}) describe('Keychain - single privateKey', function suite() { this.timeout(10000); it('should correctly errors out when not a HDPublicKey (privateKey)', () => { @@ -108,18 +162,16 @@ describe('Keychain - single privateKey', function suite() { const network = 'livenet'; const pkKeyChain = new KeyChain({ privateKey, network }); expect(pkKeyChain.network).to.equal(network); - expect(pkKeyChain.keys).to.deep.equal({}); - expect(pkKeyChain.type).to.equal('privateKey'); - expect(pkKeyChain.privateKey).to.equal(privateKey); + expect(pkKeyChain.rootKeyType).to.equal('privateKey'); + expect(pkKeyChain.rootKey.toString()).to.equal(privateKey); const expectedException1 = 'Wallet is not loaded from a mnemonic or a HDPubKey, impossible to derivate keys'; - const expectedException2 = 'Wallet is not loaded from a mnemonic or a HDPubKey, impossible to derivate child'; - expect(() => pkKeyChain.generateKeyForPath()).to.throw(expectedException1); - expect(() => pkKeyChain.generateKeyForChild()).to.throw(expectedException2); + expect(() => pkKeyChain.getForPath()).to.throw(expectedException1); }); it('should get private key', () => { const privateKey = Dashcore.PrivateKey().toString(); const pkKeyChain = new KeyChain({ privateKey, network: 'livenet' }); - expect(pkKeyChain.getPrivateKey().toString()).to.equal(privateKey); + expect(pkKeyChain.getRootKey().toString()).to.equal(privateKey); + expect(pkKeyChain.rootKey.toString()).to.equal(privateKey); }); }); diff --git a/src/types/KeyChain/methods/generateKeyForChild.js b/src/types/KeyChain/methods/generateKeyForChild.js deleted file mode 100644 index 5d6dcaeba..000000000 --- a/src/types/KeyChain/methods/generateKeyForChild.js +++ /dev/null @@ -1,19 +0,0 @@ -const { - HDPublicKey, -} = require('@dashevo/dashcore-lib'); -/** - * Derive from HDPrivateKey to a child - * @param {number} index - Child index to derivee to - * @param {HDPrivateKey|HDPublicKey} [type=HDPrivateKey] - set the type of returned keys - * @return {HDPrivateKey|HDPublicKey} - */ -function generateKeyForChild(index, type = 'HDPrivateKey') { - if (!['HDPrivateKey', 'HDPublicKey'].includes(this.type)) { - throw new Error('Wallet is not loaded from a mnemonic or a HDPubKey, impossible to derivate child'); - } - const HDKey = this[this.type]; - const hdPublicKey = HDKey.deriveChild(index); - if (type === 'HDPublicKey') return HDPublicKey(hdPublicKey); - return hdPublicKey; -} -module.exports = generateKeyForChild; diff --git a/src/types/KeyChain/methods/generateKeyForPath.js b/src/types/KeyChain/methods/generateKeyForPath.js deleted file mode 100644 index e266c96d7..000000000 --- a/src/types/KeyChain/methods/generateKeyForPath.js +++ /dev/null @@ -1,19 +0,0 @@ -const { - HDPublicKey, -} = require('@dashevo/dashcore-lib'); -/** - * Derive from HDPrivateKey to a specific path - * @param {string} path - * @param {HDPrivateKey|HDPublicKey} [type=HDPrivateKey] - set the type of returned keys - * @return {HDPrivateKey|HDPublicKey} - */ -function generateKeyForPath(path, type = 'HDPrivateKey') { - if (!['HDPrivateKey', 'HDPublicKey'].includes(this.type)) { - throw new Error('Wallet is not loaded from a mnemonic or a HDPubKey, impossible to derivate keys'); - } - const HDKey = this[this.type]; - const hdPrivateKey = HDKey.derive(path); - if (type === 'HDPublicKey') return HDPublicKey(hdPrivateKey); - return hdPrivateKey; -} -module.exports = generateKeyForPath; diff --git a/src/types/KeyChain/methods/getDIP15ExtendedKey.js b/src/types/KeyChain/methods/getDIP15ExtendedKey.js index a7eed1547..b65dc10d7 100644 --- a/src/types/KeyChain/methods/getDIP15ExtendedKey.js +++ b/src/types/KeyChain/methods/getDIP15ExtendedKey.js @@ -8,7 +8,7 @@ * @return {HDPrivateKey|HDPublicKey} */ function getDIP15ExtendedKey(userUniqueId, contactUniqueId, index = 0, accountIndex = 0, type = 'HDPrivateKey') { - if (!['HDPrivateKey', 'HDPublicKey'].includes(this.type)) { + if (!['HDPrivateKey', 'HDPublicKey'].includes(this.rootKeyType)) { throw new Error('Wallet is not loaded from a mnemonic or a HDPubKey, impossible to derivate keys'); } if (!userUniqueId || !contactUniqueId) throw new Error('Required userUniqueId and contactUniqueId to be defined'); diff --git a/src/types/KeyChain/methods/getFirstUnusedAddress.js b/src/types/KeyChain/methods/getFirstUnusedAddress.js new file mode 100644 index 000000000..cb72d60c4 --- /dev/null +++ b/src/types/KeyChain/methods/getFirstUnusedAddress.js @@ -0,0 +1,14 @@ +function getFirstUnusedAddress() { + const allUnused = this.getIssuedPaths() + .filter((path)=>{ + return path.isUsed === false + }); + + const firstUnused = allUnused.slice(0,1)[0] + + return { + path: firstUnused.path, + address: firstUnused.address.toString() + } +} +module.exports = getFirstUnusedAddress; diff --git a/src/types/KeyChain/methods/getForAddress.js b/src/types/KeyChain/methods/getForAddress.js new file mode 100644 index 000000000..4b8a01ddf --- /dev/null +++ b/src/types/KeyChain/methods/getForAddress.js @@ -0,0 +1,12 @@ +function getForAddress(address) { + const searchResult = [...this.issuedPaths.entries()] + .find(([, el]) => el.address.toString() === address.toString()); + + if (!searchResult) { + return null; + } + const [path] = searchResult; + return this.getForPath(path); +} + +module.exports = getForAddress; diff --git a/src/types/KeyChain/methods/getForPath.js b/src/types/KeyChain/methods/getForPath.js new file mode 100644 index 000000000..fcd2b032b --- /dev/null +++ b/src/types/KeyChain/methods/getForPath.js @@ -0,0 +1,39 @@ +const logger = require('../../../logger'); + +function getForPath(path, opts = {}) { + const stringifiedPath = path.toString(); + logger.silly(`KeyChain.getForPath(${stringifiedPath})`); + const isUsed = (opts && opts.isUsed !== undefined) ? opts.isUsed : false; + const isWatched = (opts && opts.isWatched !== undefined) ? opts.isWatched : false; + const isDerivable = ['HDPrivateKey', 'HDPublicKey'].includes(this.rootKeyType); + if (!isDerivable && stringifiedPath !== '0') { + throw new Error(`Wallet is not loaded from a mnemonic or a HDPrivateKey, impossible to derivate keys for path ${stringifiedPath}`); + } + + let data; + if (this.issuedPaths.has(stringifiedPath)) { + data = this.issuedPaths.get(stringifiedPath); + if (opts && opts.isWatched !== undefined && data.isWatched !== opts.isWatched) { + data.isWatched = opts.isWatched; + } + if (opts && opts.isUsed !== undefined && data.isUsed !== opts.isUsed) { + data.isUsed = opts.isUsed; + } + return data; + } + + const key = (isDerivable) ? this.rootKey.derive(stringifiedPath) : this.getRootKey(); + + data = { + path: stringifiedPath, + key, + isUsed, + isWatched, + address: key.publicKey.toAddress(this.network), + }; + + this.issuedPaths.set(stringifiedPath, data); + return { hasBeenNewlyIssued: true, ...data }; +} + +module.exports = getForPath; diff --git a/src/types/KeyChain/methods/getHardenedBIP44HDKey.js b/src/types/KeyChain/methods/getHardenedBIP44HDKey.js index 75a662847..48da61c16 100644 --- a/src/types/KeyChain/methods/getHardenedBIP44HDKey.js +++ b/src/types/KeyChain/methods/getHardenedBIP44HDKey.js @@ -2,11 +2,10 @@ const { BIP44_TESTNET_ROOT_PATH, BIP44_LIVENET_ROOT_PATH } = require('../../../C /** * Return a safier root keys to derivate from - * @param {HDPrivateKey|HDPublicKey} [type=HDPrivateKey] - set the type of returned keys * @return {HDPrivateKey|HDPublicKey} */ -function getHardenedBIP44HDKey(type = 'HDPrivateKey') { +function getHardenedBIP44HDKey() { const pathRoot = (this.network.toString() === 'testnet') ? BIP44_TESTNET_ROOT_PATH : BIP44_LIVENET_ROOT_PATH; - return this.generateKeyForPath(pathRoot, type); + return this.getForPath(pathRoot).key; } module.exports = getHardenedBIP44HDKey; diff --git a/src/types/KeyChain/methods/getHardenedDIP9FeatureHDKey.js b/src/types/KeyChain/methods/getHardenedDIP9FeatureHDKey.js index 06a4733af..422c9fab6 100644 --- a/src/types/KeyChain/methods/getHardenedDIP9FeatureHDKey.js +++ b/src/types/KeyChain/methods/getHardenedDIP9FeatureHDKey.js @@ -2,11 +2,10 @@ const { DIP9_LIVENET_ROOT_PATH, DIP9_TESTNET_ROOT_PATH } = require('../../../CON /** * Return a safier root path to derivate from - * @param {HDPrivateKey|HDPublicKey} [type=HDPrivateKey] - set the type of returned keys * @return {HDPrivateKey|HDPublicKey} */ -function getHardenedDIP9FeatureHDKey(type = 'HDPrivateKey') { +function getHardenedDIP9FeatureHDKey() { const pathRoot = (this.network.toString() === 'testnet') ? DIP9_TESTNET_ROOT_PATH : DIP9_LIVENET_ROOT_PATH; - return this.generateKeyForPath(pathRoot, type); + return this.getForPath(pathRoot).key; } module.exports = getHardenedDIP9FeatureHDKey; diff --git a/src/types/KeyChain/methods/getIssuedPaths.js b/src/types/KeyChain/methods/getIssuedPaths.js new file mode 100644 index 000000000..324750853 --- /dev/null +++ b/src/types/KeyChain/methods/getIssuedPaths.js @@ -0,0 +1,5 @@ +function getWatchedAddresses() { + return [...this.issuedPaths.values()]; +} + +module.exports = getWatchedAddresses; diff --git a/src/types/KeyChain/methods/getKeyForChild.js b/src/types/KeyChain/methods/getKeyForChild.js deleted file mode 100644 index 768f1740d..000000000 --- a/src/types/KeyChain/methods/getKeyForChild.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Generate a key by deriving it's direct child - * @param index - {Number} - * @return {HDPrivateKey | HDPublicKey} - */ -function getKeyForChild(index = 0, type = 'HDPrivateKey') { - return this.generateKeyForChild(index, type); -} - -module.exports = getKeyForChild; diff --git a/src/types/KeyChain/methods/getKeyForPath.js b/src/types/KeyChain/methods/getKeyForPath.js deleted file mode 100644 index 4f7bb180d..000000000 --- a/src/types/KeyChain/methods/getKeyForPath.js +++ /dev/null @@ -1,29 +0,0 @@ -const { HDPrivateKey, PrivateKey } = require('@dashevo/dashcore-lib'); -/** - * Get a key from the cache or generate if none - * @param path - * @param type - def : HDPrivateKey - Expected return datatype of the keys - * @return {HDPrivateKey | HDPublicKey} - */ -function getKeyForPath(path, type = 'HDPrivateKey') { - if (type === 'HDPublicKey') { - // In this case, we do not generate or keep in cache. - return this.generateKeyForPath(path, type); - } - - if (this.type === 'HDPrivateKey') { - if (!this.keys[path]) { - this.keys[path] = this.generateKeyForPath(path, type).toString(); - } - return new HDPrivateKey(this.keys[path]); - } - if (this.type === 'privateKey') { - if (!this.keys[path]) { - this.keys[path] = this.getPrivateKey(path).toString(); - return new PrivateKey(this.keys[path]); - } - return new PrivateKey(this.keys[path]); - } - return new HDPrivateKey(this.keys[path]); -} -module.exports = getKeyForPath; diff --git a/src/types/KeyChain/methods/getPrivateKey.js b/src/types/KeyChain/methods/getPrivateKey.js deleted file mode 100644 index 602c287da..000000000 --- a/src/types/KeyChain/methods/getPrivateKey.js +++ /dev/null @@ -1,18 +0,0 @@ -const { - PrivateKey, -} = require('@dashevo/dashcore-lib'); - -/** - * @return {PrivateKey} - */ -function getPrivateKey() { - let pk; - if (this.type === 'HDPrivateKey') { - pk = PrivateKey(this.HDPrivateKey.privateKey); - } - if (this.type === 'privateKey') { - pk = PrivateKey(this.privateKey); - } - return pk; -} -module.exports = getPrivateKey; diff --git a/src/types/KeyChain/methods/getRootKey.js b/src/types/KeyChain/methods/getRootKey.js new file mode 100644 index 000000000..f2b54723a --- /dev/null +++ b/src/types/KeyChain/methods/getRootKey.js @@ -0,0 +1,5 @@ +function getRootKey() { + return this.rootKey; +} + +module.exports = getRootKey; diff --git a/src/types/KeyChain/methods/getWatchedAddresses.js b/src/types/KeyChain/methods/getWatchedAddresses.js new file mode 100644 index 000000000..815432911 --- /dev/null +++ b/src/types/KeyChain/methods/getWatchedAddresses.js @@ -0,0 +1,7 @@ +function getWatchedAddresses() { + return [...this.issuedPaths.entries()] + .filter(([, el]) => el.isWatched === true) + .map(([, el]) => el.address.toString()); +} + +module.exports = getWatchedAddresses; diff --git a/src/types/KeyChain/methods/markAddressAsUsed.js b/src/types/KeyChain/methods/markAddressAsUsed.js new file mode 100644 index 000000000..9df149d7d --- /dev/null +++ b/src/types/KeyChain/methods/markAddressAsUsed.js @@ -0,0 +1,18 @@ +const logger = require('../../../logger'); + +function markAddressAsUsed(address) { + + const searchResult = [...this.issuedPaths.entries()] + .find(([, el]) => el.address.toString() === address.toString()); + + if (searchResult) { + + const [, addressData] = searchResult; + logger.silly(`KeyChain - Marking ${address} ${addressData.path} as used`); + addressData.isUsed = true; + + return this.maybeLookAhead(); + } + +} +module.exports = markAddressAsUsed; diff --git a/src/types/KeyChain/methods/maybeLookAhead.js b/src/types/KeyChain/methods/maybeLookAhead.js new file mode 100644 index 000000000..cb654dff1 --- /dev/null +++ b/src/types/KeyChain/methods/maybeLookAhead.js @@ -0,0 +1,83 @@ +function maybeLookAhead() { + const { lookAheadOpts } = this; + const generatedPaths = []; + + if (Object.keys(lookAheadOpts.paths).length === 0) { + return generatedPaths; + } + + const usedPaths = [...this.issuedPaths.entries()] + .filter(([, el]) => el.isUsed === true) + .map(([path]) => path); + + const sortedUsedPathByBase = {}; + + usedPaths + .forEach((usedPath) => { + const splitted = usedPath.split('/'); + // Removes the index to sort which and how many base path has been generated + const basePath = splitted.splice(0, splitted.length - 1).join('/'); + if (!sortedUsedPathByBase[basePath]) sortedUsedPathByBase[basePath] = []; + sortedUsedPathByBase[basePath].push(usedPath); + }); + + const lastUsedIndexes = {}; + const lastGeneratedIndexes = {}; + + Object + .entries(lookAheadOpts.paths) + .forEach(([basePath]) => { + lastUsedIndexes[basePath] = -1; + lastGeneratedIndexes[basePath] = -1; + }); + + Object + .entries(sortedUsedPathByBase) + .forEach(([basePath, basePaths]) => { + // Sorting by index is also needed as the user might have manually issue a key + // and set it up to watched or used outside of lookAhead bounds + const sortedBasePaths = basePaths.sort((a, b) => a.split('/').splice(-1) - b.split('/').splice(-1)); + + let prevIndex; + sortedBasePaths.forEach((path) => { + const addressData = this.issuedPaths.get(path); + + const currentIndex = parseInt(path.split('/').splice(-1), 10); + + if (addressData.isUsed) { + lastUsedIndexes[basePath] = currentIndex; + } + + lastGeneratedIndexes[basePath] = currentIndex; + prevIndex = currentIndex; + }); + }); + + const isWatched = lookAheadOpts.isWatched || false; + + Object + .entries(lastGeneratedIndexes) + .forEach(([basePath]) => { + const lastUsedAndLastGenGap = lastGeneratedIndexes[basePath] - lastUsedIndexes[basePath]; + const pathAmountToGenerate = lookAheadOpts.paths[basePath] - lastUsedAndLastGenGap; + + if (pathAmountToGenerate > 0) { + const lastIndex = lastGeneratedIndexes[basePath]; + const lastIndexToGenerate = lastIndex + pathAmountToGenerate; + + if (lastIndexToGenerate > lastIndex) { + for ( + let index = lastIndex + 1; + index <= lastIndexToGenerate; + index += 1) { + const pathData = this.getForPath(`${basePath}/${index}`, { isWatched }); + if (pathData.hasBeenNewlyIssued) { + generatedPaths.push(pathData); + } + } + } + } + }); + return generatedPaths; +} +module.exports = maybeLookAhead; diff --git a/src/types/KeyChainStore/KeyChainStore.js b/src/types/KeyChainStore/KeyChainStore.js new file mode 100644 index 000000000..fd5c24dfa --- /dev/null +++ b/src/types/KeyChainStore/KeyChainStore.js @@ -0,0 +1,16 @@ +class KeyChainStore { + constructor() { + this.keyChains = new Map(); + this.walletKeyChainId = null; + this.masterKeyChainId = null; + this.accountKeyChains = new Map(); + } +} + +KeyChainStore.prototype.addKeyChain = require('./methods/addKeyChain'); +KeyChainStore.prototype.getKeyChain = require('./methods/getKeyChain'); +KeyChainStore.prototype.getKeyChains = require('./methods/getKeyChains'); +KeyChainStore.prototype.makeChildKeyChainStore = require('./methods/makeChildKeyChainStore'); +KeyChainStore.prototype.getMasterKeyChain = require('./methods/getMasterKeyChain'); + +module.exports = KeyChainStore; diff --git a/src/types/KeyChainStore/KeyChainStore.spec.js b/src/types/KeyChainStore/KeyChainStore.spec.js new file mode 100644 index 000000000..b32737bb7 --- /dev/null +++ b/src/types/KeyChainStore/KeyChainStore.spec.js @@ -0,0 +1,47 @@ +const {HDPrivateKey} = require("@dashevo/dashcore-lib"); +const KeyChainsStore = require('./KeyChainStore'); +const KeyChain = require("../KeyChain/KeyChain"); +const { expect } = require('chai'); + +describe('KeyChainStore', function suite() { + let keyChainsStore; + let hdPrivateKey = new HDPrivateKey() + let hdPublicKey = new HDPrivateKey().hdPublicKey + let keyChain = new KeyChain({HDPrivateKey: hdPrivateKey}) + let keyChainPublic = new KeyChain({HDPublicKey: hdPublicKey}) + let walletKeyChain = new KeyChain({HDPrivateKey:new HDPrivateKey()}); + it('should create a KeyChainStore', () => { + keyChainsStore = new KeyChainsStore(); + expect(keyChainsStore).to.exist; + expect(keyChainsStore.keyChains).to.be.a('Map') + }); + it('should be able to add a keyChain', function () { + keyChainsStore.addKeyChain(keyChain) + expect(keyChainsStore.keyChains.has(keyChain.keyChainId)).to.equal(true); + keyChainsStore.addKeyChain(keyChainPublic) + expect(keyChainsStore.keyChains.has(keyChainPublic.keyChainId)).to.equal(true); + }); + it('should allow to specify a specific master keychain', function () { + keyChainsStore.addKeyChain(walletKeyChain, { isMasterKeyChain: true }); + expect(keyChainsStore.keyChains.has(walletKeyChain.keyChainId)).to.equal(true); + }); + it('should get all keyChains', function () { + const keyChains = keyChainsStore.getKeyChains() + expect(keyChains).to.deep.equal([keyChain, keyChainPublic, walletKeyChain]); + }); + it('should get a keychain by its ID', () => { + const requestedKeychain = keyChainsStore.getKeyChain(keyChainPublic.keyChainId); + expect(requestedKeychain).to.equal(keyChainPublic); + }) + it('should get a master keychain', function () { + const requestedWalletKeyChain = keyChainsStore.getMasterKeyChain(); + expect(requestedWalletKeyChain).to.equal(walletKeyChain); + }); + it('should make a child key chain store', function () { + const childKeyChainStore = keyChainsStore.makeChildKeyChainStore('m/0') + expect(childKeyChainStore).to.exist; + expect(childKeyChainStore.keyChains).to.be.a('Map') + expect(childKeyChainStore.getMasterKeyChain().rootKeyType).to.be.equal(HDPrivateKey.name) + }); +}); + diff --git a/src/types/KeyChainStore/methods/addKeyChain.js b/src/types/KeyChainStore/methods/addKeyChain.js new file mode 100644 index 000000000..626893434 --- /dev/null +++ b/src/types/KeyChainStore/methods/addKeyChain.js @@ -0,0 +1,15 @@ +function addKeyChain(keychain, opts = {}) { + if (this.keyChains.has(keychain.keyChainId)) { + throw new Error('Trying to add already existing keyChain'); + } + + this.keyChains.set(keychain.keyChainId, keychain); + + if (opts) { + if (opts.isMasterKeyChain && !this.masterKeyChainId) { + this.masterKeyChainId = keychain.keyChainId; + } + } +} + +module.exports = addKeyChain; diff --git a/src/types/KeyChainStore/methods/getKeyChain.js b/src/types/KeyChainStore/methods/getKeyChain.js new file mode 100644 index 000000000..d964ba395 --- /dev/null +++ b/src/types/KeyChainStore/methods/getKeyChain.js @@ -0,0 +1,5 @@ +function getKeyChain(keyChainId) { + return this.keyChains.get(keyChainId); +} + +module.exports = getKeyChain; diff --git a/src/types/KeyChainStore/methods/getKeyChains.js b/src/types/KeyChainStore/methods/getKeyChains.js new file mode 100644 index 000000000..4e0e16160 --- /dev/null +++ b/src/types/KeyChainStore/methods/getKeyChains.js @@ -0,0 +1,5 @@ +function getKeyChains() { + return Array.from(this.keyChains.values()); +} + +module.exports = getKeyChains; diff --git a/src/types/KeyChainStore/methods/getMasterKeyChain.js b/src/types/KeyChainStore/methods/getMasterKeyChain.js new file mode 100644 index 000000000..512d6a281 --- /dev/null +++ b/src/types/KeyChainStore/methods/getMasterKeyChain.js @@ -0,0 +1,6 @@ +function getMasterKeyChain() { + const keyChainId = this.masterKeyChainId; + return this.keyChains.get(keyChainId); +} + +module.exports = getMasterKeyChain; diff --git a/src/types/KeyChainStore/methods/makeChildKeyChainStore.js b/src/types/KeyChainStore/methods/makeChildKeyChainStore.js new file mode 100644 index 000000000..b341184f9 --- /dev/null +++ b/src/types/KeyChainStore/methods/makeChildKeyChainStore.js @@ -0,0 +1,21 @@ +const { Networks } = require('@dashevo/dashcore-lib'); +const { BIP44_LIVENET_ROOT_PATH, BIP44_TESTNET_ROOT_PATH } = require('../../../CONSTANTS'); +const KeyChain = require('../../KeyChain/KeyChain'); +const logger = require('../../../logger'); + +function makeChildKeyChainStore(path, opts) { + logger.debug(`KeyChainStore - make a child keychainstore for ${path}`); + const masterKeyChain = this.getMasterKeyChain(); + if (!masterKeyChain) throw new Error('Requires a master keychain to be added first.'); + + const childKeyChainStore = new this.constructor(); + const keyChainOpts = { network: masterKeyChain.network, ...opts }; + + // Accessing the type from getKeyForPath would behave on browser differently due to mangling. + keyChainOpts[masterKeyChain.rootKeyType] = masterKeyChain.getForPath(path).key; + const childKeyChain = new KeyChain(keyChainOpts); + childKeyChainStore.addKeyChain(childKeyChain, { isMasterKeyChain: true }); + return childKeyChainStore; +} + +module.exports = makeChildKeyChainStore; diff --git a/src/types/Storage/.eslintrc b/src/types/Storage/.eslintrc deleted file mode 100644 index a8022ef2b..000000000 --- a/src/types/Storage/.eslintrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "rules": { - "import/newline-after-import": "off" - } -} diff --git a/src/types/Storage/Storage.d.ts b/src/types/Storage/Storage.d.ts deleted file mode 100644 index c41cc83ba..000000000 --- a/src/types/Storage/Storage.d.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { - AddressObj, - AddressType, - Mnemonic, - Network, - TransactionInfo, TransactionMetaData, TransactionsWithMetaData, - WalletObj, WalletType -} from "../types"; -import { BlockHeader } from "@dashevo/dashcore-lib"; -import { Account } from "../.."; -import { Transaction } from "@dashevo/dashcore-lib/typings/transaction/Transaction"; - - -export declare namespace Storage { - interface IStorageOptions { - rehydrate?: boolean; - autosave?: boolean; - autosaveIntervalTime?: number; - network?: Network; - } - interface store { - wallets: {}, - transactions: {}, - chains: {}, - } -} - - -export declare class Storage { - constructor(options?: Storage.IStorageOptions); - store: {}; - rehydrate: boolean; - autosave: boolean; - autosaveIntervalTime: number; - lastRehydrate: number|null; - lastSave: number|null; - lastModified: number|null; - network: Network; - mappedAddress: MappedAddressMap; - - addNewTxToAddress(tx: TransactionInfo, address: string): boolean; - announce(type: string, el: any): boolean; - calculateDuffBalance(walletId: number, accountId: number, type: string): number; - clearAll(): Promise; - configure(opts: { - rehydrate?: boolean, - autosave?: boolean, - adapter?: any - }): Promise; - createChain(network: string): boolean; - createWallet(walletId: string, network: Network, mnemonic?:Mnemonic, type?: WalletType ): boolean; - getBlockHeader(identifier: string|number): BlockHeader; - getIdentityIdByIndex(walletId: string, identityIndex: number): string|undefined; - getIndexedIdentityIds(walletId: string): Array; - getStore(): Storage.store; - getTransaction(txid: string): Transaction; - getTransactionMetadata(txid: string): TransactionMetaData; - importAccounts(accounts: Account|[Account], walletId: string): boolean; - importAddress(address: AddressObj, walletId: string): boolean; - importAddresses(addresses: [AddressObj], walletId: string): boolean; - importBlockHeader(blockHeader: BlockHeader, height: number): void; - importSingleAddress(singleAddress: AddressObj, walletId: string): boolean; - importTransaction(transaction: Transaction, metadata?: TransactionMetaData): void; - importTransactions(transactions: Transaction|[Transaction]|[TransactionsWithMetaData]): boolean; - insertIdentityIdAtIndex(walletId: string, identityId: string, identityIndex: number): void; - rehydrateState(): Promise; - saveState(): Promise; - searchAddress(address: string, forceLoop: boolean): AddressSearchResult; - searchAddressesWithTx(txid: string): AddressesSearchResult; - searchBlockHeader(identifier: string|number): BlockHeaderSearchResult; - searchTransaction(hash: string): TransactionSearchResult; - searchTransactionMetadata(hash: string): TransactionMetadataSearchResult; - searchWallet(walletId: string): WalletSearchResult; - startWorker(): void; - stopWorker(): boolean; - updateAddress(addressObj: AddressObj, walletId: string): boolean; - updateTransaction(transaction: Transaction): boolean; -} - -interface MappedAddressMap { - [pathName: string]: MappedAddress -} - -interface MappedAddress { - [path: string]: { - walletId: string, - type: AddressType, - path: string - }; -} - -interface WalletSearchResult { - walletId: number, - found: boolean, - result?: WalletObj -} - -interface TransactionSearchResult { - hash: number, - found: boolean, - result?: TransactionInfo -} -interface TransactionMetadataSearchResult { - hash: number, - found: boolean, - result?: TransactionMetaData -} - -interface AddressSearchResult { - address: string, - type: AddressType, - found: boolean, - path?: string, - result: AddressObj, - walletId: number -} -interface BlockHeaderSearchResult { - found: boolean, - result?: BlockHeader, - identifier: number|string -} - -interface AddressesSearchResult { - txid: number, - found: boolean, - results: [AddressObj] -} diff --git a/src/types/Storage/Storage.js b/src/types/Storage/Storage.js index cf1668582..10ecd29ce 100644 --- a/src/types/Storage/Storage.js +++ b/src/types/Storage/Storage.js @@ -1,95 +1,30 @@ const EventEmitter = require('events'); -const { cloneDeep, has } = require('lodash'); -const CONSTANTS = require('../../CONSTANTS'); - -const initialStore = { - wallets: {}, - transactions: {}, - transactionsMetadata: {}, - chains: {}, - instantLocks: {}, -}; -// eslint-disable-next-line no-underscore-dangle -const _defaultOpts = { - rehydrate: true, - autosave: true, - autosaveIntervalTime: CONSTANTS.STORAGE.autosaveIntervalTime, - network: 'testnet', -}; /** - * Handle all the storage logic, it's a wrapper around the adapters - * So all the needed methods should be provided by the Storage class and the access to the adapter - * should be limited. - * */ +* Handle all the storage logic, it's a wrapper around the adapters +* So all the needed methods should be provided by the Storage class and the access to the adapter +* should be limited. +* */ class Storage extends EventEmitter { - constructor(opts = JSON.parse(JSON.stringify(_defaultOpts))) { + constructor() { super(); - const defaultOpts = JSON.parse(JSON.stringify(_defaultOpts)); - - this.store = cloneDeep(initialStore); - this.rehydrate = has(opts, 'rehydrate') ? opts.rehydrate : defaultOpts.rehydrate; - this.autosave = has(opts, 'autosave') ? opts.autosave : defaultOpts.autosave; - this.autosaveIntervalTime = has(opts, 'autosaveIntervalTime') - ? opts.autosaveIntervalTime - : defaultOpts.autosaveIntervalTime; - - this.lastRehydrate = null; - this.lastSave = null; - this.lastModified = null; - this.network = has(opts, 'network') ? opts.network.toString() : defaultOpts.network; - - // Map an address to it's walletid/path/type schema (used by searchAddress for speedup) - this.mappedAddress = {}; - - // Map height to transaction ids to facilitate search. - this.mappedTransactionsHeight = {}; + this.wallets = new Map(); + this.chains = new Map(); + this.application = { + blockHeight: 0, + }; } } -Storage.prototype.addNewTxToAddress = require('./methods/addNewTxToAddress'); -Storage.prototype.announce = require('./methods/announce'); -Storage.prototype.calculateDuffBalance = require('./methods/calculateDuffBalance'); -Storage.prototype.clearAll = require('./methods/clearAll'); -Storage.prototype.configure = require('./methods/configure'); -Storage.prototype.createAccount = require('./methods/createAccount'); -Storage.prototype.createChain = require('./methods/createChain'); -Storage.prototype.createSingleAddress = require('./methods/createSingleAddress'); -Storage.prototype.createWallet = require('./methods/createWallet'); - -Storage.prototype.exportAccounts = require('./methods/exportAccounts'); -Storage.prototype.exportChains = require('./methods/exportChains'); -Storage.prototype.exportTransactions = require('./methods/exportTransactions'); -Storage.prototype.exportWallets = require('./methods/exportWallets'); -Storage.prototype.getStore = require('./methods/getStore'); -Storage.prototype.getBlockHeader = require('./methods/getBlockHeader'); -Storage.prototype.getTransaction = require('./methods/getTransaction'); -Storage.prototype.getInstantLock = require('./methods/getInstantLock'); -Storage.prototype.importAccounts = require('./methods/importAccounts'); -Storage.prototype.importAddress = require('./methods/importAddress'); -Storage.prototype.importAddresses = require('./methods/importAddresses'); -Storage.prototype.importBlockHeader = require('./methods/importBlockHeader'); -Storage.prototype.importSingleAddress = require('./methods/importSingleAddress'); -Storage.prototype.importChains = require('./methods/importChains'); -Storage.prototype.importTransaction = require('./methods/importTransaction'); -Storage.prototype.importTransactions = require('./methods/importTransactions'); -Storage.prototype.importInstantLock = require('./methods/importInstantLock'); +Storage.prototype.configure = require('./methods/configure'); +Storage.prototype.createAccountStore = require('./methods/createAccountStore'); +Storage.prototype.createChainStore = require('./methods/createChainStore'); +Storage.prototype.createWalletStore = require('./methods/createWalletStore'); +Storage.prototype.getChainStore = require('./methods/getChainStore'); +Storage.prototype.getWalletStore = require('./methods/getWalletStore'); Storage.prototype.rehydrateState = require('./methods/rehydrateState'); Storage.prototype.saveState = require('./methods/saveState'); -Storage.prototype.searchAddress = require('./methods/searchAddress'); -Storage.prototype.searchAddressesWithTx = require('./methods/searchAddressesWithTx'); -Storage.prototype.searchBlockHeader = require('./methods/searchBlockHeader'); -Storage.prototype.searchTransaction = require('./methods/searchTransaction'); -Storage.prototype.searchTransactionMetadata = require('./methods/searchTransactionMetadata'); -Storage.prototype.searchWallet = require('./methods/searchWallet'); -Storage.prototype.updateAddress = require('./methods/updateAddress'); -Storage.prototype.updateTransaction = require('./methods/updateTransaction'); Storage.prototype.startWorker = require('./methods/startWorker'); Storage.prototype.stopWorker = require('./methods/stopWorker'); -// Identities -Storage.prototype.insertIdentityIdAtIndex = require('./methods/insertIdentityAtIndex'); -Storage.prototype.getIdentityIdByIndex = require('./methods/getIdentityIdByIndex'); -Storage.prototype.getIndexedIdentityIds = require('./methods/getIndexedIdentityIds'); - module.exports = Storage; diff --git a/src/types/Storage/Storage.spec.js b/src/types/Storage/Storage.spec.js deleted file mode 100644 index 621a01dc5..000000000 --- a/src/types/Storage/Storage.spec.js +++ /dev/null @@ -1,106 +0,0 @@ -const { expect } = require('chai'); - -const localForage = require('localforage'); -const Dashcore = require('@dashevo/dashcore-lib'); -const Storage = require('./Storage'); -const { CONFIGURED } = require('../../EVENTS'); - -describe('Storage - constructor', function suite() { - this.timeout(10000); - it('It should create a storage', () => { - const storage = new Storage(); - expect(storage.store).to.deep.equal({ wallets: {}, transactions: {}, transactionsMetadata: {}, chains: {}, instantLocks: {} }); - expect(storage.getStore()).to.deep.equal(storage.store); - expect(storage.rehydrate).to.equal(true); - expect(storage.autosave).to.equal(true); - expect(storage.lastRehydrate).to.equal(null); - expect(storage.lastSave).to.equal(null); - expect(storage.lastModified).to.equal(null); - storage.stopWorker(); - }); - it('should configure a storage with default adapter', async () => { - const storage = new Storage(); - let configuredEvent = false; - storage.on(CONFIGURED, () => configuredEvent = true); - await storage.configure(); - expect(storage.adapter).to.exist; - expect(storage.adapter.constructor.name).to.equal('InMem'); - expect(configuredEvent).to.equal(true); - storage.stopWorker(); - }); - it('should handle bad adapter', async function () { - if (process.browser){ - // Local forage is valid adapter on browser. - this.skip('LocalForage is a valid adapter on browser') - return; - } - const expectedException1 = 'Invalid Storage Adapter : No available storage method found.'; - const storageOpts1 = { adapter: localForage }; - const storage = new Storage(); - return storage.configure(storageOpts1).then( - () => Promise.reject(new Error('Expected method to reject.')), - (err) => expect(err).to.be.a('Error').with.property('message', expectedException1), - ).then(() => { - storage.stopWorker(); - }); - }); - it('should work on usage', async () => { - const storage = new Storage(); - await storage.configure(); - await storage.createChain(Dashcore.Networks.testnet); - - const defaultWalletId = 'squawk7700'; - const expectedStore1 = { - wallets: {}, - transactions: {}, - transactionsMetadata:{}, - chains: { - testnet: { - name: 'testnet', - blockHeight: -1, - blockHeaders: {}, - mappedBlockHeaderHeights: {}, - fees: { - minRelay: -1 - } - }, - }, - instantLocks: {} - }; - expect(storage.getStore()).to.deep.equal(expectedStore1); - - await storage.createWallet(); - const expectedStore2 = { - wallets: { - squawk7700: { - accounts: {}, - network: Dashcore.Networks.testnet.toString(), - mnemonic: null, - type: null, - identityIds: [], - addresses: { external: {}, internal: {}, misc: {} }, - }, - }, - transactions: {}, - transactionsMetadata: {}, - chains: { - testnet: { - name: 'testnet', - blockHeight: -1, - blockHeaders: {}, - mappedBlockHeaderHeights: {}, - fees: { - minRelay: -1 - } - }, - }, - instantLocks: {}, - }; - expect(storage.getStore()).to.deep.equal(expectedStore2); - expect(storage.store).to.deep.equal(expectedStore2); - - const account = {}; - await storage.importAccounts(account, defaultWalletId); - storage.stopWorker(); - }); -}); diff --git a/src/types/Storage/initialStore.json b/src/types/Storage/initialStore.json deleted file mode 100644 index 8b4f9541f..000000000 --- a/src/types/Storage/initialStore.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "wallets": {}, - "transactions": {}, - "chains": {} -} \ No newline at end of file diff --git a/src/types/Storage/methods/addNewTxToAddress.js b/src/types/Storage/methods/addNewTxToAddress.js deleted file mode 100644 index a2dba1b55..000000000 --- a/src/types/Storage/methods/addNewTxToAddress.js +++ /dev/null @@ -1,30 +0,0 @@ -const { InvalidAddress, InvalidTransactionObject, StorageUnableToAddTransaction } = require('../../../errors'); -const { is } = require('../../../utils'); - -/** - * Add a new transaction to an address (push a tx) -* @param {TransactionInfo} tx -* @param {String} address -* @return {boolean} - */ -const addNewTxToAddress = function addNewTxToAddress(tx, address) { - if (!is.address(address)) throw new InvalidAddress(address); - if (!is.transactionObj(tx)) throw new InvalidTransactionObject(tx); - - const searchAddr = this.searchAddress(address); - const { walletId } = searchAddr; - - if (tx.address && tx.txid) { - const { type, path, found } = this.searchAddress(tx.address); - if (!found) { - throw new StorageUnableToAddTransaction(tx); - } - this.store.wallets[walletId].addresses[type][path].transactions.push(tx.txid); - // Because of the unclear state will force a refresh - this.store.wallets[walletId].addresses[type][path].fetchedLast = 0; - this.lastModified = +new Date(); - return true; - } - throw new StorageUnableToAddTransaction(tx); -}; -module.exports = addNewTxToAddress; diff --git a/src/types/Storage/methods/addNewTxToAddress.spec.js b/src/types/Storage/methods/addNewTxToAddress.spec.js deleted file mode 100644 index 03b20df4d..000000000 --- a/src/types/Storage/methods/addNewTxToAddress.spec.js +++ /dev/null @@ -1,27 +0,0 @@ -const { expect } = require('chai'); -const addNewTxToAddress = require('./addNewTxToAddress'); - -// FIXME : We only use this method in one specific case : From the SyncWorker when receiving from the WSock from insight -// THerefore we might want to remove our dependency on this method ? -describe('Storage - addNewTxToAddress', () => { - it('should add a new transaction to an address in store', () => { - // const self = { - // store: { - // wallets: { - // '123ae': { - // addresses: { - // internal: {}, - // external: {}, - // misc: {}, - // }, - // transactions:{ - // - // } - // }, - // }, - // }, - // }; - // - // addNewTxToAddress.call(self, fd7c727155ef67fd5c1d54b73dea869e9690c439570063d6e96fec1d3bba450e, '123ae'); - }); -}); diff --git a/src/types/Storage/methods/announce.js b/src/types/Storage/methods/announce.js deleted file mode 100644 index 8c35e6b7b..000000000 --- a/src/types/Storage/methods/announce.js +++ /dev/null @@ -1,27 +0,0 @@ -const logger = require('../../../logger'); -const EVENTS = require('../../../EVENTS'); - -/** - * Used to announce some events. - * @param type - * @param el - * @return {boolean} - */ -const announce = function announce(type, el) { - switch (type) { - case EVENTS.BLOCK: - case EVENTS.BLOCKHEADER: - case EVENTS.BLOCKHEIGHT_CHANGED: - case EVENTS.CONFIRMED_BALANCE_CHANGED: - case EVENTS.UNCONFIRMED_BALANCE_CHANGED: - case EVENTS.FETCHED_UNCONFIRMED_TRANSACTION: - case EVENTS.FETCHED_CONFIRMED_TRANSACTION: - this.emit(type, { type, payload: el }); - break; - default: - this.emit(type, { type, payload: el }); - logger.warn('Storage - Not implemented, announce of ', type, el); - } - return true; -}; -module.exports = announce; diff --git a/src/types/Storage/methods/announce.spec.js b/src/types/Storage/methods/announce.spec.js deleted file mode 100644 index 55b7e6b5e..000000000 --- a/src/types/Storage/methods/announce.spec.js +++ /dev/null @@ -1,6 +0,0 @@ -const { expect } = require('chai'); -const announce = require('./announce'); - -describe('Storage - announce', () => { - describe.skip('TODO test'); -}); diff --git a/src/types/Storage/methods/calculateDuffBalance.js b/src/types/Storage/methods/calculateDuffBalance.js deleted file mode 100644 index 8f8e75841..000000000 --- a/src/types/Storage/methods/calculateDuffBalance.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * - * @param walletId - The wallet Id where to perform the calculation - * @param accountIndex - The account Index where to perform the calculation - * @param type {{'confirmed','unconfirmed','total'}} Default: total. Calculate balance by utxo type. - * @return {number} Balance in duff - */ -module.exports = function calculateDuffBalance(walletId, accountIndex, type = 'total') { - let totalSat = 0; - if (walletId === undefined || accountIndex === undefined) { - throw new Error('Cannot calculate without walletId and accountIndex params'); - } - - const { addresses } = this.getStore().wallets[walletId]; - const subwallets = Object.keys(addresses); - subwallets.forEach((subwallet) => { - const paths = Object.keys(addresses[subwallet]) - // We filter out other potential account - .filter((el) => { - const splitted = el.split('/'); - const index = parseInt((splitted.length === 1) ? splitted[0] : splitted[3], 10); - return index === accountIndex; - }); - - paths.forEach((path) => { - const address = addresses[subwallet][path]; - const { balanceSat, unconfirmedBalanceSat } = address; - switch (type) { - case 'total': - totalSat += balanceSat + unconfirmedBalanceSat; - break; - case 'confirmed': - totalSat += balanceSat; - break; - case 'unconfirmed': - totalSat += unconfirmedBalanceSat; - break; - default: - throw new Error(`Unexpected balance type. Got ${type}`); - } - }); - }); - - return totalSat; -}; diff --git a/src/types/Storage/methods/clearAll.js b/src/types/Storage/methods/clearAll.js deleted file mode 100644 index c22c665bc..000000000 --- a/src/types/Storage/methods/clearAll.js +++ /dev/null @@ -1,11 +0,0 @@ -const { cloneDeep } = require('lodash'); -const initialStore = require('../initialStore.json'); -/** - * Clear all the store and save the cleared store to the persistence adapter - * @return {Promise} - */ -const clearAll = async function clearAll() { - this.store = cloneDeep(initialStore); - return this.saveState(); -}; -module.exports = clearAll; diff --git a/src/types/Storage/methods/clearAll.spec.js b/src/types/Storage/methods/clearAll.spec.js deleted file mode 100644 index de4bfdd88..000000000 --- a/src/types/Storage/methods/clearAll.spec.js +++ /dev/null @@ -1,25 +0,0 @@ -const { expect } = require('chai'); -const clearState = require('./clearAll'); - -describe('Storage - clearAll', function suite() { - this.timeout(10000); - it('should clear the whole state', () => { - let called = 0; - const self = { - saveState: () => called++, - store: { - stuff: {}, - transactions: { - nope: true, - }, - }, - }; - clearState.call(self); - expect(self.store).to.deep.equal({ - chains: {}, - transactions: {}, - wallets: {}, - }); - expect(called).to.equal(1); - }); -}); diff --git a/src/types/Storage/methods/configure.spec.js b/src/types/Storage/methods/configure.spec.js deleted file mode 100644 index 186f5adc4..000000000 --- a/src/types/Storage/methods/configure.spec.js +++ /dev/null @@ -1,61 +0,0 @@ -const { expect } = require('chai'); -const configure = require('./configure'); - -const noop = () => {}; - -describe('Storage - configure', async function suite() { - this.timeout(10000); - it('should set save/rehydrate settings', () => { - let rehydrated = 0; - - const self = { - emit: noop, - autosaveIntervalTime: 1000, - startWorker: noop, - rehydrateState: () => (rehydrated += 1), - rehydrate: true, - autosave: true, - }; - expect(rehydrated).to.equal(0); - return configure - .call(self) - .then(() => expect(self.autosave).to.equal(true)) - .then(() => expect(self.rehydrate).to.equal(true)) - .then(() => expect(rehydrated).to.equal(1)) - .then(() => configure - .call(self, { rehydrate: false, autosave: false }) - .then(() => expect(self.autosave).to.equal(false)) - .then(() => expect(self.rehydrate).to.equal(false)) - .then(() => expect(rehydrated).to.equal(1))); - }); - it('should successfully emit', () => { - const emitted = []; - const self = { - emit: (emitType) => (emitted.push(emitType)), - autosaveIntervalTime: 1000, - startWorker: noop, - }; - expect(emitted.length).to.equal(0); - - return configure - .call(self) - .then(() => expect(emitted).to.deep.equal(['CONFIGURED'])); - }); - it('should start the autosave worker if autosave is true', () => { - let workerStarted = false; - const self = { - rehydrate: false, - autosave: false, - autosaveIntervalTime: 1000, - startWorker: () => { workerStarted = true; }, - emit: noop, - }; - - return configure - .call(self) - .then(() => expect(workerStarted).to.equal(false)) - .then(() => configure - .call(self, { autosave: true }) - .then(() => expect(workerStarted).to.equal(true))); - }); -}); diff --git a/src/types/Storage/methods/createAccount.js b/src/types/Storage/methods/createAccount.js deleted file mode 100644 index 819a17372..000000000 --- a/src/types/Storage/methods/createAccount.js +++ /dev/null @@ -1,28 +0,0 @@ -const { hasProp } = require('../../../utils'); - -/** - * Create a new account into a wallet - * @param {string} walletId - * @param {string} path - * @param {string} network - * @param {string|null} [label] - * @return {boolean} - */ -module.exports = function createAccount(walletId, path, network, label = null) { - if (!hasProp(this.store.wallets, walletId.toString())) { - if (!this.searchWallet(walletId).found) { - this.createWallet(walletId, network); - } - } - if (!hasProp(this.store.wallets[walletId].accounts, path.toString())) { - this.store.wallets[walletId].accounts[path.toString()] = { - label, - path, - network, - blockHeight: 0, // Used to keep track of local state sync of the account - }; - - return true; - } - return false; -}; diff --git a/src/types/Storage/methods/createAccountStore.js b/src/types/Storage/methods/createAccountStore.js new file mode 100644 index 000000000..3069a3b0d --- /dev/null +++ b/src/types/Storage/methods/createAccountStore.js @@ -0,0 +1,29 @@ +const { hasProp } = require('../../../utils'); + +/** + * Create a new account store into a wallet + * @param {string} walletId + * @param {string} path + * @param {string} network + * @param {string|null} [label] + * @return {boolean} + */ +module.exports = function createAccount(walletId, path, network, label = null) { + const walletStore = this.getWalletStore(walletId); + // if (!hasProp(this.store.wallets, walletId.toString())) { + // if (!this.searchWallet(walletId).found) { + // this.createWallet(walletId, network); + // } + // } + // if (!hasProp(this.store.wallets[walletId].accounts, path.toString())) { + // this.store.wallets[walletId].accounts[path.toString()] = { + // label, + // path, + // network, + // blockHeight: 0, // Used to keep track of local state sync of the account + // }; + // + // return true; + // } + // return false; +}; diff --git a/src/types/Storage/methods/createChain.js b/src/types/Storage/methods/createChain.js deleted file mode 100644 index 0fea70ed6..000000000 --- a/src/types/Storage/methods/createChain.js +++ /dev/null @@ -1,25 +0,0 @@ -const { hasProp } = require('../../../utils'); - -/** - * Create when does not yet exist a chain in the store - * @param network - * @return {boolean} - */ -const createChain = function createChain(network) { - if (!hasProp(this.store.chains, network.toString())) { - this.store.chains[network.toString()] = { - name: network.toString(), - blockHeaders: {}, - // Map a blockheader to it's height (used by searchBlockheader for speed up the process) - mappedBlockHeaderHeights: {}, - blockHeight: -1, - fees: { - // feeRate expressed per kb - minRelay: -1, - }, - }; - return true; - } - return false; -}; -module.exports = createChain; diff --git a/src/types/Storage/methods/createChain.spec.js b/src/types/Storage/methods/createChain.spec.js deleted file mode 100644 index 71d2da156..000000000 --- a/src/types/Storage/methods/createChain.spec.js +++ /dev/null @@ -1,31 +0,0 @@ -const { expect } = require('chai'); -const createChain = require('./createChain'); - -describe('Storage - createChain', function suite() { - this.timeout(10000); - it('should create a chain', () => { - const self = { - store: { chains: {} }, - }; - const testnet = 'testnet'; - - createChain.call(self, testnet); - - const expected = { - store: { - chains: { - testnet: { - name: 'testnet', - blockHeight: -1, - blockHeaders: {}, - mappedBlockHeaderHeights: {}, - fees: { - minRelay: -1 - } - }, - }, - }, - }; - expect(self).to.be.deep.equal(expected); - }); -}); diff --git a/src/types/Storage/methods/createChainStore.js b/src/types/Storage/methods/createChainStore.js new file mode 100644 index 000000000..47ae5c2bd --- /dev/null +++ b/src/types/Storage/methods/createChainStore.js @@ -0,0 +1,15 @@ +const ChainStore = require('../../ChainStore/ChainStore'); + +/** + * Create when does not yet exist a chainStore + * @param network + * @return {boolean} + */ +const createChainStore = function createChain(network) { + if (!this.chains.has(network.toString())) { + this.chains.set(network.toString(), new ChainStore(network.toString())); + return true; + } + return false; +}; +module.exports = createChainStore; diff --git a/src/types/Storage/methods/createSingleAddress.js b/src/types/Storage/methods/createSingleAddress.js deleted file mode 100644 index 62421610c..000000000 --- a/src/types/Storage/methods/createSingleAddress.js +++ /dev/null @@ -1,27 +0,0 @@ -const { hasProp } = require('../../../utils'); - -/** - * Create a new account into a wallet - * @param {string} walletId - * @param {string} network - * @param {string|null} [label] - * @return {boolean} - */ -module.exports = function createAccount(walletId, network, label = null) { - if (!hasProp(this.store.wallets, walletId.toString())) { - if (!this.searchWallet(walletId).found) { - this.createWallet(walletId, network); - } - } - - if (!hasProp(this.store.wallets[walletId].accounts, '0')) { - this.store.wallets[walletId].accounts['0'] = { - label, - network, - blockHeight: 0, // Used to keep track of local state sync of the account - }; - - return true; - } - return false; -}; diff --git a/src/types/Storage/methods/createWallet.js b/src/types/Storage/methods/createWallet.js deleted file mode 100644 index 7380cdbc5..000000000 --- a/src/types/Storage/methods/createWallet.js +++ /dev/null @@ -1,24 +0,0 @@ -const Dashcore = require('@dashevo/dashcore-lib'); -const { hasProp } = require('../../../utils'); - -const { testnet } = Dashcore.Networks; -const createWallet = function createWallet(walletId = 'squawk7700', network = testnet.toString(), mnemonic = null, type = null) { - if (!hasProp(this.store.wallets, walletId)) { - this.store.wallets[walletId] = { - accounts: {}, - network, - mnemonic, - type, - identityIds: [], - addresses: { - external: {}, - internal: {}, - misc: {}, - }, - }; - this.createChain(network); - return true; - } - return false; -}; -module.exports = createWallet; diff --git a/src/types/Storage/methods/createWallet.spec.js b/src/types/Storage/methods/createWallet.spec.js deleted file mode 100644 index 9b799c12d..000000000 --- a/src/types/Storage/methods/createWallet.spec.js +++ /dev/null @@ -1,75 +0,0 @@ -const { expect } = require('chai'); -const Dashcore = require('@dashevo/dashcore-lib'); -const createWallet = require('./createWallet'); -const createChain = require('./createChain'); - -describe('Storage - createWallet', function suite() { - this.timeout(10000); - it('should create a wallet', () => { - const self = { - store: { wallets: {}, chains: {} }, - createChain, - }; - const walletid = '123ae'; - - createWallet.call(self, walletid); - - const expected = { - wallets: { - '123ae': { - accounts: {}, - network: Dashcore.Networks.testnet.toString(), - mnemonic: null, - type: null, - identityIds: [], - addresses: { external: {}, internal: {}, misc: {} }, - }, - }, - chains: { - testnet: { - name: 'testnet', - blockHeight: -1, - blockHeaders: {}, - mappedBlockHeaderHeights: {}, - fees: { - minRelay: -1 - } - }, - }, - }; - expect(self.store).to.be.deep.equal(expected); - }); - it('should create a wallet without any walletId', () => { - const self = { - store: { wallets: {}, chains: {} }, - createChain, - }; - - createWallet.call(self); - - const expected = { - wallets: { - squawk7700: { - accounts: {}, - network: Dashcore.Networks.testnet.toString(), - mnemonic: null, - type: null, - identityIds: [], - addresses: { external: {}, internal: {}, misc: {} }, - }, - }, - chains: { - testnet: { - name: 'testnet', - blockHeight: -1, - blockHeaders: {}, - mappedBlockHeaderHeights: {}, - fees: { - minRelay: -1 - } - }, - }, - }; - expect(self.store).to.be.deep.equal(expected); - }); -}); diff --git a/src/types/Storage/methods/createWalletStore.js b/src/types/Storage/methods/createWalletStore.js new file mode 100644 index 000000000..d3053b1a8 --- /dev/null +++ b/src/types/Storage/methods/createWalletStore.js @@ -0,0 +1,13 @@ +const Dashcore = require('@dashevo/dashcore-lib'); +const WalletStore = require('../../WalletStore/WalletStore'); + +const { testnet } = Dashcore.Networks; + +const createWalletStore = function createWallet(walletId = 'squawk7700', network = testnet.toString(), mnemonic = null, type = null) { + if (!this.wallets.has(walletId)) { + this.wallets.set(walletId, new WalletStore(walletId)); + return true; + } + return false; +}; +module.exports = createWalletStore; diff --git a/src/types/Storage/methods/exportAccounts.js b/src/types/Storage/methods/exportAccounts.js deleted file mode 100644 index 89969a738..000000000 --- a/src/types/Storage/methods/exportAccounts.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = function exportAccounts(walletId) { - if (!walletId) throw new Error('Expected to export account of a specific walletId'); - - if (!this.store.wallets[walletId]) { - throw new Error(`No wallet with the following walletId found in store : ${walletId}`); - } - return this.store.wallets[walletId].accounts; -}; diff --git a/src/types/Storage/methods/exportChains.js b/src/types/Storage/methods/exportChains.js deleted file mode 100644 index 0cef2b28d..000000000 --- a/src/types/Storage/methods/exportChains.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function exportChains() { - return this.store.chains; -}; diff --git a/src/types/Storage/methods/exportTransactions.js b/src/types/Storage/methods/exportTransactions.js deleted file mode 100644 index 57daaf8e8..000000000 --- a/src/types/Storage/methods/exportTransactions.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function exportTransactions() { - return this.store.transactions; -}; diff --git a/src/types/Storage/methods/exportWallets.js b/src/types/Storage/methods/exportWallets.js deleted file mode 100644 index b2ba79562..000000000 --- a/src/types/Storage/methods/exportWallets.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function exportWallets() { - return this.store.wallets; -}; diff --git a/src/types/Storage/methods/getBlockHeader.js b/src/types/Storage/methods/getBlockHeader.js deleted file mode 100644 index 4c69a0a4d..000000000 --- a/src/types/Storage/methods/getBlockHeader.js +++ /dev/null @@ -1,13 +0,0 @@ -const { BlockHeaderNotInStore } = require('../../../errors'); - -/** - * @param identifier - block hash or height - * @return {BlockHeader} - */ -const getBlockHeader = function getBlockHeader(identifier) { - const search = this.searchBlockHeader(identifier); - if (!search.found) throw new BlockHeaderNotInStore(identifier); - return search.result; -}; - -module.exports = getBlockHeader; diff --git a/src/types/Storage/methods/getChainStore.js b/src/types/Storage/methods/getChainStore.js new file mode 100644 index 000000000..5255eb710 --- /dev/null +++ b/src/types/Storage/methods/getChainStore.js @@ -0,0 +1,4 @@ +function getChainStore(network) { + return this.chains.get(network); +} +module.exports = getChainStore; diff --git a/src/types/Storage/methods/getIdentityIdByIndex.js b/src/types/Storage/methods/getIdentityIdByIndex.js deleted file mode 100644 index 111d3a145..000000000 --- a/src/types/Storage/methods/getIdentityIdByIndex.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * - * @param {string} walletId - * @param {number} identityIndex - * @return {string|undefined} - */ -function getIdentityIdByIndex(walletId, identityIndex) { - return this.store.wallets[walletId].identityIds[identityIndex]; -} - -module.exports = getIdentityIdByIndex; diff --git a/src/types/Storage/methods/getIndexedIdentityIds.js b/src/types/Storage/methods/getIndexedIdentityIds.js deleted file mode 100644 index cb82397d3..000000000 --- a/src/types/Storage/methods/getIndexedIdentityIds.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * - * @param {string} walletId - * @return {Array} - */ -function getIndexedIdentityIds(walletId) { - return this.store.wallets[walletId].identityIds; -} - -module.exports = getIndexedIdentityIds; diff --git a/src/types/Storage/methods/getInstantLock.js b/src/types/Storage/methods/getInstantLock.js deleted file mode 100644 index 333bbd8e6..000000000 --- a/src/types/Storage/methods/getInstantLock.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * - * @param {string} transactionHash - * @return {InstantLock} - */ -function getInstantLock(transactionHash) { - return this.store.instantLocks[transactionHash]; -} - -module.exports = getInstantLock; diff --git a/src/types/Storage/methods/getStore.js b/src/types/Storage/methods/getStore.js deleted file mode 100644 index 8782c2bcd..000000000 --- a/src/types/Storage/methods/getStore.js +++ /dev/null @@ -1,8 +0,0 @@ -const { cloneDeep } = require('lodash'); -/** - * Return the content of the store - * @return {Storage.store} - */ -module.exports = function getStore() { - return cloneDeep(this.store); -}; diff --git a/src/types/Storage/methods/getTransaction.js b/src/types/Storage/methods/getTransaction.js deleted file mode 100644 index 601bcf859..000000000 --- a/src/types/Storage/methods/getTransaction.js +++ /dev/null @@ -1,14 +0,0 @@ -const { TransactionNotInStore } = require('../../../errors'); - -/** - * Get a specific transaxtion by it's transaction id - * @param {string} txid - * @return {Transaction} - */ -const getTransaction = function getTransaction(txid) { - const search = this.searchTransaction(txid); - if (!search.found) throw new TransactionNotInStore(txid); - return search.result; -}; - -module.exports = getTransaction; diff --git a/src/types/Storage/methods/getTransaction.spec.js b/src/types/Storage/methods/getTransaction.spec.js deleted file mode 100644 index 4134d5820..000000000 --- a/src/types/Storage/methods/getTransaction.spec.js +++ /dev/null @@ -1,30 +0,0 @@ -const { expect } = require('chai'); -const getTransaction = require('./getTransaction'); -const transactionsFixtures = require('../../../../fixtures/transactions'); - -describe('Storage - getTransaction', function suite() { - this.timeout(10000); - it('should throw on failed fetching', () => { - const validTx = transactionsFixtures.valid.mainnet['4f71db0c4bf3e2769a3ebd2162753b54b33028e3287e45f93c5c7df8bac5ec7e']; - const exceptedException1 = `Transaction is not in store: ${validTx.txid}`; - const self = { - store: { - transactions: {}, - }, - searchTransaction: () => ({ found: false }), - }; - expect(() => getTransaction.call(self, validTx.txid)).to.throw(exceptedException1); - }); - it('should work', () => { - const validTx = transactionsFixtures.valid.mainnet['4f71db0c4bf3e2769a3ebd2162753b54b33028e3287e45f93c5c7df8bac5ec7e']; - const self = { - store: { - transactions: {}, - }, - searchTransaction: () => ({ found: true, result: validTx }), - }; - self.store.transactions[validTx.txid] = validTx; - const { txid } = validTx; - expect(getTransaction.call(self, txid)).to.deep.equal(validTx); - }); -}); diff --git a/src/types/Storage/methods/getTransactionMetadata.js b/src/types/Storage/methods/getTransactionMetadata.js deleted file mode 100644 index 63626b058..000000000 --- a/src/types/Storage/methods/getTransactionMetadata.js +++ /dev/null @@ -1,14 +0,0 @@ -const { TransactionMetadataNotInStore } = require('../../../errors'); - -/** - * Get a specific transaction metadata by it's transaction id - * @param {string} txid - * @return {TransactionMetaData} - */ -const getTransactionMetadata = function getTransactionMetadata(txid) { - const search = this.searchTransactionMetadata(txid); - if (!search.found) throw new TransactionMetadataNotInStore(txid); - return search.result; -}; - -module.exports = getTransactionMetadata; diff --git a/src/types/Storage/methods/getTransactionMetadata.spec.js b/src/types/Storage/methods/getTransactionMetadata.spec.js deleted file mode 100644 index 2624263fb..000000000 --- a/src/types/Storage/methods/getTransactionMetadata.spec.js +++ /dev/null @@ -1,31 +0,0 @@ -const { expect } = require('chai'); -const getTransactionMetadata = require('./getTransactionMetadata'); -const transactionsFixtures = require('../../../../fixtures/transactions'); - -describe('Storage - getTransactionMetadata', function suite() { - this.timeout(10000); - const validTxId = '1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6'; - // const validTx = transactionsFixtures.valid.testnet[validTxId]; - const validMetadata = transactionsFixtures.valid.testnet.metadata[validTxId]; - - it('should throw on failed fetching', () => { - const exceptedException1 = `Transaction metadata is not in store: ${validTxId}`; - const self = { - store: { - transactionsMetadata: {}, - }, - searchTransactionMetadata: () => ({ found: false }), - }; - expect(() => getTransactionMetadata.call(self, validTxId)).to.throw(exceptedException1); - }); - it('should work', () => { - const validTx = transactionsFixtures.valid.mainnet['4f71db0c4bf3e2769a3ebd2162753b54b33028e3287e45f93c5c7df8bac5ec7e']; - const self = { - store: { - transactionsMetadata: {}, - }, - searchTransactionMetadata: () => ({ found: true, result: validMetadata }), - }; - expect(getTransactionMetadata.call(self, validTxId)).to.deep.equal(validMetadata); - }); -}); diff --git a/src/types/Storage/methods/getWalletStore.js b/src/types/Storage/methods/getWalletStore.js new file mode 100644 index 000000000..3f43fc2b2 --- /dev/null +++ b/src/types/Storage/methods/getWalletStore.js @@ -0,0 +1,5 @@ +function getWalletStore(walletId) { + if (!this.wallets.has(walletId)) return null; + return this.wallets.get(walletId); +} +module.exports = getWalletStore; diff --git a/src/types/Storage/methods/importAccounts.js b/src/types/Storage/methods/importAccounts.js deleted file mode 100644 index 51b373b08..000000000 --- a/src/types/Storage/methods/importAccounts.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Import an array of accounts or a account object to the store - * @param {Account|[Account]} accounts - * @param {string} walletId - * @return {boolean} - */ -const importAccounts = function importAccounts(accounts, walletId) { - if (!walletId) throw new Error('Expected walletId to import addresses'); - if (!this.searchWallet(walletId).found) { - this.createWallet(walletId); - } - const accList = this.store.wallets[walletId].accounts; - - const type = accounts.constructor.name; - if (type === 'Object') { - if (accounts.path) { - if (!accList[accounts.path]) { - accList[accounts.path] = accounts; - this.lastModified = +new Date(); - } - } else { - const accountsPaths = Object.keys(accounts); - accountsPaths.forEach((path) => { - const el = accounts[path]; - if (el.path) { - if (!accList[el.path]) { - accList[el.path] = el; - this.lastModified = +new Date(); - } - } - }); - } - } else if (type === 'Array') { - accounts.forEach((account) => { - importAccounts.call(this, account, walletId); - }); - } else if (type === 'Account') { - const accObj = { - label: accounts.label, - path: accounts.BIP44PATH, - network: accounts.network, - }; - return importAccounts.call(this, accObj, walletId); - } else { - throw new Error('Invalid account. Cannot import.'); - } - return true; -}; -module.exports = importAccounts; diff --git a/src/types/Storage/methods/importAccounts.spec.js b/src/types/Storage/methods/importAccounts.spec.js deleted file mode 100644 index 361f8cfba..000000000 --- a/src/types/Storage/methods/importAccounts.spec.js +++ /dev/null @@ -1,63 +0,0 @@ -const { expect } = require('chai'); -const importAccounts = require('./importAccounts'); -const Wallet = require('../../Wallet/Wallet'); -const EVENTS = require('../../../EVENTS'); - -describe('Storage - importAccounts', async function suite() { - this.timeout(15000); - it('should throw on failed import', () => { - const mockOpts1 = { }; - const walletId = '123ae'; - const exceptedException1 = 'Expected walletId to import addresses'; - - expect(() => importAccounts.call({})).to.throw(exceptedException1); - expect(() => importAccounts.call({}, walletId)).to.throw(exceptedException1); - }); - it('should create a wallet if not existing', (done) => { - const wallet = new Wallet({ offlineMode: true }); - wallet.storage.on(EVENTS.CONFIGURED, () => { - wallet.getAccount().then((acc)=>{ - let called = 0; - - const self = { - searchWallet: () => ({ found: false }), - createWallet: () => (called += 1), - store: { wallets: { } }, - }; - self.store.wallets[wallet.walletId] = { accounts: {} }; - importAccounts.call(self, acc, wallet.walletId); - - // Called twice because of recursivity. We have a Acc Instance here. - expect(called).to.be.equal(2); - wallet.disconnect(); - acc.disconnect(); - done(); - }); - }); - }); - it('should import an account', (done) => { - const wallet = new Wallet({ offlineMode: true }); - wallet.storage.on('CONFIGURED', () => { - wallet.getAccount().then((acc)=>{ - let called = 0; - - const self = { - searchWallet: () => ({ found: false }), - createWallet: () => (called += 1), - store: { wallets: { } }, - }; - acc.label = 'Heya!'; - self.store.wallets[wallet.walletId] = { accounts: {} }; - importAccounts.call(self, acc, wallet.walletId); - const walletsKeys = Object.keys(self.store.wallets); - expect(walletsKeys.length).to.equal(1); - expect(self.store.wallets[walletsKeys[0]].accounts['m/44\'/1\'/0\''].label).to.equal('Heya!'); - setTimeout(() => { - wallet.disconnect(); - acc.disconnect(); - done(); - }, 50); - }) - }); - }); -}); diff --git a/src/types/Storage/methods/importAddress.js b/src/types/Storage/methods/importAddress.js deleted file mode 100644 index 178869212..000000000 --- a/src/types/Storage/methods/importAddress.js +++ /dev/null @@ -1,43 +0,0 @@ -const { cloneDeep } = require('lodash'); -const { InvalidAddressObject } = require('../../../errors'); -const { is } = require('../../../utils'); -/** - * Import one address to the store - * @param {AddressObj} addressObj - * @param {string} walletId - * @return {boolean} - */ -const importAddress = function importAddress(addressObj, walletId) { - if (!walletId) throw new Error('Expected walletId to import addresses'); - if (!this.searchWallet(walletId).found) { - this.createWallet(walletId); - } - const addressesStore = this.store.wallets[walletId].addresses; - if (is.undef(walletId)) throw new Error('Expected walletId to import an address'); - if (!is.addressObj(addressObj)) throw new InvalidAddressObject(addressObj); - const { path } = addressObj; - const modifiedAddressObject = cloneDeep(addressObj); - const index = (addressObj.index !== undefined) ? addressObj.index : parseInt(path.split('/')[5], 10); - const typeInt = path.split('/')[4]; - let type; - switch (typeInt) { - case '0': - type = 'external'; - break; - case '1': - type = 'internal'; - break; - default: - type = 'misc'; - } - if (!modifiedAddressObject.index) modifiedAddressObject.index = index; - if (addressesStore[type][path]) { - if (addressesStore[type][path].fetchedLast < modifiedAddressObject.fetchedLast) { - this.updateAddress(modifiedAddressObject, walletId); - } - } else { - this.updateAddress(modifiedAddressObject, walletId); - } - return true; -}; -module.exports = importAddress; diff --git a/src/types/Storage/methods/importAddress.spec.js b/src/types/Storage/methods/importAddress.spec.js deleted file mode 100644 index 714f3cfd5..000000000 --- a/src/types/Storage/methods/importAddress.spec.js +++ /dev/null @@ -1,18 +0,0 @@ -const { expect } = require('chai'); -const logger = require('../../../logger'); -const importAddress = require('./importAddress'); - -describe('Storage - importAddress', () => { - it('should throw on failed import', () => { - const walletId = '123ae'; - const exceptedException1 = 'Expected walletId to import addresses'; - - expect(() => importAddress.call({})).to.throw(exceptedException1); - expect(() => importAddress.call({}, walletId)).to.throw(exceptedException1); - }); - it('should import an address', () => { - logger.warn('FIXME'); - // const self = {}; - // importAddress.call(self, {}); - }); -}); diff --git a/src/types/Storage/methods/importAddresses.js b/src/types/Storage/methods/importAddresses.js deleted file mode 100644 index 5b2287d0a..000000000 --- a/src/types/Storage/methods/importAddresses.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Import one or multiple addresses to the store - * @param {[AddressObj]} addresses - * @param {string} walletId - * @return {boolean} - */ -const importAddresses = function importAddresses(addresses, walletId) { - if (!walletId) throw new Error('Expected walletId to import addresses'); - if (!this.searchWallet(walletId).found) { - this.createWallet(walletId); - } - const type = addresses.constructor.name; - if (type === 'Object') { - if (addresses.path) { - const address = addresses; - this.importAddress(address, walletId); - } else { - const addressPaths = Object.keys(addresses); - addressPaths.forEach((path) => { - const address = addresses[path]; - this.importAddress(address, walletId); - }); - } - } else if (type === 'Array') { - throw new Error('Not implemented. Please create an issue on github if needed.'); - } else { - throw new Error('Not implemented. Please create an issue on github if needed.'); - } - return true; -}; -module.exports = importAddresses; diff --git a/src/types/Storage/methods/importAddresses.spec.js b/src/types/Storage/methods/importAddresses.spec.js deleted file mode 100644 index 7ad30ca8b..000000000 --- a/src/types/Storage/methods/importAddresses.spec.js +++ /dev/null @@ -1,7 +0,0 @@ -const { expect } = require('chai'); -const importAddresses = require('./importAddresses'); - -describe('Storage - importAddresses', () => { - it('should import an array of addresses', () => { - }); -}); diff --git a/src/types/Storage/methods/importBlockHeader.js b/src/types/Storage/methods/importBlockHeader.js deleted file mode 100644 index a96ca0f11..000000000 --- a/src/types/Storage/methods/importBlockHeader.js +++ /dev/null @@ -1,30 +0,0 @@ -const EVENTS = require('../../../EVENTS'); -/** - * This method is used to import a blockheader in Store. - * @param {BlockHeader} blockHeader - A Blockheader - * @param {number} height - */ -const importBlockHeader = function importBlockHeader(blockHeader, height) { - const self = this; - const { store, network } = this; - - const chainStore = store.chains[network]; - const { blockHeight: currentChainHeight } = store.chains[network]; - - if (!chainStore.blockHeaders[blockHeader.hash]) { - if (height) { - if (height > currentChainHeight) store.chains[network].blockHeight = height; - else { - store.chains[network].blockHeight += 1; - self.announce(EVENTS.BLOCKHEIGHT_CHANGED, store.chains[network].blockHeight); - } - } - const blockHeight = height || currentChainHeight; - - chainStore.blockHeaders[blockHeader.hash] = blockHeader; - chainStore.mappedBlockHeaderHeights[blockHeight] = blockHeader.hash; - - self.announce(EVENTS.BLOCKHEADER, blockHeader); - } -}; -module.exports = importBlockHeader; diff --git a/src/types/Storage/methods/importChains.js b/src/types/Storage/methods/importChains.js deleted file mode 100644 index df1afb978..000000000 --- a/src/types/Storage/methods/importChains.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Import chains to the store - * - * @param {object} chains - * @return {void} - */ -const importChains = function importChains(chains) { - Object.assign(this.store.chains, chains); -}; -module.exports = importChains; diff --git a/src/types/Storage/methods/importInstantLock.js b/src/types/Storage/methods/importInstantLock.js deleted file mode 100644 index 8ce00b63f..000000000 --- a/src/types/Storage/methods/importInstantLock.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Imports instant lock to the storage - * @param {InstantLock} instantLock - */ -function importInstantLock(instantLock) { - this.store.instantLocks[instantLock.txid] = instantLock; -} - -module.exports = importInstantLock; diff --git a/src/types/Storage/methods/importSingleAddress.js b/src/types/Storage/methods/importSingleAddress.js deleted file mode 100644 index f408f472b..000000000 --- a/src/types/Storage/methods/importSingleAddress.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * SingleAddress differs from importAddress is the type being linked to a - * single PrivateKey (when not a HDWallet). - * @param {AddressObj} singleAddress - * @param {string} walletId - * @returns {boolean} - */ -const importSingleAddress = function importSingleAddress(singleAddress, walletId) { - const type = singleAddress.constructor.name; - if (!walletId) throw new Error('Expected walletId to import single address'); - if (!this.searchWallet(walletId).found) { - this.createWallet(walletId); - } - const accList = this.store.wallets[walletId].accounts; - - if (type === 'Object') { - if (singleAddress.path) { - accList[singleAddress.path] = (singleAddress); - this.lastModified = +new Date(); - } - } else if (type === 'Array') { - throw new Error('Not implemented. Please create an issue on github if needed.'); - } else { - throw new Error('Invalid account. Cannot import.'); - } - return true; -}; -module.exports = importSingleAddress; diff --git a/src/types/Storage/methods/importSingleAddress.spec.js b/src/types/Storage/methods/importSingleAddress.spec.js deleted file mode 100644 index 44023d1db..000000000 --- a/src/types/Storage/methods/importSingleAddress.spec.js +++ /dev/null @@ -1,8 +0,0 @@ -const { expect } = require('chai'); -const importSingleAddress = require('./importSingleAddress'); - -describe('Storage - importSingleAddress', () => { - it('should import a SingleAddress (non-bip44)', () => { - - }); -}); diff --git a/src/types/Storage/methods/importTransaction.js b/src/types/Storage/methods/importTransaction.js deleted file mode 100644 index 3c9943def..000000000 --- a/src/types/Storage/methods/importTransaction.js +++ /dev/null @@ -1,123 +0,0 @@ -const { Transaction } = require('@dashevo/dashcore-lib'); -const { Output } = Transaction; -const { InvalidDashcoreTransaction } = require('../../../errors'); -const { FETCHED_CONFIRMED_TRANSACTION } = require('../../../EVENTS'); - -const parseStringifiedTransaction = (stringified) => new Transaction(stringified); -/** - * This method is used to import a transaction in Store. - * if a transaction is already existing, we verify if the metadata needs an update as well. - * @param {Transaction/String} transaction - A valid Transaction - * @param {TransactionMetaData} transactionMetadata - Transaction Metadata - * @return void - */ -const importTransaction = function importTransaction(transaction, transactionMetadata) { - if (!(transaction instanceof Transaction)) { - try { - // eslint-disable-next-line no-param-reassign - transaction = parseStringifiedTransaction(transaction); - if (!transaction.hash || !transaction.inputs.length || !transaction.outputs.length) { - throw new InvalidDashcoreTransaction(transaction); - } - } catch (e) { - throw new InvalidDashcoreTransaction(transaction); - } - } - const { - store, - network, - mappedAddress, - mappedTransactionsHeight, - } = this; - const { transactions, transactionsMetadata } = store; - const { inputs, outputs } = transaction; - - let hasUpdateStorage = false; - let outputIndex = -1; - const processedAddressesForTx = {}; - - transactions[transaction.hash] = transaction; - if (transactionMetadata) { - const { height } = transactionMetadata; - if (Number.isInteger(height) && height !== 0) { - transactionsMetadata[transaction.hash] = transactionMetadata; - const mappedTransactionObject = { hash: transaction.hash, ...transactionMetadata }; - - if (mappedTransactionsHeight[height]) { - // If we had this transaction locally, and it might have not been final (confirmed) - // We require to look if it previously existed and need replace or to add it - const findIndex = mappedTransactionsHeight[height] - .findIndex((el) => el.hash === transaction.hash); - - if (findIndex >= 0) { - mappedTransactionsHeight[height][findIndex] = mappedTransactionObject; - } else { - mappedTransactionsHeight[height].push(mappedTransactionObject); - } - } else { - mappedTransactionsHeight[height] = ([mappedTransactionObject]); - } - } - } - - // even if we had this transaction locally, we need to - // process it to ensure no new address (BIP44) needs to be generated - [...inputs, ...outputs].forEach((element) => { - const isOutput = (element instanceof Output); - if (isOutput) outputIndex += 1; - - if (element.script) { - const address = element.script.toAddress(network).toString(); - - if (mappedAddress && mappedAddress[address]) { - const { path, type, walletId } = mappedAddress[address]; - const addressObject = store.wallets[walletId].addresses[type][path]; - // If the transactions has already been processed in a previous insertion, - // we can skip the processing now - if (addressObject.transactions.includes(transaction.hash)) { - return; - } - - if (!addressObject.used) addressObject.used = true; - - // We mark our address as affected so we update the tx later on - if (!processedAddressesForTx[addressObject.address]) { - processedAddressesForTx[addressObject.address] = addressObject; - } - - if (!isOutput) { - const vin = element; - const utxoKey = `${vin.prevTxId.toString('hex')}-${vin.outputIndex}`; - if (addressObject.utxos[utxoKey]) { - const previousOutput = addressObject.utxos[utxoKey]; - addressObject.balanceSat -= previousOutput.satoshis; - delete addressObject.utxos[utxoKey]; - hasUpdateStorage = true; - } - } else { - const vout = element; - - const utxoKey = `${transaction.hash}-${outputIndex}`; - if (!addressObject.utxos[utxoKey]) { - addressObject.utxos[utxoKey] = vout; - addressObject.balanceSat += vout.satoshis; - hasUpdateStorage = true; - } - } - } - } - }); - - // As the same address can have one or more inputs and one or more outputs in the same tx - // we update it's transactions array as last step of importing - Object.values(processedAddressesForTx).forEach((addressObject) => { - addressObject.transactions.push(transaction.hash); - }); - - if (hasUpdateStorage) { - this.lastModified = +new Date(); - // Announce only confirmed transaction imported that are our. - this.announce(FETCHED_CONFIRMED_TRANSACTION, { transaction }); - } -}; -module.exports = importTransaction; diff --git a/src/types/Storage/methods/importTransaction.spec.js b/src/types/Storage/methods/importTransaction.spec.js deleted file mode 100644 index 8c37c9f74..000000000 --- a/src/types/Storage/methods/importTransaction.spec.js +++ /dev/null @@ -1,407 +0,0 @@ -const {expect} = require('chai'); -const importTransaction = require('./importTransaction'); -const transactionFixtures = require('../../../../fixtures/transactions'); -const {fd7c727155ef67fd5c1d54b73dea869e9690c439570063d6e96fec1d3bba450e} = transactionFixtures.valid.mainnet -const { Transaction, Script } = require('@dashevo/dashcore-lib'); - -const faltyTx = '03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff0602cc0c028800ffffffff0200902f50090000001976a91446e502918c04a65a3830ce89cc364b0cd301793388ac00e40b54020000001976a914ecfd5aaebcbb8f4791e716e188b20d4f0183265c88ac00000000460200cc0c0000be0c7d02ff51a9d30e39873ebb953d763595565fcbe0512a04bfa25ed0455e380000000000000000000000000000000000000000000000000000000000000000'; - -const tx = new Transaction({ - hash: 'ea9c4066394aa09cb7ee8f3997b8dc10b999a8d709c4046f81d8bf9341ae6e5b', - version: 3, - inputs: [ - { - prevTxId: '9f398515b6fc898ebf4e7b49bbfc4359b8c89f508c6cd677e53946bd86064b28', - outputIndex: 0, - sequenceNumber: 4294967295, - script: '47304402205bb4f7880fb0fc13218940ba341c30e817363e5590343d28639af921b2a5f1d40220010920ae4b00bbb657f8653cb44172b8cb13447bb5105ddaf32a2845ea0666b90121025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - scriptString: '71 0x304402205bb4f7880fb0fc13218940ba341c30e817363e5590343d28639af921b2a5f1d40220010920ae4b00bbb657f8653cb44172b8cb13447bb5105ddaf32a2845ea0666b901 33 0x025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - }, - { - prevTxId: 'b812d9345fa8ea06af1d19b935eec65824d53779db74cd325690ad1d38a82757', - outputIndex: 0, - sequenceNumber: 4294967295, - script: '483045022100ea2d17ffc417e1f70c9c9ae11b7d95a07ab359c1d9d634baba145bab7b1deb0802207507296e12acc83ce038e5bbd54c46fa78b9475536f64fb313fedb978d12b73b0121025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - scriptString: '72 0x3045022100ea2d17ffc417e1f70c9c9ae11b7d95a07ab359c1d9d634baba145bab7b1deb0802207507296e12acc83ce038e5bbd54c46fa78b9475536f64fb313fedb978d12b73b01 33 0x025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - }, - { - prevTxId: '370b7bbd5b6e0de42a95d59e3277041ac20e945ffb93f56bb6984ba42f28a2ac', - outputIndex: 0, - sequenceNumber: 4294967295, - script: '47304402207926bf9176bdc88f38dde2140b2b8b0e4f331f33bb48af12c1bcce5efbb2593c022073c188d2149d5a0bfe4adff82b63d0bc62e04f2769cdcfda50a2c5e34ab7cbf60121025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - scriptString: '71 0x304402207926bf9176bdc88f38dde2140b2b8b0e4f331f33bb48af12c1bcce5efbb2593c022073c188d2149d5a0bfe4adff82b63d0bc62e04f2769cdcfda50a2c5e34ab7cbf601 33 0x025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - }, - ], - outputs: [ - { - satoshis: 12999997493, - script: '76a9143ec33076ba72b36b66b7ec571dd7417abdeb76f888ac', - }, - ], - nLockTime: 0, -}); - -describe('Storage - importTransaction', function suite() { - this.timeout(10000); - it('should throw on failed import', () => { - const mockStorage = { - store:{ - transactions:{} - } - } - const mockOpts1 = {}; - const mockOpts2 = '688dd18dea2b6f3c2d3892d13b41922fde7be01cd6040be9f3568dafbf9b1a23'; - const mockOpts3 = {'688dd18dea2b6f3c2d3892d13b41922fde7be01cd6040be9f3568dafbf9b1a23': {}}; - const mockOpts4 = {txid: '688dd18dea2b6f3c2d3892d13b41922fde7be01cd6040be9f3568dafbf9b1a23'}; - const mockOpts5 = {txid: '688dd18dea2b6f3c2d3892d13b41922fde7be01cd6040be9f3568dafbf9b1a23', vin: []}; - - const exceptedException1 = 'A Dashcore Transaction object or valid rawTransaction is required'; - - expect(() => importTransaction.call(mockStorage, mockOpts1)).to.throw(exceptedException1); - expect(() => importTransaction.call(mockStorage, mockOpts2)).to.throw(exceptedException1); - expect(() => importTransaction.call(mockStorage, mockOpts3)).to.throw(exceptedException1); - expect(() => importTransaction.call(mockStorage, mockOpts4)).to.throw(exceptedException1); - expect(() => importTransaction.call(mockStorage, mockOpts5)).to.throw(exceptedException1); - }); - it('should import a transaction', () => { - const mockedSearchAddress = () => ({found: false}); - let announceCalled = 0; - const self = { - store: { - wallets: { - 'db158d08df': { - addresses: { - external: { - "m/44'/1'/0'/0/0": { - path: "m/44'/1'/0'/0/0", - index: 0, - address: 'yS3Ja63BpkH7qHYVQvdEuiBd9xo8ZoPjZB', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - } - } - } - } - }, - transactions: {}, - chains: {testnet: {blockHeight: 50000}}, - }, - mappedAddress: { - 'yS3Ja63BpkH7qHYVQvdEuiBd9xo8ZoPjZB': {walletId: 'db158d08df', type: 'external', path: "m/44'/1'/0'/0/0"} - }, - network: 'testnet', - lastModified: 0, - searchAddress: mockedSearchAddress, - announce: (annType) => { - announceCalled += 1; - expect(annType).to.equal('FETCHED/CONFIRMED_TRANSACTION'); - }, - }; - importTransaction.call(self, tx); - importTransaction.call(self, tx); - const expectedStore = { - wallets: { - 'db158d08df': { - addresses: { - external: { - "m/44'/1'/0'/0/0": { - path: "m/44'/1'/0'/0/0", - index: 0, - address: 'yS3Ja63BpkH7qHYVQvdEuiBd9xo8ZoPjZB', - transactions: ["ea9c4066394aa09cb7ee8f3997b8dc10b999a8d709c4046f81d8bf9341ae6e5b"], - balanceSat: 12999997493, - unconfirmedBalanceSat: 0, - utxos: {"ea9c4066394aa09cb7ee8f3997b8dc10b999a8d709c4046f81d8bf9341ae6e5b-0": tx.outputs[0]}, - fetchedLast: 0, - used: true - } - } - } - } - }, - - transactions: {ea9c4066394aa09cb7ee8f3997b8dc10b999a8d709c4046f81d8bf9341ae6e5b: tx}, - chains: {testnet: {blockHeight: 50000}}, - }; - const expectedMappedAddress = { - 'yS3Ja63BpkH7qHYVQvdEuiBd9xo8ZoPjZB': {walletId: 'db158d08df', type: 'external', path: "m/44'/1'/0'/0/0"} - }; - - expect(self.store).to.be.deep.equal(expectedStore); - expect(self.mappedAddress).to.be.deep.equal(expectedMappedAddress); - expect(self.lastModified).to.be.not.equal(0); - expect(announceCalled).to.be.equal(1); - }); - it('should impact input and output correctly', function () { - let announceCalled = 0; - const tx_79fd_1 = new Transaction('0200000002de85b10c3e4e95e94597969cd7ffda3f8dc9237d36b225326fc8b24ea895039c010000006a47304402206f3b27083662213cadcc8d511f991c6cd57a45374829f32c707f99b046aaa6e8022021bfda9808d3adda06c9535edbdfe419db27ce3cb628ab0e9b1e3eeba01732c1012103a65caff6ca4c0415a3ac182dfc2a6d3a4dceb98e8b831e71501df38aa156f2c1feffffff17bed9b68cd3c1077b1776936b78a3c964bfe27d695708a196d0f35f4dcd3cef000000006a47304402200926de33076dfd2f6a0c6830ff447a9adab4e3143f7f34883e96cb3a9513f20f0220535a42cf40c5ba6095779393f8c702c913ce2f2a62d45a2cd37c56ccdbe60445012102372247aa7ef740c54fd126f3080537be5f834f0c16ba20edfe671d7c9b538c67feffffff0240782715000000001976a914e8f859254d24c98f64253a0388ba81ef2c68712788ac60f72e9e030000001976a914300dccc87c4811311c94525c7b208fc371ab654088ac1b150000'); - const tx_79fd_2 = new Transaction('0300000001853852da3974e3e1f8548256e1930781700b2f2c6bf420c0284033d61e1f4092010000006b483045022100d601a80702d3d599b338992d05f3475688a7c5febdabf372a053aaf0cfccc1d20220284410a6095a9777ca9b0f1ca56b42f536735ab39c7158682e7dba6a47cbd50c0121033807498e192fde6bfe27933365227e262e12fbfcf4d7b37ecff100228a0b04a2ffffffff0210270000000000001976a914e330440072a28e1da250fb63f3cd07e3d5a9b6cc88ac59cf2e9e030000001976a914c0c59ed9a83f91d070876941a362ed18f1b9223788ac00000000'); - const mockedSelf = { - store: { - wallets: { - '79fd90175d': { - addresses: { - external: { - "m/44'/1'/0'/0/0": { - path: "m/44'/1'/0'/0/0", - index: 0, - address: 'yQhXpFHfxk9pLyR1sPDYWZK5xqEMWbXrCd', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/0/1": { - path: "m/44'/1'/0'/0/1", - index: 1, - address: 'yh2i4JZ51rCFLbgk6RStaGKWB5JkQeeQYr', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/0/2": { - path: "m/44'/1'/0'/0/2", - index: 2, - address: 'yikykkDREFzxM7gNjxszrw2LYmJGHfJsdv', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - } - }, - internal: { - "m/44'/1'/0'/1/0": { - path: "m/44'/1'/0'/1/0", - index: 0, - address: 'ydtjKwwrxsq2Czeoeqk5ULoSXvdrnKWKWR', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/1/1": { - path: "m/44'/1'/0'/1/1", - index: 1, - address: 'yZ7zwKLSwuvZyYQXU2UGRPf1qr7nRqdH7b', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - } - } - } - }, - transactions: {}, - chains: {testnet: {blockHeight: 50000}}, - }, - mappedAddress: { - 'yQhXpFHfxk9pLyR1sPDYWZK5xqEMWbXrCd': {walletId: '79fd90175d', type: 'external', path: "m/44'/1'/0'/0/0"}, - 'ydtjKwwrxsq2Czeoeqk5ULoSXvdrnKWKWR': {walletId: '79fd90175d', type: 'internal', path: "m/44'/1'/0'/1/0"}, - 'yh2i4JZ51rCFLbgk6RStaGKWB5JkQeeQYr': {walletId: '79fd90175d', type: 'external', path: "m/44'/1'/0'/0/1"}, - 'yZ7zwKLSwuvZyYQXU2UGRPf1qr7nRqdH7b': {walletId: '79fd90175d', type: 'internal', path: "m/44'/1'/0'/1/1"}, - 'yikykkDREFzxM7gNjxszrw2LYmJGHfJsdv': {walletId: '79fd90175d', type: 'external', path: "m/44'/1'/0'/0/2"} - }, - network: 'testnet', - lastModified: 0, - announce: (annType) => { - announceCalled += 1; - expect(annType).to.equal('FETCHED/CONFIRMED_TRANSACTION'); - }, - }; - importTransaction.call(mockedSelf, tx_79fd_1); - const expectedTransactionsAfterTx1 = { - '92401f1ed6334028c020f46b2c2f0b70810793e1568254f8e1e37439da523885': tx_79fd_1 - }; - const expectedExternalStoreAfterTx1 = { - "m/44'/1'/0'/0/0":{ - "path": "m/44'/1'/0'/0/0", - "index": 0, - "address": "yQhXpFHfxk9pLyR1sPDYWZK5xqEMWbXrCd", - "transactions": ['92401f1ed6334028c020f46b2c2f0b70810793e1568254f8e1e37439da523885'], - "balanceSat": 15538780000, - "unconfirmedBalanceSat": 0, - "utxos": { - "92401f1ed6334028c020f46b2c2f0b70810793e1568254f8e1e37439da523885-1": new Transaction.Output({ - "satoshis": 15538780000, - "script": "76a914300dccc87c4811311c94525c7b208fc371ab654088ac" - }) - }, - "fetchedLast": 0, - "used": true - } - } - expect(mockedSelf.store.transactions).to.deep.equal(expectedTransactionsAfterTx1); - expect(mockedSelf.store.wallets['79fd90175d'].addresses.external["m/44'/1'/0'/0/0"]).to.deep.equal(expectedExternalStoreAfterTx1["m/44'/1'/0'/0/0"]); - - // We need to ensure it do not duplicate UTXO or TXs - importTransaction.call(mockedSelf, tx_79fd_1); - expect(mockedSelf.store.transactions).to.deep.equal(expectedTransactionsAfterTx1); - expect(mockedSelf.store.wallets['79fd90175d'].addresses.external["m/44'/1'/0'/0/0"]).to.deep.equal(expectedExternalStoreAfterTx1["m/44'/1'/0'/0/0"]); - - importTransaction.call(mockedSelf, tx_79fd_2); - const expectedTransactionsAfterTx2 = { - '92401f1ed6334028c020f46b2c2f0b70810793e1568254f8e1e37439da523885': tx_79fd_1, - 'df7792f30588150c94b68e869bcb219b55f66f7ef97d4e5c4bb48c3a6db1250e': tx_79fd_2 - }; - expect(mockedSelf.store.transactions).to.deep.equal(expectedTransactionsAfterTx2); - - const expectedExternalStoreAfterTx2 = { - "m/44'/1'/0'/0/0":{ - "path": "m/44'/1'/0'/0/0", - "index": 0, - "address": "yQhXpFHfxk9pLyR1sPDYWZK5xqEMWbXrCd", - "transactions": [ - '92401f1ed6334028c020f46b2c2f0b70810793e1568254f8e1e37439da523885', - 'df7792f30588150c94b68e869bcb219b55f66f7ef97d4e5c4bb48c3a6db1250e' - ], - "balanceSat": 0, - "unconfirmedBalanceSat": 0, - "utxos": {}, - "fetchedLast": 0, - "used": true - }, - "m/44'/1'/0'/0/1":{ - "path": "m/44'/1'/0'/0/1", - "index": 1, - "address": "yh2i4JZ51rCFLbgk6RStaGKWB5JkQeeQYr", - "transactions": [ - 'df7792f30588150c94b68e869bcb219b55f66f7ef97d4e5c4bb48c3a6db1250e' - ], - "balanceSat": 10000, - "unconfirmedBalanceSat": 0, - "utxos": { - "df7792f30588150c94b68e869bcb219b55f66f7ef97d4e5c4bb48c3a6db1250e-0": new Transaction.Output({ - "satoshis": 10000, - "script": '76a914e330440072a28e1da250fb63f3cd07e3d5a9b6cc88ac' - }), - }, - "fetchedLast": 0, - "used": true - },"m/44'/1'/0'/1/0":{ - "path": "m/44'/1'/0'/1/0", - "index": 0, - "address": "ydtjKwwrxsq2Czeoeqk5ULoSXvdrnKWKWR", - "transactions": [ - 'df7792f30588150c94b68e869bcb219b55f66f7ef97d4e5c4bb48c3a6db1250e' - ], - "balanceSat": 15538769753, - "unconfirmedBalanceSat": 0, - "utxos": { - "df7792f30588150c94b68e869bcb219b55f66f7ef97d4e5c4bb48c3a6db1250e-1": new Transaction.Output({ - "satoshis": 15538769753, - "script": '76a914c0c59ed9a83f91d070876941a362ed18f1b9223788ac' - }), - }, - "fetchedLast": 0, - "used": true - } - }; - expect(mockedSelf.store.wallets['79fd90175d'].addresses.external["m/44'/1'/0'/0/0"]).to.deep.equal(expectedExternalStoreAfterTx2["m/44'/1'/0'/0/0"]); - expect(mockedSelf.store.wallets['79fd90175d'].addresses.external["m/44'/1'/0'/0/1"]).to.deep.equal(expectedExternalStoreAfterTx2["m/44'/1'/0'/0/1"]); - expect(mockedSelf.store.wallets['79fd90175d'].addresses.internal["m/44'/1'/0'/1/0"]).to.deep.equal(expectedExternalStoreAfterTx2["m/44'/1'/0'/1/0"]); - - importTransaction.call(mockedSelf, tx_79fd_2); - importTransaction.call(mockedSelf, tx_79fd_1); - importTransaction.call(mockedSelf, tx_79fd_2); - expect(mockedSelf.store.transactions).to.deep.equal(expectedTransactionsAfterTx2); - expect(mockedSelf.store.wallets['79fd90175d'].addresses.external["m/44'/1'/0'/0/0"]).to.deep.equal(expectedExternalStoreAfterTx2["m/44'/1'/0'/0/0"]); - expect(mockedSelf.store.wallets['79fd90175d'].addresses.external["m/44'/1'/0'/0/1"]).to.deep.equal(expectedExternalStoreAfterTx2["m/44'/1'/0'/0/1"]); - expect(mockedSelf.store.wallets['79fd90175d'].addresses.internal["m/44'/1'/0'/1/0"]).to.deep.equal(expectedExternalStoreAfterTx2["m/44'/1'/0'/1/0"]); - - }); - it('should import transaction metadata', function () { - let announceCalled = 0; - - const mockedSelf = { - store: { - wallets: { - '60ee3a92b6': { - accounts:{ - "m/44'/1'/0'": { - label: null, - path: "m/44'/1'/0'", - network: 'testnet', - blockHeight: 552160 - } - }, - network: 'testnet', - mnemonic: null, - type: null, - identityIds: [], - addresses: { - external: { - "m/44'/1'/0'/0/0": { - path: "m/44'/1'/0'/0/0", - index: 0, - address: 'yd1ohc12LgCYp56CDuckTEHwoa6LbPghMd', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - }, - } - } - }, - transactionsMetadata: {}, - transactions: {}, - - chains: {testnet: { - mappedBlockHeaderHeights: { - '552160': '0000019156265f85695bf62285e32408d6057406b19374a57c009afa9116396f' - }, - blockHeight: 552160 - }}, - }, - mappedAddress: { - 'yd1ohc12LgCYp56CDuckTEHwoa6LbPghMd': {walletId: '60ee3a92b6', type: 'external', path: "m/44'/1'/0'/0/0"}, - }, - mappedTransactionsHeight: { - - }, - network: 'testnet', - lastModified: 0, - announce: (annType) => { - announceCalled += 1; - expect(annType).to.equal('FETCHED/CONFIRMED_TRANSACTION'); - }, - }; - - importTransaction.call(mockedSelf, transactionFixtures.valid.testnet["1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6"], transactionFixtures.valid.testnet.metadata["1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6"]); - - expect(mockedSelf.store.transactions['1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6']).to.exist; - const expectedTransaction = new Transaction(transactionFixtures.valid.testnet["1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6"]); - expect(mockedSelf.store.transactions['1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6'].toString()).to.equal(expectedTransaction.toString()); - - expect(mockedSelf.store.transactionsMetadata).to.exist; - expect(mockedSelf.store.transactionsMetadata['1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6']).to.deep.equal({ - blockHash: '0000007a84abfe1d2b4201f4844bb1e59f24daf965c928281589269f281abc01', - height: 551438, - instantLocked: true, - chainLocked: true - }) - - expect(mockedSelf.mappedTransactionsHeight).to.exist; - const expectedMetadata = transactionFixtures.valid.testnet.metadata["1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6"]; - expectedMetadata.hash = '1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6'; - expect(mockedSelf.mappedTransactionsHeight['551438']).to.deep.equal([expectedMetadata]); - }); -}); diff --git a/src/types/Storage/methods/importTransactions.js b/src/types/Storage/methods/importTransactions.js deleted file mode 100644 index a2e29244d..000000000 --- a/src/types/Storage/methods/importTransactions.js +++ /dev/null @@ -1,34 +0,0 @@ -const { Transaction } = require('@dashevo/dashcore-lib'); - -/** - * Import an array of transactions or a transaction object to the store - * @param {[TransactionsWithMetaData][Transaction]|Transaction} transactions - * @return {number} - * */ -const importTransactions = function importTransactions(transactions) { - const type = transactions.constructor.name; - const self = this; - if (type === Transaction.name) { - self.importTransaction(transactions); - } else if (type === 'Object') { - const transactionsIds = Object.keys(transactions); - if (transactionsIds.length === 0) { - throw new Error('Invalid transaction'); - } - transactionsIds.forEach((id) => { - const transaction = transactions[id]; - self.importTransaction(transaction); - }); - } else if (type === 'Array') { - transactions.forEach((transactionData) => { - if (transactionData.constructor.name === 'Array') { - self.importTransaction(transactionData[0], transactionData[1]); - } else { - self.importTransaction(transactionData); - } - }); - } else { - throw new Error('Invalid transaction. Cannot import.'); - } -}; -module.exports = importTransactions; diff --git a/src/types/Storage/methods/importTransactions.spec.js b/src/types/Storage/methods/importTransactions.spec.js deleted file mode 100644 index 307a5c326..000000000 --- a/src/types/Storage/methods/importTransactions.spec.js +++ /dev/null @@ -1,11 +0,0 @@ -const { expect } = require('chai'); -const importTransactions = require('./importTransactions'); - -describe('Storage - importTransactions', () => { - it('should import an array of transaction', () => { - - }); - it('should import an object with one or multiples transaction', () => { - - }); -}); diff --git a/src/types/Storage/methods/insertIdentityAtIndex.js b/src/types/Storage/methods/insertIdentityAtIndex.js deleted file mode 100644 index fd46c1f20..000000000 --- a/src/types/Storage/methods/insertIdentityAtIndex.js +++ /dev/null @@ -1,25 +0,0 @@ -const IdentityReplaceError = require('../../../errors/IndentityIdReplaceError'); - -/** - * - * @param {string} walletId - * @param {string} identityId - * @param {number} identityIndex - * @return void - */ -function insertIdentityAtIndex(walletId, identityId, identityIndex) { - if (!this.store.wallets[walletId].identityIds) { - this.store.wallets[walletId].identityIds = []; - } - - const existingId = this.getIdentityIdByIndex(walletId, identityIndex); - - if (Boolean(existingId) && existingId !== identityId) { - throw new IdentityReplaceError(`Trying to replace identity at index ${identityIndex}`); - } - - this.store.wallets[walletId].identityIds[identityIndex] = identityId; - this.lastModified = Date.now(); -} - -module.exports = insertIdentityAtIndex; diff --git a/src/types/Storage/methods/rehydrateState.spec.js b/src/types/Storage/methods/rehydrateState.spec.js deleted file mode 100644 index 310107f04..000000000 --- a/src/types/Storage/methods/rehydrateState.spec.js +++ /dev/null @@ -1,8 +0,0 @@ -const { expect } = require('chai'); -const rehydrateState = require('./rehydrateState'); - -describe('Storage - rehydrateState', () => { - it('should rehydrate the state', () => { - - }); -}); diff --git a/src/types/Storage/methods/saveState.spec.js b/src/types/Storage/methods/saveState.spec.js deleted file mode 100644 index 261a5aa67..000000000 --- a/src/types/Storage/methods/saveState.spec.js +++ /dev/null @@ -1,8 +0,0 @@ -const { expect } = require('chai'); -const saveState = require('./saveState'); - -describe('Storage - saveState', () => { - it('should state the state', () => { - - }); -}); diff --git a/src/types/Storage/methods/searchAddress.js b/src/types/Storage/methods/searchAddress.js deleted file mode 100644 index b19382c69..000000000 --- a/src/types/Storage/methods/searchAddress.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Search a specific address in the store - * @param {string} address - * @param {boolean} [forceLoop=false] - When set at true, will force a full search - * @return {AddressSearchResult} - */ -const searchAddress = function searchAddress(address, forceLoop = false) { - const search = { - address, - type: null, - found: false, - }; - const { store } = this; - if (forceLoop === true) { - // Look up by looping over all addresses todo:optimisation - const existingWallets = Object.keys(store.wallets); - existingWallets.forEach((walletId) => { - const existingTypes = Object.keys(store.wallets[walletId].addresses); - existingTypes.forEach((type) => { - const existingPaths = Object.keys(store.wallets[walletId].addresses[type]); - existingPaths.forEach((path) => { - const el = store.wallets[walletId].addresses[type][path]; - if (el.address === search.address) { - search.path = path; - search.type = type; - search.found = true; - search.result = el; - search.walletId = walletId; - } - }); - }); - }); - } else if (this.mappedAddress[address]) { - const { path, type, walletId } = this.mappedAddress[address]; - const el = store.wallets[walletId].addresses[type][path]; - - search.path = path; - search.type = type; - search.found = true; - search.result = el; - search.walletId = walletId; - } - - return search; -}; -module.exports = searchAddress; diff --git a/src/types/Storage/methods/searchAddress.spec.js b/src/types/Storage/methods/searchAddress.spec.js deleted file mode 100644 index 0d0b92ca7..000000000 --- a/src/types/Storage/methods/searchAddress.spec.js +++ /dev/null @@ -1,8 +0,0 @@ -const { expect } = require('chai'); -const searchAddress = require('./searchAddress'); - -describe('Storage - searchAddress', () => { - it('should search an address', () => { - - }); -}); diff --git a/src/types/Storage/methods/searchAddressWithTx.spec.js b/src/types/Storage/methods/searchAddressWithTx.spec.js deleted file mode 100644 index f8df278e8..000000000 --- a/src/types/Storage/methods/searchAddressWithTx.spec.js +++ /dev/null @@ -1,8 +0,0 @@ -const { expect } = require('chai'); -const searchAddressesWithTx = require('./searchAddressesWithTx'); - -describe('Storage - searchAddressesWithTx', () => { - it('should search an address having a specific tx', () => { - - }); -}); diff --git a/src/types/Storage/methods/searchAddressesWithTx.js b/src/types/Storage/methods/searchAddressesWithTx.js deleted file mode 100644 index 6aa36d625..000000000 --- a/src/types/Storage/methods/searchAddressesWithTx.js +++ /dev/null @@ -1,36 +0,0 @@ -const _ = require('lodash'); -/** - * Search an address having a specific txid - * todo : Handle when multiples one (inbound/outbound) - * @param {string} txid - * @return {AddressesSearchResult} - */ -const searchAddressesWithTx = function searchAddressesWithTx(txid) { - const search = { - txid, - results: [], - found: false, - }; - const store = this.getStore(); - - // Look up by looping over all addresses todo:optimisation - const existingWallets = Object.keys(store.wallets); - existingWallets.forEach((walletId) => { - if (_.has(store.wallets[walletId], 'addresses')) { - const existingTypes = Object.keys(store.wallets[walletId].addresses); - existingTypes.forEach((type) => { - const existingPaths = Object.keys(store.wallets[walletId].addresses[type]); - existingPaths.forEach((path) => { - const el = store.wallets[walletId].addresses[type][path]; - if (el.transactions.includes(search.txid)) { - search.results.push({ type, ...el }); - search.found = true; - } - }); - }); - } - }); - - return search; -}; -module.exports = searchAddressesWithTx; diff --git a/src/types/Storage/methods/searchBlockHeader.js b/src/types/Storage/methods/searchBlockHeader.js deleted file mode 100644 index c93463a19..000000000 --- a/src/types/Storage/methods/searchBlockHeader.js +++ /dev/null @@ -1,25 +0,0 @@ -const { is } = require('../../../utils'); -/** - * Search a specific blockheader in the store - * @param {string|number} identifier - block hash or height - * @return {BlockHeaderSearchResult} - */ -const searchBlockHeader = function searchBlockHeader(identifier) { - const store = this.getStore(); - const search = { - identifier, - found: false, - }; - const chainStore = store.chains[this.network.toString()]; - const blockheader = (is.num(identifier) - // eslint-disable-next-line no-underscore-dangle - ? chainStore.blockHeaders[chainStore.mappedBlockHeaderHeights[identifier]] - : chainStore.blockHeaders[identifier]); - - if (blockheader) { - search.found = true; - search.result = blockheader; - } - return search; -}; -module.exports = searchBlockHeader; diff --git a/src/types/Storage/methods/searchBlockHeader.spec.js b/src/types/Storage/methods/searchBlockHeader.spec.js deleted file mode 100644 index 137b3b2e9..000000000 --- a/src/types/Storage/methods/searchBlockHeader.spec.js +++ /dev/null @@ -1,53 +0,0 @@ -const {expect} = require('chai'); -const {BlockHeader} = require('@dashevo/dashcore-lib'); -const searchBlockHeader = require('./searchBlockHeader'); - -const existingBlockHeader = new BlockHeader.fromObject({ - hash: '00000ac3a0c9df709260e41290d6902e5a4a073099f11fe8c1ce80aadc4bb331', - version: 2, - prevHash: '00000ce430de949c85a145b02e33ebbaed3772dc8f3d668f66edc6852c24d002', - merkleRoot: '663360403b5fba9cd8744c3706f9660c7d3fee4e5a9ee98ce0ad5e5ad7824c1d', - time: 1398712821, - bits: 504365040, - nonce: 312363 -}); -const notExistingBlockHeader = new BlockHeader.fromObject({ - hash: '00000b526b34e733532d706c1f4cef93eefe707b87c2c3cb2978e1a84b97c501', - version: 2, - prevHash: '00000ac3a0c9df709260e41290d6902e5a4a073099f11fe8c1ce80aadc4bb331', - merkleRoot: 'c2ed22a3e6712b842359dfbb6f0a133ae122ffb601e4cf60e30b8c99f9438f4f', - time: 1398712821, - bits: 504365040, - nonce: 8325 -}) -describe('Storage - searchBlockHeader', function suite() { - this.timeout(10000); - it('should find a transaction', () => { - const self = { - network: 'testnet', - store: { - chains:{ - 'testnet':{ - blockHeaders: { - '00000ac3a0c9df709260e41290d6902e5a4a073099f11fe8c1ce80aadc4bb331': existingBlockHeader, - }, - } - }, - }, - }; - - self.getStore = () => self.store; - - const existingBlockHash = existingBlockHeader.hash; - const notExistingBlockHash = notExistingBlockHeader.hash; - const search = searchBlockHeader.call(self, existingBlockHash); - - expect(search.found).to.be.equal(true); - expect(search.identifier).to.be.equal(existingBlockHash); - expect(search.result).to.be.deep.equal(existingBlockHeader); - - const search2 = searchBlockHeader.call(self, notExistingBlockHash); - expect(search2.found).to.be.equal(false); - expect(search2.identifier).to.be.equal(notExistingBlockHash); - }); -}); diff --git a/src/types/Storage/methods/searchTransaction.js b/src/types/Storage/methods/searchTransaction.js deleted file mode 100644 index 1bd8b729b..000000000 --- a/src/types/Storage/methods/searchTransaction.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Search a specific txid in the store - * @param {string} hash - * @return {TransactionSearchResult} - */ -const searchTransaction = function searchTransaction(hash) { - const search = { - hash, - found: false, - }; - const store = this.getStore(); - - if (store.transactions[hash]) { - const tx = store.transactions[hash]; - if (tx.hash === hash) { - search.found = true; - search.result = tx; - } - } - return search; -}; -module.exports = searchTransaction; diff --git a/src/types/Storage/methods/searchTransaction.spec.js b/src/types/Storage/methods/searchTransaction.spec.js deleted file mode 100644 index 831a804be..000000000 --- a/src/types/Storage/methods/searchTransaction.spec.js +++ /dev/null @@ -1,31 +0,0 @@ -const { expect } = require('chai'); -const searchTransaction = require('./searchTransaction'); -const transactionsFixtures = require('../../../../fixtures/transactions'); - -const { faa430b0fe84a074d981e6fa3995a13363478415ca029a12f6432bf3d90dfa60, fd7c727155ef67fd5c1d54b73dea869e9690c439570063d6e96fec1d3bba450e } = transactionsFixtures.valid.mainnet; -describe('Storage - searchTransaction', function suite() { - this.timeout(10000); - it('should find a transaction', () => { - const self = { - store: { - transactions: { - faa430b0fe84a074d981e6fa3995a13363478415ca029a12f6432bf3d90dfa60, - }, - }, - }; - - self.getStore = () => self.store; - - const existingTxID = faa430b0fe84a074d981e6fa3995a13363478415ca029a12f6432bf3d90dfa60.hash; - const notExistingTxID = fd7c727155ef67fd5c1d54b73dea869e9690c439570063d6e96fec1d3bba450e.hash; - const search = searchTransaction.call(self, existingTxID); - - expect(search.found).to.be.equal(true); - expect(search.hash).to.be.equal(existingTxID); - expect(search.result).to.be.equal(faa430b0fe84a074d981e6fa3995a13363478415ca029a12f6432bf3d90dfa60); - - const search2 = searchTransaction.call(self, notExistingTxID); - expect(search2.found).to.be.equal(false); - expect(search2.hash).to.be.equal(notExistingTxID); - }); -}); diff --git a/src/types/Storage/methods/searchTransactionMetadata.js b/src/types/Storage/methods/searchTransactionMetadata.js deleted file mode 100644 index eae1b9627..000000000 --- a/src/types/Storage/methods/searchTransactionMetadata.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Search a specific txid's metadata in the store - * @param {string} hash - * @return {TransactionMetadataSearchResult} - */ -const searchTransactionMetadata = function searchTransactionMetadata(hash) { - const search = { - hash, - found: false, - }; - const store = this.getStore(); - - if (store.transactionsMetadata[hash]) { - const txMetadata = store.transactionsMetadata[hash]; - if (txMetadata.hash === hash) { - search.found = true; - search.result = txMetadata; - } - } - return search; -}; -module.exports = searchTransactionMetadata; diff --git a/src/types/Storage/methods/searchTransactionMetadata.spec.js b/src/types/Storage/methods/searchTransactionMetadata.spec.js deleted file mode 100644 index 6cc013b5a..000000000 --- a/src/types/Storage/methods/searchTransactionMetadata.spec.js +++ /dev/null @@ -1,37 +0,0 @@ -const { expect } = require('chai'); -const searchTransactionMetadata = require('./searchTransactionMetadata'); -const transactionsFixtures = require('../../../../fixtures/transactions'); - -const validTxId = '1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6'; -const validTx = transactionsFixtures.valid.testnet[validTxId]; -const validMetadata = transactionsFixtures.valid.testnet.metadata[validTxId]; -describe('Storage - searchTransactionMetadata', function suite() { - this.timeout(10000); - it('should find a transaction metadata', () => { - const self = { - store: { - transactions: { - "1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6": validTx, - }, - transactionsMetadata: { - "1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6": {hash:"1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6",...validMetadata} - } - }, - }; - - self.getStore = () => self.store; - - const existingTxID = "1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6"; - const notExistingTxID = "fd7c727155ef67fd5c1d54b73dea869e9690c439570063d6e96fec1d3bba450e"; - const search = searchTransactionMetadata.call(self, existingTxID); - - expect(search.found).to.be.equal(true); - expect(search.hash).to.be.equal(existingTxID); - const expectedResult = {hash: "1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6", ...validMetadata}; - expect(search.result).to.be.deep.equal(expectedResult); - - const search2 = searchTransactionMetadata.call(self, notExistingTxID); - expect(search2.found).to.be.equal(false); - expect(search2.hash).to.be.equal(notExistingTxID); - }); -}); diff --git a/src/types/Storage/methods/searchWallet.js b/src/types/Storage/methods/searchWallet.js deleted file mode 100644 index 9719d057b..000000000 --- a/src/types/Storage/methods/searchWallet.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Search a wallet in store based from it's walletId - * @param {string} walletId - * @return {WalletSearchResult} - */ -const searchWallet = function searchWallet(walletId) { - const search = { - walletId, - found: false, - }; - const store = this.getStore(); - if (store.wallets[walletId]) { - search.found = true; - search.result = store.wallets[walletId]; - } - return search; -}; -module.exports = searchWallet; diff --git a/src/types/Storage/methods/searchWallet.spec.js b/src/types/Storage/methods/searchWallet.spec.js deleted file mode 100644 index 4f40be20c..000000000 --- a/src/types/Storage/methods/searchWallet.spec.js +++ /dev/null @@ -1,33 +0,0 @@ -const { expect } = require('chai'); -const Dashcore = require('@dashevo/dashcore-lib'); -const searchWallet = require('./searchWallet'); - -describe('Storage - searchWallet', function suite() { - this.timeout(10000); - it('should find a wallet', () => { - const self = { - store: { - wallets: { - '123ae': { - accounts: {}, - network: Dashcore.Networks.testnet, - mnemonic: null, - type: null, - blockHeight: 0, - addresses: { external: {}, internal: {}, misc: {} }, - }, - }, - chains: { testnet: { name: 'testnet', blockHeight: -1 } }, - }, - }; - self.getStore = () => self.store; - - const existingWalletid = '123ae'; - const search = searchWallet.call(self, existingWalletid); - - expect(search.found).to.be.equal(true); - expect(search.walletId).to.be.equal(existingWalletid); - expect(search.result.accounts).to.be.deep.equal({}); - expect(search.result.addresses).to.be.deep.equal({ external: {}, internal: {}, misc: {} }); - }); -}); diff --git a/src/types/Storage/methods/startWorker.spec.js b/src/types/Storage/methods/startWorker.spec.js deleted file mode 100644 index 0711f4094..000000000 --- a/src/types/Storage/methods/startWorker.spec.js +++ /dev/null @@ -1,53 +0,0 @@ -const { expect } = require('chai'); -const startWorker = require('./startWorker'); - -let testInterval = null; -const simulateChangeEvery = function (ms) { - const self = this; - testInterval = setInterval(() => { - self.lastModified = Date.now(); - }, ms); -}; -describe('Storage - startWorker', function suite() { - this.timeout(60000); - it('should set an interval', function () { - const defaultIntervalValue = 10000; - const self = { - autosaveIntervalTime: defaultIntervalValue, - }; - startWorker.call(self); - if (process.browser){ - this.skip('doesn\'t work in browser') - // Need to clear to not hang-on forever - clearInterval(self.interval); - return; - } - expect(self.interval.constructor.name).to.be.equal('Timeout'); - expect(self.interval._repeat).to.be.equal(defaultIntervalValue); // Timeout are null btw - clearInterval(self.interval); - }); - it('should work', async () => new Promise((res) => { - let saved = 0; - const self = { - saveState: () => { - saved += 1; - self.lastSave = Date.now(); - }, - autosaveIntervalTime: 500, - lastModified: Date.now(), - lastSave: 0, - }; - startWorker.call(self); - simulateChangeEvery.call(self, 200); - - setTimeout(() => { - clearInterval(self.interval); - testInterval = clearInterval(testInterval); - - expect(saved < 11).to.be.equal(true); - // First autosave + 9 induced changes - // However it can be less as we do not hard force the place in the event loop (simple setInterval) - res(expect(saved >= 8).to.be.equal(true)); - }, 5499); - })); -}); diff --git a/src/types/Storage/methods/updateAddress.js b/src/types/Storage/methods/updateAddress.js deleted file mode 100644 index 5bff96fea..000000000 --- a/src/types/Storage/methods/updateAddress.js +++ /dev/null @@ -1,123 +0,0 @@ -const { cloneDeep, xor } = require('lodash'); -const { InvalidAddressObject, TransactionNotInStore } = require('../../../errors'); -const { is } = require('../../../utils'); -const EVENTS = require('../../../EVENTS'); - -/** -* Update a specific address information in the store -* @param {AddressObj} addressObj -* @param {string} walletId -* @return {boolean} -*/ -const updateAddress = function updateAddress(addressObj, walletId) { - if (!walletId) throw new Error('Expected walletId to update an address'); - if (!is.addressObj(addressObj)) throw new InvalidAddressObject(addressObj); - const { path } = addressObj; - if (!path) throw new Error('Expected path to update an address'); - const accountIndex = parseInt(path.split('/')[3], 10); - - const typeInt = path.split('/')[4]; - let type; - switch (typeInt) { - case '0': - type = 'external'; - break; - case '1': - type = 'internal'; - break; - default: - type = 'misc'; - } - const walletStore = this.store.wallets[walletId]; - const addressesStore = walletStore.addresses; - const previousObject = cloneDeep(addressesStore[type][path]); - - const newObject = cloneDeep(addressObj); - // We do not autorize to alter UTXO using this - // if(newObject.utxos.length==0 && previousObject.utxos.length>0){ - // - // } - // const currentBlockHeight = this.store.chains[walletStore.network].blockHeight; - - // We calculate here the balanceSat and unconfirmedBalanceSat of our addressObj - // We do that to avoid getBalance to be slow, so we have to keep that in mind or then - // Move that to an event type of calculation or somth - const { utxos } = newObject; - - const newObjectUtxosKeys = Object.keys(utxos); - if (newObjectUtxosKeys.length > 0) { - // we compare the diff between the two utxos sets - - const previousUTXOS = (previousObject !== undefined) ? previousObject.utxos : []; - - const newUtxos = xor(newObjectUtxosKeys, Object.keys(previousUTXOS)); - // Then we verify the outputs - newUtxos.forEach((utxoKey) => { - const [txid, outputIndex] = utxoKey.split('-'); - const utxo = utxos[utxoKey]; - utxo.txId = txid; - utxo.outputIndex = parseInt(outputIndex, 10); - - try { - this.getTransaction(txid); - // TODO : We removed here the confirmations verification - // We should ensure we had a locked block before being able to really spend those. - newObject.balanceSat += utxo.satoshis; - } catch (e) { - if (!(e instanceof TransactionNotInStore)) throw e; - } - }); - } - - // Check if there is a balance but no utxo. - addressesStore[type][path] = newObject; - if (previousObject === undefined) { - if (newObject.balanceSat > 0) { - this.announce( - EVENTS.CONFIRMED_BALANCE_CHANGED, - { - delta: newObject.balanceSat, - currentValue: this.calculateDuffBalance(walletId, accountIndex, 'confirmed') || newObject.unconfirmedBalanceSat, - // currentValue: newObject.balanceSat, - }, - ); - } - if (newObject.unconfirmedBalanceSat > 0) { - this.announce( - EVENTS.UNCONFIRMED_BALANCE_CHANGED, - { - delta: newObject.unconfirmedBalanceSat, - // currentValue: newObject.unconfirmedBalanceSat, - currentValue: this.calculateDuffBalance(walletId, accountIndex, 'unconfirmed'), - }, - ); - } - } else { - if (previousObject.balanceSat !== newObject.balanceSat) { - this.announce( - EVENTS.CONFIRMED_BALANCE_CHANGED, - { - delta: newObject.balanceSat - previousObject.balanceSat, - // currentValue: newObject.balanceSat, - currentValue: this.calculateDuffBalance(walletId, accountIndex, 'confirmed'), - }, - ); - } - if (previousObject.unconfirmedBalanceSat !== newObject.unconfirmedBalanceSat) { - this.announce(EVENTS.UNCONFIRMED_BALANCE_CHANGED, - { - delta: newObject.unconfirmedBalanceSat - previousObject.unconfirmedBalanceSat, - // currentValue: newObject.unconfirmedBalanceSat, - currentValue: this.calculateDuffBalance(walletId, accountIndex, 'unconfirmed'), - }); - } - } - - this.lastModified = Date.now(); - - if (!this.mappedAddress[newObject.address]) { - this.mappedAddress[newObject.address] = { walletId, type, path }; - } - return true; -}; -module.exports = updateAddress; diff --git a/src/types/Storage/methods/updateAddress.spec.js b/src/types/Storage/methods/updateAddress.spec.js deleted file mode 100644 index 0de48ab1e..000000000 --- a/src/types/Storage/methods/updateAddress.spec.js +++ /dev/null @@ -1,35 +0,0 @@ -const { expect } = require('chai'); -const updateAddress = require('./updateAddress'); -const orangeWStore = require('../../../../fixtures/walletStore').valid.orange.store; - -describe('Storage - updateAddress', function suite() { - this.timeout(10000); - it('should throw errors on failed update', () => { - const self = {}; - expect(() => updateAddress.call(self)).to.throw('Expected walletId to update an address'); - - expect(() => updateAddress.call(self, {}, '123ae')).to.throw('Address should have property path of type string'); - }); - it('should update an address', () => { - const self = { store: orangeWStore, mappedAddress: {} }; - const validAddrObj = { - path: "m/44'/1'/0'/0/0", - index: '0', - address: 'yLhsYLXW5sFHLDPLj2EHgrmQRhP712ANda', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: true, - }; - const validWalletId = Object.keys(orangeWStore.wallets)[0]; - updateAddress.call(self, validAddrObj, validWalletId); - const expectedMappedAddress = { yLhsYLXW5sFHLDPLj2EHgrmQRhP712ANda: { walletId: 'a3771aaf93', type: 'external', path: "m/44'/1'/0'/0/0" } }; - const expectedUpdatedAddress = { - path: "m/44'/1'/0'/0/0", index: '0', address: 'yLhsYLXW5sFHLDPLj2EHgrmQRhP712ANda', transactions: [], balanceSat: 0, unconfirmedBalanceSat: 0, utxos: {}, fetchedLast: 0, used: true, - }; - expect(self.mappedAddress).to.be.deep.equal(expectedMappedAddress); - expect(self.store.wallets.a3771aaf93.addresses.external["m/44'/1'/0'/0/0"]).to.deep.equal(expectedUpdatedAddress); - }); -}); diff --git a/src/types/Storage/methods/updateTransaction.js b/src/types/Storage/methods/updateTransaction.js deleted file mode 100644 index 18c359505..000000000 --- a/src/types/Storage/methods/updateTransaction.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Update a specific transaction information in the store - * It do not handle any merging right now and write over previous data. - * @param {Transaction} transaction - * @return {boolean} - */ -const updateTransaction = function updateTransaction(transaction) { - if (!transaction) throw new Error('Expected a transaction to update'); - - const transactionStore = this.store.transactions; - const storeTx = transactionStore[transaction.hash]; - if (JSON.stringify(storeTx) !== JSON.stringify(transaction)) { - transactionStore[transaction.hash] = transaction; - this.lastModified = Date.now(); - } - return true; -}; -module.exports = updateTransaction; diff --git a/src/types/Storage/methods/updateTransaction.spec.js b/src/types/Storage/methods/updateTransaction.spec.js deleted file mode 100644 index 04c359171..000000000 --- a/src/types/Storage/methods/updateTransaction.spec.js +++ /dev/null @@ -1,70 +0,0 @@ -const { expect } = require('chai'); -const updateTransaction = require('./updateTransaction'); -const orangeWStore = require('../../../../fixtures/walletStore').valid.orange.store; -const { Transaction } = require('@dashevo/dashcore-lib'); - -const tx = new Transaction({ - hash: 'ea9c4066394aa09cb7ee8f3997b8dc10b999a8d709c4046f81d8bf9341ae6e5b', - version: 3, - inputs: [ - { - prevTxId: '9f398515b6fc898ebf4e7b49bbfc4359b8c89f508c6cd677e53946bd86064b28', - outputIndex: 0, - sequenceNumber: 4294967295, - script: '47304402205bb4f7880fb0fc13218940ba341c30e817363e5590343d28639af921b2a5f1d40220010920ae4b00bbb657f8653cb44172b8cb13447bb5105ddaf32a2845ea0666b90121025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - scriptString: '71 0x304402205bb4f7880fb0fc13218940ba341c30e817363e5590343d28639af921b2a5f1d40220010920ae4b00bbb657f8653cb44172b8cb13447bb5105ddaf32a2845ea0666b901 33 0x025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - }, - { - prevTxId: 'b812d9345fa8ea06af1d19b935eec65824d53779db74cd325690ad1d38a82757', - outputIndex: 0, - sequenceNumber: 4294967295, - script: '483045022100ea2d17ffc417e1f70c9c9ae11b7d95a07ab359c1d9d634baba145bab7b1deb0802207507296e12acc83ce038e5bbd54c46fa78b9475536f64fb313fedb978d12b73b0121025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - scriptString: '72 0x3045022100ea2d17ffc417e1f70c9c9ae11b7d95a07ab359c1d9d634baba145bab7b1deb0802207507296e12acc83ce038e5bbd54c46fa78b9475536f64fb313fedb978d12b73b01 33 0x025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - }, - { - prevTxId: '370b7bbd5b6e0de42a95d59e3277041ac20e945ffb93f56bb6984ba42f28a2ac', - outputIndex: 0, - sequenceNumber: 4294967295, - script: '47304402207926bf9176bdc88f38dde2140b2b8b0e4f331f33bb48af12c1bcce5efbb2593c022073c188d2149d5a0bfe4adff82b63d0bc62e04f2769cdcfda50a2c5e34ab7cbf60121025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - scriptString: '71 0x304402207926bf9176bdc88f38dde2140b2b8b0e4f331f33bb48af12c1bcce5efbb2593c022073c188d2149d5a0bfe4adff82b63d0bc62e04f2769cdcfda50a2c5e34ab7cbf601 33 0x025ae98eff89505fa5ff60f919ae690de638d31f4f2fcab9a9deeaf4d48eda794b', - }, - ], - outputs: [ - { - satoshis: 12999997493, - script: '76a9143ec33076ba72b36b66b7ec571dd7417abdeb76f888ac', - }, - ], -}); -describe('Storage - updateTransaction',function suite() { - this.timeout(10000); - it('should throw on failed update', () => { - const exceptedException1 = 'Expected a transaction to update'; - const self = { - store: { - transactions: {}, - }, - }; - - expect(() => updateTransaction.call(self, null)).to.throw(exceptedException1); - }); - it('should work', () => { - const self = { - store: { - transactions: { - ea9c4066394aa09cb7ee8f3997b8dc10b999a8d709c4046f81d8bf9341ae6e5b: tx, - }, - }, - }; - const txObj = new Transaction(tx); - txObj.nLockTime = 0; - const expected = { - ea9c4066394aa09cb7ee8f3997b8dc10b999a8d709c4046f81d8bf9341ae6e5b: txObj, - }; - - - const update = updateTransaction.call(self, txObj); - expect(update).to.equal(true); - expect(self.store.transactions).to.deep.equal(expected); - }); -}); diff --git a/src/types/Wallet/Wallet.js b/src/types/Wallet/Wallet.js index bffff130b..2e11fb7ca 100644 --- a/src/types/Wallet/Wallet.js +++ b/src/types/Wallet/Wallet.js @@ -3,6 +3,8 @@ const { PrivateKey, Networks } = require('@dashevo/dashcore-lib'); const EventEmitter = require('events'); const _ = require('lodash'); const Storage = require('../Storage/Storage'); +const KeyChainStore = require('../KeyChainStore/KeyChainStore'); +const KeyChain = require('../KeyChain/KeyChain'); const { generateNewMnemonic, } = require('../../utils'); @@ -86,21 +88,21 @@ class Wallet extends EventEmitter { mnemonic = generateNewMnemonic(); createdFromNewMnemonic = true; } - this.fromMnemonic(mnemonic); + this.fromMnemonic(mnemonic, this.network, this.passphrase); } else if ('seed' in opts) { - this.fromSeed(opts.seed); + this.fromSeed(opts.seed, this.network); } else if ('HDPrivateKey' in opts) { this.fromHDPrivateKey(opts.HDPrivateKey); } else if ('privateKey' in opts) { this.fromPrivateKey((opts.privateKey === null) ? new PrivateKey(network).toString() - : opts.privateKey); + : opts.privateKey, this.network); } else if ('publicKey' in opts) { - this.fromPublicKey(opts.publicKey); + this.fromPublicKey(opts.publicKey, this.network); } else if ('HDPublicKey' in opts) { this.fromHDPublicKey(opts.HDPublicKey); } else if ('address' in opts) { - this.fromAddress(opts.address); + this.fromAddress(opts.address, this.network); } else { this.fromMnemonic(generateNewMnemonic()); createdFromNewMnemonic = true; @@ -112,21 +114,22 @@ class Wallet extends EventEmitter { this.storage = new Storage({ rehydrate: true, autosave: true, - network, }); this.storage.configure({ adapter: opts.adapter, }); - this.store = this.storage.store; + this.storage.application.network = this.network; + this.storage.createWalletStore(this.walletId); + this.storage.createChainStore(this.network); if (createdFromNewMnemonic) { // As it is pretty complicated to pass any of wallet options // to a specific plugin, using `store` as an options mediator // is easier. - this.store.syncOptions = { + this.storage.application.syncOptions = { skipSynchronization: true, }; @@ -135,7 +138,7 @@ class Wallet extends EventEmitter { + ' created from the new mnemonic'); } } else if (this.unsafeOptions.skipSynchronizationBeforeHeight) { - this.store.syncOptions = { + this.storage.application.syncOptions = { skipSynchronizationBeforeHeight: this.unsafeOptions.skipSynchronizationBeforeHeight, }; } @@ -173,7 +176,6 @@ class Wallet extends EventEmitter { this.accounts = []; this.interface = opts.interface; - // Suppressed global require to avoid cyclic dependencies // eslint-disable-next-line global-require const Identities = require('../Identities/Identities'); diff --git a/src/types/Wallet/Wallet.spec.js b/src/types/Wallet/Wallet.spec.js index 180f5fc18..a6d416bfb 100644 --- a/src/types/Wallet/Wallet.spec.js +++ b/src/types/Wallet/Wallet.spec.js @@ -23,7 +23,7 @@ describe('Wallet - class', function suite() { expect(wallet1.plugins).to.be.deep.equal({}); expect(wallet1.accounts).to.be.deep.equal([]); - expect(wallet1.keyChain.type).to.be.deep.equal('HDPrivateKey'); + expect(wallet1.keyChainStore.getMasterKeyChain().rootKeyType).to.be.deep.equal('HDPrivateKey'); expect(wallet1.passphrase).to.be.deep.equal(null); expect(wallet1.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet1.injectDefaultPlugins).to.be.deep.equal(true); @@ -50,7 +50,7 @@ describe('Wallet - class', function suite() { expect(wallet1.plugins).to.be.deep.equal({}); expect(wallet1.accounts).to.be.deep.equal([]); expect(wallet1.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); - expect(wallet1.keyChain.type).to.be.deep.equal('HDPrivateKey'); + expect(wallet1.keyChainStore.getMasterKeyChain().rootKeyType).to.be.deep.equal('HDPrivateKey'); expect(wallet1.passphrase).to.be.deep.equal(null); expect(wallet1.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet1.injectDefaultPlugins).to.be.deep.equal(true); @@ -77,7 +77,7 @@ describe('Wallet - class', function suite() { expect(wallet1.plugins).to.be.deep.equal({}); expect(wallet1.accounts).to.be.deep.equal([]); expect(wallet1.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); - expect(wallet1.keyChain.type).to.be.deep.equal('HDPrivateKey'); + expect(wallet1.keyChainStore.getMasterKeyChain().rootKeyType).to.be.deep.equal('HDPrivateKey'); expect(wallet1.passphrase).to.be.deep.equal(null); expect(wallet1.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet1.injectDefaultPlugins).to.be.deep.equal(true); @@ -95,7 +95,7 @@ describe('Wallet - class', function suite() { expect(wallet1.plugins).to.be.deep.equal({}); expect(wallet1.accounts).to.be.deep.equal([]); expect(wallet1.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); - expect(wallet1.keyChain.type).to.be.deep.equal('HDPublicKey'); + expect(wallet1.keyChainStore.getMasterKeyChain().rootKeyType).to.be.deep.equal('HDPublicKey'); expect(wallet1.passphrase).to.be.deep.equal(null); expect(wallet1.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet1.injectDefaultPlugins).to.be.deep.equal(true); @@ -112,7 +112,7 @@ describe('Wallet - class', function suite() { expect(wallet1.plugins).to.be.deep.equal({}); expect(wallet1.accounts).to.be.deep.equal([]); expect(wallet1.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); - expect(wallet1.keyChain.type).to.be.deep.equal('privateKey'); + expect(wallet1.keyChainStore.getMasterKeyChain().rootKeyType).to.be.deep.equal('privateKey'); expect(wallet1.passphrase).to.be.deep.equal(null); expect(wallet1.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet1.injectDefaultPlugins).to.be.deep.equal(true); @@ -132,7 +132,7 @@ describe('Wallet - class', function suite() { expect(wallet1.plugins).to.be.deep.equal({}); expect(wallet1.accounts).to.be.deep.equal([]); expect(wallet1.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); - expect(wallet1.keyChain.type).to.be.deep.equal('publicKey'); + expect(wallet1.keyChainStore.getMasterKeyChain().rootKeyType).to.be.deep.equal('publicKey'); expect(wallet1.passphrase).to.be.deep.equal(null); expect(wallet1.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet1.injectDefaultPlugins).to.be.deep.equal(true); @@ -149,7 +149,7 @@ describe('Wallet - class', function suite() { expect(wallet2.plugins).to.be.deep.equal({}); expect(wallet2.accounts).to.be.deep.equal([]); expect(wallet2.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); - expect(wallet2.keyChain.type).to.be.deep.equal('publicKey'); + expect(wallet2.keyChainStore.getMasterKeyChain().rootKeyType).to.be.deep.equal('publicKey'); expect(wallet2.passphrase).to.be.deep.equal(null); expect(wallet2.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet2.injectDefaultPlugins).to.be.deep.equal(true); @@ -246,6 +246,5 @@ describe('Wallet - Get/Create Account', function suite() { it('should not leak', () => { const mockOpts1 = { }; fromHDPublicKey.call(mockOpts1, gatherSail.testnet.external.hdpubkey); - expect(mockOpts1.keyChain.keys).to.deep.equal({}); }); }); diff --git a/src/types/Wallet/methods/exportWallet.js b/src/types/Wallet/methods/exportWallet.js index f64c4afec..39cc12bc4 100644 --- a/src/types/Wallet/methods/exportWallet.js +++ b/src/types/Wallet/methods/exportWallet.js @@ -25,7 +25,7 @@ function exportAddressWallet(outputType = 'address') { } } -function exportSingleAddressWallet(outputType = 'privateKey') { +function exportPrivateKeyWallet(outputType = 'privateKey') { switch (outputType) { case 'privateKey': if (!this.privateKey) throw new Error('No PrivateKey to export'); @@ -81,7 +81,7 @@ module.exports = function exportWallet(outputType) { switch (this.walletType) { case WALLET_TYPES.PRIVATEKEY: case WALLET_TYPES.SINGLE_ADDRESS: - return exportSingleAddressWallet.call(this, outputType); + return exportPrivateKeyWallet.call(this, outputType); case WALLET_TYPES.ADDRESS: return exportAddressWallet.call(this, outputType); case WALLET_TYPES.PUBLICKEY: diff --git a/src/types/Wallet/methods/exportWallet.spec.js b/src/types/Wallet/methods/exportWallet.spec.js index d7396d6f2..b0af3f6c3 100644 --- a/src/types/Wallet/methods/exportWallet.spec.js +++ b/src/types/Wallet/methods/exportWallet.spec.js @@ -11,9 +11,9 @@ const cR4t6ePublicKey = new PrivateKey(cR4t6eFixture.privateKey).toPublicKey(); describe('Wallet - export Wallet', function suite() { this.timeout(10000); it('should indicate on missing data', () => { - const mockOpts1 = {}; - const mockOpts2 = { walletType: WALLET_TYPES.SINGLE_ADDRESS }; - const mockOpts3 = { walletType: WALLET_TYPES.HDWALLET }; + const mockOpts1 = { }; + const mockOpts2 = { walletType: WALLET_TYPES.PRIVATEKEY }; + const mockOpts3 = { walletType: WALLET_TYPES.HDWALLET }; const exceptedException1 = 'Trying to export from an unknown wallet type'; const exceptedException2 = 'No PrivateKey to export'; diff --git a/src/types/Wallet/methods/fromAddress.js b/src/types/Wallet/methods/fromAddress.js index c69c7749b..2397a91aa 100644 --- a/src/types/Wallet/methods/fromAddress.js +++ b/src/types/Wallet/methods/fromAddress.js @@ -1,14 +1,18 @@ const { is } = require('../../../utils'); const KeyChain = require('../../KeyChain/KeyChain'); const { WALLET_TYPES } = require('../../../CONSTANTS'); +const KeyChainStore = require('../../KeyChainStore/KeyChainStore'); /** * @param address */ -module.exports = function fromAddress(address) { +module.exports = function fromAddress(address, network) { if (!is.address(address)) throw new Error('Expected a valid address (typeof Address or String)'); this.walletType = WALLET_TYPES.ADDRESS; this.mnemonic = null; this.address = address.toString(); - this.keyChain = new KeyChain({ address }); + + const keyChain = new KeyChain({ address, network }); + this.keyChainStore = new KeyChainStore(); + this.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); }; diff --git a/src/types/Wallet/methods/fromAddress.spec.js b/src/types/Wallet/methods/fromAddress.spec.js index 44bd996a4..f0839308f 100644 --- a/src/types/Wallet/methods/fromAddress.spec.js +++ b/src/types/Wallet/methods/fromAddress.spec.js @@ -18,18 +18,20 @@ describe('Wallet - fromAddress', function suite() { expect(self1.walletType).to.equal(WALLET_TYPES.ADDRESS); expect(self1.mnemonic).to.equal(null); expect(self1.address).to.equal(cR4t6ePublicKey.toAddress().toString()); - expect(self1.keyChain.type).to.equal('address'); - expect(self1.keyChain.address).to.equal(cR4t6ePublicKey.toAddress().toString()); - expect(self1.keyChain.keys).to.deep.equal({}); + + const keyChain = self1.keyChainStore.getMasterKeyChain() + expect(keyChain.rootKeyType).to.equal('address'); + expect(keyChain.rootKey).to.equal(cR4t6ePublicKey.toAddress().toString()); const self2 = {}; fromAddress.call(self2, cR4t6ePublicKey.toAddress().toString()); expect(self2.walletType).to.equal(WALLET_TYPES.ADDRESS); expect(self2.mnemonic).to.equal(null); expect(self2.address).to.equal(cR4t6ePublicKey.toAddress().toString()); - expect(self2.keyChain.type).to.equal('address'); - expect(self2.keyChain.address).to.equal(cR4t6ePublicKey.toAddress().toString()); - expect(self2.keyChain.keys).to.deep.equal({}); + + const keyChain2 = self2.keyChainStore.getMasterKeyChain() + expect(keyChain.rootKeyType).to.equal('address'); + expect(keyChain.rootKey).to.equal(cR4t6ePublicKey.toAddress().toString()); }); it('should reject invalid mnemonic', () => { const invalidInputs = [ diff --git a/src/types/Wallet/methods/fromHDPrivateKey.js b/src/types/Wallet/methods/fromHDPrivateKey.js index da2aba6e0..90b6f1a90 100644 --- a/src/types/Wallet/methods/fromHDPrivateKey.js +++ b/src/types/Wallet/methods/fromHDPrivateKey.js @@ -3,6 +3,7 @@ const { is, } = require('../../../utils'); const KeyChain = require('../../KeyChain/KeyChain'); +const KeyChainStore = require('../../KeyChainStore/KeyChainStore'); const { WALLET_TYPES } = require('../../../CONSTANTS'); /** @@ -14,5 +15,8 @@ module.exports = function fromHDPrivateKey(hdPrivateKey) { this.walletType = WALLET_TYPES.HDWALLET; this.mnemonic = null; this.HDPrivateKey = HDPrivateKey(hdPrivateKey); - this.keyChain = new KeyChain({ HDPrivateKey: hdPrivateKey }); + + const keyChain = new KeyChain({ HDPrivateKey: this.HDPrivateKey }); + this.keyChainStore = new KeyChainStore(); + this.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); }; diff --git a/src/types/Wallet/methods/fromHDPrivateKey.spec.js b/src/types/Wallet/methods/fromHDPrivateKey.spec.js index 5c2774831..c947a076b 100644 --- a/src/types/Wallet/methods/fromHDPrivateKey.spec.js +++ b/src/types/Wallet/methods/fromHDPrivateKey.spec.js @@ -16,9 +16,10 @@ describe('Wallet - fromHDPrivateKey', function suite() { expect(self1.walletType).to.equal(WALLET_TYPES.HDWALLET); expect(self1.mnemonic).to.equal(null); expect(self1.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); - expect(self1.keyChain.type).to.equal('HDPrivateKey'); - expect(self1.keyChain.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); - expect(self1.keyChain.keys).to.deep.equal({}); + + const keyChain = self1.keyChainStore.getMasterKeyChain() + expect(keyChain.rootKeyType).to.equal('HDPrivateKey'); + expect(keyChain.rootKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); }); it('should reject invalid mnemonic', () => { const invalidInputs = [ diff --git a/src/types/Wallet/methods/fromHDPublicKey.js b/src/types/Wallet/methods/fromHDPublicKey.js index 0631c0511..aa6a84078 100644 --- a/src/types/Wallet/methods/fromHDPublicKey.js +++ b/src/types/Wallet/methods/fromHDPublicKey.js @@ -2,6 +2,7 @@ const Dashcore = require('@dashevo/dashcore-lib'); const { is } = require('../../../utils'); const KeyChain = require('../../KeyChain/KeyChain'); const { WALLET_TYPES } = require('../../../CONSTANTS'); +const KeyChainStore = require('../../KeyChainStore/KeyChainStore'); const normalizeHDPubKey = (key) => (is.string(key) ? Dashcore.HDPublicKey(key) : key); /** @@ -13,5 +14,8 @@ module.exports = function fromHDPublicKey(_hdPublicKey) { this.walletType = WALLET_TYPES.HDPUBLIC; this.mnemonic = null; this.HDPublicKey = normalizeHDPubKey(_hdPublicKey); - this.keyChain = new KeyChain({ HDPublicKey: this.HDPublicKey }); + + const keyChain = new KeyChain({ HDPublicKey: this.HDPublicKey }); + this.keyChainStore = new KeyChainStore(); + this.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); }; diff --git a/src/types/Wallet/methods/fromHDPublicKey.spec.js b/src/types/Wallet/methods/fromHDPublicKey.spec.js index 96a1c9cf8..fca3387d3 100644 --- a/src/types/Wallet/methods/fromHDPublicKey.spec.js +++ b/src/types/Wallet/methods/fromHDPublicKey.spec.js @@ -30,9 +30,10 @@ describe('Wallet - HDPublicKey', function suite() { expect(mockOpts1.mnemonic).to.equal(null); expect(mockOpts1.HDPublicKey.toString()).to.equal(gatherTestnet.external.hdpubkey); expect(new Dashcore.HDPublicKey(mockOpts1.HDPublicKey)).to.equal(mockOpts1.HDPublicKey); - expect(mockOpts1.keyChain.type).to.equal('HDPublicKey'); - expect(mockOpts1.keyChain.HDPublicKey).to.deep.equal(Dashcore.HDPublicKey(gatherTestnet.external.hdpubkey)); - expect(mockOpts1.keyChain.keys).to.deep.equal({}); + + const keyChain = mockOpts1.keyChainStore.getMasterKeyChain() + expect(keyChain.rootKeyType).to.equal('HDPublicKey'); + expect(keyChain.rootKey).to.deep.equal(Dashcore.HDPublicKey(gatherTestnet.external.hdpubkey)); }); it('should work from a HDPubKey', () => { const wallet1 = new Wallet( @@ -45,7 +46,9 @@ describe('Wallet - HDPublicKey', function suite() { expect(wallet1.plugins).to.be.deep.equal({}); expect(wallet1.accounts).to.be.deep.equal([]); expect(wallet1.network).to.be.deep.equal(Dashcore.Networks.testnet.toString()); - expect(wallet1.keyChain.type).to.be.deep.equal('HDPublicKey'); + + const keyChain = wallet1.keyChainStore.getMasterKeyChain() + expect(keyChain.rootKeyType).to.be.deep.equal('HDPublicKey'); expect(wallet1.passphrase).to.be.deep.equal(null); expect(wallet1.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet1.injectDefaultPlugins).to.be.deep.equal(true); @@ -54,22 +57,28 @@ describe('Wallet - HDPublicKey', function suite() { expect(wallet1.exportWallet()).to.be.equal(gatherTestnet.external.hdpubkey); - wallet1.getAccount().then((account)=>{ - const unusedAddress = account.getUnusedAddress(); - const expectedUnused = { - path: "m/44'/1'/0'/0/0", - index: 0, - address: 'yNJ3xxTXXBBf39VfMBbBuLH2k57uAwxBxj', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false, - }; - expect(unusedAddress).to.deep.equal(expectedUnused); + // FIXME: it appears we had introduced a bug here, + // as it is not possible to have a HDPublicKey derivation with hardened + // Either our path is m/44/1/0/0/0 or it is m/0/0. + // We should clarify this before merging TODO + wallet1 + .getAccount() + .then((account)=>{ + const unusedAddress = account.getUnusedAddress(); + const expectedUnused = { + path: "m/0/0", + index: 0, + address: 'yNJ3xxTXXBBf39VfMBbBuLH2k57uAwxBxj', + transactions: [], + balanceSat: 0, + unconfirmedBalanceSat: 0, + utxos: {}, + fetchedLast: 0, + used: false, + }; + expect(unusedAddress).to.deep.equal(expectedUnused); - wallet1.disconnect(); - }) + wallet1.disconnect(); + }) }); }); diff --git a/src/types/Wallet/methods/fromMnemonic.js b/src/types/Wallet/methods/fromMnemonic.js index 4bdeaefef..1e2cbdda7 100644 --- a/src/types/Wallet/methods/fromMnemonic.js +++ b/src/types/Wallet/methods/fromMnemonic.js @@ -3,19 +3,25 @@ const { is, } = require('../../../utils'); const KeyChain = require('../../KeyChain/KeyChain'); +const KeyChainStore = require('../../KeyChainStore/KeyChainStore'); const { WALLET_TYPES } = require('../../../CONSTANTS'); /** * Will set a wallet to work with a mnemonic (keychain, walletType & HDPrivateKey) * @param mnemonic */ -module.exports = function fromMnemonic(mnemonic) { +module.exports = function fromMnemonic(mnemonic, network, passphrase = '') { if (!is.mnemonic(mnemonic)) { throw new Error('Expected a valid mnemonic (typeof String or Mnemonic)'); } const trimmedMnemonic = mnemonic.toString().trim(); this.walletType = WALLET_TYPES.HDWALLET; - this.mnemonic = trimmedMnemonic; // todo : What about without this ? - this.HDPrivateKey = mnemonicToHDPrivateKey(trimmedMnemonic, this.network, this.passphrase); - this.keyChain = new KeyChain({ HDPrivateKey: this.HDPrivateKey }); + // As we do not require the mnemonic except in this.exportWallet + // users of wallet-lib are free to clear this prop at anytime. + this.mnemonic = trimmedMnemonic; + this.HDPrivateKey = mnemonicToHDPrivateKey(trimmedMnemonic, network, passphrase); + + this.keyChainStore = new KeyChainStore(); + const keyChain = new KeyChain({ HDPrivateKey: this.HDPrivateKey }); + this.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); }; diff --git a/src/types/Wallet/methods/fromMnemonic.spec.js b/src/types/Wallet/methods/fromMnemonic.spec.js index 14e810286..85f498c61 100644 --- a/src/types/Wallet/methods/fromMnemonic.spec.js +++ b/src/types/Wallet/methods/fromMnemonic.spec.js @@ -15,27 +15,29 @@ describe('Wallet - fromMnemonic', function suite() { const self1 = { network: 'livenet', }; - fromMnemonic.call(self1, knifeFixture.mnemonic); + fromMnemonic.call(self1, knifeFixture.mnemonic, 'livenet'); expect(self1.walletType).to.equal(WALLET_TYPES.HDWALLET); expect(self1.mnemonic).to.equal(knifeFixture.mnemonic); expect(self1.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); expect(new Dashcore.HDPrivateKey(self1.HDPrivateKey)).to.equal(self1.HDPrivateKey); - expect(self1.keyChain.type).to.equal('HDPrivateKey'); - expect(self1.keyChain.network.name).to.equal('livenet'); - expect(self1.keyChain.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); - expect(self1.keyChain.keys).to.deep.equal({}); + + const keyChain = self1.keyChainStore.getMasterKeyChain() + expect(keyChain.rootKeyType).to.equal('HDPrivateKey'); + expect(keyChain.network).to.equal('livenet'); + expect(keyChain.rootKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); const self2 = {}; fromMnemonic.call(self2, knifeFixture.mnemonic); expect(self2.walletType).to.equal(WALLET_TYPES.HDWALLET); expect(self2.mnemonic).to.equal(knifeFixture.mnemonic); - expect(self2.keyChain.network.name).to.equal('testnet'); + + const keyChain2 = self2.keyChainStore.getMasterKeyChain() + expect(keyChain2.network).to.equal('testnet'); expect(self2.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyTestnet); expect(new Dashcore.HDPrivateKey(self2.HDPrivateKey)).to.equal(self2.HDPrivateKey); - expect(self2.keyChain.type).to.equal('HDPrivateKey'); - expect(self2.keyChain.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyTestnet); - expect(self2.keyChain.keys).to.deep.equal({}); + expect(keyChain2.rootKeyType).to.equal('HDPrivateKey'); + expect(keyChain2.rootKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyTestnet); }); it('should reject invalid mnemonic', () => { const invalidInputs = [ @@ -55,38 +57,35 @@ describe('Wallet - fromMnemonic - with passphrase', function suite() { this.timeout(10000); it('should correctly works with passphrase', () => { const self1 = { - network: 'livenet', - passphrase: knifeFixture.passphrase, }; - fromMnemonic.call(self1, knifeFixture.mnemonic); + fromMnemonic.call(self1, knifeFixture.mnemonic, 'livenet', knifeFixture.passphrase); expect(self1.walletType).to.equal(WALLET_TYPES.HDWALLET); expect(self1.mnemonic).to.equal(knifeFixture.mnemonic); expect(self1.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootEncryptedPrivateKeyMainnet); expect(new Dashcore.HDPrivateKey(self1.HDPrivateKey)).to.equal(self1.HDPrivateKey); - expect(self1.keyChain.type).to.equal('HDPrivateKey'); - expect(self1.keyChain.network.name).to.equal('livenet'); - expect(self1.keyChain.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootEncryptedPrivateKeyMainnet); - expect(self1.keyChain.keys).to.deep.equal({}); + const keyChain = self1.keyChainStore.getMasterKeyChain() + expect(keyChain.rootKeyType).to.equal('HDPrivateKey'); + expect(keyChain.network).to.equal('livenet'); + expect(keyChain.rootKey.toString()).to.equal(knifeFixture.HDRootEncryptedPrivateKeyMainnet); const path1 = 'm/44\'/5\'/0\'/0/0'; - const pubKey1 = self1.keyChain.getKeyForPath(path1).publicKey.toAddress(); + const pubKey1 = keyChain.getForPath(path1).key.publicKey.toAddress(); expect(new Dashcore.Address(pubKey1).toString()).to.equal('Xq3zjky18WjwAHpLgGLasvX5g8TeLRKaxt'); const self2 = { - passphrase: knifeFixture.passphrase, }; - fromMnemonic.call(self2, knifeFixture.mnemonic); + fromMnemonic.call(self2, knifeFixture.mnemonic, 'testnet', knifeFixture.passphrase); expect(self2.walletType).to.equal(WALLET_TYPES.HDWALLET); expect(self2.mnemonic).to.equal(knifeFixture.mnemonic); expect(self2.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootEncryptedPrivateKeyTestnet); expect(new Dashcore.HDPrivateKey(self2.HDPrivateKey)).to.equal(self2.HDPrivateKey); - expect(self2.keyChain.type).to.equal('HDPrivateKey'); - expect(self2.keyChain.network.name).to.equal('testnet'); - expect(self2.keyChain.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootEncryptedPrivateKeyTestnet); - expect(self2.keyChain.keys).to.deep.equal({}); + const keyChain2 = self2.keyChainStore.getMasterKeyChain() + expect(keyChain2.rootKeyType).to.equal('HDPrivateKey'); + expect(keyChain2.network).to.equal('testnet'); + expect(keyChain2.rootKey.toString()).to.equal(knifeFixture.HDRootEncryptedPrivateKeyTestnet); const path2 = 'm/44\'/1\'/0\'/0/0'; - const pubKey2 = self2.keyChain.getKeyForPath(path2).publicKey.toAddress(); + const pubKey2 = keyChain2.getForPath(path2).key.publicKey.toAddress(); expect(new Dashcore.Address(pubKey2, 'testnet').toString()).to.equal('yWYCH9XDRnpdNxh67jQJFkovToBVwWr8Ck'); }); }); diff --git a/src/types/Wallet/methods/fromPrivateKey.js b/src/types/Wallet/methods/fromPrivateKey.js index 2f8f919bc..870af9937 100644 --- a/src/types/Wallet/methods/fromPrivateKey.js +++ b/src/types/Wallet/methods/fromPrivateKey.js @@ -1,15 +1,19 @@ const { is } = require('../../../utils'); const KeyChain = require('../../KeyChain/KeyChain'); const { WALLET_TYPES } = require('../../../CONSTANTS'); +const KeyChainStore = require('../../KeyChainStore/KeyChainStore'); /** * Will set a wallet to work with a mnemonic (keychain, walletType & HDPrivateKey) * @param privateKey */ -module.exports = function fromPrivateKey(privateKey) { +module.exports = function fromPrivateKey(privateKey, network) { if (!is.privateKey(privateKey)) throw new Error('Expected a valid private key (typeof PrivateKey or String)'); this.walletType = WALLET_TYPES.PRIVATEKEY; this.mnemonic = null; this.privateKey = privateKey; - this.keyChain = new KeyChain({ privateKey }); + + const keyChain = new KeyChain({ privateKey, network }); + this.keyChainStore = new KeyChainStore(); + this.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); }; diff --git a/src/types/Wallet/methods/fromPrivateKey.spec.js b/src/types/Wallet/methods/fromPrivateKey.spec.js index c707c2fad..4b6ca5e89 100644 --- a/src/types/Wallet/methods/fromPrivateKey.spec.js +++ b/src/types/Wallet/methods/fromPrivateKey.spec.js @@ -16,18 +16,18 @@ describe('Wallet - fromPrivateKey', function suite() { expect(self1.walletType).to.equal(WALLET_TYPES.PRIVATEKEY); expect(self1.mnemonic).to.equal(null); expect(self1.privateKey).to.equal(cR4t6eFixture.privateKey); - expect(self1.keyChain.type).to.equal('privateKey'); - expect(self1.keyChain.privateKey).to.equal(cR4t6eFixture.privateKey); - expect(self1.keyChain.keys).to.deep.equal({}); + const keyChain = self1.keyChainStore.getMasterKeyChain() + expect(keyChain.rootKeyType).to.equal('privateKey'); + expect(keyChain.rootKey.toWIF()).to.equal(cR4t6eFixture.privateKey); const self2 = {}; fromPrivateKey.call(self2, cR4t6eFixture.privateKey); expect(self2.walletType).to.equal(WALLET_TYPES.PRIVATEKEY); expect(self2.mnemonic).to.equal(null); expect(self2.privateKey).to.equal(cR4t6eFixture.privateKey); - expect(self2.keyChain.type).to.equal('privateKey'); - expect(self2.keyChain.privateKey).to.equal(cR4t6eFixture.privateKey); - expect(self2.keyChain.keys).to.deep.equal({}); + const keyChain2 = self2.keyChainStore.getMasterKeyChain() + expect(keyChain2.rootKeyType).to.equal('privateKey'); + expect(keyChain2.rootKey.toWIF()).to.equal(cR4t6eFixture.privateKey); }); it('should reject invalid mnemonic', () => { const invalidInputs = [ diff --git a/src/types/Wallet/methods/fromPublicKey.js b/src/types/Wallet/methods/fromPublicKey.js index 9e6867ac7..ffbf41344 100644 --- a/src/types/Wallet/methods/fromPublicKey.js +++ b/src/types/Wallet/methods/fromPublicKey.js @@ -1,15 +1,19 @@ const { is } = require('../../../utils'); const KeyChain = require('../../KeyChain/KeyChain'); const { WALLET_TYPES } = require('../../../CONSTANTS'); +const KeyChainStore = require('../../KeyChainStore/KeyChainStore'); /** * Will set a wallet to work with a mnemonic (keychain, walletType & HDPrivateKey) * @param privateKey */ -module.exports = function fromPublicKey(publicKey) { +module.exports = function fromPublicKey(publicKey, network) { if (!is.publicKey(publicKey)) throw new Error('Expected a valid public key (typeof PublicKey or String)'); this.walletType = WALLET_TYPES.PUBLICKEY; this.mnemonic = null; this.publicKey = publicKey; - this.keyChain = new KeyChain({ publicKey }); + + const keyChain = new KeyChain({ publicKey, network }); + this.keyChainStore = new KeyChainStore(); + this.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); }; diff --git a/src/types/Wallet/methods/fromPublicKey.spec.js b/src/types/Wallet/methods/fromPublicKey.spec.js index 8852ee6c6..07890f2d4 100644 --- a/src/types/Wallet/methods/fromPublicKey.spec.js +++ b/src/types/Wallet/methods/fromPublicKey.spec.js @@ -18,18 +18,18 @@ describe('Wallet - fromPublicKey', function suite() { expect(self1.walletType).to.equal(WALLET_TYPES.PUBLICKEY); expect(self1.mnemonic).to.equal(null); expect(self1.publicKey).to.equal(cR4t6ePublicKey); - expect(self1.keyChain.type).to.equal('publicKey'); - expect(self1.keyChain.publicKey).to.equal(cR4t6ePublicKey); - expect(self1.keyChain.keys).to.deep.equal({}); + const keyChain = self1.keyChainStore.getMasterKeyChain() + expect(keyChain.rootKeyType).to.equal('publicKey'); + expect(keyChain.rootKey.toString()).to.equal(cR4t6ePublicKey.toString()); const self2 = {}; fromPublicKey.call(self2, cR4t6ePublicKey.toString()); expect(self2.walletType).to.equal(WALLET_TYPES.PUBLICKEY); expect(self2.mnemonic).to.equal(null); expect(self2.publicKey).to.equal(cR4t6ePublicKey.toString()); - expect(self2.keyChain.type).to.equal('publicKey'); - expect(self2.keyChain.publicKey).to.equal(cR4t6ePublicKey.toString()); - expect(self2.keyChain.keys).to.deep.equal({}); + const keyChain2 = self2.keyChainStore.getMasterKeyChain() + expect(keyChain2.rootKeyType).to.equal('publicKey'); + expect(keyChain2.rootKey.toString()).to.equal(cR4t6ePublicKey.toString()); }); it('should reject invalid mnemonic', () => { const invalidInputs = [ diff --git a/src/types/Wallet/methods/fromSeed.js b/src/types/Wallet/methods/fromSeed.js index 978adc9ec..038fc138e 100644 --- a/src/types/Wallet/methods/fromSeed.js +++ b/src/types/Wallet/methods/fromSeed.js @@ -8,7 +8,7 @@ const { * fixme: Term seed is often use, but we might want to rename to fromHDPrivateKey * @param seed */ -module.exports = function fromSeed(seed) { +module.exports = function fromSeed(seed, network) { if (!is.seed(seed)) throw new Error('Expected a valid seed (typeof string)'); - return this.fromHDPrivateKey(seedToHDPrivateKey(seed, this.network)); + return this.fromHDPrivateKey(seedToHDPrivateKey(seed, network)); }; diff --git a/src/types/Wallet/methods/fromSeed.spec.js b/src/types/Wallet/methods/fromSeed.spec.js index 75e9d32cd..a0a9ec827 100644 --- a/src/types/Wallet/methods/fromSeed.spec.js +++ b/src/types/Wallet/methods/fromSeed.spec.js @@ -19,22 +19,22 @@ describe('Wallet - fromSeed', function suite() { expect(self1.walletType).to.equal(WALLET_TYPES.HDWALLET); expect(self1.mnemonic).to.equal(null); expect(self1.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyTestnet); - expect(self1.keyChain.type).to.equal('HDPrivateKey'); - expect(self1.keyChain.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyTestnet); - expect(self1.keyChain.keys).to.deep.equal({}); + const keyChain = self1.keyChainStore.getMasterKeyChain() + expect(keyChain.rootKeyType).to.equal('HDPrivateKey'); + expect(keyChain.rootKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyTestnet); const self2 = { fromHDPrivateKey, network: 'mainnet', }; - fromSeed.call(self2, knifeFixture.seed); + fromSeed.call(self2, knifeFixture.seed, self2.network); expect(self2.walletType).to.equal(WALLET_TYPES.HDWALLET); expect(self2.mnemonic).to.equal(null); expect(self2.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); - expect(self2.keyChain.type).to.equal('HDPrivateKey'); - expect(self2.keyChain.HDPrivateKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); - expect(self2.keyChain.keys).to.deep.equal({}); + const keyChain2 = self2.keyChainStore.getMasterKeyChain() + expect(keyChain2.rootKeyType).to.equal('HDPrivateKey'); + expect(keyChain2.rootKey.toString()).to.equal(knifeFixture.HDRootPrivateKeyMainnet); }); it('should reject invalid mnemonic', () => { const invalidInputs = [ diff --git a/src/types/WalletStore/WalletStore.js b/src/types/WalletStore/WalletStore.js new file mode 100644 index 000000000..b105d6792 --- /dev/null +++ b/src/types/WalletStore/WalletStore.js @@ -0,0 +1,21 @@ +class WalletStore { + constructor(walletId) { + this.walletId = walletId; + + this.state = { + mnemonic: null, + paths: new Map(), + identities: new Map(), + }; + } +} + +WalletStore.prototype.createPathState = require('./methods/createPathState'); +WalletStore.prototype.exportState = require('./methods/exportState'); +WalletStore.prototype.getIdentityIdByIndex = require('./methods/getIdentityIdByIndex'); +WalletStore.prototype.getIndexedIdentityIds = require('./methods/getIndexedIdentityIds'); +WalletStore.prototype.getPathState = require('./methods/getPathState'); +WalletStore.prototype.importState = require('./methods/importState'); +WalletStore.prototype.insertIdentityIdAtIndex = require('./methods/insertIdentityIdAtIndex'); + +module.exports = WalletStore; diff --git a/src/types/WalletStore/methods/createPathState.js b/src/types/WalletStore/methods/createPathState.js new file mode 100644 index 000000000..2dd215942 --- /dev/null +++ b/src/types/WalletStore/methods/createPathState.js @@ -0,0 +1,12 @@ +const logger = require('../../../logger'); + +function createPathState(path) { + logger.debug(`WalletStore - Creating path state ${path}`); + if (!this.state.paths.has(path)) { + this.state.paths.set(path, { + path, + addresses: {}, + }); + } +} +module.exports = createPathState; diff --git a/src/types/WalletStore/methods/exportState.js b/src/types/WalletStore/methods/exportState.js new file mode 100644 index 000000000..03b37ea66 --- /dev/null +++ b/src/types/WalletStore/methods/exportState.js @@ -0,0 +1,16 @@ +const { cloneDeep } = require('lodash'); + +function exportState() { + const { walletId } = this; + const { mnemonic, paths, identities } = this.state; + + return { + walletId, + state: { + mnemonic, + paths: cloneDeep(Object.fromEntries(paths)), + identities: cloneDeep(Object.fromEntries(identities)), + }, + }; +} +module.exports = exportState; diff --git a/src/types/WalletStore/methods/getIdentityIdByIndex.js b/src/types/WalletStore/methods/getIdentityIdByIndex.js new file mode 100644 index 000000000..f90d1e578 --- /dev/null +++ b/src/types/WalletStore/methods/getIdentityIdByIndex.js @@ -0,0 +1,4 @@ +function getIdentityIdByIndex(identityIndex) { + return this.state.identities.get(identityIndex); +} +module.exports = getIdentityIdByIndex; diff --git a/src/types/WalletStore/methods/getIndexedIdentityIds.js b/src/types/WalletStore/methods/getIndexedIdentityIds.js new file mode 100644 index 000000000..e251ba480 --- /dev/null +++ b/src/types/WalletStore/methods/getIndexedIdentityIds.js @@ -0,0 +1,4 @@ +function getIndexedIdentityIds() { + return [...this.state.identities.values()]; +} +module.exports = getIndexedIdentityIds; diff --git a/src/types/WalletStore/methods/getPathState.js b/src/types/WalletStore/methods/getPathState.js new file mode 100644 index 000000000..d2143cd4d --- /dev/null +++ b/src/types/WalletStore/methods/getPathState.js @@ -0,0 +1,4 @@ +function getPathState(path) { + return this.state.paths.get(path); +} +module.exports = getPathState; diff --git a/src/types/WalletStore/methods/importState.js b/src/types/WalletStore/methods/importState.js new file mode 100644 index 000000000..e4eb70910 --- /dev/null +++ b/src/types/WalletStore/methods/importState.js @@ -0,0 +1,9 @@ +const { cloneDeep } = require('lodash'); + +function importState(state) { + this.walletId = state.walletId; + this.state.mnemonic = state.state.mnemonic; + this.state.paths = new Map(cloneDeep(Object.entries(state.state.paths))); + this.state.identities = new Map(cloneDeep(Object.entries(state.state.identities))); +} +module.exports = importState; diff --git a/src/types/WalletStore/methods/insertIdentityIdAtIndex.js b/src/types/WalletStore/methods/insertIdentityIdAtIndex.js new file mode 100644 index 000000000..ee8a93251 --- /dev/null +++ b/src/types/WalletStore/methods/insertIdentityIdAtIndex.js @@ -0,0 +1,12 @@ +const IdentityReplaceError = require('../../../errors/IndentityIdReplaceError'); + +function insertIdentityIdAtIndex(identityId, identityIndex) { + const existingId = this.getIdentityIdByIndex(identityIndex); + + if (Boolean(existingId) && existingId !== identityId) { + throw new IdentityReplaceError(`Trying to replace identity at index ${identityIndex}`); + } + + this.state.identities.set(identityIndex, identityId); +} +module.exports = insertIdentityIdAtIndex; diff --git a/src/types/types.d.ts b/src/types/types.d.ts index 103a3e8fa..1673d707e 100644 --- a/src/types/types.d.ts +++ b/src/types/types.d.ts @@ -91,13 +91,13 @@ export declare type broadcastTransactionOpts = T & { export declare type AddressInfo = T & { path: string; address: string; - balanceSat: number; index: number; - fetchedLast:number; + transaction: [object]; + balanceSat: number; unconfirmedBalanceSat: number; - transaction: object; - used:boolean; utxos:[object] + fetchedLast:number; + used:boolean; } export declare type Network = "livenet" | "testnet" | "evonet" | "regtest" | "local" | "devnet" | "mainnet"; diff --git a/src/utils/bip44/ensureAddressesToGapLimit.js b/src/utils/bip44/ensureAddressesToGapLimit.js index 318d7959f..a4c84cbc7 100644 --- a/src/utils/bip44/ensureAddressesToGapLimit.js +++ b/src/utils/bip44/ensureAddressesToGapLimit.js @@ -1,5 +1,5 @@ const logger = require('../../logger'); -const { BIP44_ADDRESS_GAP } = require('../../CONSTANTS'); +const { BIP44_ADDRESS_GAP, WALLET_TYPES } = require('../../CONSTANTS'); const is = require('../is'); const getMissingIndexes = require('./getMissingIndexes'); @@ -77,13 +77,15 @@ function ensureAccountAddressesToGapLimit(walletStore, walletType, accountIndex, const gapBetweenLastUsedAndLastGenerated = { external: lastGeneratedIndexes.external - lastUsedIndexes.external, - internal: lastGeneratedIndexes.internal - lastUsedIndexes.internal, }; const addressesToGenerate = { external: BIP44_ADDRESS_GAP - gapBetweenLastUsedAndLastGenerated.external, - internal: BIP44_ADDRESS_GAP - gapBetweenLastUsedAndLastGenerated.internal, }; - + if (walletType.includes(WALLET_TYPES.HDWALLET)) { + // eslint-disable-next-line max-len + gapBetweenLastUsedAndLastGenerated.internal = lastGeneratedIndexes.internal - lastUsedIndexes.internal; + addressesToGenerate.internal = BIP44_ADDRESS_GAP - gapBetweenLastUsedAndLastGenerated.internal; + } Object.entries(addressesToGenerate) .forEach(([typeToGenerate, numberToGenerate]) => { if (numberToGenerate > 0) { diff --git a/src/utils/bip44/getBIP44AccountPath.js b/src/utils/bip44/getBIP44AccountPath.js new file mode 100644 index 000000000..55890e024 --- /dev/null +++ b/src/utils/bip44/getBIP44AccountPath.js @@ -0,0 +1,11 @@ +const { Networks } = require('@dashevo/dashcore-lib'); +const { BIP44_LIVENET_ROOT_PATH, BIP44_TESTNET_ROOT_PATH } = require('../../CONSTANTS'); + +function getBIP44AccountPath(accountIndex, network = 'testnet') { + const path = (network === Networks.livenet.toString()) + ? `${BIP44_LIVENET_ROOT_PATH}/${accountIndex}'` + : `${BIP44_TESTNET_ROOT_PATH}/${accountIndex}'`; + + return path; +} +module.exports = getBIP44AccountPath; diff --git a/src/utils/calculateDuffBalance.js b/src/utils/calculateDuffBalance.js new file mode 100644 index 000000000..b5c7da969 --- /dev/null +++ b/src/utils/calculateDuffBalance.js @@ -0,0 +1,62 @@ +/** + * + * @param walletId - The wallet Id where to perform the calculation + * @param accountIndex - The account Index where to perform the calculation + * @param type {{'confirmed','unconfirmed','total'}} Default: total. Calculate balance by utxo type. + * @return {number} Balance in duff + */ +module.exports = function calculateDuffBalance(addresses, chainStore, type = 'total') { + let totalSat = 0; + + addresses.forEach((address) => { + const addressData = chainStore.getAddress(address); + switch (type) { + case 'total': + totalSat += addressData.balanceSat + addressData.unconfirmedBalanceSat; + break; + case 'confirmed': + totalSat += addressData.balanceSat; + break; + case 'unconfirmed': + totalSat += addressData.unconfirmedBalanceSat; + break; + default: + throw new Error(`Unexpected balance type. Got ${type}`); + } + }); + // if (walletId === undefined || accountIndex === undefined) { + // throw new Error('Cannot calculate without walletId and accountIndex params'); + // } + // + // const { addresses } = this.getStore().wallets[walletId]; + // const subwallets = Object.keys(addresses); + // subwallets.forEach((subwallet) => { + // const paths = Object.keys(addresses[subwallet]) + // // We filter out other potential account + // .filter((el) => { + // const splitted = el.split('/'); + // const index = parseInt((splitted.length === 1) ? splitted[0] : splitted[3], 10); + // return index === accountIndex; + // }); + // + // paths.forEach((path) => { + // const address = addresses[subwallet][path]; + // const { balanceSat, unconfirmedBalanceSat } = address; + // switch (type) { + // case 'total': + // totalSat += balanceSat + unconfirmedBalanceSat; + // break; + // case 'confirmed': + // totalSat += balanceSat; + // break; + // case 'unconfirmed': + // totalSat += unconfirmedBalanceSat; + // break; + // default: + // throw new Error(`Unexpected balance type. Got ${type}`); + // } + // }); + // }); + + return totalSat; +}; diff --git a/src/utils/categorizeTransactions.js b/src/utils/categorizeTransactions.js index b13bbfaf4..a56c025db 100644 --- a/src/utils/categorizeTransactions.js +++ b/src/utils/categorizeTransactions.js @@ -30,14 +30,20 @@ const determineType = (inputsDetection, outputsDetection) => { return type; }; -function categorizeTransactions(transactionsWithMetadata, accountStore, accountIndex, walletType, network = 'testnet') { +function categorizeTransactions( + transactionsWithMetadata, + walletStore, + accountIndex, + walletType, + network, +) { const categorizedTransactions = []; const { - externalAddressList, - internalAddressList, - otherAccountAddressList, - } = classifyAddresses(accountStore.addresses, accountIndex, walletType); + externalAddressesList, + internalAddressesList, + otherAccountAddressesList, + } = classifyAddresses(walletStore, accountIndex, walletType, network); each(transactionsWithMetadata, (transactionWithMetadata) => { const [transaction, metadata] = transactionWithMetadata; @@ -57,9 +63,9 @@ function categorizeTransactions(transactionsWithMetadata, accountStore, accountI const { satoshis, script } = vout; const address = script.toAddress(network).toString(); if (address) { - if (internalAddressList.includes(address)) outputsHasChangeAddress = true; - if (externalAddressList.includes(address)) outputsHasExternalAddress = true; - if (otherAccountAddressList.includes(address)) outputsHasOtherAccountAddress = true; + if (internalAddressesList.includes(address)) outputsHasChangeAddress = true; + if (externalAddressesList.includes(address)) outputsHasExternalAddress = true; + if (otherAccountAddressesList.includes(address)) outputsHasOtherAccountAddress = true; to.push({ address, satoshis, @@ -72,9 +78,9 @@ function categorizeTransactions(transactionsWithMetadata, accountStore, accountI const { script } = vin; const address = script.toAddress(network).toString(); if (address) { - if (internalAddressList.includes(address)) inputsHasChangeAddress = true; - if (externalAddressList.includes(address)) inputsHasExternalAddress = true; - if (otherAccountAddressList.includes(address)) inputsHasOtherAccountAddress = true; + if (internalAddressesList.includes(address)) inputsHasChangeAddress = true; + if (externalAddressesList.includes(address)) inputsHasExternalAddress = true; + if (otherAccountAddressesList.includes(address)) inputsHasOtherAccountAddress = true; from.push({ address, }); diff --git a/src/utils/classifyAddresses.js b/src/utils/classifyAddresses.js index 52dfcff9b..15869d3fe 100644 --- a/src/utils/classifyAddresses.js +++ b/src/utils/classifyAddresses.js @@ -1,34 +1,37 @@ const { map, filter, difference } = require('lodash'); -const { WALLET_TYPES } = require('../CONSTANTS'); +const { WALLET_TYPES, BIP44_LIVENET_ROOT_PATH, BIP44_TESTNET_ROOT_PATH } = require('../CONSTANTS'); -function classifyAddresses(addressStore, accountIndex, walletType) { - const { external, internal, misc } = addressStore; +function classifyAddresses(walletStore, accountIndex, walletType, network) { + const externalAddressesList = []; + const internalAddressesList = []; + const otherAccountAddressesList = []; + const miscAddressesList = []; - // This will filter addresses to return only the one that are directly one the account manage. - // TODO: Computational improvement can be made by having accountIndex - // member of the address info format and thus comparing only 2 numbers - const filterPathByAccount = (address) => (parseInt(address.path.split('/')[3], 10) === accountIndex); - const addressMappingPredicate = (addressInfo) => (addressInfo.address); + const rootPath = (network.toString() === 'testnet') + ? BIP44_TESTNET_ROOT_PATH + : BIP44_LIVENET_ROOT_PATH; - const externalAddressList = (walletType === WALLET_TYPES.HDWALLET) - ? map(filter(external, filterPathByAccount), addressMappingPredicate) - : map(misc, addressMappingPredicate); + const accountsPaths = [...walletStore.state.paths.keys()]; - const internalAddressList = (walletType === WALLET_TYPES.HDWALLET) - ? map(filter(internal, filterPathByAccount), addressMappingPredicate) - : []; + const currentAccountPath = `${rootPath}/${accountIndex}'`; - const otherAccountAddressList = (walletType === WALLET_TYPES.HDWALLET) - ? difference( - [...map(external, addressMappingPredicate), ...map(internal, addressMappingPredicate)], - [...externalAddressList, ...internalAddressList], - ) - : []; + accountsPaths.forEach((accountPath) => { + const isCurrentAccountPath = accountPath === currentAccountPath; + const accountPaths = walletStore.getPathState(accountPath); + Object.entries(accountPaths.addresses) + .forEach(([path, address]) => { + if (isCurrentAccountPath && path.startsWith('m/0')) externalAddressesList.push(address); + else if (isCurrentAccountPath && path.startsWith('m/1')) internalAddressesList.push(address); + else if (isCurrentAccountPath) miscAddressesList.push(address); + else otherAccountAddressesList.push(address); + }); + }); return { - externalAddressList, - internalAddressList, - otherAccountAddressList, + externalAddressesList, + internalAddressesList, + otherAccountAddressesList, + miscAddressesList, }; } module.exports = classifyAddresses; diff --git a/src/utils/index.js b/src/utils/index.js index ac209d5d5..c23bb6a6e 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,6 +1,7 @@ const extendTransactionsWithMetadata = require('./extendTransactionsWithMetadata'); const calculateTransactionFees = require('./calculateTransactionFees'); const categorizeTransactions = require('./categorizeTransactions'); +const calculateDuffBalance = require('./calculateDuffBalance'); const filterTransactions = require('./filterTransactions'); const { hash, doubleSha256, sha256 } = require('./crypto'); const { varIntSizeBytesFromLength } = require('./varInt'); @@ -29,6 +30,7 @@ module.exports = { calculateTransactionFees, categorizeTransactions, mnemonicToHDPrivateKey, + calculateDuffBalance, generateNewMnemonic, seedToHDPrivateKey, mnemonicToWalletId, diff --git a/tests/functional/chainStore.spec.js b/tests/functional/chainStore.spec.js new file mode 100644 index 000000000..3a7aedefd --- /dev/null +++ b/tests/functional/chainStore.spec.js @@ -0,0 +1,153 @@ +const { expect } = require('chai'); +const { ChainStore } = require('../../src'); +const { BlockHeader,Transaction } = require("@dashevo/dashcore-lib"); + +let testnetChainStore; +const testnetBlockHeadersFixtures = require('../../fixtures/chains/testnet/blockheaders.json'); +const testnetTransactionsFixtures = require('../../fixtures/chains/testnet/transactions.json'); + +describe('ChainStore - Functional', ()=>{ + describe('simple usage', ()=>{ + it('should create a testnet chain store', function () { + testnetChainStore = new ChainStore('testnet'); + expect(testnetChainStore.network).to.equal('testnet') + }); + it('should have a initial state', function () { + expect(testnetChainStore.state.blockHeight).to.equal(0); + expect(testnetChainStore.state.fees).to.deep.equal({ + minRelay: -1 + }); + }); + it('should allow to import a blockHeaders', function () { + const stringifiedBlockHeader = testnetBlockHeadersFixtures[0].blockheader; + const blockHeader = new BlockHeader(Buffer.from(stringifiedBlockHeader, 'hex')); + testnetChainStore.importBlockHeader(blockHeader) + expect(testnetChainStore.getBlockHeader(blockHeader.hash).toString()).to.equal(stringifiedBlockHeader) + }); + it('should allow to watch for an address state', function () { + testnetChainStore.importAddress('yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq'); + const expectedGetAddress = { + "address": "yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq", + "balanceSat": 0, + "transactions": [], + "unconfirmedBalanceSat": 0, + "utxos": {} + } + expect(testnetChainStore.getAddress('yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq')).to.deep.equal(expectedGetAddress) + }); + it('should allow to import a transaction with metadata', function () { + const { blockHash, height, isInstantLocked, isChainLocked } = testnetTransactionsFixtures[0] + const stringifiedTransaction = testnetTransactionsFixtures[0].transaction; + const metadata = { + blockHash, + height, + isInstantLocked, + isChainLocked + } + const transaction = new Transaction(Buffer.from(stringifiedTransaction, 'hex')); + testnetChainStore.importTransaction(transaction, metadata) + expect(testnetChainStore.getTransaction(transaction.hash).transaction.toString()).to.equal(stringifiedTransaction) + expect(testnetChainStore.getTransaction(transaction.hash).metadata).to.deep.equal({ + blockHash, + height, + isInstantLocked, + isChainLocked + }) + }); + it('should have update address state', function () { + const expectedGetAddress = { + "address": "yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq", + "balanceSat": 114820000, + "transactions": [ + "7a3c3401462d5dc116db799edb768a2cc0c5e8c05c5483f052b6cab08367a353", + ], + "unconfirmedBalanceSat": 0, + "utxos": { + "7a3c3401462d5dc116db799edb768a2cc0c5e8c05c5483f052b6cab08367a353-0": { + "satoshis": 114820000, + "script": "76a91464220a1c12690ec26d837b3be0a2e3588bb4b79188ac" + } + } + } + expect(testnetChainStore.getAddress('yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq')).to.deep.equal(expectedGetAddress) + }); + it('should allow to import a transaction without metadata', function () { + const stringifiedTransaction = testnetTransactionsFixtures[1].transaction; + const transaction = new Transaction(Buffer.from(stringifiedTransaction, 'hex')); + testnetChainStore.importTransaction(transaction) + expect(testnetChainStore.getTransaction(transaction.hash).transaction.toString()).to.equal(stringifiedTransaction) + expect(testnetChainStore.getTransaction(transaction.hash).metadata).to.deep.equal({ + blockHash: null, + height: null, + isInstantLocked: null, + isChainLocked: null + }) + }); + it('should have update address state', function () { + const expectedGetAddress = { + "address": "yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq", + "balanceSat": 214890000, + "transactions": [ + "7a3c3401462d5dc116db799edb768a2cc0c5e8c05c5483f052b6cab08367a353", + "61e5a9ffbf505ad9e5b0a715673ec3c89d68dc9b1d1af8fd980240b8ac14c29c" + ], + "unconfirmedBalanceSat": 0, + "utxos": { + "61e5a9ffbf505ad9e5b0a715673ec3c89d68dc9b1d1af8fd980240b8ac14c29c-0": { + "satoshis": 100070000, + "script": "76a91464220a1c12690ec26d837b3be0a2e3588bb4b79188ac" + }, + "7a3c3401462d5dc116db799edb768a2cc0c5e8c05c5483f052b6cab08367a353-0": { + "satoshis": 114820000, + "script": "76a91464220a1c12690ec26d837b3be0a2e3588bb4b79188ac" + } + } + } + expect(testnetChainStore.getAddress('yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq')).to.deep.equal(expectedGetAddress) + }); + it('should update a transaction', function () { + const stringifiedTransaction = testnetTransactionsFixtures[1].transaction; + const transaction = new Transaction(Buffer.from(stringifiedTransaction, 'hex')); + testnetChainStore.importTransaction(transaction, { + blockHash: '0000005c81a683007e86e75c76b4b2feca229f806702ca92953562f2ae628ce7', + height: 618023, + isInstantLocked: true, + isChainLocked: true + }) + expect(testnetChainStore.getTransaction(transaction.hash).transaction.toString()).to.equal(stringifiedTransaction) + expect(testnetChainStore.getTransaction(transaction.hash).metadata).to.deep.equal({ + blockHash: '0000005c81a683007e86e75c76b4b2feca229f806702ca92953562f2ae628ce7', + height: 618023, + isInstantLocked: true, + isChainLocked: true + }) + }); + // it('should also have updated watched address', function () { + // + // }); + // it('should allow to import an instantLock for a transaction', function () { + // + // }); + // it('should have updated address state with locks', function () { + // + // }); + let exportedState; + it('should allow to export', function () { + exportedState = testnetChainStore.exportState() + }); + it('should allow to import store', function () { + const importedChainStore = new ChainStore(); + importedChainStore.importState(exportedState) + expect(importedChainStore.exportState()).to.deep.equal(exportedState) + }); + it('should consider previous transaction when address is added afterwards', function () { + const importedChainStore = new ChainStore(); + const modifiedState = JSON.parse(JSON.stringify(exportedState)); + modifiedState.state.addresses = {}; + importedChainStore.importState(modifiedState) + importedChainStore.importAddress("yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq") + expect(importedChainStore.exportState()).to.deep.equal(exportedState) + }); + }) +}); + diff --git a/tests/functional/keys.spec.js b/tests/functional/keys.spec.js new file mode 100644 index 000000000..ebe0f756b --- /dev/null +++ b/tests/functional/keys.spec.js @@ -0,0 +1,359 @@ +const { expect } = require('chai'); +const { HDPrivateKey, HDPublicKey, Mnemonic } = require('@dashevo/dashcore-lib'); +const { KeyChain, KeyChainStore, CONSTANTS: { BIP44_ADDRESS_GAP, BIP44_TESTNET_ROOT_PATH } } = require('../../src'); +const getBIP44AccountPath = require("../../src/utils/bip44/getBIP44AccountPath"); + +const mnemonic = new Mnemonic('one dumb media liquid zero apple jeans nurse click busy cannon number'); + +const DPFriendReceivingHDPrivateKey = new HDPrivateKey('tprv8ZgxMBicQKsPfALquMaAd23GpFgRB7oJVa3pWPjLDf6rAhyNcttncn7hbALux3SUsohPuviHDJfC7LjZQgZjHhWsP1RYGx3vjkWuzXuZ9WZ'); +const DPFriendHDPublicKey = new HDPublicKey('tpubD6NzVbkrYhZ4XLcVv7LhkVJRkZVUy185BeeAoBD94uDoHTNMupQFGkAMgAkPKy6mb3CMSKmqMdJ6JMm4gy7tcfBADWRuiqLoGHtktaPc8Ep'); +describe('Keys - Functional', ()=>{ + describe('simple usage', ()=>{ + let walletKeyChainStore; + let walletKeyChain; + it('should be able to setup a walletKeyChain to a walletKeyChainStore ', function () { + walletKeyChainStore = new KeyChainStore(); + walletKeyChain = new KeyChain({ + network: 'testnet', + mnemonic + }); + expect(walletKeyChain.rootKeyType).to.equal(HDPrivateKey.name); + expect(walletKeyChain.network).to.equal('testnet'); + expect(walletKeyChain.rootKey.toString()).to.equal('tprv8ZgxMBicQKsPeu48xtibzy9daEN1arEjRCe6iSZp2rXxV8pk27SPYvEyYirgMJnS8FdJREBddHW3nGckS9PT284hPVGCfDoUMiXxhaSXQvn'); + + const walletKeyChain2 = new KeyChain({ + network: 'livenet', + mnemonic + }); + expect(walletKeyChain2.rootKey.toString()).to.equal('xprv9s21ZrQH143K45pcJKs6qKXeG6woMLCj5eiyr29MYt3UhY5f2k6e3AsXdYh2LwQ7kp6XR8ZsTvvFKR51Jw3WD4o6rr3tzs5RScnYFs46Apa'); + + walletKeyChainStore.addKeyChain(walletKeyChain, { isMasterKeyChain: true }); + expect(walletKeyChainStore.getMasterKeyChain()).to.deep.equal(walletKeyChain) + }); + it('should get any accountKeyChainStore and its accountKeyChain', function () { + const accountKeyChainStore0 = walletKeyChainStore.makeChildKeyChainStore(getBIP44AccountPath(0, 'testnet')); + const accountKeyChain0 = accountKeyChainStore0.getMasterKeyChain(); + expect(accountKeyChain0.rootKey.toString()).to.equal('tprv8gNcoB29rES5PBtjviTp3iK4Vu4BA4rrxsU6gVtjzRwnSDhAQVx5WH32U6Z76Cmge1eBrfaaku9tJonAStBPDvYn9bRQEFi5d4nU9TZ5KK7') + + const accountKeyChainStore1 = walletKeyChainStore.makeChildKeyChainStore(getBIP44AccountPath(1, 'testnet')); + const accountKeyChain1 = accountKeyChainStore1.getMasterKeyChain(); + expect(accountKeyChain1.rootKey.toString()).to.equal('tprv8gNcoB29rES5PR9P3uJixRXtB7VjamA9QM6gKXmw8gxoGVbqfJsi1U6A92gpdegjAYx4PJ5HLzB33MdPZnpsq6ejYcnb5YjfZsADNrDmebA') + }); + + it('should get any address', function () { + const accountKeyChain0 = walletKeyChainStore + .makeChildKeyChainStore(getBIP44AccountPath(0, 'testnet')) + .getMasterKeyChain(); + + const accountExternalAddress0 = accountKeyChain0.getForPath(`m/0/0`).address; + expect(accountExternalAddress0.toString()).to.equal('yQH4MsDtfti8xnpagDZVLGHueZE4jEcEfj') + const accountInternalAddress0 = accountKeyChain0.getForPath(`m/1/0`).address; + expect(accountInternalAddress0.toString()).to.equal('yjcnQuzZwhrFnUDA2Kj4GbtTjB7qpZaqq6') + }); + it('should get the signing key for an address', function () { + const accountKeyChain0 = walletKeyChainStore + .makeChildKeyChainStore(getBIP44AccountPath(0, 'testnet')) + .getMasterKeyChain(); + + const accountExternalAddress0 = accountKeyChain0.getForPath(`m/0/0`).address; + + const signingKey = accountKeyChain0.getForAddress(accountExternalAddress0).key; + expect(signingKey.toString()).to.equal('tprv8jCCNzw9WdTAroytWTx4nJcWgykYLB9bnL8MWk62yJW5WSoznEoUVNzZtFPYTHQURxiAHh3yLQi5v91jtbW7AxDfu14wy78V59spYG5ETAc') + expect(signingKey.privateKey.toString()).to.equal('bdf5982ff67da261a31451933636825021e67be9289c9ec522087788f5593098') + }); + }) + describe('Gapped usage with watching and marking of address', ()=>{ + const walletKeyChainStore = new KeyChainStore(); + const walletKeyChain = new KeyChain({ + network: 'testnet', + mnemonic, + }); + walletKeyChainStore.addKeyChain(walletKeyChain, { isMasterKeyChain: true }) + + let accountKeyChain0; + it('should set keychain with gap', function () { + const lookAheadOpts = { + paths: { + 'm/0': BIP44_ADDRESS_GAP, + 'm/1': BIP44_ADDRESS_GAP + } + } + accountKeyChain0 = walletKeyChainStore + .makeChildKeyChainStore( + getBIP44AccountPath(0, 'testnet'), + { lookAheadOpts } + ) + .getMasterKeyChain(); + expect(accountKeyChain0.lookAheadOpts).to.deep.equal({isWatched:true, ...lookAheadOpts}) + expect(BIP44_ADDRESS_GAP).to.equal(20) + }); + + it('should have 20 internal and external set as issued address', function () { + expect(Array.from(accountKeyChain0.issuedPaths.keys())).to.deep.equal([ + 'm/0/0', 'm/0/1', 'm/0/2', + 'm/0/3', 'm/0/4', 'm/0/5', + 'm/0/6', 'm/0/7', 'm/0/8', + 'm/0/9', 'm/0/10', 'm/0/11', + 'm/0/12', 'm/0/13', 'm/0/14', + 'm/0/15', 'm/0/16', 'm/0/17', + 'm/0/18', 'm/0/19', 'm/1/0', + 'm/1/1', 'm/1/2', 'm/1/3', + 'm/1/4', 'm/1/5', 'm/1/6', + 'm/1/7', 'm/1/8', 'm/1/9', + 'm/1/10', 'm/1/11', 'm/1/12', + 'm/1/13', 'm/1/14', 'm/1/15', + 'm/1/16', 'm/1/17', 'm/1/18', + 'm/1/19' + ]) + }); + it('should have 20 internal and external to watched address', function () { + expect(accountKeyChain0.getWatchedAddresses()).to.deep.equal([ + 'yQH4MsDtfti8xnpagDZVLGHueZE4jEcEfj', + 'yZA33e7ojAvegoCELThDzt1Ywnpf86CDtc', + 'yZhFqYxNyvD4J4ySQzfCfLkGG3k9wnSx9C', + 'yfZ2NijF39BH11dLEzSZJhYSZSoeUHhKv2', + 'ydYVMsNn6QzzivjyqdqgRPT1yLh81ycZ3W', + 'yXd5TLmqBmPLQSpdGBwgB7JB34JBmWkWUp', + 'yaHEgNjWHXHFirCTZQkRkSiGhz6xaWN7he', + 'yWdHf3g4FhCfNbP6LoZTi12jy1u7hh5iZ9', + 'yj6WzZLtT1Nby9dsDNo988QzqAchjfz6cw', + 'yifEMSD9SQTu9tmyft4d3aTvQh82afxkdK', + 'yaXCzdyL4LCui8SffErsPSr7dU6jV3zftj', + 'yPfH7yfbsHkqSezzMfDbReyLaw1hcwtxHw', + 'ybmmVgnfpzsEVwk269vsBgkZdgBUbkbxcb', + 'ygDCyp8yndRq9bNZetuDLzC3uByuf2aH3a', + 'ycwrVbzQy8PPpaJME9uutAjy8vAuAKpNuF', + 'yjd8ThFK8oCK5TKk7inUtUWh2AtNTtcJyu', + 'yYcb4K5aba3a96WNxoeoaqRgfXdz8uTAtP', + 'ydVPiyNEvFqNWgdpXiavRrkm1NoJexw9op', + 'yQjqbHEcK2b8Tw18j4aMnLJWrBM3hMd2jY', + 'yMindyX74LaHdkdxGemyxjPWgmdaPAQHV5', + 'yjcnQuzZwhrFnUDA2Kj4GbtTjB7qpZaqq6', + 'ycMG3EvpsaodChznzoW6d6TK2AJCXCofvK', + 'yeBmB9zZm54xG13qPe4uouBee1P41NPoFG', + 'yMc5SbTnRwuoNyx4hpBkJ2yULdYG7cmPrR', + 'yQgnwDHQNm3zXY24MpSbxpkhmsYfTCV2uf', + 'yazGoXCSRj3d7H7y8D9FBo34KN1rxWRTxj', + 'yMNPrtNdAD5nET98tVqUHU4j64oAuwEdZT', + 'ySHYJjCXe2q5qv4WqDHssYBzhq1MUwNC94', + 'yi5LCMKMGGv8ELhec8hJHQzD7tgVZka74T', + 'yNKFzqLWQyQNoTCuzayLgzLtAkVr1Cr97A', + 'yRgg5gBFtzuvMUT8vZRUH5mJS3DV7Jr2r6', + 'yduxdiYt2GZEv77kdyuPWRwEnFJNnGPSWK', + 'yb45zLKCAEvMpKMmQ9k3vv81g63PDCe5Mk', + 'ySxrW1JpQwx9HN9cYGnsYX99Tie95KDJfK', + 'yVXV45Gybn7QpqoRVDC9cBSiQdTsJ4rHSE', + 'yaj2HUVuPzJdXcpDh8jYyeQ9TVLPAE2Caa', + 'yZBjfbFMih1tf2i7zABdeuJkeJ3PLD2ecY', + 'yW7iE3DmqpM1etQWquToC3LsSNGw8r3Z9r', + 'yasbGEiKerEZiYvQDB2YfDEqX8pzf4GBjm', + 'yivH7ouWmGkkpwHtXfQxYK3YM1ct15q75o' + ]); + }); + it('should mark address as used and generate as per gap', function () { + accountKeyChain0.markAddressAsUsed('yQH4MsDtfti8xnpagDZVLGHueZE4jEcEfj'); + expect(Array.from(accountKeyChain0.issuedPaths.keys())) + .to.deep.equal([ + 'm/0/0', 'm/0/1', 'm/0/2', + 'm/0/3', 'm/0/4', 'm/0/5', + 'm/0/6', 'm/0/7', 'm/0/8', + 'm/0/9', 'm/0/10', 'm/0/11', + 'm/0/12', 'm/0/13', 'm/0/14', + 'm/0/15', 'm/0/16', 'm/0/17', + 'm/0/18', 'm/0/19', 'm/1/0', + 'm/1/1', 'm/1/2', 'm/1/3', + 'm/1/4', 'm/1/5', 'm/1/6', + 'm/1/7', 'm/1/8', 'm/1/9', + 'm/1/10', 'm/1/11', 'm/1/12', + 'm/1/13', 'm/1/14', 'm/1/15', + 'm/1/16', 'm/1/17', 'm/1/18', + 'm/1/19', 'm/0/20' + ]) + expect(accountKeyChain0.getWatchedAddresses()).to.deep.equal([ + 'yQH4MsDtfti8xnpagDZVLGHueZE4jEcEfj', + 'yZA33e7ojAvegoCELThDzt1Ywnpf86CDtc', + 'yZhFqYxNyvD4J4ySQzfCfLkGG3k9wnSx9C', + 'yfZ2NijF39BH11dLEzSZJhYSZSoeUHhKv2', + 'ydYVMsNn6QzzivjyqdqgRPT1yLh81ycZ3W', + 'yXd5TLmqBmPLQSpdGBwgB7JB34JBmWkWUp', + 'yaHEgNjWHXHFirCTZQkRkSiGhz6xaWN7he', + 'yWdHf3g4FhCfNbP6LoZTi12jy1u7hh5iZ9', + 'yj6WzZLtT1Nby9dsDNo988QzqAchjfz6cw', + 'yifEMSD9SQTu9tmyft4d3aTvQh82afxkdK', + 'yaXCzdyL4LCui8SffErsPSr7dU6jV3zftj', + 'yPfH7yfbsHkqSezzMfDbReyLaw1hcwtxHw', + 'ybmmVgnfpzsEVwk269vsBgkZdgBUbkbxcb', + 'ygDCyp8yndRq9bNZetuDLzC3uByuf2aH3a', + 'ycwrVbzQy8PPpaJME9uutAjy8vAuAKpNuF', + 'yjd8ThFK8oCK5TKk7inUtUWh2AtNTtcJyu', + 'yYcb4K5aba3a96WNxoeoaqRgfXdz8uTAtP', + 'ydVPiyNEvFqNWgdpXiavRrkm1NoJexw9op', + 'yQjqbHEcK2b8Tw18j4aMnLJWrBM3hMd2jY', + 'yMindyX74LaHdkdxGemyxjPWgmdaPAQHV5', + 'yjcnQuzZwhrFnUDA2Kj4GbtTjB7qpZaqq6', + 'ycMG3EvpsaodChznzoW6d6TK2AJCXCofvK', + 'yeBmB9zZm54xG13qPe4uouBee1P41NPoFG', + 'yMc5SbTnRwuoNyx4hpBkJ2yULdYG7cmPrR', + 'yQgnwDHQNm3zXY24MpSbxpkhmsYfTCV2uf', + 'yazGoXCSRj3d7H7y8D9FBo34KN1rxWRTxj', + 'yMNPrtNdAD5nET98tVqUHU4j64oAuwEdZT', + 'ySHYJjCXe2q5qv4WqDHssYBzhq1MUwNC94', + 'yi5LCMKMGGv8ELhec8hJHQzD7tgVZka74T', + 'yNKFzqLWQyQNoTCuzayLgzLtAkVr1Cr97A', + 'yRgg5gBFtzuvMUT8vZRUH5mJS3DV7Jr2r6', + 'yduxdiYt2GZEv77kdyuPWRwEnFJNnGPSWK', + 'yb45zLKCAEvMpKMmQ9k3vv81g63PDCe5Mk', + 'ySxrW1JpQwx9HN9cYGnsYX99Tie95KDJfK', + 'yVXV45Gybn7QpqoRVDC9cBSiQdTsJ4rHSE', + 'yaj2HUVuPzJdXcpDh8jYyeQ9TVLPAE2Caa', + 'yZBjfbFMih1tf2i7zABdeuJkeJ3PLD2ecY', + 'yW7iE3DmqpM1etQWquToC3LsSNGw8r3Z9r', + 'yasbGEiKerEZiYvQDB2YfDEqX8pzf4GBjm', + 'yivH7ouWmGkkpwHtXfQxYK3YM1ct15q75o', + 'yNti2kmrf8rXDvqB8ydSbwpkfuVEb2HUrQ' + ]); + }); + }); + describe('DashPay - ContactKeyChain', ()=>{ + const keyChainStore = new KeyChainStore(); + const walletKeyChain = new KeyChain({ + network: 'testnet', + mnemonic, + }); + const lookAheadOpts = { + paths: { + 'm/0': BIP44_ADDRESS_GAP, + 'm/1': BIP44_ADDRESS_GAP + } + } + keyChainStore.addKeyChain(walletKeyChain, { isMasterKeyChain: true }) + const accountKeyChainStore = keyChainStore + .makeChildKeyChainStore(getBIP44AccountPath(0, 'testnet'), {lookAheadOpts}) + + it('should add a receiving keychain', function () { + const lookAheadOpts = { + paths:{ + 'm/0': 10, + 'm/1': 10 + }, + } + const contactReceivingKeyChain = new KeyChain({ + network: 'testnet', + HDPrivateKey: DPFriendReceivingHDPrivateKey, + lookAheadOpts + }); + accountKeyChainStore.addKeyChain(contactReceivingKeyChain) + expect(Array.from(contactReceivingKeyChain.issuedPaths.keys())).to.deep.equal([ + 'm/0/0', + 'm/0/1', + 'm/0/2', + 'm/0/3', + 'm/0/4', + 'm/0/5', + 'm/0/6', + 'm/0/7', + 'm/0/8', + 'm/0/9', + 'm/1/0', + 'm/1/1', + 'm/1/2', + 'm/1/3', + 'm/1/4', + 'm/1/5', + 'm/1/6', + 'm/1/7', + 'm/1/8', + 'm/1/9' + ]) + expect(contactReceivingKeyChain.getWatchedAddresses()).to.deep.equal([ + 'ygXopQ36SWcK56fy6rD6qiogZcRv3HxMRw', + 'ygeagGpXd8tWFCNSYjYs1PnGKVSgvg5ndr', + 'yddpt9d3JttZeRHtuXMmp2pqiBmZRpf5bM', + 'yg61bJDLee3VRErvm5zVi2f7bBPQkVHSUo', + 'yT1sPoqaWf4Yu4Ddm3huGc72DBixLsQVD5', + 'yPvPxvoWFn1KCGRmtqwAzHBaS8UDq2kj6U', + 'yNNmU45y9tmgdq8BLtZzNAYxSPovhnTnkA', + 'yS7LoFDvrNfa6uNqNr1rKRAUU7HRKux3rm', + 'yjGhDPFwKWvBH5RqhimJUeKfh4723SyZSw', + 'yUMVKzNBo29N9G8fYWpGH4t7ka7Pxoxp1g', + 'yjSZFPSMMjW94gxky8GzNrDmibvCZsHeev', + 'yVEJExicNKEwAQR6hZpoKVjDpS6KQTXxRT', + 'yae21tB2umekfyNSCRiv2efCuC5AuJxUjx', + 'yWKMoKkMs9cY5ZrG1T8TUv5XJngw32H3JP', + 'yXpTUWPFzAVgfNS31AH5SgNfcGm2uhhXej', + 'yfQdpiNbnPDPY7qm91R9jx865fdpgsQUt5', + 'yQpGvLZ1wg5rBTt6BANb5pHj7nVm1yGsom', + 'yUxmwPFvtSiCXySoQXNY5xhTU4RZrJdMLE', + 'yUHCHewhGZrpDgJEiA3QeZqabBu53u7WBD', + 'yLzzpBayHs94ZDHukJL6KTzkFPvfyE2SZc' + ]) + }); + it('should add a sending keychain', function () { + const lookAheadOpts = { + paths:{ + 'm/0': 10, + 'm/1': 10 + }, + } + const contactSendingKeyChain = new KeyChain({ + network: 'testnet', + HDPublicKey: DPFriendHDPublicKey, + lookAheadOpts + }); + accountKeyChainStore.addKeyChain(contactSendingKeyChain) + + expect(Array.from(contactSendingKeyChain.issuedPaths.keys())).to.deep.equal([ + 'm/0/0', + 'm/0/1', + 'm/0/2', + 'm/0/3', + 'm/0/4', + 'm/0/5', + 'm/0/6', + 'm/0/7', + 'm/0/8', + 'm/0/9', + 'm/1/0', + 'm/1/1', + 'm/1/2', + 'm/1/3', + 'm/1/4', + 'm/1/5', + 'm/1/6', + 'm/1/7', + 'm/1/8', + 'm/1/9' + ]) + expect(contactSendingKeyChain.getWatchedAddresses()).to.deep.equal([ + 'ycjHcm5BTWNgucy5dZGy8ELRvLptxZKvkr', + 'yMNYq1LoSLCH6Bta4T7hbDdNmsLNqi47Ua', + 'ybca2FKwup3y2fsfVz9KABx81JtDH7F4zX', + 'yQrtYuoWfKv6BKY1xBqQVLnGvf4up3TsWo', + 'yWhMUL65dFoPbCZyW286QAZCjdSmLVcqQa', + 'yanMQ79PN5nTK6UwhpdUpiPAkRYoAe5UbZ', + 'yYq1LRkdvDxF7Spk8KPyUmPmHdbf9qmW4E', + 'yVuNQ412G8JvGYmdamwcHPNTmUqzWemJMx', + 'ycodktzX9pYAE29vUs8bVQquAkVUbZVuv2', + 'ybPry3LB5RH4CabqqAjm3QkN9n7PtLQvzJ', + 'yUfPQaqcZ8cviuhrmvi8mU7XykYL3WfCse', + 'ySzzXYet9CdBCpYgGMBu2GKPbtjBhXkDoU', + 'yfz51NpWoR6JvnMhkB1RTKWaCvyXfmudio', + 'yVQ5CtUUSNLEHSzR1EJ1y8GNEWorCP8ape', + 'ycEzBg7S85Qk13CjbfJzmtDda8sQKQc33Z', + 'yVrZBeUEYGNw3jY7PX37zcGh7t1qzvYbJc', + 'yVtfJdLKxhyoEHAuhwDSdd1UZcHQuVZH6D', + 'yZbCr237LvakaR2UHW8aqiQztX9xXSWK4o', + 'ySw8JxoCuqvzb5AWV8ZY7RkSEvzzh2bpu2', + 'yRZ2WPrNeTHH1G7nFD63Su5qwhfuKMruLU' + ]) + }); + it('should get watched address from all keychain', function () { + const addresses = []; + for(let [keychainId, keychain] of accountKeyChainStore.keyChains){ + addresses.push(...keychain.getWatchedAddresses()) + } + expect(addresses.length).to.equal(80); + }); + }) +}); + diff --git a/tests/functional/offlineWallet.spec.js b/tests/functional/offlineWallet.spec.js new file mode 100644 index 000000000..ac056f9e8 --- /dev/null +++ b/tests/functional/offlineWallet.spec.js @@ -0,0 +1,92 @@ +const { expect, use} = require('chai'); +const { Wallet } = require('../../src/index'); +const { Transaction } = require('@dashevo/dashcore-lib'); +let offlineWallet; +let account0; +describe('Wallet-lib - functional - offline Wallet', function suite() { + this.timeout(700000); + describe('Wallet', () => { + describe('Create a new offline wallet', () => { + it('should create a new offline wallet', () => { + offlineWallet = new Wallet({ + offlineMode: true, + mnemonic: 'replace eternal resource drill side kidney sudden thought account fog fluid wire' + }); + }); + }); + }); + + describe('Account', () => { + it('should allow to get the first account', async function () { + account0 = await offlineWallet.getAccount(); + expect(account0.offlineMode).to.equal(true) + expect(account0.accountPath).to.equal(`m/44'/1'/0'`) + }); + it('should get all addresses', function () { + const externalAddressesSet = account0.getAddresses(); + const internalAddressesSet = account0.getAddresses('internal'); + expect(Object.keys(externalAddressesSet)).to.deep.equal([ + 'm/0/0', 'm/0/1', 'm/0/2', + 'm/0/3', 'm/0/4', 'm/0/5', + 'm/0/6', 'm/0/7', 'm/0/8', + 'm/0/9', 'm/0/10', 'm/0/11', + 'm/0/12', 'm/0/13', 'm/0/14', + 'm/0/15', 'm/0/16', 'm/0/17', + 'm/0/18', 'm/0/19' + ]) + expect(Object.keys(internalAddressesSet)).to.deep.equal([ + 'm/1/0', 'm/1/1', 'm/1/2', + 'm/1/3', 'm/1/4', 'm/1/5', + 'm/1/6', 'm/1/7', 'm/1/8', + 'm/1/9', 'm/1/10', 'm/1/11', + 'm/1/12', 'm/1/13', 'm/1/14', + 'm/1/15', 'm/1/16', 'm/1/17', + 'm/1/18', 'm/1/19' + ]) + }); + it('should generate new address on tx affecting address', function () { + expect(account0.getUnusedAddress()).to.deep.equal({ + address: 'yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq', + path: 'm/0/0', + index: 0 + }); + const transactionsWithMetadata = [ + [ + new Transaction('0200000001c7d66bb85e0069c221b44b07f49f52cc4f2e54f70e14430b94888327763a66a9010000006b483045022100da5b319f73e6adfee751f33308f5a8c1fceeab2683e15e132d79053b3118639602204262022fb85f88d9802649a289a1134b678efcf708faaeae8f101e8eab785054012102bc626898b49f31f5194de7bc68004401639a20cfa82e4c2eac9684a91fc47a57feffffff0270f2f605000000001976a91464220a1c12690ec26d837b3be0a2e3588bb4b79188ac912e250c250000001976a91415e1edb5c5d9e67d0e36f94343b3eff26bb76d1088ac266e0900'), + {"blockHash":"0000005c81a683007e86e75c76b4b2feca229f806702ca92953562f2ae628ce7","height":618023,"instantLocked":true,"chainLocked":true} + ] + ] + account0.importTransactions(transactionsWithMetadata); + expect(account0.getUnusedAddress()).to.deep.equal({ + address: 'yWFHBxc6c9jmkL82v795PtJJteSkcVKbt5', + path: 'm/0/1', + index: 1 + }); + expect(account0.getTotalBalance()).to.equal(100070000); + expect(account0.getUTXOS().length).to.equal(1); + const utxo = account0.getUTXOS()[0]; + expect(utxo.toString()).to.equal('61e5a9ffbf505ad9e5b0a715673ec3c89d68dc9b1d1af8fd980240b8ac14c29c:0'); + expect(utxo).to.deep.equal(new Transaction.UnspentOutput({"address":"yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq","txid":"61e5a9ffbf505ad9e5b0a715673ec3c89d68dc9b1d1af8fd980240b8ac14c29c","vout":0,"scriptPubKey":"76a91464220a1c12690ec26d837b3be0a2e3588bb4b79188ac","amount":1.0007})) + expect(account0.getPrivateKeys([utxo.address.toString()])[0].toString()).to.equal('tprv8k5Lf2T5uY7BZVxCvssWR9txCj5rEvT19Nd291gfuDJf1wnibAB9GTRzke7FvwKnpXBYiYWvjthnnCWPFUvJbsN3StwcL63EnJNcMSmorfC') + }); + it('should have issued new addresses', function () { + const externalAddressesSet = account0.getAddresses(); + expect(Object.keys(externalAddressesSet)).to.deep.equal([ + 'm/0/0', 'm/0/1', 'm/0/2', + 'm/0/3', 'm/0/4', 'm/0/5', + 'm/0/6', 'm/0/7', 'm/0/8', + 'm/0/9', 'm/0/10', 'm/0/11', + 'm/0/12', 'm/0/13', 'm/0/14', + 'm/0/15', 'm/0/16', 'm/0/17', + 'm/0/18', 'm/0/19', 'm/0/20' + ]) + }); + it('should be able to create and sign transaction', function () { + const tx = account0.createTransaction({ + recipient: 'yVSuCVTGpViqV8bzG3kdtofkPhSRWH8dbq', + satoshis: 100000 + }) + expect(tx.isFullySigned()).to.equal(true); + }); + }); +}); diff --git a/tests/functional/storage.spec.js b/tests/functional/storage.spec.js new file mode 100644 index 000000000..3fb7868c6 --- /dev/null +++ b/tests/functional/storage.spec.js @@ -0,0 +1,35 @@ +const { expect } = require('chai'); +const { Storage } = require('../../src'); + +let storage; +describe('Storage - Functional', ()=> { + describe('simple usage', () => { + it('should create a storage', function () { + storage = new Storage(); + }); + it('should create a chain', function () { + storage.createChainStore('testnet') + }); + it('should get a chain store', function () { + const chainStore = storage.getChainStore('testnet') + expect(chainStore.network).to.equal('testnet'); + expect(chainStore.state.blockHeight).to.equal(0); + }); + it('should create a wallet', function () { + storage.createWalletStore('73a5575413') + }); + it('should get a wallet store', function () { + const walletStore = storage.getWalletStore('73a5575413') + expect(walletStore.walletId).to.equal('73a5575413'); + }); + it('should create paths store', function () { + const walletStore = storage.getWalletStore('73a5575413') + // const publicKeyStore = walletStore.createPathStore(`m/0'`); + const accountStore = walletStore.createPathState(`m/44'/1'/0'`); + // const contactReceivingStore = walletStore.createPathStore(`m/9'/1'/15'/0'/123/456`); + }); + it('should ', function () { + + }); + }) +}) \ No newline at end of file diff --git a/tests/functional/walletStore.spec.js b/tests/functional/walletStore.spec.js new file mode 100644 index 000000000..61fbaa292 --- /dev/null +++ b/tests/functional/walletStore.spec.js @@ -0,0 +1,14 @@ +const { expect } = require('chai'); +const { WalletStore } = require('../../src'); + +let walletStore; +describe('WalletStore - Functional', ()=> { + describe('simple usage', () => { + it('should create a walletStore', function () { + walletStore = new WalletStore(); + }); + it('should create account', function () { + walletStore.createAccount() + }); + }) +}) \ No newline at end of file