From dccedd5593c41c932ae0ca3f1e76a4a2bd8ee4f8 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Mon, 7 Feb 2022 12:10:09 +0100 Subject: [PATCH 1/8] feat(wallet-lib): rework storage for multiple key chains (#231) --- .../DerivableKeyChain/DerivableKeyChain.d.ts | 50 +++++ .../DerivableKeyChain/DerivableKeyChain.js | 107 +++++++++++ .../DerivableKeyChain.spec.js | 181 ++++++++++++++++++ .../methods/getDIP15ExtendedKey.js | 26 +++ .../methods/getFirstUnusedAddress.js | 12 ++ .../methods/getForAddress.js | 12 ++ .../DerivableKeyChain/methods/getForPath.js | 42 ++++ .../methods/getHardenedBIP44HDKey.js | 11 ++ .../methods/getHardenedDIP15AccountKey.js | 14 ++ .../methods/getHardenedDIP9FeatureHDKey.js | 11 ++ .../methods/getIssuedPaths.js | 5 + .../DerivableKeyChain/methods/getRootKey.js | 5 + .../methods/getWatchedAddresses.js | 7 + .../methods/markAddressAsUsed.js | 17 ++ .../methods/maybeLookAhead.js | 82 ++++++++ .../types/DerivableKeyChain/methods/sign.js | 29 +++ .../Identities/methods/getIdentityIds.spec.js | 2 - .../src/types/KeyChain/KeyChain.d.ts | 62 +++--- .../src/types/KeyChainStore/KeyChainStore.js | 16 ++ .../types/KeyChainStore/KeyChainStore.spec.js | 47 +++++ .../KeyChainStore/methods/addKeyChain.js | 15 ++ .../KeyChainStore/methods/getKeyChain.js | 5 + .../KeyChainStore/methods/getKeyChains.js | 5 + .../methods/getMasterKeyChain.js | 6 + .../methods/makeChildKeyChainStore.js | 19 ++ 25 files changed, 754 insertions(+), 34 deletions(-) create mode 100644 packages/wallet-lib/src/types/DerivableKeyChain/DerivableKeyChain.d.ts create mode 100644 packages/wallet-lib/src/types/DerivableKeyChain/DerivableKeyChain.js create mode 100644 packages/wallet-lib/src/types/DerivableKeyChain/DerivableKeyChain.spec.js create mode 100644 packages/wallet-lib/src/types/DerivableKeyChain/methods/getDIP15ExtendedKey.js create mode 100644 packages/wallet-lib/src/types/DerivableKeyChain/methods/getFirstUnusedAddress.js create mode 100644 packages/wallet-lib/src/types/DerivableKeyChain/methods/getForAddress.js create mode 100644 packages/wallet-lib/src/types/DerivableKeyChain/methods/getForPath.js create mode 100644 packages/wallet-lib/src/types/DerivableKeyChain/methods/getHardenedBIP44HDKey.js create mode 100644 packages/wallet-lib/src/types/DerivableKeyChain/methods/getHardenedDIP15AccountKey.js create mode 100644 packages/wallet-lib/src/types/DerivableKeyChain/methods/getHardenedDIP9FeatureHDKey.js create mode 100644 packages/wallet-lib/src/types/DerivableKeyChain/methods/getIssuedPaths.js create mode 100644 packages/wallet-lib/src/types/DerivableKeyChain/methods/getRootKey.js create mode 100644 packages/wallet-lib/src/types/DerivableKeyChain/methods/getWatchedAddresses.js create mode 100644 packages/wallet-lib/src/types/DerivableKeyChain/methods/markAddressAsUsed.js create mode 100644 packages/wallet-lib/src/types/DerivableKeyChain/methods/maybeLookAhead.js create mode 100644 packages/wallet-lib/src/types/DerivableKeyChain/methods/sign.js create mode 100644 packages/wallet-lib/src/types/KeyChainStore/KeyChainStore.js create mode 100644 packages/wallet-lib/src/types/KeyChainStore/KeyChainStore.spec.js create mode 100644 packages/wallet-lib/src/types/KeyChainStore/methods/addKeyChain.js create mode 100644 packages/wallet-lib/src/types/KeyChainStore/methods/getKeyChain.js create mode 100644 packages/wallet-lib/src/types/KeyChainStore/methods/getKeyChains.js create mode 100644 packages/wallet-lib/src/types/KeyChainStore/methods/getMasterKeyChain.js create mode 100644 packages/wallet-lib/src/types/KeyChainStore/methods/makeChildKeyChainStore.js diff --git a/packages/wallet-lib/src/types/DerivableKeyChain/DerivableKeyChain.d.ts b/packages/wallet-lib/src/types/DerivableKeyChain/DerivableKeyChain.d.ts new file mode 100644 index 00000000000..406d2baf5ff --- /dev/null +++ b/packages/wallet-lib/src/types/DerivableKeyChain/DerivableKeyChain.d.ts @@ -0,0 +1,50 @@ +import {PrivateKey, Network,} from "../types"; +import {HDPrivateKey, HDPublicKey} from "@dashevo/dashcore-lib"; +import {Transaction} from "@dashevo/dashcore-lib/typings/transaction/Transaction"; + +export declare namespace DerivableKeyChain { + interface IDerivableKeyChainOptions { + network?: Network; + keys?: [Keys] + } +} + +export declare class DerivableKeyChain { + constructor(options?: DerivableKeyChain.IDerivableKeyChainOptions); + network: Network; + keys: [Keys]; + + type: HDKeyTypesParam|PrivateKeyTypeParam; + HDPrivateKey?: HDPrivateKey; + privateKey?: PrivateKey; + + generateKeyForChild(index: number, type?: HDKeyTypesParam): HDPrivateKey|HDPublicKey; + generateKeyForPath(path: string, type?: HDKeyTypesParam): HDPrivateKey|HDPublicKey; + + getDIP15ExtendedKey(userUniqueId: string, contactUniqueId: string, index?: number, accountIndex?: number, type?: HDKeyTypesParam): HDKeyTypes; + getHardenedDIP15AccountKey(index?: number, type?: HDKeyTypesParam): HDKeyTypes; + getHardenedBIP44HDKey(type?: HDKeyTypesParam): HDKeyTypes; + getHardenedDIP9FeatureHDKey(type?: HDKeyTypesParam): HDKeyTypes; + getKeyForChild(index: number, type?: HDKeyTypesParam): HDKeyTypes; + getKeyForPath(path: string, type?: HDKeyTypesParam): HDKeyTypes; + getPrivateKey(): PrivateKey; + + sign(object: Transaction|any, privateKeys:[PrivateKey], sigType: number): any; +} + +type HDKeyTypes = HDPublicKey | HDPrivateKey; + +export declare enum HDKeyTypesParam { + HDPrivateKey="HDPrivateKey", + HDPublicKey="HDPrivateKey", +} +export declare enum PrivateKeyTypeParam { + privateKey='privateKey' +} +export declare interface Keys { + [path: string]: { + path: string + }; +} + + diff --git a/packages/wallet-lib/src/types/DerivableKeyChain/DerivableKeyChain.js b/packages/wallet-lib/src/types/DerivableKeyChain/DerivableKeyChain.js new file mode 100644 index 00000000000..9191f40d7e2 --- /dev/null +++ b/packages/wallet-lib/src/types/DerivableKeyChain/DerivableKeyChain.js @@ -0,0 +1,107 @@ +const { Networks, HDPrivateKey, HDPublicKey } = require('@dashevo/dashcore-lib'); +const { PrivateKey, PublicKey } = require('@dashevo/dashcore-lib'); +const { doubleSha256 } = require('../../utils/crypto'); +const { mnemonicToHDPrivateKey } = require('../../utils/mnemonic'); + +function generateKeyChainId(key) { + const keyChainIdSuffix = doubleSha256(key.toString()).toString('hex').slice(0, 10); + return `kc${keyChainIdSuffix}`; +} + +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), + }); + } + } + + const lookAheadOpts = { + isWatched: true, + paths: {}, + ...opts.lookAheadOpts, + }; + + return { + rootKeyType, + rootKey, + network, + passphrase, + lookAheadOpts, + }; +} + +class DerivableKeyChain { + 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(); + } +} +DerivableKeyChain.prototype.getForPath = require('./methods/getForPath'); +DerivableKeyChain.prototype.getForAddress = require('./methods/getForAddress'); +DerivableKeyChain.prototype.getDIP15ExtendedKey = require('./methods/getDIP15ExtendedKey'); +DerivableKeyChain.prototype.getFirstUnusedAddress = require('./methods/getFirstUnusedAddress'); +DerivableKeyChain.prototype.getHardenedBIP44HDKey = require('./methods/getHardenedBIP44HDKey'); +DerivableKeyChain.prototype.getHardenedDIP9FeatureHDKey = require('./methods/getHardenedDIP9FeatureHDKey'); +DerivableKeyChain.prototype.getHardenedDIP15AccountKey = require('./methods/getHardenedDIP15AccountKey'); +DerivableKeyChain.prototype.getRootKey = require('./methods/getRootKey'); +DerivableKeyChain.prototype.getWatchedAddresses = require('./methods/getWatchedAddresses'); +DerivableKeyChain.prototype.getIssuedPaths = require('./methods/getIssuedPaths'); +DerivableKeyChain.prototype.maybeLookAhead = require('./methods/maybeLookAhead'); +DerivableKeyChain.prototype.markAddressAsUsed = require('./methods/markAddressAsUsed'); +DerivableKeyChain.prototype.sign = require('./methods/sign'); + +module.exports = DerivableKeyChain; diff --git a/packages/wallet-lib/src/types/DerivableKeyChain/DerivableKeyChain.spec.js b/packages/wallet-lib/src/types/DerivableKeyChain/DerivableKeyChain.spec.js new file mode 100644 index 00000000000..02ae0c03abd --- /dev/null +++ b/packages/wallet-lib/src/types/DerivableKeyChain/DerivableKeyChain.spec.js @@ -0,0 +1,181 @@ +const Dashcore = require('@dashevo/dashcore-lib'); +const { expect } = require('chai'); +const DerivableKeyChain = require('./DerivableKeyChain'); +const { mnemonicToHDPrivateKey } = require('../../utils/mnemonic'); + +let derivableKeyChain; +let derivableKeyChain2; +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 hdPublicKey = 'xpub661MyMwAqRbcFGB6XSWBsD725rJDUbFUpy4zWe2u22nJ2BxpoHFxtVDfKnTnvVQHohnY7AsVpRTHDv6PyPQTYu1KxFPKw29MAVXPEpz1G7V'; +const expectedRootDIP15AccountKey_0 = 'tprv8hRzmheQujhJN5XP2dj955nAFCKeEoSifJRWuutdbwWRtusdDQ426jbp75EqErUSuTxmPyxYmP1TpcF5qdxGhXLNXRLMGsRLG6NFCv1WnaQ'; +const expectedRootDIP15AccountKey_1 = 'tprv8hRzmheQujhJQyCtFTuUFHxB3Ag5VLB994zhH4CfxbA41cq73HT2mpYq5M33V54oJyn6g514saxxVJB886G55eYX56J6D6x87UNNT6iQHkR'; +const expectedKeyForChild_0 = 'tprv8d4podc2Tg459CH2bwLHXj3vdJFBT2rdsk5Nr1djH7hzHdt5LRdvN6QyFwMiDy7ffRdik7fEVRKKgsHB4F18sh8xF6jFXpKq4sUgGBoSbKw'; + +describe('DerivableKeyChain', function suite() { + this.timeout(1000); + it('should create a DerivableKeyChain', () => { + const expectedException1 = 'Expect one of [mnemonic, HDPrivateKey, HDPublicKey, privateKey, publicKey, address] to be provided.'; + expect(() => new DerivableKeyChain()).to.throw(expectedException1); + + derivableKeyChain = new DerivableKeyChain({ mnemonic: mnemonic, network: 'testnet' }); + expect(derivableKeyChain.rootKeyType).to.equal('HDPrivateKey'); + expect(derivableKeyChain.network.toString()).to.equal('testnet'); + expect(derivableKeyChain.rootKey.network.toString()).to.equal('testnet'); + + derivableKeyChain2 = new DerivableKeyChain({ mnemonic: mnemonic2, network: 'livenet' }); + }); + + it('should generate key for full path', () => { + const path = 'm/44\'/1\'/0\'/0/0'; + const pk2 = derivableKeyChain.getForPath(path).key; + const address = new Dashcore.Address(pk2.publicKey.toAddress()).toString(); + expect(address).to.equal('yNfUebksUc5HoSfg8gv98ruC3jUNJUM8pT'); + }); + + it('should get DIP15 account key', function () { + const rootDIP15AccountKey_0 = derivableKeyChain.getHardenedDIP15AccountKey(0); + expect(rootDIP15AccountKey_0.toString()).to.deep.equal(expectedRootDIP15AccountKey_0); + const rootDIP15AccountKey_1 = derivableKeyChain.getHardenedDIP15AccountKey(1); + expect(rootDIP15AccountKey_1.toString()).to.deep.equal(expectedRootDIP15AccountKey_1); + }); + + it('should get DIP15 extended key', function () { + const userUniqueId = '0x555d3854c910b7dee436869c4724bed2fe0784e198b8a39f02bbb49d8ebcfc3a'; + const contactUniqueId = '0xa137439f36d04a15474ff7423e4b904a14373fafb37a41db74c84f1dbb5c89b5'; + + // m/9'/5'/15'/0'/0x555d3854c910b7dee436869c4724bed2fe0784e198b8a39f02bbb49d8ebcfc3a'/0xa137439f36d04a15474ff7423e4b904a14373fafb37a41db74c84f1dbb5c89b5'/0 + const DIP15ExtPubKey_0 = derivableKeyChain2.getDIP15ExtendedKey(userUniqueId, contactUniqueId, 0, 0, 'HDPublicKey'); + expect(DIP15ExtPubKey_0.toString()).to.equal('xpub6LTkTQFSb8KMgMSz4B6sMZLpkQAY6wSTDprDkHDmLwWLpnjxazuxZn13FrSLKUafitsxuaaffM5a49P6aswhpppWUuYW6eFnwBXshR2W2eY'); + expect(DIP15ExtPubKey_0.publicKey.toString()).to.equal('038030c88ab0106e1f4af3b939db2bafc56f892554106f08da1ce1f9ef10f807bd') + + const DIP15ExtPrivKey_0 = derivableKeyChain2.getDIP15ExtendedKey(userUniqueId, contactUniqueId, 0, 0); + expect(DIP15ExtPrivKey_0.toString()).to.equal('xprvA7UQ3tiYkkm4TsNWx9ZrzRQ6CNL3hUibrbvcwtp9nbyMwzQp3Tbi1ygZQaPoigDhCf8XUjMmGK2NbnB2kLXPYg99Lp6e3iki318sdWcFN3q'); + expect(DIP15ExtPrivKey_0.privateKey.toString()).to.equal('fac40790776d171ee1db90899b5eb2df2f7d2aaf35ad56f07ffb8ed2c57f8e60') + expect(DIP15ExtPrivKey_0.publicKey.toString()).to.equal('038030c88ab0106e1f4af3b939db2bafc56f892554106f08da1ce1f9ef10f807bd') + + // This comes from the test factor of DIP-15 + const userAhash = "0xa11ce14f698b32e9bb306dba7bbbee831263dcf658abeebb39930460ead117e5"; + const userBhash = "0xb0b052ff075c5ca3c16c3e20e9ac8223834475cc1324ab07889cb24ce6a62793"; + const DIP15ExtKey_1 = derivableKeyChain.getDIP15ExtendedKey(userAhash, userBhash, 0, 0); + expect(DIP15ExtKey_1.privateKey.toString()).to.equal('60581b6dca8244d3fb3cfe619b5a22277e5423b01e5285f356981f247e0f4a60') + expect(DIP15ExtKey_1.publicKey.toString()).to.equal('03deaac00f721151307fbc7bf80d7b8afab98c1f026d67e5f56b21e2013f551ce6') + }); + + it('should derive from hardened path and get address', () => { + const hardenedHDKey = derivableKeyChain.getHardenedBIP44HDKey(); + const pk2 = derivableKeyChain.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); + // m/44'/1'/0'/0/0 (this is first external address of the account 0) + const address = new Dashcore.Address(derivedPk.publicKey.toAddress()).toString(); + expect(address).to.equal('yNfUebksUc5HoSfg8gv98ruC3jUNJUM8pT'); + }); + + it('should get hardened DIP9FeatureHDKey', function () { + const hardenedHDKey = derivableKeyChain.getHardenedDIP9FeatureHDKey(); + const pk2 = derivableKeyChain.getForPath(`m/9'/1'`).key; + expect(pk2.toString()).to.equal(hardenedHDKey.toString()); + expect(hardenedHDKey.toString()).to.deep.equal('tprv8fBJjWoGgCpGRCbyzE9RUA59rmoN1RUijhLnXGL4VHnLxvSe523yVg4GrGzbR6TyXtdynAEh5z8UX55EXt2Cb3xjvrsx2PgTY9BHxzFVkWn'); + }); + + it('should get key for path using the HDPrivateKey', () => { + const derivableKeyChain2 = new DerivableKeyChain({ HDPrivateKey: mnemonicToHDPrivateKey(mnemonic, 'testnet') }); + const keyForChild = derivableKeyChain2.getForPath('m/0').key; + expect(keyForChild.toString()).to.equal(expectedKeyForChild_0); + }); + + it('should mark address watched and get watched addresses', function () { + const key0 = derivableKeyChain.getForPath('m/0'); + derivableKeyChain.getForPath('m/0').isWatched = true + key0.isWatched = true; + derivableKeyChain.getForPath('m/1', { isWatched: false }); + derivableKeyChain.getForPath('m/2', { isWatched: true }); + + const watchedAddresses = derivableKeyChain.getWatchedAddresses(); + let expectedWatchedAddresses = [ + derivableKeyChain.getForPath('m/0').address.toString(), + derivableKeyChain.getForPath('m/2').address.toString() + ]; + expect(watchedAddresses).to.deep.equal(expectedWatchedAddresses); + }); + + it('should get watched addresses', function () { + derivableKeyChain.getForPath('m/1').isWatched = true + const watchedAddresses = derivableKeyChain.getWatchedAddresses(); + const expectedWatchedAddresses = [ + 'ybQDfNwiDjk8ZH5UUmHQzAMEmjbrbK5dAj', + 'yhFX5rseJPitV45HUCaa9haeGHtLuooBaq', + 'yhqxsmYk6jfoGWf1hJKq7d4U2cGHCgzpFU' + ] + expect(watchedAddresses).to.deep.equal(expectedWatchedAddresses); + }); + + it('should remove an address from watched addresses', function () { + derivableKeyChain.getForPath('m/0', { isWatched: false }); + derivableKeyChain.getForPath('m/1'); + const data2 = derivableKeyChain.getForPath('m/2'); + data2.isWatched = false; + + expect(derivableKeyChain.getWatchedAddresses().length).to.equal(1); + }); + + it('should get address for path', function (){ + const address0_1 = derivableKeyChain.getForPath('m/1').address; + expect(address0_1.toString()).to.equal('yhFX5rseJPitV45HUCaa9haeGHtLuooBaq') + }) + + it('should mark address as used', function () { + const address0_0 = derivableKeyChain.getForPath('m/0').address; + derivableKeyChain.markAddressAsUsed(address0_0); + expect(derivableKeyChain.issuedPaths.get('m/0').isUsed).to.equal(true) + }); +}); + +describe('DerivableKeyChain - HDPublicKey', function suite(){ + let hdpubDerivableKeyChain; + it('should initiate from a HDPublicKey', function () { + hdpubDerivableKeyChain = new DerivableKeyChain({ + 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(hdpubDerivableKeyChain.network.toString()).to.equal('livenet'); + expect(hdpubDerivableKeyChain.keyChainId).to.equal('kc5059442d66'); + expect(hdpubDerivableKeyChain.getRootKey().toString()).to.equal(hdPublicKey); + }); + + it('should derivate', function () { + const key0_1 = hdpubDerivableKeyChain.getForPath('m/1').key; + expect(key0_1.publicKey.toAddress(hdpubDerivableKeyChain.network).toString()).to.equal('XoL5LcBiDWcj6L7fFwytsFoX5Vz7BVXw9w') + }); + + it('should get address for path', function (){ + const address0_1 = hdpubDerivableKeyChain.getForPath('m/2').address; + expect(address0_1.toString()).to.equal('XwAzpxQKbgebaLiadq1c6rDeFJ4FKPUufy') + }) +}) + +describe('DerivableKeyChain - single privateKey', function suite() { + this.timeout(10000); + + it('should correctly throw errors out when not a HDPublicKey (privateKey)', () => { + const privateKey = Dashcore.PrivateKey().toString(); + const network = 'livenet'; + const pkDerivableKeyChain = new DerivableKeyChain({ privateKey, network }); + expect(pkDerivableKeyChain.network).to.equal(network); + expect(pkDerivableKeyChain.rootKeyType).to.equal('privateKey'); + expect(pkDerivableKeyChain.rootKey.toString()).to.equal(privateKey); + + const expectedException1 = 'Wallet is not loaded from a mnemonic or a HDPrivateKey, impossible to derivate keys for path m/0'; + expect(() => pkDerivableKeyChain.getForPath('m/0')).to.throw(expectedException1); + }); + + it('should get private key', () => { + const privateKey = Dashcore.PrivateKey().toString(); + const pkDerivableKeyChain = new DerivableKeyChain({ privateKey, network: 'livenet' }); + expect(pkDerivableKeyChain.getRootKey().toString()).to.equal(privateKey); + expect(pkDerivableKeyChain.rootKey.toString()).to.equal(privateKey); + }); +}); diff --git a/packages/wallet-lib/src/types/DerivableKeyChain/methods/getDIP15ExtendedKey.js b/packages/wallet-lib/src/types/DerivableKeyChain/methods/getDIP15ExtendedKey.js new file mode 100644 index 00000000000..b65dc10d7da --- /dev/null +++ b/packages/wallet-lib/src/types/DerivableKeyChain/methods/getDIP15ExtendedKey.js @@ -0,0 +1,26 @@ +/** + * Return the extended key of the relationship between two dashpay contacts. + * @param userUniqueId - Current userID + * @param contactUniqueId - Contact userID + * @param index - the key index. + * @param accountIndex[=0] - the internal wallet account from which derivation is done + * @param type {HDPrivateKey|HDPublicKey} [type=HDPrivateKey] - set the type of returned keys + * @return {HDPrivateKey|HDPublicKey} + */ +function getDIP15ExtendedKey(userUniqueId, contactUniqueId, index = 0, accountIndex = 0, type = 'HDPrivateKey') { + 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'); + + // Require a HDPrivateKey for hardened derivation + const extendedPrivateKey = this + .getHardenedDIP15AccountKey(accountIndex, 'HDPrivateKey') + .deriveChild((userUniqueId), true) + .deriveChild((contactUniqueId), true) + .deriveChild(index, false); + + return (type === 'HDPublicKey' ? extendedPrivateKey.hdPublicKey : extendedPrivateKey); +} + +module.exports = getDIP15ExtendedKey; diff --git a/packages/wallet-lib/src/types/DerivableKeyChain/methods/getFirstUnusedAddress.js b/packages/wallet-lib/src/types/DerivableKeyChain/methods/getFirstUnusedAddress.js new file mode 100644 index 00000000000..ab7f3fefeeb --- /dev/null +++ b/packages/wallet-lib/src/types/DerivableKeyChain/methods/getFirstUnusedAddress.js @@ -0,0 +1,12 @@ +function getFirstUnusedAddress() { + const allUnused = this.getIssuedPaths() + .filter((path) => path.isUsed === false); + + const firstUnused = allUnused.slice(0, 1)[0]; + + return { + path: firstUnused.path, + address: firstUnused.address.toString(), + }; +} +module.exports = getFirstUnusedAddress; diff --git a/packages/wallet-lib/src/types/DerivableKeyChain/methods/getForAddress.js b/packages/wallet-lib/src/types/DerivableKeyChain/methods/getForAddress.js new file mode 100644 index 00000000000..4b8a01ddf04 --- /dev/null +++ b/packages/wallet-lib/src/types/DerivableKeyChain/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/packages/wallet-lib/src/types/DerivableKeyChain/methods/getForPath.js b/packages/wallet-lib/src/types/DerivableKeyChain/methods/getForPath.js new file mode 100644 index 00000000000..1388abdf05f --- /dev/null +++ b/packages/wallet-lib/src/types/DerivableKeyChain/methods/getForPath.js @@ -0,0 +1,42 @@ +const logger = require('../../../logger'); + +function getForPath(path, opts = {}) { + if (path === undefined) throw new Error('Expect a valid path to derivate'); + 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), + issuedTime: +new Date(), + }; + + this.issuedPaths.set(stringifiedPath, data); + + return data; +} + +module.exports = getForPath; diff --git a/packages/wallet-lib/src/types/DerivableKeyChain/methods/getHardenedBIP44HDKey.js b/packages/wallet-lib/src/types/DerivableKeyChain/methods/getHardenedBIP44HDKey.js new file mode 100644 index 00000000000..48da61c1616 --- /dev/null +++ b/packages/wallet-lib/src/types/DerivableKeyChain/methods/getHardenedBIP44HDKey.js @@ -0,0 +1,11 @@ +const { BIP44_TESTNET_ROOT_PATH, BIP44_LIVENET_ROOT_PATH } = require('../../../CONSTANTS'); + +/** + * Return a safier root keys to derivate from + * @return {HDPrivateKey|HDPublicKey} + */ +function getHardenedBIP44HDKey() { + const pathRoot = (this.network.toString() === 'testnet') ? BIP44_TESTNET_ROOT_PATH : BIP44_LIVENET_ROOT_PATH; + return this.getForPath(pathRoot).key; +} +module.exports = getHardenedBIP44HDKey; diff --git a/packages/wallet-lib/src/types/DerivableKeyChain/methods/getHardenedDIP15AccountKey.js b/packages/wallet-lib/src/types/DerivableKeyChain/methods/getHardenedDIP15AccountKey.js new file mode 100644 index 00000000000..c01f240fc85 --- /dev/null +++ b/packages/wallet-lib/src/types/DerivableKeyChain/methods/getHardenedDIP15AccountKey.js @@ -0,0 +1,14 @@ +/** + * Return a safier root path to derivate from + * @param {number} [accountIndex=0] - set the account index + * @param {HDPrivateKey|HDPublicKey} [type=HDPrivateKey] - set the type of returned keys + * @return {HDPrivateKey|HDPublicKey} + */ +function getHardenedDIP15AccountKey(accountIndex = 0, type = 'HDPrivateKey') { + const hardenedFeatureRootKey = this.getHardenedDIP9FeatureHDKey(type); + + // Feature is set to 15' for all DashPay Incoming Funds derivation paths (see DIP15). + const featureKey = hardenedFeatureRootKey.deriveChild(15, true); + return featureKey.deriveChild(accountIndex, true); +} +module.exports = getHardenedDIP15AccountKey; diff --git a/packages/wallet-lib/src/types/DerivableKeyChain/methods/getHardenedDIP9FeatureHDKey.js b/packages/wallet-lib/src/types/DerivableKeyChain/methods/getHardenedDIP9FeatureHDKey.js new file mode 100644 index 00000000000..422c9fab667 --- /dev/null +++ b/packages/wallet-lib/src/types/DerivableKeyChain/methods/getHardenedDIP9FeatureHDKey.js @@ -0,0 +1,11 @@ +const { DIP9_LIVENET_ROOT_PATH, DIP9_TESTNET_ROOT_PATH } = require('../../../CONSTANTS'); + +/** + * Return a safier root path to derivate from + * @return {HDPrivateKey|HDPublicKey} + */ +function getHardenedDIP9FeatureHDKey() { + const pathRoot = (this.network.toString() === 'testnet') ? DIP9_TESTNET_ROOT_PATH : DIP9_LIVENET_ROOT_PATH; + return this.getForPath(pathRoot).key; +} +module.exports = getHardenedDIP9FeatureHDKey; diff --git a/packages/wallet-lib/src/types/DerivableKeyChain/methods/getIssuedPaths.js b/packages/wallet-lib/src/types/DerivableKeyChain/methods/getIssuedPaths.js new file mode 100644 index 00000000000..324750853ba --- /dev/null +++ b/packages/wallet-lib/src/types/DerivableKeyChain/methods/getIssuedPaths.js @@ -0,0 +1,5 @@ +function getWatchedAddresses() { + return [...this.issuedPaths.values()]; +} + +module.exports = getWatchedAddresses; diff --git a/packages/wallet-lib/src/types/DerivableKeyChain/methods/getRootKey.js b/packages/wallet-lib/src/types/DerivableKeyChain/methods/getRootKey.js new file mode 100644 index 00000000000..f2b54723a48 --- /dev/null +++ b/packages/wallet-lib/src/types/DerivableKeyChain/methods/getRootKey.js @@ -0,0 +1,5 @@ +function getRootKey() { + return this.rootKey; +} + +module.exports = getRootKey; diff --git a/packages/wallet-lib/src/types/DerivableKeyChain/methods/getWatchedAddresses.js b/packages/wallet-lib/src/types/DerivableKeyChain/methods/getWatchedAddresses.js new file mode 100644 index 00000000000..8154329119a --- /dev/null +++ b/packages/wallet-lib/src/types/DerivableKeyChain/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/packages/wallet-lib/src/types/DerivableKeyChain/methods/markAddressAsUsed.js b/packages/wallet-lib/src/types/DerivableKeyChain/methods/markAddressAsUsed.js new file mode 100644 index 00000000000..cecf2922e1f --- /dev/null +++ b/packages/wallet-lib/src/types/DerivableKeyChain/methods/markAddressAsUsed.js @@ -0,0 +1,17 @@ +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(); + } + + return false; +} +module.exports = markAddressAsUsed; diff --git a/packages/wallet-lib/src/types/DerivableKeyChain/methods/maybeLookAhead.js b/packages/wallet-lib/src/types/DerivableKeyChain/methods/maybeLookAhead.js new file mode 100644 index 00000000000..fd3841f40bd --- /dev/null +++ b/packages/wallet-lib/src/types/DerivableKeyChain/methods/maybeLookAhead.js @@ -0,0 +1,82 @@ +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)); + + 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; + }); + }); + + 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 timeNow = +new Date(); + const pathData = this.getForPath(`${basePath}/${index}`, { isWatched }); + if (pathData.issuedTime >= timeNow) { + generatedPaths.push(pathData); + } + } + } + } + }); + return generatedPaths; +} +module.exports = maybeLookAhead; diff --git a/packages/wallet-lib/src/types/DerivableKeyChain/methods/sign.js b/packages/wallet-lib/src/types/DerivableKeyChain/methods/sign.js new file mode 100644 index 00000000000..b10b561f2c2 --- /dev/null +++ b/packages/wallet-lib/src/types/DerivableKeyChain/methods/sign.js @@ -0,0 +1,29 @@ +const { + crypto, Transaction, Message, +} = require('@dashevo/dashcore-lib'); + +/** + * Allow to sign any transaction or a transition object from a valid privateKeys list + * @param {Transaction|any} object + * @param {[PrivateKey]} privateKeys + * @param {number} [sigType=crypto.Signature.SIGHASH_ALL] + */ +function sign(object, privateKeys, sigType = crypto.Signature.SIGHASH_ALL) { + const handledTypes = [Transaction.name, Transaction.Payload.SubTxRegisterPayload, Message.name]; + if (!privateKeys) throw new Error('Require one or multiple privateKeys to sign'); + if (!object) throw new Error('Nothing to sign'); + if (!handledTypes.includes(object.constructor.name)) { + throw new Error(`Keychain sign : Unhandled object of type ${object.constructor.name}`); + } + const obj = object.sign(privateKeys, sigType); + + if (obj.isFullySigned && !obj.isFullySigned()) { + throw new Error('Not fully signed transaction'); + } + if (object.constructor.name === 'Message') { + // When signed, message are in string form. + return Message(obj); + } + return obj; +} +module.exports = sign; diff --git a/packages/wallet-lib/src/types/Identities/methods/getIdentityIds.spec.js b/packages/wallet-lib/src/types/Identities/methods/getIdentityIds.spec.js index df90868e4c2..df2e887176b 100644 --- a/packages/wallet-lib/src/types/Identities/methods/getIdentityIds.spec.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/KeyChain/KeyChain.d.ts b/packages/wallet-lib/src/types/KeyChain/KeyChain.d.ts index 0c1ee65a322..0d774d60b6f 100644 --- a/packages/wallet-lib/src/types/KeyChain/KeyChain.d.ts +++ b/packages/wallet-lib/src/types/KeyChain/KeyChain.d.ts @@ -3,48 +3,46 @@ import {HDPrivateKey, HDPublicKey} from "@dashevo/dashcore-lib"; import {Transaction} from "@dashevo/dashcore-lib/typings/transaction/Transaction"; export declare namespace KeyChain { - interface IKeyChainOptions { - network?: Network; - keys?: [Keys] - } + interface IKeyChainOptions { + network?: Network; + keys?: [Keys] + } } export declare class KeyChain { - constructor(options?: KeyChain.IKeyChainOptions); - network: Network; - keys: [Keys]; - - type: HDKeyTypesParam|PrivateKeyTypeParam; - HDPrivateKey?: HDPrivateKey; - privateKey?: PrivateKey; - - generateKeyForChild(index: number, type?: HDKeyTypesParam): HDPrivateKey|HDPublicKey; - generateKeyForPath(path: string, type?: HDKeyTypesParam): HDPrivateKey|HDPublicKey; - - getDIP15ExtendedKey(userUniqueId: string, contactUniqueId: string, index?: number, accountIndex?: number, type?: HDKeyTypesParam): HDKeyTypes; - getHardenedDIP15AccountKey(index?: number, type?: HDKeyTypesParam): HDKeyTypes; - getHardenedBIP44HDKey(type?: HDKeyTypesParam): HDKeyTypes; - getHardenedDIP9FeatureHDKey(type?: HDKeyTypesParam): HDKeyTypes; - getKeyForChild(index: number, type?: HDKeyTypesParam): HDKeyTypes; - getKeyForPath(path: string, type?: HDKeyTypesParam): HDKeyTypes; - getPrivateKey(): PrivateKey; - - sign(object: Transaction|any, privateKeys:[PrivateKey], sigType: number): any; + constructor(options?: KeyChain.IKeyChainOptions); + network: Network; + keys: [Keys]; + + type: HDKeyTypesParam|PrivateKeyTypeParam; + HDPrivateKey?: HDPrivateKey; + privateKey?: PrivateKey; + + generateKeyForChild(index: number, type?: HDKeyTypesParam): HDPrivateKey|HDPublicKey; + generateKeyForPath(path: string, type?: HDKeyTypesParam): HDPrivateKey|HDPublicKey; + + getDIP15ExtendedKey(userUniqueId: string, contactUniqueId: string, index?: number, accountIndex?: number, type?: HDKeyTypesParam): HDKeyTypes; + getHardenedDIP15AccountKey(index?: number, type?: HDKeyTypesParam): HDKeyTypes; + getHardenedBIP44HDKey(type?: HDKeyTypesParam): HDKeyTypes; + getHardenedDIP9FeatureHDKey(type?: HDKeyTypesParam): HDKeyTypes; + getKeyForChild(index: number, type?: HDKeyTypesParam): HDKeyTypes; + getKeyForPath(path: string, type?: HDKeyTypesParam): HDKeyTypes; + getPrivateKey(): PrivateKey; + + sign(object: Transaction|any, privateKeys:[PrivateKey], sigType: number): any; } type HDKeyTypes = HDPublicKey | HDPrivateKey; export declare enum HDKeyTypesParam { - HDPrivateKey="HDPrivateKey", - HDPublicKey="HDPrivateKey", + HDPrivateKey="HDPrivateKey", + HDPublicKey="HDPrivateKey", } export declare enum PrivateKeyTypeParam { - privateKey='privateKey' + privateKey='privateKey' } export declare interface Keys { - [path: string]: { - path: string - }; + [path: string]: { + path: string + }; } - - diff --git a/packages/wallet-lib/src/types/KeyChainStore/KeyChainStore.js b/packages/wallet-lib/src/types/KeyChainStore/KeyChainStore.js new file mode 100644 index 00000000000..fd5c24dfacb --- /dev/null +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/KeyChainStore/KeyChainStore.spec.js b/packages/wallet-lib/src/types/KeyChainStore/KeyChainStore.spec.js new file mode 100644 index 00000000000..bfb74a6cd81 --- /dev/null +++ b/packages/wallet-lib/src/types/KeyChainStore/KeyChainStore.spec.js @@ -0,0 +1,47 @@ +const {HDPrivateKey} = require("@dashevo/dashcore-lib"); +const KeyChainsStore = require('./KeyChainStore'); +const DerivableKeyChain = require("../DerivableKeyChain/DerivableKeyChain"); +const { expect } = require('chai'); + +describe('KeyChainStore', function suite() { + let keyChainsStore; + let hdPrivateKey = new HDPrivateKey() + let hdPublicKey = new HDPrivateKey().hdPublicKey + let keyChain = new DerivableKeyChain({HDPrivateKey: hdPrivateKey}) + let keyChainPublic = new DerivableKeyChain({HDPublicKey: hdPublicKey}) + let walletKeyChain = new DerivableKeyChain({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/packages/wallet-lib/src/types/KeyChainStore/methods/addKeyChain.js b/packages/wallet-lib/src/types/KeyChainStore/methods/addKeyChain.js new file mode 100644 index 00000000000..04e60a50739 --- /dev/null +++ b/packages/wallet-lib/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 ${keychain.keyChainId}`); + } + + this.keyChains.set(keychain.keyChainId, keychain); + + if (opts) { + if (opts.isMasterKeyChain && !this.masterKeyChainId) { + this.masterKeyChainId = keychain.keyChainId; + } + } +} + +module.exports = addKeyChain; diff --git a/packages/wallet-lib/src/types/KeyChainStore/methods/getKeyChain.js b/packages/wallet-lib/src/types/KeyChainStore/methods/getKeyChain.js new file mode 100644 index 00000000000..d964ba395ee --- /dev/null +++ b/packages/wallet-lib/src/types/KeyChainStore/methods/getKeyChain.js @@ -0,0 +1,5 @@ +function getKeyChain(keyChainId) { + return this.keyChains.get(keyChainId); +} + +module.exports = getKeyChain; diff --git a/packages/wallet-lib/src/types/KeyChainStore/methods/getKeyChains.js b/packages/wallet-lib/src/types/KeyChainStore/methods/getKeyChains.js new file mode 100644 index 00000000000..4e0e16160cd --- /dev/null +++ b/packages/wallet-lib/src/types/KeyChainStore/methods/getKeyChains.js @@ -0,0 +1,5 @@ +function getKeyChains() { + return Array.from(this.keyChains.values()); +} + +module.exports = getKeyChains; diff --git a/packages/wallet-lib/src/types/KeyChainStore/methods/getMasterKeyChain.js b/packages/wallet-lib/src/types/KeyChainStore/methods/getMasterKeyChain.js new file mode 100644 index 00000000000..512d6a28133 --- /dev/null +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/KeyChainStore/methods/makeChildKeyChainStore.js b/packages/wallet-lib/src/types/KeyChainStore/methods/makeChildKeyChainStore.js new file mode 100644 index 00000000000..383baee3ce0 --- /dev/null +++ b/packages/wallet-lib/src/types/KeyChainStore/methods/makeChildKeyChainStore.js @@ -0,0 +1,19 @@ +const DerivableKeyChain = require('../../DerivableKeyChain/DerivableKeyChain'); +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 DerivableKeyChain(keyChainOpts); + childKeyChainStore.addKeyChain(childKeyChain, { isMasterKeyChain: true }); + return childKeyChainStore; +} + +module.exports = makeChildKeyChainStore; From 306deaddafaf97fa35d85a99496bc305540813d7 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Tue, 8 Mar 2022 16:09:36 +0100 Subject: [PATCH 2/8] feat(wallet-lib)!: storage layer refactoring (#232) Co-authored-by: Igor Markin --- .../js-dash-sdk/src/SDK/Client/Client.spec.ts | 4 +- .../Platform/methods/identities/register.ts | 9 +- packages/js-dash-sdk/src/SDK/SDK.ts | 2 +- .../createIdentityFixtureInAccount.ts | 7 +- .../createTransactionFixtureInAccount.ts | 4 +- .../chains/for_wallet_c922713eac.json | 1087 +++++++++++++++++ .../fixtures/chains/testnet/blockheaders.json | 12 + .../fixtures/chains/testnet/transactions.json | 14 + .../getFixtureAccountWithStorage.js | 32 + .../2a331817b9d6bf85100ef0/wallet-store.json | 15 + .../categorizeTransactions.expectedResults.js | 2 +- .../getFixtureAccountWithStorage.js | 33 + .../transactions-with-metadata.json | 44 +- .../apart-trip-dignity/wallet-store.json | 116 ++ .../fixtures/wallets/c922713eac.json | 97 ++ packages/wallet-lib/src/CONSTANTS.js | 3 +- .../src/errors/InjectionToPluginUnallowed.js | 4 +- packages/wallet-lib/src/index.d.ts | 22 +- packages/wallet-lib/src/index.js | 16 +- .../src/plugins/Plugins/ChainPlugin.js | 18 +- .../wallet-lib/src/plugins/StandardPlugin.js | 2 +- .../src/plugins/Workers/IdentitySyncWorker.js | 14 +- .../TransactionSyncStreamWorker.js | 28 +- .../handlers/onStreamError.js | 1 + .../methods/getAddressesToSync.js | 16 +- .../methods/getAddressesToSync.spec.js | 34 +- .../methods/getLastSyncedBlockHeight.js | 8 +- .../methods/handleTransactionFromStream.js | 4 +- .../methods/processChunks.js | 20 +- .../methods/setLastSyncedBlockHeight.js | 14 +- .../test/mocks/createTransactionInAccount.js | 4 +- .../src/test/mocks/mockAccountWithStorage.js | 32 + .../wallet-lib/src/types/Account/Account.d.ts | 4 +- .../wallet-lib/src/types/Account/Account.js | 81 +- .../src/types/Account/Account.spec.js | 27 +- .../src/types/Account/_initializeAccount.js | 57 +- .../src/types/Account/_sortPlugins.spec.js | 8 +- .../Account/methods/broadcastTransaction.js | 48 +- .../methods/broadcastTransaction.spec.js | 52 +- .../Account/methods/createTransaction.js | 2 +- .../Account/methods/createTransaction.spec.js | 30 +- .../src/types/Account/methods/encrypt.spec.js | 2 +- .../types/Account/methods/generateAddress.js | 72 +- .../src/types/Account/methods/getAddress.js | 46 +- .../src/types/Account/methods/getAddresses.js | 42 +- .../types/Account/methods/getBalance.spec.js | 42 - .../Account/methods/getConfirmedBalance.js | 11 +- .../methods/getConfirmedBalance.spec.js | 36 + .../types/Account/methods/getPrivateKeys.js | 23 +- .../types/Account/methods/getTotalBalance.js | 11 +- .../Account/methods/getTotalBalance.spec.js | 30 + .../types/Account/methods/getTransaction.js | 15 +- .../Account/methods/getTransaction.spec.js | 141 +-- .../Account/methods/getTransactionHistory.js | 44 +- .../methods/getTransactionHistory.spec.js | 133 +- .../types/Account/methods/getTransactions.js | 22 +- .../Account/methods/getTransactions.spec.js | 292 +---- .../src/types/Account/methods/getUTXOS.js | 65 +- .../types/Account/methods/getUTXOS.spec.js | 177 +-- .../Account/methods/getUnconfirmedBalance.js | 10 +- .../methods/getUnconfirmedBalance.spec.js | 35 + .../types/Account/methods/getUnusedAddress.js | 56 +- .../Account/methods/getUnusedAddress.spec.js | 59 +- .../Account/methods/getUnusedIdentityIndex.js | 2 +- .../Account/methods/importBlockHeader.js | 16 +- .../Account/methods/importTransactions.js | 60 +- .../src/types/Account/methods/injectPlugin.js | 4 +- .../Account/methods/injectPlugin.spec.js | 10 +- .../src/types/Account/methods/sign.js | 2 +- .../src/types/Account/methods/sign.spec.js | 2 +- .../src/types/ChainStore/ChainStore.d.ts | 38 + .../src/types/ChainStore/ChainStore.js | 5 +- .../ChainStore/methods/considerTransaction.js | 14 + .../types/ChainStore/methods/importState.js | 2 +- .../DerivableKeyChain/DerivableKeyChain.d.ts | 24 +- .../DerivableKeyChain.spec.js | 55 +- .../src/types/Identities/Identities.js | 2 +- .../methods/getIdentityHDKeyById.js | 5 +- .../methods/getIdentityHDKeyById.spec.js | 8 +- .../Identities/methods/getIdentityIds.js | 5 +- .../Identities/methods/getIdentityIds.spec.js | 15 +- .../src/types/KeyChain/KeyChain.d.ts | 48 - .../wallet-lib/src/types/KeyChain/KeyChain.js | 52 - .../src/types/KeyChain/KeyChain.spec.js | 125 -- .../KeyChain/methods/generateKeyForChild.js | 19 - .../KeyChain/methods/generateKeyForPath.js | 19 - .../KeyChain/methods/getDIP15ExtendedKey.js | 26 - .../KeyChain/methods/getHardenedBIP44HDKey.js | 12 - .../methods/getHardenedDIP15AccountKey.js | 14 - .../methods/getHardenedDIP9FeatureHDKey.js | 12 - .../types/KeyChain/methods/getKeyForChild.js | 10 - .../types/KeyChain/methods/getKeyForPath.js | 29 - .../types/KeyChain/methods/getPrivateKey.js | 18 - .../src/types/KeyChain/methods/sign.js | 29 - .../types/KeyChainStore/KeyChainStore.d.ts | 20 + .../src/types/KeyChainStore/KeyChainStore.js | 2 - .../wallet-lib/src/types/Storage/.eslintrc | 5 - .../wallet-lib/src/types/Storage/Storage.d.ts | 127 -- .../wallet-lib/src/types/Storage/Storage.js | 83 +- .../src/types/Storage/Storage.spec.js | 106 -- .../src/types/Storage/initialStore.json | 5 - .../Storage/methods/addNewTxToAddress.js | 30 - .../Storage/methods/addNewTxToAddress.spec.js | 27 - .../src/types/Storage/methods/announce.js | 28 - .../types/Storage/methods/announce.spec.js | 6 - .../Storage/methods/calculateDuffBalance.js | 45 - .../src/types/Storage/methods/clearAll.js | 11 - .../types/Storage/methods/clearAll.spec.js | 25 - .../types/Storage/methods/configure.spec.js | 61 - .../types/Storage/methods/createAccount.js | 28 - .../src/types/Storage/methods/createChain.js | 25 - .../types/Storage/methods/createChain.spec.js | 31 - .../types/Storage/methods/createChainStore.js | 28 + .../Storage/methods/createSingleAddress.js | 27 - .../src/types/Storage/methods/createWallet.js | 24 - .../Storage/methods/createWallet.spec.js | 75 -- .../Storage/methods/createWalletStore.js | 10 + .../types/Storage/methods/exportAccounts.js | 8 - .../src/types/Storage/methods/exportChains.js | 3 - .../Storage/methods/exportTransactions.js | 3 - .../types/Storage/methods/exportWallets.js | 3 - .../types/Storage/methods/getBlockHeader.js | 13 - .../types/Storage/methods/getChainStore.js | 4 + .../Storage/methods/getIdentityIdByIndex.js | 11 - .../Storage/methods/getIndexedIdentityIds.js | 10 - .../types/Storage/methods/getInstantLock.js | 10 - .../src/types/Storage/methods/getStore.js | 8 - .../types/Storage/methods/getTransaction.js | 14 - .../Storage/methods/getTransaction.spec.js | 30 - .../Storage/methods/getTransactionMetadata.js | 14 - .../methods/getTransactionMetadata.spec.js | 31 - .../types/Storage/methods/getWalletStore.js | 5 + .../types/Storage/methods/importAccounts.js | 49 - .../Storage/methods/importAccounts.spec.js | 63 - .../types/Storage/methods/importAddress.js | 43 - .../Storage/methods/importAddress.spec.js | 18 - .../types/Storage/methods/importAddresses.js | 31 - .../Storage/methods/importAddresses.spec.js | 7 - .../Storage/methods/importBlockHeader.js | 30 - .../src/types/Storage/methods/importChains.js | 10 - .../Storage/methods/importInstantLock.js | 9 - .../Storage/methods/importSingleAddress.js | 28 - .../methods/importSingleAddress.spec.js | 8 - .../Storage/methods/importTransaction.js | 129 -- .../Storage/methods/importTransaction.spec.js | 407 ------ .../Storage/methods/importTransactions.js | 34 - .../methods/importTransactions.spec.js | 11 - .../Storage/methods/insertIdentityAtIndex.js | 25 - .../types/Storage/methods/rehydrateState.js | 114 +- .../Storage/methods/rehydrateState.spec.js | 226 ---- .../src/types/Storage/methods/saveState.js | 7 +- .../types/Storage/methods/saveState.spec.js | 8 - .../types/Storage/methods/searchAddress.js | 46 - .../Storage/methods/searchAddress.spec.js | 8 - .../methods/searchAddressWithTx.spec.js | 8 - .../Storage/methods/searchAddressesWithTx.js | 36 - .../Storage/methods/searchBlockHeader.js | 25 - .../Storage/methods/searchBlockHeader.spec.js | 53 - .../Storage/methods/searchTransaction.js | 22 - .../Storage/methods/searchTransaction.spec.js | 31 - .../methods/searchTransactionMetadata.js | 22 - .../methods/searchTransactionMetadata.spec.js | 37 - .../src/types/Storage/methods/searchWallet.js | 18 - .../Storage/methods/searchWallet.spec.js | 33 - .../types/Storage/methods/startWorker.spec.js | 53 - .../types/Storage/methods/updateAddress.js | 123 -- .../Storage/methods/updateAddress.spec.js | 35 - .../Storage/methods/updateTransaction.js | 18 - .../Storage/methods/updateTransaction.spec.js | 70 -- .../wallet-lib/src/types/Wallet/Wallet.js | 20 +- .../src/types/Wallet/Wallet.spec.js | 17 +- .../src/types/Wallet/methods/exportWallet.js | 4 +- .../types/Wallet/methods/exportWallet.spec.js | 6 +- .../src/types/Wallet/methods/fromAddress.js | 10 +- .../types/Wallet/methods/fromAddress.spec.js | 14 +- .../types/Wallet/methods/fromHDPrivateKey.js | 8 +- .../Wallet/methods/fromHDPrivateKey.spec.js | 7 +- .../types/Wallet/methods/fromHDPublicKey.js | 8 +- .../Wallet/methods/fromHDPublicKey.spec.js | 49 +- .../src/types/Wallet/methods/fromMnemonic.js | 16 +- .../types/Wallet/methods/fromMnemonic.spec.js | 47 +- .../types/Wallet/methods/fromPrivateKey.js | 10 +- .../Wallet/methods/fromPrivateKey.spec.js | 12 +- .../src/types/Wallet/methods/fromPublicKey.js | 10 +- .../Wallet/methods/fromPublicKey.spec.js | 12 +- .../src/types/Wallet/methods/fromSeed.js | 4 +- .../src/types/Wallet/methods/fromSeed.spec.js | 14 +- .../types/Wallet/methods/sweepWallet.spec.js | 12 +- .../src/types/WalletStore/WalletStore.d.ts | 26 + .../src/utils/calculateDuffBalance.js | 28 + .../src/utils/categorizeTransactions.js | 95 +- .../src/utils/categorizeTransactions.spec.js | 14 +- .../wallet-lib/src/utils/classifyAddresses.js | 63 +- .../src/utils/classifyAddresses.spec.js | 526 +------- packages/wallet-lib/src/utils/index.js | 2 + .../wallet-lib/tests/functional/wallet.js | 6 +- .../TransactionSyncStreamWorker.spec.js | 85 +- .../tests/integration/types/Account.spec.js | 12 +- 198 files changed, 2948 insertions(+), 5153 deletions(-) create mode 100644 packages/wallet-lib/fixtures/chains/for_wallet_c922713eac.json create mode 100644 packages/wallet-lib/fixtures/chains/testnet/blockheaders.json create mode 100644 packages/wallet-lib/fixtures/chains/testnet/transactions.json create mode 100644 packages/wallet-lib/fixtures/wallets/2a331817b9d6bf85100ef0/getFixtureAccountWithStorage.js create mode 100644 packages/wallet-lib/fixtures/wallets/2a331817b9d6bf85100ef0/wallet-store.json create mode 100644 packages/wallet-lib/fixtures/wallets/apart-trip-dignity/getFixtureAccountWithStorage.js create mode 100644 packages/wallet-lib/fixtures/wallets/apart-trip-dignity/wallet-store.json create mode 100644 packages/wallet-lib/fixtures/wallets/c922713eac.json create mode 100644 packages/wallet-lib/src/test/mocks/mockAccountWithStorage.js delete mode 100644 packages/wallet-lib/src/types/Account/methods/getBalance.spec.js create mode 100644 packages/wallet-lib/src/types/Account/methods/getConfirmedBalance.spec.js create mode 100644 packages/wallet-lib/src/types/Account/methods/getTotalBalance.spec.js create mode 100644 packages/wallet-lib/src/types/Account/methods/getUnconfirmedBalance.spec.js create mode 100644 packages/wallet-lib/src/types/ChainStore/ChainStore.d.ts delete mode 100644 packages/wallet-lib/src/types/KeyChain/KeyChain.d.ts delete mode 100644 packages/wallet-lib/src/types/KeyChain/KeyChain.js delete mode 100644 packages/wallet-lib/src/types/KeyChain/KeyChain.spec.js delete mode 100644 packages/wallet-lib/src/types/KeyChain/methods/generateKeyForChild.js delete mode 100644 packages/wallet-lib/src/types/KeyChain/methods/generateKeyForPath.js delete mode 100644 packages/wallet-lib/src/types/KeyChain/methods/getDIP15ExtendedKey.js delete mode 100644 packages/wallet-lib/src/types/KeyChain/methods/getHardenedBIP44HDKey.js delete mode 100644 packages/wallet-lib/src/types/KeyChain/methods/getHardenedDIP15AccountKey.js delete mode 100644 packages/wallet-lib/src/types/KeyChain/methods/getHardenedDIP9FeatureHDKey.js delete mode 100644 packages/wallet-lib/src/types/KeyChain/methods/getKeyForChild.js delete mode 100644 packages/wallet-lib/src/types/KeyChain/methods/getKeyForPath.js delete mode 100644 packages/wallet-lib/src/types/KeyChain/methods/getPrivateKey.js delete mode 100644 packages/wallet-lib/src/types/KeyChain/methods/sign.js create mode 100644 packages/wallet-lib/src/types/KeyChainStore/KeyChainStore.d.ts delete mode 100644 packages/wallet-lib/src/types/Storage/.eslintrc delete mode 100644 packages/wallet-lib/src/types/Storage/Storage.d.ts delete mode 100644 packages/wallet-lib/src/types/Storage/Storage.spec.js delete mode 100644 packages/wallet-lib/src/types/Storage/initialStore.json delete mode 100644 packages/wallet-lib/src/types/Storage/methods/addNewTxToAddress.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/addNewTxToAddress.spec.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/announce.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/announce.spec.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/calculateDuffBalance.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/clearAll.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/clearAll.spec.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/configure.spec.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/createAccount.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/createChain.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/createChain.spec.js create mode 100644 packages/wallet-lib/src/types/Storage/methods/createChainStore.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/createSingleAddress.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/createWallet.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/createWallet.spec.js create mode 100644 packages/wallet-lib/src/types/Storage/methods/createWalletStore.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/exportAccounts.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/exportChains.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/exportTransactions.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/exportWallets.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/getBlockHeader.js create mode 100644 packages/wallet-lib/src/types/Storage/methods/getChainStore.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/getIdentityIdByIndex.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/getIndexedIdentityIds.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/getInstantLock.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/getStore.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/getTransaction.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/getTransaction.spec.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/getTransactionMetadata.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/getTransactionMetadata.spec.js create mode 100644 packages/wallet-lib/src/types/Storage/methods/getWalletStore.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/importAccounts.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/importAccounts.spec.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/importAddress.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/importAddress.spec.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/importAddresses.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/importAddresses.spec.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/importBlockHeader.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/importChains.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/importInstantLock.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/importSingleAddress.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/importSingleAddress.spec.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/importTransaction.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/importTransaction.spec.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/importTransactions.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/importTransactions.spec.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/insertIdentityAtIndex.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/rehydrateState.spec.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/saveState.spec.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/searchAddress.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/searchAddress.spec.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/searchAddressWithTx.spec.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/searchAddressesWithTx.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/searchBlockHeader.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/searchBlockHeader.spec.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/searchTransaction.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/searchTransaction.spec.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/searchTransactionMetadata.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/searchTransactionMetadata.spec.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/searchWallet.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/searchWallet.spec.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/startWorker.spec.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/updateAddress.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/updateAddress.spec.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/updateTransaction.js delete mode 100644 packages/wallet-lib/src/types/Storage/methods/updateTransaction.spec.js create mode 100644 packages/wallet-lib/src/types/WalletStore/WalletStore.d.ts create mode 100644 packages/wallet-lib/src/utils/calculateDuffBalance.js diff --git a/packages/js-dash-sdk/src/SDK/Client/Client.spec.ts b/packages/js-dash-sdk/src/SDK/Client/Client.spec.ts index 8c8cf7817e0..6aec6cd2f06 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Client.spec.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Client.spec.ts @@ -37,7 +37,7 @@ describe('Dash - Client', function suite() { beforeEach(async function beforeEach() { testMnemonic = 'agree country attract master mimic ball load beauty join gentle turtle hover'; - testHDKey = "xprv9s21ZrQH143K4PgfRZPuYjYUWRZkGfEPuWTEUESMoEZLC274ntC4G49qxgZJEPgmujsmY52eVggtwZgJPrWTMXmbYgqDVySWg46XzbGXrSZ"; + testHDKey = "tprv8ZgxMBicQKsPeGi4CikhacVPz6UmErenu1PoD3S4XcEDSPP8auRaS8hG3DQtsQ2i9HACgohHwF5sgMVJNksoKqYoZbis8o75Pp1koCme2Yo"; client = new Client({ wallet: { @@ -145,7 +145,7 @@ describe('Dash - Client', function suite() { const importedIdentityIds = account.identities.getIdentityIds(); // Check that we've imported identities properly expect(importedIdentityIds.length).to.be.equal(accountIdentitiesCountBeforeTest + 1); - expect(importedIdentityIds[0]).to.be.equal(interceptedIdentityStateTransition.getIdentityId().toString()); + expect(importedIdentityIds[1]).to.be.equal(interceptedIdentityStateTransition.getIdentityId().toString()); }); it('should throw TransitionBroadcastError when transport resolves error', async () => { diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/register.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/register.ts index 69d89ba16eb..fc38f769a68 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/register.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/register.ts @@ -36,10 +36,11 @@ export default async function register( await broadcastStateTransition(this, identityCreateTransition); // If state transition was broadcast without any errors, import identity to the account - account.storage.insertIdentityIdAtIndex( - account.walletId, - identity.getId().toString(), - identityIndex, + account.storage + .getWalletStore(account.walletId) + .insertIdentityIdAtIndex( + identity.getId().toString(), + identityIndex, ); return identity; diff --git a/packages/js-dash-sdk/src/SDK/SDK.ts b/packages/js-dash-sdk/src/SDK/SDK.ts index 9d7a4957b10..82ae6401c4d 100644 --- a/packages/js-dash-sdk/src/SDK/SDK.ts +++ b/packages/js-dash-sdk/src/SDK/SDK.ts @@ -6,7 +6,7 @@ import { default as _DAPIClient } from '@dashevo/dapi-client'; import { Wallet as _Wallet, Account as _Account, - KeyChain as _KeyChain, + DerivableKeyChain as _KeyChain, CONSTANTS as _WalletLibCONSTANTS, EVENTS as _WalletLibEVENTS, utils as _WalletLibUtils, diff --git a/packages/js-dash-sdk/src/test/fixtures/createIdentityFixtureInAccount.ts b/packages/js-dash-sdk/src/test/fixtures/createIdentityFixtureInAccount.ts index 1f19dc70964..c631b97a100 100644 --- a/packages/js-dash-sdk/src/test/fixtures/createIdentityFixtureInAccount.ts +++ b/packages/js-dash-sdk/src/test/fixtures/createIdentityFixtureInAccount.ts @@ -4,7 +4,7 @@ const getIdentityFixture = require('@dashevo/dpp/lib/test/fixtures/getIdentityFi export function createIdentityFixtureInAccount(account) { const identityFixture = getIdentityFixture(); - const identityFixtureIndex = 10000; + const identityFixtureIndex = 0; const { privateKey: identityPrivateKey } = account.identities.getIdentityHDKeyByIndex(identityFixtureIndex, 0); identityFixture.publicKeys[0] = new IdentityPublicKey({ @@ -15,8 +15,9 @@ export function createIdentityFixtureInAccount(account) { securityLevel: IdentityPublicKey.SECURITY_LEVELS.MASTER }); - account.storage.insertIdentityIdAtIndex( - account.walletId, + account.storage + .getWalletStore(account.walletId) + .insertIdentityIdAtIndex( identityFixture.getId().toString(), identityFixtureIndex, ); diff --git a/packages/js-dash-sdk/src/test/fixtures/createTransactionFixtureInAccount.ts b/packages/js-dash-sdk/src/test/fixtures/createTransactionFixtureInAccount.ts index a5f95a24139..6e88f507f0c 100644 --- a/packages/js-dash-sdk/src/test/fixtures/createTransactionFixtureInAccount.ts +++ b/packages/js-dash-sdk/src/test/fixtures/createTransactionFixtureInAccount.ts @@ -11,7 +11,7 @@ export async function createTransactionInAccount(account) { }]) .to(account.getAddress(10).address, 100000); - await account.importTransactions([walletTransaction.serialize(true)]); + await account.importTransactions([[walletTransaction.serialize(true)]]); return walletTransaction; -} \ No newline at end of file +} diff --git a/packages/wallet-lib/fixtures/chains/for_wallet_c922713eac.json b/packages/wallet-lib/fixtures/chains/for_wallet_c922713eac.json new file mode 100644 index 00000000000..fba5af71c98 --- /dev/null +++ b/packages/wallet-lib/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/packages/wallet-lib/fixtures/chains/testnet/blockheaders.json b/packages/wallet-lib/fixtures/chains/testnet/blockheaders.json new file mode 100644 index 00000000000..d58e0885f60 --- /dev/null +++ b/packages/wallet-lib/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/packages/wallet-lib/fixtures/chains/testnet/transactions.json b/packages/wallet-lib/fixtures/chains/testnet/transactions.json new file mode 100644 index 00000000000..5fd376a7a61 --- /dev/null +++ b/packages/wallet-lib/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/packages/wallet-lib/fixtures/wallets/2a331817b9d6bf85100ef0/getFixtureAccountWithStorage.js b/packages/wallet-lib/fixtures/wallets/2a331817b9d6bf85100ef0/getFixtureAccountWithStorage.js new file mode 100644 index 00000000000..79a622b07de --- /dev/null +++ b/packages/wallet-lib/fixtures/wallets/2a331817b9d6bf85100ef0/getFixtureAccountWithStorage.js @@ -0,0 +1,32 @@ +const walletStoreMock = require('./wallet-store.json'); +const chainStoreMock = require('./chain-store.json'); +const Storage = require('../../../src/types/Storage/Storage'); +const { KeyChainStore, DerivableKeyChain } = require('../../../src/index'); + +module.exports = (opts = {}) => { + const { walletId } = walletStoreMock; + + const mockedAccount = { + walletId, + index: 0, + storage: new Storage(), + accountPath: 'm/0', + network: 'testnet', + walletType: 'privateKey', + ...opts, + }; + + mockedAccount.keyChainStore = new KeyChainStore(); + mockedAccount.keyChainStore.addKeyChain(new DerivableKeyChain({ + privateKey: '2a331817b9d6bf85100ef05503d16f9f57c8855dbf13766b2f26c382b716d396', + lookAheadOpts: { + 'm/0': 1, + }, + }), { 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/packages/wallet-lib/fixtures/wallets/2a331817b9d6bf85100ef0/wallet-store.json b/packages/wallet-lib/fixtures/wallets/2a331817b9d6bf85100ef0/wallet-store.json new file mode 100644 index 00000000000..3c472465951 --- /dev/null +++ b/packages/wallet-lib/fixtures/wallets/2a331817b9d6bf85100ef0/wallet-store.json @@ -0,0 +1,15 @@ +{ + "walletId": "6101b44d50", + "state": { + "mnemonic": null, + "paths": { + "m/0": { + "path": "m/0", + "addresses": { + "0": "ycDeuTfs4U77bTb5cq17dame28zdWHVYfk" + } + } + }, + "identities": {} + } +} diff --git a/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/categorizeTransactions.expectedResults.js b/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/categorizeTransactions.expectedResults.js index 11f2d1f2c69..14c900525c4 100644 --- a/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/categorizeTransactions.expectedResults.js +++ b/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/categorizeTransactions.expectedResults.js @@ -187,7 +187,7 @@ const expectedResultTx9 = { } ], transaction: transactionsWithMetadataFixtures[10][0], - type: 'unknown', + type: 'account_transfer', blockHash: '0000016fb685b4b1efed743d2263de34a9f8323ed75e732654b1b951c5cb4dde', height: 558236, isInstantLocked: true, diff --git a/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/getFixtureAccountWithStorage.js b/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/getFixtureAccountWithStorage.js new file mode 100644 index 00000000000..46955463144 --- /dev/null +++ b/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/getFixtureAccountWithStorage.js @@ -0,0 +1,33 @@ +const walletStoreMock = require('./wallet-store.json'); +const chainStoreMock = require('./chain-store.json'); +const Storage = require('../../../src/types/Storage/Storage'); +const { KeyChainStore, DerivableKeyChain } = require('../../../src/index'); + +module.exports = (opts = {}) => { + const { walletId } = walletStoreMock; + + const mockedAccount = { + walletId, + index: 0, + storage: new Storage(), + accountPath: "m/44'/1'/0'", + network: 'testnet', + walletType: 'hdwallet', + ...opts, + }; + + mockedAccount.keyChainStore = new KeyChainStore(); + mockedAccount.keyChainStore.addKeyChain(new DerivableKeyChain({ + mnemonic: 'apart trip dignity try point rocket damp reflect raw ten normal young', + 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/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/transactions-with-metadata.json b/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/transactions-with-metadata.json index 5ae5f2850c2..2e6847d4b7c 100644 --- a/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/transactions-with-metadata.json +++ b/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/transactions-with-metadata.json @@ -4,8 +4,8 @@ { "blockHash": "00000c1e4556add15119392ed36ec6af2640569409abfa23a9972bc3be1b3717", "height": 558036, - "instantLocked": true, - "chainLocked": true + "isInstantLocked": true, + "isChainLocked": true } ], [ @@ -13,8 +13,8 @@ { "blockHash": "00000221952c2a60adcb929de837f659308cb5c6bb7783016479381fb550fbad", "height": 557481, - "instantLocked": true, - "chainLocked": true + "isInstantLocked": true, + "isChainLocked": true } ], [ @@ -22,8 +22,8 @@ { "blockHash": "000001deee9f99e8219a9abcaaea135dbaae8a9b0f1ea214e6b6a37a5c5b115d", "height": 555506, - "instantLocked": true, - "chainLocked": true + "isInstantLocked": true, + "isChainLocked": true } ], [ @@ -31,8 +31,8 @@ { "blockHash": "00000084b4d9e887a6ad3f37c576a17d79c35ec9301e55210eded519e8cdcd3a", "height": 558102, - "instantLocked": true, - "chainLocked": true + "isInstantLocked": true, + "isChainLocked": true } ], [ @@ -40,8 +40,8 @@ { "blockHash": "000000b6006c758eda23ec7e2a640a0bf2c6a0c44827be216faff6bf4fd388e8", "height": 555507, - "instantLocked": true, - "chainLocked": true + "isInstantLocked": true, + "isChainLocked": true } ], [ @@ -49,8 +49,8 @@ { "blockHash": "0000012cf6377c6cf2b317a4deed46573c09f04f6880dca731cc9ccea6691e19", "height": 555508, - "instantLocked": true, - "chainLocked": true + "isInstantLocked": true, + "isChainLocked": true } ], [ @@ -58,8 +58,8 @@ { "blockHash": "000001953ea0bbb8ad04a9a1a2a707fef207ad22a712d7d3c619f0f9b63fa98c", "height": 558229, - "instantLocked": true, - "chainLocked": true + "isInstantLocked": true, + "isChainLocked": true } ], [ @@ -67,8 +67,8 @@ { "blockHash": "000000dffb05c071a8c05082a475b7ce9c1e403f3b89895a6c448fe08535a5f5", "height": 558230, - "instantLocked": true, - "chainLocked": true + "isInstantLocked": true, + "isChainLocked": true } ], [ @@ -76,8 +76,8 @@ { "blockHash": "000000444b3f2f02085f8befe72da5442c865c290658766cf935e1a71a4f4ba7", "height": 558242, - "instantLocked": true, - "chainLocked": true + "isInstantLocked": true, + "isChainLocked": true } ], [ @@ -85,8 +85,8 @@ { "blockHash": "000001f9c5de4d2b258a975bfbf7b9a3346890af6389512bea3cb6926b9be330", "height": 558246, - "instantLocked": true, - "chainLocked": true + "isInstantLocked": true, + "isChainLocked": true } ], [ @@ -94,8 +94,8 @@ { "blockHash": "0000016fb685b4b1efed743d2263de34a9f8323ed75e732654b1b951c5cb4dde", "height": 558236, - "instantLocked": true, - "chainLocked": true + "isInstantLocked": true, + "isChainLocked": true } ] ] diff --git a/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/wallet-store.json b/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/wallet-store.json new file mode 100644 index 00000000000..e6858bf2dc3 --- /dev/null +++ b/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/wallet-store.json @@ -0,0 +1,116 @@ +{ + "walletId": "d6143ef4e6", + "state": { + "mnemonic": null, + "paths": { + "m/44'/1'/1'": { + "path": "m/44'/1'/1'", + "addresses": { + "m/0/0": "yYJmzWey5kNecAThet5BFxAga1F4b4DKQ2", + "m/0/1": "yNCqctyQaq51WU1hN5aNwsgMsZ5fRiB7GY", + "m/0/2": "yNPbYz5cZKw2EwxtkL3VSVzPi2FYp9VKjQ", + "m/0/3": "ybsGWzsnSCAZufgSeUjScVxqEdved99UM2", + "m/0/4": "yfNHuPojk8XKWP5nuueDptX4nM7qToudgx", + "m/0/5": "yXxLnDkk6s8h1PSnYaFM6MAyRarc1Kc1rY", + "m/0/6": "yipResSzN2zUvL7UYkmptKKmQTv7sNssRn", + "m/0/7": "yZPtNwimHdRiKYbNQW49qezw1Kc1YwUJeT", + "m/0/8": "yPMjYYfQbga2nBiuqqfUyX41U1vwRZ8fG8", + "m/0/9": "yLueLWWcLQsaXQ8D5o9tcyo8tfTxMWXvG4", + "m/0/10": "yN8gzgsc1RVjXThMQT5qZH2jjpnMymz6zP", + "m/0/11": "yPQLWBNwMdLxUW2oUwHGwQtfyYxD41BARJ", + "m/0/12": "yg5g2AfWFdwWexWGfbSXYbUHf1y5WWrFPs", + "m/0/13": "yWyABu4naV1Jzw7w9sn1gqhebPRSkCndsS", + "m/0/14": "ycuUPzUBjhKyUjezQR1LNot79a6C4aRLaR", + "m/0/15": "yQ7YjvAXgDAUCekveHVjr6NBveXrUemVno", + "m/0/16": "yi8bghcw627cMGpuH4bJqH6bqR5ywv1NLH", + "m/0/17": "yizHu8i2rfwzwBgnJ62s2WUe6wLoDjne6N", + "m/0/18": "yW1u3tySeUKAKJsz7sjZFyjUiTyKLB6xBv", + "m/0/19": "yNaSkdy1Q8JNubUdbLMGsGf7sTRofEJYZq", + "m/1/0": "yXMrw79LPgu78EJsfGGYpm6fXKc1EMnQ49", + "m/1/1": "yh6Hcyipdvp6WJpQxjNbaXP4kzPQUJpY3n", + "m/1/2": "yNphpXuaTZRpU9FBh2W7NkUYcr3kBDE8me", + "m/1/3": "yXFppDT59xYD41mT2pmAdnvr7aZEFdgdrN", + "m/1/4": "yeKGAiiEHBGRujvLoYewA77jDDpeDamxvF", + "m/1/5": "yaxTG66CVzKgHhHZXojRHC9ztLTvz3fwdT", + "m/1/6": "yYw6qU7dwGoELZkSTj3oSKRpM4U8qTMc1U", + "m/1/7": "yQE2MksEnSfbeNre19oja9Jj8tvpj64C5a", + "m/1/8": "yaRnvHo8oLvVmv46vMj5XPbDJouQSnmcLT", + "m/1/9": "yj5ofWf2uYQQkSavYm2WXgu1QkaZCyP3Cm", + "m/1/10": "yUCjGmEwrHJwNDrE1o2rMre6MkSbiE6yz7", + "m/1/11": "yfJzd1nE2rEqz5XEurD6vs4ykizwmw9xTv", + "m/1/12": "yUk8U3jRZMHKVTa1eFDEtZpa1G4E13FP4d", + "m/1/13": "yMr59YWQFCADq4FbWrtxDUtMwwshSrmAyK", + "m/1/14": "yetSehBupzGS9yps5ogqARUGmTMAs2xVcQ", + "m/1/15": "yNcESKLwriNrhM6EyoSpZEXrzdY3uht92T", + "m/1/16": "yN2FihGU7KdaEspp39bKrhsHypeyeYzoM2", + "m/1/17": "yirpWLxHuhwFzA6LfUPKUh1Ke9RB9BUjit", + "m/1/18": "yVDN66vvdshWdNzhUaQNB6xExAHkzs1zj8", + "m/1/19": "yPzofnEhRVfDisL2nCUJtAoSHkuyMirHZS", + "m/0/20": "yjG4jiCMNbx3MyFCAsaNaU29CBVmgPf8hS", + "m/0/21": "yWrEYTtydaZCyJoKE1vzX4e4RXDNPGdnw9", + "m/1/20": "yM7Smr8HEhbRW4CXga898brATrJSsk1QUh", + "m/1/21": "yUprvaE5KrKVPgABmGtxba9MmZqDzKQHom" + } + }, + "m/44'/1'/0'": { + "path": "m/44'/1'/0'", + "addresses": { + "m/0/0": "yTwEca67QSkZ6axGdpNFzWPaCj8zqYybY7", + "m/0/1": "yercyhdN9oEkZcB9BsW5ktFaDxFEuK6qXN", + "m/0/2": "ygk3GCSba2J3L9G665Snozhj9HSkh5ByVE", + "m/0/3": "ybuL6rM6dgrKzCg8s99f3jxGuv5oz5JcDA", + "m/0/4": "ygHAVkMtYSqoTWHebDv7qkhMV6dHyuRsp2", + "m/0/5": "yMLhEsiP2ajSh8STmXnNmkWXtoHsmawZxd", + "m/0/6": "yj8rRKATAUHcAgXvNZekob58xKm2oNyvhv", + "m/0/7": "yhaAB6e8m3F8zmGX7WAVYa6eEfmSrrnY8x", + "m/0/8": "yiXh4Yo5djG6QH8WzXkKm5EFzqLRJWakXz", + "m/0/9": "yQYv3Um6DsdtANo1ZPTUte75wAGMstLRex", + "m/0/10": "yiYPJmu7eEm1cXUNumQRdjv1fvPhsfgMS4", + "m/0/11": "yii4aUZhNfL6EWN9KAgAFrJzGJmqHnF4wx", + "m/0/12": "yLpTquSct2SGz2Ka45uTPDd81Kzro2Jt2k", + "m/0/13": "yMiJtpzb1Qthy9TGnavsf5NZ6EZZa4j9q3", + "m/0/14": "yacgSfW7RkwWakEZPg8USAVdzCypiG3vxS", + "m/0/15": "yVvrmoRPFLy6nUpCQBT8ZExxF5wF3DhiGU", + "m/0/16": "yaJf2aG6cFUtfv4o6TuEKsh5kr4xq5iAY4", + "m/0/17": "yfardJQ4ucgWLKQPaRHGMRMbSGm5H4ExJR", + "m/0/18": "yLSCqx7dcM5JKR2fG7vHbF2axMvuYqomaw", + "m/0/19": "yVij8XpJ78LM5hepSV1KF7T8vRpUEXCpK5", + "m/1/0": "yNDpPsJqXKM36zHSNEW7c1zSvNnrZ699FY", + "m/1/1": "yLk4Hw3w4zDudrDVP6W8J9TggkY57zQUki", + "m/1/2": "yirJaK8KCE5YAmwvLadizqFw3TCXqBuZXL", + "m/1/3": "yhdRfg5gNr587dtEC4YYMcSHmLVEGqqtHc", + "m/1/4": "yYwKP1FQae5kbjXkmuirGx6Xzf8NzHpLqW", + "m/1/5": "yX9gmsm8aSxZZjYhq4w35aidT7qbhcpNjU", + "m/1/6": "ybgXCTGMHEBbQeUib8c3xAjtGAc12XtWiU", + "m/1/7": "yS31WpdMT2b34uL9C37fbUoACHhiupHCyP", + "m/1/8": "yTSpFqRoX3vyN286AUtKKhgmX5Xb41YKQe", + "m/1/9": "yQU5YsqN7psTTASuYbcMi7N5nNZGaxXb2X", + "m/1/10": "yVGGFj9BLgEab5rucSGLC6UGVLQKB4U1wJ", + "m/1/11": "yQCh5yYCHEbJzgSJE9rdHiqXHidKm3kwr5", + "m/1/12": "yX7T3Ac3yaLk5CTC5UaR93Fc7SjYkeT5hn", + "m/1/13": "yXx3WXq8kYNPbYEg5U6bL8Xfih4g5LCYVo", + "m/1/14": "yYnLMTz3jCi2KKKNuo3TVkEAGyUFg8tgkJ", + "m/1/15": "yiKa1dA6B4tSTNJqJP9Y5pQfQEffnQQDTL", + "m/1/16": "yf7vcuDnE9DVhXdMfBMQQTEi43otYQzkWE", + "m/1/17": "yTmSmocwERCeRHqNNG5SbpYKUra1HTmj8m", + "m/1/18": "yivUe5NeJsGsREwPQZUGYaTSwWB3E1oLcz", + "m/1/19": "ygfsZojdfW9UjCRU4ra95Aq6YgCC7UqZFx", + "m/1/20": "yU9fdXaUVtefwDZvxjJAr9xj1z2MtYi34A", + "m/1/21": "yXgMN6FgrgZCnTN1vhoZMh8afKMBmi3JC4", + "m/1/22": "yiqaCbXscvR8y3VFYMzdaKCaAGuDuZxMzt", + "m/0/20": "ydJpjuJGossAZR7S5oS7cWvjygEwoj8Xwp", + "m/0/21": "yW3TmWnmhvpxRbgFcQ8oXqDRkn3RhRH6jj", + "m/0/22": "yRegVX85DThKRkH8C61TtRacfzrkiBfNy5", + "m/0/23": "yPtDCqDFRe1JuDp8pvdiEMQMz2erGwS3VG", + "m/0/24": "yM9pSw3L4oBfG7uQL5o522Hu3WTvy9awgZ", + "m/0/25": "yNC6qYJYungzuk5XUynDFKCn54Dy8ngox4", + "m/0/26": "yR5KcLr1bceLT4teTk2qoJx6pFLik1zyzL", + "m/0/27": "yRrKLGJa9JmdjBWvrHtedKjHTao6CRDTKf", + "m/0/28": "yP5dShZBydpbEzgGoXL6kcjv2KzervRrYB", + "m/1/23": "ydcgWDxheSxrLAqDBP4JXBndMCzUNf77gq" + } + } + }, + "identities": {} + } +} diff --git a/packages/wallet-lib/fixtures/wallets/c922713eac.json b/packages/wallet-lib/fixtures/wallets/c922713eac.json new file mode 100644 index 00000000000..3fac8277713 --- /dev/null +++ b/packages/wallet-lib/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/packages/wallet-lib/src/CONSTANTS.js b/packages/wallet-lib/src/CONSTANTS.js index 87edf80f4bd..838efb8ef15 100644 --- a/packages/wallet-lib/src/CONSTANTS.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/errors/InjectionToPluginUnallowed.js b/packages/wallet-lib/src/errors/InjectionToPluginUnallowed.js index 3d7e57f6785..6d6767f0d32 100644 --- a/packages/wallet-lib/src/errors/InjectionToPluginUnallowed.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/index.d.ts b/packages/wallet-lib/src/index.d.ts index 9ea8f7c7434..ab1ab579cc8 100644 --- a/packages/wallet-lib/src/index.d.ts +++ b/packages/wallet-lib/src/index.d.ts @@ -4,20 +4,24 @@ import { Account } from "./types/Account/Account"; import { Wallet } from "./types/Wallet/Wallet"; import { Identities } from "./types/Identities/Identities"; -import { KeyChain } from "./types/KeyChain/KeyChain"; +import { ChainStore } from "./types/ChainStore/ChainStore"; +import { DerivableKeyChain } from "./types/DerivableKeyChain/DerivableKeyChain"; +import { KeyChainStore } from "./types/KeyChainStore/KeyChainStore"; import CONSTANTS from "./CONSTANTS"; import EVENTS from "./EVENTS"; import utils from "./utils"; import plugins from "./plugins"; export { - Account, - Wallet, - KeyChain, - Identities, - EVENTS, - CONSTANTS, - utils, - plugins, + Account, + Wallet, + ChainStore, + DerivableKeyChain, + KeyChainStore, + Identities, + EVENTS, + CONSTANTS, + utils, + plugins, }; declare module '@dashevo/wallet-lib'; diff --git a/packages/wallet-lib/src/index.js b/packages/wallet-lib/src/index.js index 2fa9d8a9e30..71610cdc033 100644 --- a/packages/wallet-lib/src/index.js +++ b/packages/wallet-lib/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 DerivableKeyChain = require('./types/DerivableKeyChain/DerivableKeyChain'); +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, + DerivableKeyChain, + KeyChainStore, + Storage, + Wallet, + WalletStore, EVENTS, CONSTANTS, utils, diff --git a/packages/wallet-lib/src/plugins/Plugins/ChainPlugin.js b/packages/wallet-lib/src/plugins/Plugins/ChainPlugin.js index af0d1c45d43..eccbfd61873 100644 --- a/packages/wallet-lib/src/plugins/Plugins/ChainPlugin.js +++ b/packages/wallet-lib/src/plugins/Plugins/ChainPlugin.js @@ -33,15 +33,15 @@ 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); if (!this.isSubscribedToBlocks) { self.transport.on(EVENTS.BLOCK, async (ev) => { - // const { network } = self.storage.store.wallets[self.walletId]; 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 +50,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 +69,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/packages/wallet-lib/src/plugins/StandardPlugin.js b/packages/wallet-lib/src/plugins/StandardPlugin.js index 4b2ec8fdf8a..6a5c5a22bb8 100644 --- a/packages/wallet-lib/src/plugins/StandardPlugin.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/plugins/Workers/IdentitySyncWorker.js b/packages/wallet-lib/src/plugins/Workers/IdentitySyncWorker.js index d44d101d9de..920a847adb4 100644 --- a/packages/wallet-lib/src/plugins/Workers/IdentitySyncWorker.js +++ b/packages/wallet-lib/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 = []; @@ -100,11 +101,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/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js index 73d4bfda672..f623a080e7d 100644 --- a/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js +++ b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js @@ -3,7 +3,6 @@ const { } = require('@dashevo/dashcore-lib'); const GrpcError = require('@dashevo/grpc-common/lib/server/error/GrpcError'); const GrpcErrorCodes = require('@dashevo/grpc-common/lib/server/error/GrpcErrorCodes'); -const { WALLET_TYPES } = require('../../../CONSTANTS'); const sleep = require('../../../utils/sleep'); const Worker = require('../../Worker'); @@ -25,6 +24,7 @@ class TransactionSyncStreamWorker extends Worker { 'importBlockHeader', 'importInstantLock', 'storage', + 'keyChainStore', 'transport', 'walletId', 'getAddress', @@ -49,7 +49,7 @@ 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) => { @@ -135,11 +135,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()).state.blockHeight; this.setLastSyncedBlockHeight(bestBlockHeight); return; } @@ -238,25 +238,13 @@ class TransactionSyncStreamWorker extends Worker { } setLastSyncedBlockHash(hash) { - const { walletId } = this; - const accountsStore = this.storage.store.wallets[walletId].accounts; - - const accountStore = ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(this.walletType)) - ? accountsStore[this.BIP44PATH.toString()] - : accountsStore[this.index.toString()]; - - accountStore.blockHash = hash; - - return accountStore.blockHash; + const applicationStore = this.storage.application; + applicationStore.blockHash = hash; + 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/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/handlers/onStreamError.js b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/handlers/onStreamError.js index fb3577c98f9..624c5821010 100644 --- a/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/handlers/onStreamError.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js index 7e3f57e5301..e87d86e14aa 100644 --- a/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.spec.js b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.spec.js index 16597f841cb..b78d93b64dd 100644 --- a/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.spec.js +++ b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.spec.js @@ -1,6 +1,26 @@ const { expect } = require('chai'); const getAddressesToSync = require('./getAddressesToSync'); +const KeyChainStore = require("../../../../types/KeyChainStore/KeyChainStore"); +const DerivableKeyChain = require("../../../../types/DerivableKeyChain/DerivableKeyChain"); +const { HDPrivateKey, HDPublicKey, PrivateKey } = require("@dashevo/dashcore-lib"); + +const privateKey = new PrivateKey('ee56be968a42e58fda23b83da17f90e002cafbe35a702c2f5598b13fdaa238db', 'testnet') +const hdprivateKey1 = new HDPrivateKey("xprv9s21ZrQH143K39R9Ux28kCBUHcQFdBeVE2CXFVz6GnA2a6pqTsPhHR5QHtMP5ZTRpYkKqc9ifjkJ2V1h318qWsYgyxCBUurRdTNthjgwKMw", 'mainnet'); +const hdpublicKey1 = new HDPublicKey("xpub661MyMwAqRbcFhaucFQun3ivEyA5gy5NKnjr1xMUVkyqdF3VNNy3TLinwnYMSUye5FF5pDSrn2SPX3zvKRQGrpZ44VVUBeuxuzov7enWpkf",'mainnet'); + +const keychainPrivate1 = new DerivableKeyChain({privateKey}); +const keychainHDPrivate1 = new DerivableKeyChain({HDPrivateKey: hdprivateKey1}); +const keychainHDPublic1 = new DerivableKeyChain({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: { 123456789: { @@ -107,11 +127,13 @@ 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'` @@ -125,13 +147,13 @@ const mockSelfIndex1 = { 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/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/getLastSyncedBlockHeight.js b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/getLastSyncedBlockHeight.js index ef9ca277d76..85e0a4aea13 100644 --- a/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/getLastSyncedBlockHeight.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/handleTransactionFromStream.js b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/handleTransactionFromStream.js index 99326c9d495..29104fa3450 100644 --- a/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/handleTransactionFromStream.js +++ b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/handleTransactionFromStream.js @@ -77,8 +77,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/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/processChunks.js b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/processChunks.js index 19e51bff8f1..f0b5975942b 100644 --- a/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/processChunks.js +++ b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/processChunks.js @@ -23,20 +23,23 @@ async function processChunks(dataChunk) { const transactionsFromResponse = this.constructor .getTransactionListFromStreamResponse(dataChunk); - const walletTransactions = this.constructor - .filterWalletTransactions(transactionsFromResponse, addresses, network); + 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]); - if (walletTransactions.transactions.length) { // 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); }); @@ -95,7 +99,9 @@ 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/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/setLastSyncedBlockHeight.js b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/setLastSyncedBlockHeight.js index 19bd654ac85..c54075b44e4 100644 --- a/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/setLastSyncedBlockHeight.js +++ b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/setLastSyncedBlockHeight.js @@ -1,5 +1,3 @@ -const { WALLET_TYPES } = require('../../../../CONSTANTS'); - /** * Set last synced block height * @@ -7,14 +5,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 accountStore = ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(this.walletType)) - ? accountsStore[this.BIP44PATH.toString()] - : accountsStore[this.index.toString()]; - - accountStore.blockHeight = blockHeight; + const applicationStore = this.storage.application; + applicationStore.blockHeight = blockHeight; - return accountStore.blockHeight; + return applicationStore.blockHeight; }; diff --git a/packages/wallet-lib/src/test/mocks/createTransactionInAccount.js b/packages/wallet-lib/src/test/mocks/createTransactionInAccount.js index 4df5b64bee0..52496b44e31 100644 --- a/packages/wallet-lib/src/test/mocks/createTransactionInAccount.js +++ b/packages/wallet-lib/src/test/mocks/createTransactionInAccount.js @@ -16,8 +16,8 @@ async function createTransactionInAccount(account) { }]) .to(account.getAddress(10).address, 100000); - await account.importTransactions([walletTransaction.serialize(true)]); - + await account.importTransactions([[walletTransaction.serialize(true)]]); + // console.log(account.storage.wallets.get('361032c8a0').state.paths) return walletTransaction; } diff --git a/packages/wallet-lib/src/test/mocks/mockAccountWithStorage.js b/packages/wallet-lib/src/test/mocks/mockAccountWithStorage.js new file mode 100644 index 00000000000..1e5b300ddf6 --- /dev/null +++ b/packages/wallet-lib/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, DerivableKeyChain } = 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 DerivableKeyChain({ + 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/packages/wallet-lib/src/types/Account/Account.d.ts b/packages/wallet-lib/src/types/Account/Account.d.ts index 526aa9d7ccf..eea478bab5f 100644 --- a/packages/wallet-lib/src/types/Account/Account.d.ts +++ b/packages/wallet-lib/src/types/Account/Account.d.ts @@ -12,7 +12,7 @@ import { broadcastTransactionOpts, Plugins, RawTransaction, TransactionsMap, WalletObj, StatusInfo } from "../types"; -import { KeyChain } from "../KeyChain/KeyChain"; +import { DerivableKeyChain } from "../DerivableKeyChain/DerivableKeyChain"; import { InstantLock } from "@dashevo/dashcore-lib"; import { Identities, Wallet} from "../../index"; import { Transport } from "../../transport/Transport"; @@ -31,7 +31,7 @@ export declare class Account { cacheBlockHeaders?: boolean; label?: string | null; strategy?: Strategy; - keyChain: KeyChain; + keyChainSore: KeyChainStore; state: any; storage: Storage; store: Storage.store; diff --git a/packages/wallet-lib/src/types/Account/Account.js b/packages/wallet-lib/src/types/Account/Account.js index 78383aeb621..f2a7d6ee900 100644 --- a/packages/wallet-lib/src/types/Account/Account.js +++ b/packages/wallet-lib/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'); @@ -95,7 +95,6 @@ class Account extends EventEmitter { // If transport is null or invalid, we won't try to fetch anything this.transport = wallet.transport; - this.store = wallet.storage.store; this.storage = wallet.storage; // Forward all storage event @@ -122,29 +121,59 @@ 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); 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'; 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) @@ -157,25 +186,6 @@ 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; - } - } - } this.emit(EVENTS.CREATED, { type: EVENTS.CREATED, payload: null }); /** @@ -223,7 +233,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); } diff --git a/packages/wallet-lib/src/types/Account/Account.spec.js b/packages/wallet-lib/src/types/Account/Account.spec.js index a7b01b6ff7b..b7c2462b8e7 100644 --- a/packages/wallet-lib/src/types/Account/Account.spec.js +++ b/packages/wallet-lib/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 DerivableKeyChain = require("../DerivableKeyChain/DerivableKeyChain"); 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 DerivableKeyChain({mnemonic: fluidMnemonic.mnemonic}), { isMasterKeyChain: true }) }); it('should be specify on missing params', () => { const expectedException1 = 'Expected wallet to be passed as param'; @@ -92,12 +100,13 @@ describe('Account - class', function suite() { it('should forward events', function (done) { const mockWallet = mocks.wallet; const account = new Account(mockWallet, { injectDefaultPlugins: false }); - account.init(mockWallet).then(async ()=>{ - await account.on(EVENTS.BLOCKHEADER, ()=>{ - done(); - }); - account.storage.importBlockHeader(blockHeader); - }) + account.init(mockWallet) + .then(async ()=>{ + account.on(EVENTS.BLOCKHEADER, ()=>{ + done(); + }); + account.importBlockHeader(blockHeader); + }) }); }); diff --git a/packages/wallet-lib/src/types/Account/_initializeAccount.js b/packages/wallet-lib/src/types/Account/_initializeAccount.js index 858e748833e..adee1ea8a00 100644 --- a/packages/wallet-lib/src/types/Account/_initializeAccount.js +++ b/packages/wallet-lib/src/types/Account/_initializeAccount.js @@ -1,31 +1,32 @@ const logger = require('../../logger'); const EVENTS = require('../../EVENTS'); -const { WALLET_TYPES } = require('../../CONSTANTS'); const preparePlugins = require('./_preparePlugins'); -const ensureAddressesToGapLimit = require('../../utils/bip44/ensureAddressesToGapLimit'); // eslint-disable-next-line no-underscore-dangle async function _initializeAccount(account, userUnsafePlugins) { const self = account; + + 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 // eslint-disable-next-line no-async-promise-executor 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. - } - } - // Will sort and inject plugins. await preparePlugins(account, userUnsafePlugins); @@ -60,7 +61,7 @@ async function _initializeAccount(account, userUnsafePlugins) { }); logger.debug(`Initializing - ${readyPlugins}/${watchedPlugins.length} plugins`); if (readyPlugins === watchedPlugins.length) { - // At this stage, our worker are initialized + // At this stage, our worker are initialized sendInitialized(); // If both of the plugins are present @@ -68,35 +69,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/packages/wallet-lib/src/types/Account/_sortPlugins.spec.js b/packages/wallet-lib/src/types/Account/_sortPlugins.spec.js index 06f22b0827d..bd75e148714 100644 --- a/packages/wallet-lib/src/types/Account/_sortPlugins.spec.js +++ b/packages/wallet-lib/src/types/Account/_sortPlugins.spec.js @@ -274,8 +274,8 @@ describe('Account - _sortPlugins', () => { }); it('should handle userDefinedConflictingDependencies', function () { expect(() => sortPlugins(accountOnlineWithDefaultPlugins, userDefinedConflictingDependencies)) - .to - .throw('Conflicting dependency order for userDefinedConflictingDependenciesWorker'); + .to + .throw('Conflicting dependency order for userDefinedConflictingDependenciesWorker'); }); it('should handle userDefinedSimpleDependencyPluginDependenciesPlugins', async function () { const sortedPlugins = sortPlugins(accountOnlineWithDefaultPlugins, userDefinedSimpleDependencyPluginDependenciesPlugins); @@ -294,8 +294,8 @@ describe('Account - _sortPlugins', () => { // TODO: User specified wrongly sorted plugins with deps is not yet handled. // rejecting with error for now. expect(() => sortPlugins(accountOnlineWithDefaultPlugins, userDefinedComplexPluginDependenciesPlugins)) - .to - .throw('Dependency withSinglePluginDependenciesWorker not found'); + .to + .throw('Dependency withSinglePluginDependenciesWorker not found'); // const sortedPlugins = await sortPlugins(accountOnlineWithDefaultPlugins, userDefinedComplexPluginDependenciesPlugins); // expect(sortedPlugins).to.deep.equal([ // [ChainPlugin, true], diff --git a/packages/wallet-lib/src/types/Account/methods/broadcastTransaction.js b/packages/wallet-lib/src/types/Account/methods/broadcastTransaction.js index 604e60c5f6f..ea35774f636 100644 --- a/packages/wallet-lib/src/types/Account/methods/broadcastTransaction.js +++ b/packages/wallet-lib/src/types/Account/methods/broadcastTransaction.js @@ -7,40 +7,41 @@ const { } = require('../../../errors'); const EVENTS = require('../../../EVENTS'); -function impactAffectedInputs({ transaction, txid }) { - const { inputs, changeIndex } = transaction.toObject(); - +function impactAffectedInputs({ transaction }) { const { - storage, walletId, network, + 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); - - const mapped = storage.mappedAddress[addressString]; + const addressString = changeOutput.script.toAddress(network).toString(); - const { type, path } = mapped; - const address = storage.store.wallets[walletId].addresses[type][path]; + const address = addresses.get(addressString); const utxoKey = `${txid}-${changeIndex}`; /** @@ -69,7 +70,6 @@ function impactAffectedInputs({ transaction, txid }) { // eslint-disable-next-line no-underscore-dangle 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 @@ -88,7 +88,7 @@ async function _broadcastTransaction(transaction, options = {}) { throw new Error('Transaction not signed.'); } - 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; @@ -105,9 +105,7 @@ async function _broadcastTransaction(transaction, options = {}) { // so we clear them out from UTXOset. impactAffectedInputs.call(this, { transaction, - txid, }); - return txid; } diff --git a/packages/wallet-lib/src/types/Account/methods/broadcastTransaction.spec.js b/packages/wallet-lib/src/types/Account/methods/broadcastTransaction.spec.js index 0613a8e3ad1..6ea735154c1 100644 --- a/packages/wallet-lib/src/types/Account/methods/broadcastTransaction.spec.js +++ b/packages/wallet-lib/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,12 @@ describe('Account - broadcastTransaction', function suite() { let keysToSign; let oneToOneTx; let fee; + + 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 = [ @@ -34,12 +34,12 @@ describe('Account - broadcastTransaction', function suite() { fee = 680; address = 'yTBXsrcGw74yMUsK34fBKAWJx3RNCq97Aq'; keysToSign = [ - new PrivateKey('26d6b24119d1a71de6372ea2d3dc22a014d37e4828b43db6936cb41ea461cce8') + new PrivateKey('26d6b24119d1a71de6372ea2d3dc22a014d37e4828b43db6936cb41ea461cce8') ]; oneToOneTx = new Dashcore.Transaction() - .from(utxos) - .to(address, 138) - .fee(fee); + .from(utxos) + .to(address, 138) + .fee(fee); oneToOneTx.sign(keysToSign); }); @@ -87,11 +87,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 +105,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 +124,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,20 +139,16 @@ 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; tx.fee(0); return broadcastTransaction - .call(self, tx, {skipFeeValidation: true}) - .then( - () => expect(sendCalled).to.equal(1) && expect(searchCalled).to.equal(1), - ); + .call(self, tx, {skipFeeValidation: true}) + .then( + () => expect(sendCalled).to.equal(1) && expect(searchCalled).to.equal(1), + ); }); }); diff --git a/packages/wallet-lib/src/types/Account/methods/createTransaction.js b/packages/wallet-lib/src/types/Account/methods/createTransaction.js index 5aca76c0730..71334ed4be5 100644 --- a/packages/wallet-lib/src/types/Account/methods/createTransaction.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Account/methods/createTransaction.spec.js b/packages/wallet-lib/src/types/Account/methods/createTransaction.spec.js index 7dedef8c951..4f2c796b65b 100644 --- a/packages/wallet-lib/src/types/Account/methods/createTransaction.spec.js +++ b/packages/wallet-lib/src/types/Account/methods/createTransaction.spec.js @@ -3,15 +3,16 @@ const { expect } = require('chai'); const { HDPrivateKey, Transaction } = require('@dashevo/dashcore-lib'); const createTransaction = require('./createTransaction'); -const { mnemonic } = require('../../../../fixtures/wallets/mnemonics/during-develop-before'); const FixtureTransport = require('../../../transport/FixtureTransport/FixtureTransport'); const getUTXOS = require('./getUTXOS'); -const { simpleDescendingAccumulator } = require('../../../utils/coinSelections/strategies'); +const getPrivateKeys = require('./getPrivateKeys'); +const getUnusedAddress = require('./getUnusedAddress'); + +const getFixtureHDAccountWithStorage = require('../../../../fixtures/wallets/apart-trip-dignity/getFixtureAccountWithStorage'); const addressesFixtures = require('../../../../fixtures/addresses.json'); const fixtureUTXOS = require('../../../transport/FixtureTransport/data/utxos/yQ1fb64aeLfgqFKyeV9Hg9KTaTq5ehHm22.json'); -const validStore = require('../../../../fixtures/walletStore').valid.orange.store; const craftedGenerousMinerStrategy = require('../../../../fixtures/strategies/craftedGenerousMinerStrategy'); @@ -20,13 +21,13 @@ describe('Account - createTransaction', function suite() { let mockWallet; it('sould warn on missing inputs', function () { - const self = { - store: validStore, - walletId: 'a3771aaf93', - getUTXOS, - network: 'testnet' - }; + const self = getFixtureHDAccountWithStorage() + self.getUTXOS = getUTXOS; + self.getUnusedAddress = getUnusedAddress; + self.getPrivateKeys = getPrivateKeys; + const selfWithNoUTXOS = { ...self }; + selfWithNoUTXOS.getUTXOS = () => { return []}; const mockOpts1 = {}; const mockOpts2 = { satoshis: 1000, @@ -40,9 +41,8 @@ describe('Account - createTransaction', function suite() { const expectedException3 = 'Error: utxosList must contain at least 1 utxo'; expect(() => createTransaction.call(self, mockOpts1)).to.throw(expectedException1); expect(() => createTransaction.call(self, mockOpts2)).to.throw(expectedException2); - expect(() => createTransaction.call(self, mockOpts3)).to.throw(expectedException3); + expect(() => createTransaction.call(selfWithNoUTXOS, mockOpts3)).to.throw(expectedException3); }); - it('should create valid and deterministic transactions', async function () { if(process.browser){ // FixtureTransport relies heavily on fs.existSync and fs.readFile which are not available on browser @@ -62,8 +62,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/packages/wallet-lib/src/types/Account/methods/encrypt.spec.js b/packages/wallet-lib/src/types/Account/methods/encrypt.spec.js index 89653f84bdc..facc317ea96 100644 --- a/packages/wallet-lib/src/types/Account/methods/encrypt.spec.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Account/methods/generateAddress.js b/packages/wallet-lib/src/types/Account/methods/generateAddress.js index 450f582fe73..6d19109c108 100644 --- a/packages/wallet-lib/src/types/Account/methods/generateAddress.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Account/methods/getAddress.js b/packages/wallet-lib/src/types/Account/methods/getAddress.js index d4771dc7738..86efad07386 100644 --- a/packages/wallet-lib/src/types/Account/methods/getAddress.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Account/methods/getAddresses.js b/packages/wallet-lib/src/types/Account/methods/getAddresses.js index 92a64e65368..6d957f63d61 100644 --- a/packages/wallet-lib/src/types/Account/methods/getAddresses.js +++ b/packages/wallet-lib/src/types/Account/methods/getAddresses.js @@ -2,20 +2,36 @@ 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 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/packages/wallet-lib/src/types/Account/methods/getBalance.spec.js b/packages/wallet-lib/src/types/Account/methods/getBalance.spec.js deleted file mode 100644 index 9ade6080a04..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Account/methods/getConfirmedBalance.js b/packages/wallet-lib/src/types/Account/methods/getConfirmedBalance.js index c84a71c48b1..158ca50f02e 100644 --- a/packages/wallet-lib/src/types/Account/methods/getConfirmedBalance.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Account/methods/getConfirmedBalance.spec.js b/packages/wallet-lib/src/types/Account/methods/getConfirmedBalance.spec.js new file mode 100644 index 00000000000..d8c6f4b6590 --- /dev/null +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Account/methods/getPrivateKeys.js b/packages/wallet-lib/src/types/Account/methods/getPrivateKeys.js index 9c8392af502..f7a068f8e17 100644 --- a/packages/wallet-lib/src/types/Account/methods/getPrivateKeys.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Account/methods/getTotalBalance.js b/packages/wallet-lib/src/types/Account/methods/getTotalBalance.js index 8b490ac8f6c..9c0161549d6 100644 --- a/packages/wallet-lib/src/types/Account/methods/getTotalBalance.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Account/methods/getTotalBalance.spec.js b/packages/wallet-lib/src/types/Account/methods/getTotalBalance.spec.js new file mode 100644 index 00000000000..0368595db06 --- /dev/null +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Account/methods/getTransaction.js b/packages/wallet-lib/src/types/Account/methods/getTransaction.js index 4c7dce06a5e..da7a097052d 100644 --- a/packages/wallet-lib/src/types/Account/methods/getTransaction.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Account/methods/getTransaction.spec.js b/packages/wallet-lib/src/types/Account/methods/getTransaction.spec.js index 2a4f80cd037..ea65ce935f1 100644 --- a/packages/wallet-lib/src/types/Account/methods/getTransaction.spec.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.js b/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.js index 253e19bb259..f2cbdca2c6a 100644 --- a/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.js +++ b/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.js @@ -1,8 +1,6 @@ const { each } = require('lodash'); const { - filterTransactions, categorizeTransactions, - extendTransactionsWithMetadata, // calculateTransactionFees, } = require('../../../utils'); @@ -25,35 +23,25 @@ function getTransactionHistory() { } = this; const transactions = this.getTransactions(); - const store = storage.getStore(); - const chainStore = store.chains[network.toString()]; - const { blockHeaders } = chainStore; + const walletStore = storage.getWalletStore(walletId); + const chainStore = storage.getChainStore(network); - const { wallets: walletStore, transactionsMetadata } = store; + const transactionsWithMetadata = Object.keys(transactions).reduce((acc, hash) => { + const { metadata } = chainStore.getTransaction(hash); + acc.push([transactions[hash], metadata]); + return acc; + }, []); - 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 { blockHeaders } = chainStore.state; const categorizedTransactions = categorizeTransactions( - filteredTransactionsWithMetadata, - accountStore, + transactionsWithMetadata, + walletStore, accountIndex, walletType, network, ); - const sortedCategorizedTransactions = categorizedTransactions.sort(sortByHeightDescending); each(sortedCategorizedTransactions, (categorizedTransaction) => { @@ -65,19 +53,18 @@ function getTransactionHistory() { isChainLocked, isInstantLocked, } = categorizedTransaction; - - const blockHash = categorizedTransaction.blockHash !== '' ? categorizedTransaction.blockHash : null; - + 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 : 9999999999; 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), + // fees: calculateTransactionFees(categorizedTransaction.transaction), from, to, type, @@ -90,6 +77,7 @@ function getTransactionHistory() { transactionHistory.push(normalizedTransactionHistory); }); + // Sort by decreasing time. return transactionHistory.sort(sortbyTimeDescending); } diff --git a/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.spec.js b/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.spec.js index 6dd869b2a33..04e72c5dac6 100644 --- a/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.spec.js +++ b/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.spec.js @@ -3,15 +3,15 @@ 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'); +const getFixtureHDAccountWithStorage = require('../../../../fixtures/wallets/apart-trip-dignity/getFixtureAccountWithStorage'); +const getFixturePrivateAccountWithStorage = require('../../../../fixtures/wallets/2a331817b9d6bf85100ef0/getFixtureAccountWithStorage'); + + + const normalizedHDStoreFixtures = require('../../../../fixtures/wallets/apart-trip-dignity/store.json'); const normalizedPKStoreFixtures = require('../../../../fixtures/wallets/2a331817b9d6bf85100ef0/store.json'); const CONSTANTS = require("../../../CONSTANTS"); @@ -39,7 +39,7 @@ const normalizedStoreToStore = (normalizedStore) => { } return store; } -const mockedSelf = { +const mockedEmptySelf = { getTransactions, network: 'testnet', walletId: 'd6143ef4e6', @@ -72,20 +72,35 @@ const mockedSelf = { } } } + +const mockedHDAccount = getFixtureHDAccountWithStorage(); +mockedHDAccount.getTransactions = getTransactions; + +const mockedPKAccount = getFixturePrivateAccountWithStorage(); +mockedPKAccount.getTransactions = getTransactions; + describe('Account - getTransactionHistory', () => { it('should return empty array on no transaction history', async function () { const mockedHDSelf = { - ...mockedSelf + ...getFixtureHDAccountWithStorage(), } + mockedHDSelf.getTransactions = getTransactions; + const chainStore = mockedHDSelf.storage.getChainStore('testnet') + chainStore.state.blockHeaders = new Map(); + chainStore.state.transactions = new Map(); + chainStore.state.addresses.forEach((address)=>{ + address.transactions = []; + address.utxos = {}; + address.balanceSat = 0; + }) const transactionHistoryHD = await getTransactionHistory.call(mockedHDSelf); const expectedTransactionHistoryHD = []; expect(transactionHistoryHD).to.deep.equal(expectedTransactionHistoryHD); }); it('should return valid transaction for HDWallet', async function () { const mockedHDSelf = { - ...mockedSelf + ...mockedHDAccount } - mockedHDSelf.storage.store = normalizedStoreToStore(normalizedHDStoreFixtures) const timestartTs = +new Date(); const transactionHistoryHD = await getTransactionHistory.call(mockedHDSelf); const timeendTs = +new Date(); @@ -176,6 +191,7 @@ describe('Account - getTransactionHistory', () => { isChainLocked: true, isInstantLocked: true }, + { from: [ {address: 'ygHAVkMtYSqoTWHebDv7qkhMV6dHyuRsp2'}, @@ -219,16 +235,77 @@ describe('Account - getTransactionHistory', () => { blockHash: '00000c1e4556add15119392ed36ec6af2640569409abfa23a9972bc3be1b3717', isChainLocked: true, isInstantLocked: true + }, + { + from: [{address: 'yXxUiAnB31voBDPqnwxkffcPnUvwJz6a2k'},{address: 'yNh6Xzw4rs1kenAo8VWCswdyUnkdYXDZsg'}], + to: [ + {"address": "yXiTNo71QQAqiw2u1i6vkEEj3m6y4sEGae","satoshis": 1768694}, + {"address": "yMLhEsiP2ajSh8STmXnNmkWXtoHsmawZxd","satoshis": 840010000} + ], + time: 1629126597, + txId: "eb1a7fc8e3b43d3021653b1176f8f9b41e9667d05b65ee225d14c149a5b14f77", + blockHash: "00000221952c2a60adcb929de837f659308cb5c6bb7783016479381fb550fbad", + type: "received", + isChainLocked: true, + isInstantLocked: true, + }, + { + from: [{address: 'yTcjWB7v7opDzpfYKpFdFEtEvSKFsh3bW3'}], + to: [ + {"address": "ygk3GCSba2J3L9G665Snozhj9HSkh5ByVE","satoshis": 10000000}, + {"address": "yiDVYtUZ2mKV4teSJzKBArqY4BRsZoFLYs","satoshis": 522649259} + ], + time: 1628846998, + txId: "7d1b78157f9f2238669f260d95af03aeefc99577ff0cddb91b3e518ee557a2fd", + blockHash: "0000012cf6377c6cf2b317a4deed46573c09f04f6880dca731cc9ccea6691e19", + type: "received", + isChainLocked: true, + isInstantLocked: true, + }, + { + from: [{address: 'yaLhoAZ4iex2zKmfvS9rvEmxXmRiPrjHdD'}], + to: [ + {"address": "yercyhdN9oEkZcB9BsW5ktFaDxFEuK6qXN","satoshis": 10000000}, + {"address": "yTcjWB7v7opDzpfYKpFdFEtEvSKFsh3bW3","satoshis": 532649506} + ], + type: 'received', + time: 1628846768, + txId: 'd37b6c7dd449d605bea9997af8bbeed2f3fbbcb23a4068b1f1ad694db801912d', + blockHash: '000000b6006c758eda23ec7e2a640a0bf2c6a0c44827be216faff6bf4fd388e8', + isChainLocked: true, + isInstantLocked: true + }, + { + from: [ + {address: 'ygrRyPRf9vSHnP1ieoRRvY9THtFbTMc66e'}, + {address: 'yhDaDMNRUAB93S2ZcprNLuEGHPG4VT8kYL'}, + {address: 'ygZ5fgrtGQDtwsN8K7sftSNPXN4Srhz99s'}, + {address: 'yb39TanhfUKeqaBtzqDvAE3ad9UsDuj3Fd'}, + {address: 'yToX9gDE6tn2Sv1zhq88WNfJSomeHee3rR'}, + {address: 'yViAv63brJ5kB7Gyc7yX2c7rJ9NuykCzRh'}, + {address: 'yfnJMvdE32izNQP68PhMPiHAeJKYo2PBdH'}, + ], + to: [ + {"address": "ySE2UYPf7PWMJ5oYikSscVifzQEoGiGRmd","satoshis": 1823313}, + {"address": "yTwEca67QSkZ6axGdpNFzWPaCj8zqYybY7","satoshis": 187980000} + ], + type: 'received', + time: 1628846677, + txId: 'a43845e580ad01f31bc06ce47ab39674e40316c4c6b765b6e54d6d35777ef456', + blockHash: '000001deee9f99e8219a9abcaaea135dbaae8a9b0f1ea214e6b6a37a5c5b115d', + isChainLocked: true, + isInstantLocked: true } + ] expect(transactionHistoryHD).to.deep.equal(expectedTransactionHistoryHD); }); - it('should correctly deal with HDWallet accounts', async function () { + it('should correctly deal with multiple HDWallet accounts', async function () { const mockedHDSelf = { - ...mockedSelf + ...mockedHDAccount } - mockedHDSelf.storage.store = normalizedStoreToStore(normalizedHDStoreFixtures) mockedHDSelf.index = 1; + mockedHDSelf.accountPath = `m/44'/1'/1'`; const transactionHistoryHD = await getTransactionHistory.call(mockedHDSelf); const expectedTransactionHistoryHD = [ { @@ -250,25 +327,6 @@ describe('Account - getTransactionHistory', () => { isChainLocked: true, isInstantLocked: true }, - { - from: [ { address: 'yYJmzWey5kNecAThet5BFxAga1F4b4DKQ2' } ], - to: [ - { - address: 'yNCqctyQaq51WU1hN5aNwsgMsZ5fRiB7GY', - satoshis: 1200000000 - }, - { - address: 'yXMrw79LPgu78EJsfGGYpm6fXKc1EMnQ49', - satoshis: 59999753 - } - ], - type: 'address_transfer', - time: 1629235557, - txId: '9cd3d44a87a7f99a33aebc6957105d5fb41698ef642189a36bac59ec0b5cd840', - blockHash: '0000016fb685b4b1efed743d2263de34a9f8323ed75e732654b1b951c5cb4dde', - isChainLocked: true, - isInstantLocked: true - }, { from: [ { address: 'yj8rRKATAUHcAgXvNZekob58xKm2oNyvhv' } ], to: [ @@ -289,17 +347,18 @@ describe('Account - getTransactionHistory', () => { isInstantLocked: true } ] - expect(transactionHistoryHD).to.deep.equal(expectedTransactionHistoryHD); + expect(transactionHistoryHD.slice(0,5)).to.deep.equal(expectedTransactionHistoryHD); }); it('should correctly compute transaction history for private key based wallet', async function (){ const mockedPKSelf = { - ...mockedSelf + ...mockedPKAccount } - mockedPKSelf.storage.store = normalizedStoreToStore(normalizedPKStoreFixtures) - mockedPKSelf.walletType = 'single_address'; - mockedPKSelf.walletId = '6101b44d50'; + // mockedPKSelf.storage.store = normalizedStoreToStore(normalizedPKStoreFixtures) + // mockedPKSelf.walletType = 'single_address'; + // mockedPKSelf.walletId = '6101b44d50'; const transactionHistoryPK = await getTransactionHistory.call(mockedPKSelf); + const expectedTransactionHistoryPK = [ { from: [ { address: 'ycDeuTfs4U77bTb5cq17dame28zdWHVYfk' } ], @@ -313,7 +372,7 @@ describe('Account - getTransactionHistory', () => { satoshis: 8999753 } ], - type: 'address_transfer', + type: 'sent', time: 1629510092, txId: '47d13f7f713f4258953292c2298c1d91e2d6dee309d689f3c8b44ccf457bab52', blockHash: '0000007b7356e715b43ed7d5b7135fb9a2bf403e079bbcf7faec0f0da5c40117', diff --git a/packages/wallet-lib/src/types/Account/methods/getTransactions.js b/packages/wallet-lib/src/types/Account/methods/getTransactions.js index 453fe3884b8..8ef9ec7ee9f 100644 --- a/packages/wallet-lib/src/types/Account/methods/getTransactions.js +++ b/packages/wallet-lib/src/types/Account/methods/getTransactions.js @@ -3,6 +3,24 @@ * @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 addressData = chainStore.getAddress(address); + if (addressData) { + const transactionIds = addressData.transactions; + transactionIds.forEach((transactionId) => { + const tx = chainStore.getTransaction(transactionId); + transactions[tx.transaction.hash] = tx.transaction; + }); + } + }); + + return transactions; }; diff --git a/packages/wallet-lib/src/types/Account/methods/getTransactions.spec.js b/packages/wallet-lib/src/types/Account/methods/getTransactions.spec.js index 2c9aefa41b8..defa1d59220 100644 --- a/packages/wallet-lib/src/types/Account/methods/getTransactions.spec.js +++ b/packages/wallet-lib/src/types/Account/methods/getTransactions.spec.js @@ -1,280 +1,30 @@ const { expect } = require('chai'); -const Dashcore = require('@dashevo/dashcore-lib'); const getTransactions = require('./getTransactions'); +const getFixtureHDAccountWithStorage = require('../../../../fixtures/wallets/apart-trip-dignity/getFixtureAccountWithStorage'); + +const mockedHDSelf = { + ...getFixtureHDAccountWithStorage(), +} +mockedHDSelf.getTransactions = getTransactions; describe('Account - getTransactions', function suite() { this.timeout(10000); it('should get the transactions', () => { - const getStore = () => mockedStore; - const transactions = getTransactions.call({ - storage: { getStore }, - walletId: '123456789', - }, 'internal'); - expect(transactions).to.equal(mockedStore.transactions); + const transactions = getTransactions.call(mockedHDSelf); + const transactionsHash = Object.keys(transactions); + + expect(transactionsHash).to.deep.equal([ + 'a43845e580ad01f31bc06ce47ab39674e40316c4c6b765b6e54d6d35777ef456', + 'f230a9414bf577d93d6f7f2515d9b549ede78cfba4168920892970fa8aa1eef8', + 'd37b6c7dd449d605bea9997af8bbeed2f3fbbcb23a4068b1f1ad694db801912d', + '7d1b78157f9f2238669f260d95af03aeefc99577ff0cddb91b3e518ee557a2fd', + '1cbb35edc105918b956838570f122d6f3a1fba2b67467e643e901d09f5f8ac1b', + 'eb1a7fc8e3b43d3021653b1176f8f9b41e9667d05b65ee225d14c149a5b14f77', + 'c3fb3620ebd1c7678879b40df1495cc86a179b5a6f9e48ce0b687a5c6f5a1db5', + '6f37b0d6284aab627c31c50e1c9d7cce39912dd4f2393f91734f794bc6408533', + '6f76ca8038c6cb1b373bbbf80698afdc0d638e4a223be12a4feb5fd8e1801135', + 'e6b6f85a18d77974f376f05d6c96d0fdde990e733664248b1a00391565af6841' + ]); }); }); -const mockedStore = { - 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: [ - { - address: 'yizmJb63ygipuJaRgYtpWCV2erQodmaZt8', - txid: 'dd7afaadedb5f022cec6e33f1c8520aac897df152bd9f876842f3723ab9614bc', - outputIndex: 0, - scriptPubKey: '76a914f8c2652847720ab6d401291e5a48e2c8fe5d3c9f88ac', - satoshis: 100000000, - }, - ], - }, - "m/44'/1'/0'/0/1": { - address: 'yhLGmtf5Jmdb3DUvsaNJUHyCjjxTcBJEry', - balanceSat: 0, - fetchedLast: 1534867406705, - path: "m/44'/1'/0'/0/1", - transactions: [], - unconfirmedBalanceSat: 0, - utxos: [], - used: false, - }, - "m/44'/1'/0'/0/2": { - address: 'yfX4zBhV6syJTbRJLwxhCNdXkDnE7Mj1N7', - balanceSat: 0, - fetchedLast: 1534867406711, - path: "m/44'/1'/0'/0/2", - transactions: [], - unconfirmedBalanceSat: 0, - utxos: [], - used: false, - }, - }, - internal: { - "m/44'/1'/0'/1/0": { - address: 'yjSivd8eWH1vVywaeePiHBLXqMbHFXxxXE', - balanceSat: 1220560, - fetchedLast: 1534867407092, - path: "m/44'/1'/0'/1/0", - transactions: [ - '688dd18dea2b6f3c2d3892d13b41922fde7be01cd6040be9f3568dafbf9b1a23', - ], - unconfirmedBalanceSat: 0, - utxos: [ - '688dd18dea2b6f3c2d3892d13b41922fde7be01cd6040be9f3568dafbf9b1a23', - ], - used: true, - }, - "m/44'/1'/0'/1/1": { - address: 'yTM7nPiekjMBkMCU6cPmFD2KReeFUeVwCp', - balanceSat: 99890000, - fetchedLast: 1534867407080, - path: "m/44'/1'/0'/1/1", - transactions: [ - '1d8f924bef2e24d945d7de2ac66e98c8625e4cefeee4e07db2ea334ce17f9c35', - ], - unconfirmedBalanceSat: 0, - utxos: [], - used: true, - }, - "m/44'/1'/0'/1/2": { - address: 'yXy7rfGH5tZJs9GmFbdk6hPg2mzb8MRdfC', - balanceSat: 0, - fetchedLast: 1534867407080, - path: "m/44'/1'/0'/1/2", - transactions: [], - unconfirmedBalanceSat: 0, - utxos: [], - used: false, - }, - }, - }, - }, - }, - transactions: { - '9ab39713e9ce713d41ca6974db83e57bced02402e9516b8a662ed60d5c08f6d1': { - blockhash: '000000000a84c4703da7a69cfa65837251e4aac80e1621f2a2cc9504e0c149ba', - blockHeight: 201436, - blocktime: 1533525448, - fees: 1000, - size: 225, - txid: '9ab39713e9ce713d41ca6974db83e57bced02402e9516b8a662ed60d5c08f6d1', - txlock: true, - vin: [ - { - txid: 'e4524e918977b70ab47160d8e3b87a5fa9f88f22e43f0eec2abbee2cf364c93b', - vout: 1, - sequence: 4294967295, - n: 0, - scriptSig: { - hex: '4730440220154e37879e70784daff6cf04993cc88e8cf7e5357f82e98df9c117941cd5b3f702200d02f583085fbfd28c77d31c6b9a69f641a8fef5e99aaec8b8aedd4a3326e4100121025deea4fcd79eb876daa0f5829659c76f00f6b3fe6bf12e3ea83ecc763219bf88', - asm: '30440220154e37879e70784daff6cf04993cc88e8cf7e5357f82e98df9c117941cd5b3f702200d02f583085fbfd28c77d31c6b9a69f641a8fef5e99aaec8b8aedd4a3326e410[ALL] 025deea4fcd79eb876daa0f5829659c76f00f6b3fe6bf12e3ea83ecc763219bf88', - }, - addr: 'yhzoBe1aCTTganFBzFb3ErF4ufwMqonK5a', - valueSat: 81246619083, - value: 812.46619083, - doubleSpentTxID: null, - }, - ], - vout: [ - { - value: '2.00000000', - n: 0, - scriptPubKey: { - hex: '76a914df128447b46f9c81edbf13494d12aabca066b65688ac', - asm: 'OP_DUP OP_HASH160 df128447b46f9c81edbf13494d12aabca066b656 OP_EQUALVERIFY OP_CHECKSIG', - addresses: [ - 'ygewiYb7ZJxU4uuNGEVzbbA3wZEpEQJKhr', - ], - type: 'pubkeyhash', - }, - spentTxId: '6b90bf01b10a0c6cac018d376823f6b330edf2cbb783cc3d02004f8706bbc311', - spentIndex: 7, - spentHeight: 203517, - }, - { - value: '810.46609083', - n: 1, - scriptPubKey: { - hex: '76a914f67b2c4f47ea0a2bae829d3816a01cc486463d7988ac', - asm: 'OP_DUP OP_HASH160 f67b2c4f47ea0a2bae829d3816a01cc486463d79 OP_EQUALVERIFY OP_CHECKSIG', - addresses: [ - 'yinidcHwrfzb4bEJDSq3wtQyxRAgQxsQia', - ], - type: 'pubkeyhash', - }, - spentTxId: '22c368e09ad8b36553b383c6a4ae989f91d1f66622b2b685262580c8a45175a4', - spentIndex: 0, - spentHeight: 203155, - }, - ], - }, - '7ae825f4ecccd1e04e6c123e0c55d236c79cd04c6ab64e839aed2ae0af3003e6': { - txid: '7ae825f4ecccd1e04e6c123e0c55d236c79cd04c6ab64e839aed2ae0af3003e6', - blockhash: '000000000388f8a1de91702e25209aad802d28fcd2710ac2e2acd12e9738dbe2', - blockHeight: 203633, - blocktime: 1533827521, - fees: 1000, - size: 225, - txlock: true, - vout: [ - { - value: '1.00000000', - n: 0, - scriptPubKey: { - hex: '76a914f8c2652847720ab6d401291e5a48e2c8fe5d3c9f88ac', - asm: 'OP_DUP OP_HASH160 f8c2652847720ab6d401291e5a48e2c8fe5d3c9f OP_EQUALVERIFY OP_CHECKSIG', - addresses: [ - 'yizmJb63ygipuJaRgYtpWCV2erQodmaZt8', - ], - type: 'pubkeyhash', - }, - spentTxId: '1d8f924bef2e24d945d7de2ac66e98c8625e4cefeee4e07db2ea334ce17f9c35', - spentIndex: 0, - spentHeight: 204161, - }, - { - value: '18.99980000', - n: 1, - scriptPubKey: { - hex: '76a9147d39e5ff6ea7b82c6dc69994f1075f157bee9ef688ac', - asm: 'OP_DUP OP_HASH160 7d39e5ff6ea7b82c6dc69994f1075f157bee9ef6 OP_EQUALVERIFY OP_CHECKSIG', - addresses: [ - 'yXjag2zjTwA5Ya2sSV5KoFzTvE6uKdrnKa', - ], - type: 'pubkeyhash', - }, - spentTxId: '6ca8795f2534972e1371249c3d7b6c5095e1513bc8cc351eeaa2f364020dbc01', - spentIndex: 1, - spentHeight: 203674, - }, - ], - vin: [ - { - txid: '4ae8d1960c9a4ed83dbeaf1ad94b4a82f11c8574207144beda87113d94a31da1', - vout: 1, - sequence: 4294967295, - n: 0, - scriptSig: { - hex: '47304402205483f0b26a04876fe8eceb9a5d86b1da93012be27b5482a3d53443cda5ee7a13022027410cef1e19c2620ad0da1b800252b620e3e8dc576efb069e95eb1d2222ccfb01210221d2f6bd1b101dc4eb40f570af2a87221e3ffa166b6c5eac2faf496b0c53cbdf', - asm: '304402205483f0b26a04876fe8eceb9a5d86b1da93012be27b5482a3d53443cda5ee7a13022027410cef1e19c2620ad0da1b800252b620e3e8dc576efb069e95eb1d2222ccfb[ALL] 0221d2f6bd1b101dc4eb40f570af2a87221e3ffa166b6c5eac2faf496b0c53cbdf', - }, - addr: 'yUvr9AxuFx3ifp8HHFdYnVGbvK8Qqz25SQ', - valueSat: 1999990000, - value: 19.9999, - doubleSpentTxID: null, - }, - ], - }, - '1d8f924bef2e24d945d7de2ac66e98c8625e4cefeee4e07db2ea334ce17f9c35': { - txid: '1d8f924bef2e24d945d7de2ac66e98c8625e4cefeee4e07db2ea334ce17f9c35', - blockhash: '0000000002922d7d0a69ce3e29908dc26aba6565af760d208dac77a8b520fbf3', - blockHeight: 204161, - blocktime: 1533900199, - fees: 1000, - size: 226, - txlock: false, - vout: [ - { - value: '0.00100000', - n: 0, - scriptPubKey: { - hex: '76a914f3145d42d98196ca439eee48da05af56c2d7c4a688ac', - asm: 'OP_DUP OP_HASH160 f3145d42d98196ca439eee48da05af56c2d7c4a6 OP_EQUALVERIFY OP_CHECKSIG', - addresses: [ - 'yiUjSkhkAfaHfYYmTMhc27NCmogJ3iRBaS', - ], - type: 'pubkeyhash', - }, - spentTxId: null, - spentIndex: null, - spentHeight: null, - }, - { - value: '0.99890000', - n: 1, - scriptPubKey: { - hex: '76a9144d19cc13106cac13b386c89b003b02992ae8c5e688ac', - asm: 'OP_DUP OP_HASH160 4d19cc13106cac13b386c89b003b02992ae8c5e6 OP_EQUALVERIFY OP_CHECKSIG', - addresses: [ - 'yTM7nPiekjMBkMCU6cPmFD2KReeFUeVwCp', - ], - type: 'pubkeyhash', - }, - spentTxId: null, - spentIndex: null, - spentHeight: null, - }, - ], - vin: [ - { - txid: '7ae825f4ecccd1e04e6c123e0c55d236c79cd04c6ab64e839aed2ae0af3003e6', - vout: 0, - sequence: 4294967295, - n: 0, - scriptSig: { - hex: '483045022100ad3c7a5eae5ccb491c0e2cc3521df2cc410bf403340d6587848dc5ceb2b6831f022016b622ee329a4ceb1364d3e7295832af30e4fbc63420257b5b4971185048ac410121022efe5f45f47813efa1a279296c0171823736ae90c617ef2bda52becc56611536', - asm: '3045022100ad3c7a5eae5ccb491c0e2cc3521df2cc410bf403340d6587848dc5ceb2b6831f022016b622ee329a4ceb1364d3e7295832af30e4fbc63420257b5b4971185048ac41[ALL] 022efe5f45f47813efa1a279296c0171823736ae90c617ef2bda52becc56611536', - }, - addr: 'yizmJb63ygipuJaRgYtpWCV2erQodmaZt8', - valueSat: 100000000, - value: 1, - doubleSpentTxID: null, - }, - ], - }, - }, -}; diff --git a/packages/wallet-lib/src/types/Account/methods/getUTXOS.js b/packages/wallet-lib/src/types/Account/methods/getUTXOS.js index d0dcfcd6b76..292798cc173 100644 --- a/packages/wallet-lib/src/types/Account/methods/getUTXOS.js +++ b/packages/wallet-lib/src/types/Account/methods/getUTXOS.js @@ -1,6 +1,6 @@ /* eslint-disable no-continue, no-restricted-syntax */ const { Address, Transaction } = require('@dashevo/dashcore-lib'); -const { WALLET_TYPES, COINBASE_MATURITY } = require('../../../CONSTANTS'); +const { COINBASE_MATURITY } = require('../../../CONSTANTS'); /** * Return all the utxos @@ -11,67 +11,60 @@ 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]; + Object.values(accountState.addresses).forEach((address) => { + const addressData = chainStore.getAddress(address); + const utxosKeys = Object.keys(addressData.utxos); - if (isHDWallet && !path.startsWith(BIP44PATH)) continue; + utxosKeys.forEach((utxoIdentifier) => { + let skipUtxo = false; + const [txid, outputIndex] = utxoIdentifier.split('-'); - for (const identifier in address.utxos) { - if (!identifier) continue; - const [txid, outputIndex] = identifier.split('-'); - const transaction = new Transaction(this.store.transactions[txid]); + const txInStore = 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()) { - continue; - } - - const transactionHeight = (this.store.transactionsMetadata[txid]) - ? this.store.transactionsMetadata[txid].height + if (txInStore && txInStore.transaction.isCoinbase()) { + const { transaction, metadata } = txInStore; + // 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 = metadata + ? metadata.height : transaction.extraPayload.height; // 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/packages/wallet-lib/src/types/Account/methods/getUTXOS.spec.js b/packages/wallet-lib/src/types/Account/methods/getUTXOS.spec.js index eafae7efce1..0e2d8fbb77f 100644 --- a/packages/wallet-lib/src/types/Account/methods/getUTXOS.spec.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Account/methods/getUnconfirmedBalance.js b/packages/wallet-lib/src/types/Account/methods/getUnconfirmedBalance.js index 6b320a0ff34..9833525b4b1 100644 --- a/packages/wallet-lib/src/types/Account/methods/getUnconfirmedBalance.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Account/methods/getUnconfirmedBalance.spec.js b/packages/wallet-lib/src/types/Account/methods/getUnconfirmedBalance.spec.js new file mode 100644 index 00000000000..ad562c6bb2a --- /dev/null +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Account/methods/getUnusedAddress.js b/packages/wallet-lib/src/types/Account/methods/getUnusedAddress.js index 7341d40b4a6..f07c81d4b4b 100644 --- a/packages/wallet-lib/src/types/Account/methods/getUnusedAddress.js +++ b/packages/wallet-lib/src/types/Account/methods/getUnusedAddress.js @@ -1,5 +1,3 @@ -const logger = require('../../../logger'); - /** * Get an unused address from the store * @param {AddressType} [type="external"] - Type of the requested usused address @@ -10,27 +8,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; } } @@ -40,11 +55,6 @@ function getUnusedAddress(type = 'external', skip = 0) { if (unused.address === '') { return this.getAddress(accountIndex, type); } - - if (!this.storage.mappedAddress[unused.address]) { - this.storage.mappedAddress[unused.address] = { walletId, type, path: unused.path }; - } - return unused; } diff --git a/packages/wallet-lib/src/types/Account/methods/getUnusedAddress.spec.js b/packages/wallet-lib/src/types/Account/methods/getUnusedAddress.spec.js index dd6552a07b0..1f94b0442b3 100644 --- a/packages/wallet-lib/src/types/Account/methods/getUnusedAddress.spec.js +++ b/packages/wallet-lib/src/types/Account/methods/getUnusedAddress.spec.js @@ -1,59 +1,28 @@ const { expect } = require('chai'); -const Dashcore = require('@dashevo/dashcore-lib'); const getUnusedAddress = require('./getUnusedAddress'); -const getAddress = require('./getAddress'); -const generateAddress = require('./generateAddress'); -const KeyChain = require('../../KeyChain/KeyChain'); +const getFixtureHDAccountWithStorage = require('../../../../fixtures/wallets/apart-trip-dignity/getFixtureAccountWithStorage'); -const mockedStore = require('../../../../fixtures/duringdevelop-fullstore-snapshot-1548538361'); - -const HDRootKeyMockedStore = 'tprv8ZgxMBicQKsPfEan1JB7NF4STbvnjGvP9318CN7FPGZp5nsUTBqmerxtDVpsJjFufyfkTgoe6QfHcDhMqjN3ZoFKtb8SnXFeubNjQreZSq6'; +const mockedHDSelf = { + ...getFixtureHDAccountWithStorage(), +} describe('Account - getUnusedAddress', function suite() { this.timeout(10000); - it('should get the proper unused address', () => { - const self = { - store: mockedStore, - storage: { - getStore: () => mockedStore, - importAddresses: (_) => (_), - mappedAddress: {} - }, - 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'); + it('should get the proper unused address', () => { + const unusedAddressExternal = getUnusedAddress.call(mockedHDSelf); + const unusedAddressInternal = getUnusedAddress.call(mockedHDSelf, 'internal'); - // console.log(mockedStore.wallets[self.walletId].addresses.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: 'yTrBDbJ6635RuBMRYGvUs2s68jwhpSMyQD', + index: 3, + path: 'm/0/3' }); + 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: 'ycxSmwSVQsRmuq2ivzU6NowuB11Wi9kioj', + path: 'm/1/4', + index: 4 }); }); }); diff --git a/packages/wallet-lib/src/types/Account/methods/getUnusedIdentityIndex.js b/packages/wallet-lib/src/types/Account/methods/getUnusedIdentityIndex.js index 7cf353d075c..0e32b274e18 100644 --- a/packages/wallet-lib/src/types/Account/methods/getUnusedIdentityIndex.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Account/methods/importBlockHeader.js b/packages/wallet-lib/src/types/Account/methods/importBlockHeader.js index 01e4517a42b..9200695a187 100644 --- a/packages/wallet-lib/src/types/Account/methods/importBlockHeader.js +++ b/packages/wallet-lib/src/types/Account/methods/importBlockHeader.js @@ -1,5 +1,5 @@ const logger = require('../../../logger'); -const { WALLET_TYPES } = require('../../../CONSTANTS'); +const EVENTS = require('../../../EVENTS'); /** * Import transactions and always keep a number of unused addresses up to gap * @@ -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, + storage, network, } = 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); + applicationStore.blockHash = blockHeader.id; - localAccountStore.blockHash = blockHeader.id; - - storage.importBlockHeader(blockHeader); + chainStore.importBlockHeader(blockHeader); + this.emit(EVENTS.BLOCKHEADER, { type: EVENTS.BLOCKHEADER, payload: blockHeader }); logger.silly(`Account.importBlockHeader(${blockHeader.id})`); }; diff --git a/packages/wallet-lib/src/types/Account/methods/importTransactions.js b/packages/wallet-lib/src/types/Account/methods/importTransactions.js index 2e3bb0dd123..42c5f167884 100644 --- a/packages/wallet-lib/src/types/Account/methods/importTransactions.js +++ b/packages/wallet-lib/src/types/Account/methods/importTransactions.js @@ -1,6 +1,4 @@ const logger = require('../../../logger'); -const { WALLET_TYPES } = require('../../../CONSTANTS'); -const ensureAddressesToGapLimit = require('../../../utils/bip44/ensureAddressesToGapLimit'); /** * Import transactions and always keep a number of unused addresses up to gap @@ -8,30 +6,50 @@ 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]; + let addressesGenerated = 0; - storage.importTransactions(transactions); - logger.silly(`Account.importTransactions(len: ${transactions.length})`); + const chainStore = storage.getChainStore(network); + const accountStore = storage + .getWalletStore(walletId) + .getPathState(accountPath); - 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), - ); - } + 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.`); - return 0; + affectedAddresses.forEach((address) => { + keyChains.forEach((keyChain) => { + const issuedPaths = keyChain.markAddressAsUsed(address); + if (issuedPaths) { + addressesGenerated += issuedPaths.length; + 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 addressesGenerated; }; diff --git a/packages/wallet-lib/src/types/Account/methods/injectPlugin.js b/packages/wallet-lib/src/types/Account/methods/injectPlugin.js index 202e5b61261..4405e29b49e 100644 --- a/packages/wallet-lib/src/types/Account/methods/injectPlugin.js +++ b/packages/wallet-lib/src/types/Account/methods/injectPlugin.js @@ -90,9 +90,9 @@ module.exports = async function injectPlugin( const setReadyWatch = (_watcher) => _watcher.ready = true; const onStartedEvent = () => startWatcher(watcher) - && logger.silly(`WORKER/${pluginName.toUpperCase()}/STARTED`); + && logger.silly(`WORKER/${pluginName.toUpperCase()}/STARTED`); const onExecuteEvent = () => setReadyWatch(watcher) - && logger.silly(`WORKER/${pluginName.toUpperCase()}/EXECUTED`); + && logger.silly(`WORKER/${pluginName.toUpperCase()}/EXECUTED`); self.on(`WORKER/${pluginName.toUpperCase()}/STARTED`, onStartedEvent); self.on(`WORKER/${pluginName.toUpperCase()}/EXECUTED`, onExecuteEvent); diff --git a/packages/wallet-lib/src/types/Account/methods/injectPlugin.spec.js b/packages/wallet-lib/src/types/Account/methods/injectPlugin.spec.js index 68bd7c9b885..161c1f76cc1 100644 --- a/packages/wallet-lib/src/types/Account/methods/injectPlugin.spec.js +++ b/packages/wallet-lib/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) { @@ -42,9 +42,9 @@ describe('Account - injectPlugin', function suite() { it('should handle faulty worker', async function () { const expectedException1 = 'Some reason.'; await expectThrowsAsync(async () => await injectPlugin.call(mockedSelf, FaultyWorker, true), expectedException1); - expect(mockedSelf.plugins.workers['faultyworker']).to.exist; - expect(mockedSelf.plugins.workers['faultyworker'].worker).to.equal(null); - expect(mockedSelf.plugins.workers['faultyworker'].isWorkerRunning).to.equal(false); - expect(mockedSelf.plugins.workers['faultyworker'].state.started).to.equal(false); + expect(mockedSelf.plugins.workers['faultyworker']).to.exist; + expect(mockedSelf.plugins.workers['faultyworker'].worker).to.equal(null); + expect(mockedSelf.plugins.workers['faultyworker'].isWorkerRunning).to.equal(false); + expect(mockedSelf.plugins.workers['faultyworker'].state.started).to.equal(false); }); }); diff --git a/packages/wallet-lib/src/types/Account/methods/sign.js b/packages/wallet-lib/src/types/Account/methods/sign.js index 33509703a97..33756ea29a0 100644 --- a/packages/wallet-lib/src/types/Account/methods/sign.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Account/methods/sign.spec.js b/packages/wallet-lib/src/types/Account/methods/sign.spec.js index 6e3b6e8e44b..e2de5251eb0 100644 --- a/packages/wallet-lib/src/types/Account/methods/sign.spec.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/ChainStore/ChainStore.d.ts b/packages/wallet-lib/src/types/ChainStore/ChainStore.d.ts new file mode 100644 index 00000000000..1d63afc46d9 --- /dev/null +++ b/packages/wallet-lib/src/types/ChainStore/ChainStore.d.ts @@ -0,0 +1,38 @@ +export declare interface feeState { + minRelay: number +} + +export declare interface ChainStoreState { + fees: feeState; + blockHeight: number; + blockHeaders: Map + transactions: Map + instantLocks: Map + addresses: Map +} + +type networkIdentifier = string; +type exportedState = any; + +export declare class ChainStore { + constructor(networkIdentifier: networkIdentifier); + network: networkIdentifier; + + state: ChainStoreState; + + considerTransaction(transactionHash: string): any; + exportState(): exportedState; + importState(exportedState): void; + + getAddress(address: string): any; + getAddresses(address: string): Map + + getBlockHeader(blockHeaderHash: string): any; + getInstantLock(transactionHash: string): any; + getTransaction(transactionHash: string): any; + + importAddress(address: any): void; + importAddress(blockHeader: any): void; + importInstantLock(instantLock: any): void; + importTransaction(transaction: any, metadata: any): any; +} diff --git a/packages/wallet-lib/src/types/ChainStore/ChainStore.js b/packages/wallet-lib/src/types/ChainStore/ChainStore.js index 0c9e4df6b11..e1ebed074d5 100644 --- a/packages/wallet-lib/src/types/ChainStore/ChainStore.js +++ b/packages/wallet-lib/src/types/ChainStore/ChainStore.js @@ -1,10 +1,13 @@ +const EventEmitter = require('events'); + /** * ChainStore holds any information that is relatives to a specific network. * Information such as blockHeaders, transactions, instantLocks. * Also holds the state of addresses based on the transactions imported (e.g: balances and utxos). */ -class ChainStore { +class ChainStore extends EventEmitter { constructor(networkIdentifier = 'testnet') { + super(); this.network = networkIdentifier; this.state = { diff --git a/packages/wallet-lib/src/types/ChainStore/methods/considerTransaction.js b/packages/wallet-lib/src/types/ChainStore/methods/considerTransaction.js index b8c991a4954..bbfd5e895a8 100644 --- a/packages/wallet-lib/src/types/ChainStore/methods/considerTransaction.js +++ b/packages/wallet-lib/src/types/ChainStore/methods/considerTransaction.js @@ -1,5 +1,6 @@ const { Transaction } = require('@dashevo/dashcore-lib'); const logger = require('../../../logger'); +const EVENTS = require('../../../EVENTS'); const { Output } = Transaction; @@ -12,6 +13,8 @@ function considerTransaction(transactionHash) { const processedAddressesForTx = {}; + let broadcastTxEvent = false; + [...inputs, ...outputs].forEach((element) => { const isOutput = (element instanceof Output); if (isOutput) outputIndex += 1; @@ -39,6 +42,7 @@ function considerTransaction(transactionHash) { const previousOutput = watchedAddress.utxos[utxoKey]; watchedAddress.balanceSat -= previousOutput.satoshis; delete watchedAddress.utxos[utxoKey]; + broadcastTxEvent = true; } } else { const vout = element; @@ -47,6 +51,11 @@ function considerTransaction(transactionHash) { if (!watchedAddress.utxos[utxoKey]) { watchedAddress.utxos[utxoKey] = vout.toJSON(); watchedAddress.balanceSat += vout.satoshis; + broadcastTxEvent = true; + } else if (watchedAddress.unconfirmedBalanceSat >= vout.satoshis) { + watchedAddress.unconfirmedBalanceSat -= vout.satoshis; + watchedAddress.balanceSat += vout.satoshis; + broadcastTxEvent = true; } } } @@ -77,6 +86,11 @@ function considerTransaction(transactionHash) { }); } + // TODO: restore EVENTS.FETCHED_UNCONFIRMED_TRANSACTION + if (broadcastTxEvent) { + this.emit(EVENTS.FETCHED_CONFIRMED_TRANSACTION, { transaction }); + } + return processedAddressesForTx; } diff --git a/packages/wallet-lib/src/types/ChainStore/methods/importState.js b/packages/wallet-lib/src/types/ChainStore/methods/importState.js index ab0feb9a2c6..64460e0954f 100644 --- a/packages/wallet-lib/src/types/ChainStore/methods/importState.js +++ b/packages/wallet-lib/src/types/ChainStore/methods/importState.js @@ -36,7 +36,7 @@ function importState(state) { this.importTransaction(new Transaction(Buffer.from(serializedTransaction, 'hex')), metadata); }); Object.values(instantLocks).forEach((serializedInstantLock) => { - this.importTransaction(new InstantLock(Buffer.from(serializedInstantLock, 'hex'))); + this.importInstantLock(new InstantLock(Buffer.from(serializedInstantLock, 'hex'))); }); } module.exports = importState; diff --git a/packages/wallet-lib/src/types/DerivableKeyChain/DerivableKeyChain.d.ts b/packages/wallet-lib/src/types/DerivableKeyChain/DerivableKeyChain.d.ts index 406d2baf5ff..1989fd6784c 100644 --- a/packages/wallet-lib/src/types/DerivableKeyChain/DerivableKeyChain.d.ts +++ b/packages/wallet-lib/src/types/DerivableKeyChain/DerivableKeyChain.d.ts @@ -9,6 +9,14 @@ export declare namespace DerivableKeyChain { } } +type keyChainId = string; +type rootKey = any; +type firstUnusedAddress = { + path: string; + address: string +} + + export declare class DerivableKeyChain { constructor(options?: DerivableKeyChain.IDerivableKeyChainOptions); network: Network; @@ -18,17 +26,19 @@ export declare class DerivableKeyChain { HDPrivateKey?: HDPrivateKey; privateKey?: PrivateKey; - generateKeyForChild(index: number, type?: HDKeyTypesParam): HDPrivateKey|HDPublicKey; - generateKeyForPath(path: string, type?: HDKeyTypesParam): HDPrivateKey|HDPublicKey; + getForPath(path: string, opts: any): any; + getForAddress(address): any; getDIP15ExtendedKey(userUniqueId: string, contactUniqueId: string, index?: number, accountIndex?: number, type?: HDKeyTypesParam): HDKeyTypes; - getHardenedDIP15AccountKey(index?: number, type?: HDKeyTypesParam): HDKeyTypes; + getFirstUnusedAddress(): firstUnusedAddress; getHardenedBIP44HDKey(type?: HDKeyTypesParam): HDKeyTypes; getHardenedDIP9FeatureHDKey(type?: HDKeyTypesParam): HDKeyTypes; - getKeyForChild(index: number, type?: HDKeyTypesParam): HDKeyTypes; - getKeyForPath(path: string, type?: HDKeyTypesParam): HDKeyTypes; - getPrivateKey(): PrivateKey; - + getHardenedDIP15AccountKey(index?: number, type?: HDKeyTypesParam): HDKeyTypes; + getRootKey(): rootKey; + getWatchedAddresses(): Array; + getIssuedPaths(): Array; + maybeLookAhead(): any; + markAddressAsUsed(address: string): any; sign(object: Transaction|any, privateKeys:[PrivateKey], sigType: number): any; } diff --git a/packages/wallet-lib/src/types/DerivableKeyChain/DerivableKeyChain.spec.js b/packages/wallet-lib/src/types/DerivableKeyChain/DerivableKeyChain.spec.js index 02ae0c03abd..98dfc69bb5b 100644 --- a/packages/wallet-lib/src/types/DerivableKeyChain/DerivableKeyChain.spec.js +++ b/packages/wallet-lib/src/types/DerivableKeyChain/DerivableKeyChain.spec.js @@ -7,11 +7,11 @@ let derivableKeyChain; let derivableKeyChain2; 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('DerivableKeyChain', function suite() { this.timeout(1000); it('should create a DerivableKeyChain', () => { @@ -25,14 +25,17 @@ describe('DerivableKeyChain', function suite() { derivableKeyChain2 = new DerivableKeyChain({ mnemonic: mnemonic2, network: 'livenet' }); }); - it('should generate key for full path', () => { const path = 'm/44\'/1\'/0\'/0/0'; const pk2 = derivableKeyChain.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 = derivableKeyChain.getHardenedBIP44HDKey(); + const pk2 = derivableKeyChain.getForPath('m/44\'/1\'').key; + expect(pk2.toString()).to.equal(hardenedPk.toString()); + }); it('should get DIP15 account key', function () { const rootDIP15AccountKey_0 = derivableKeyChain.getHardenedDIP15AccountKey(0); expect(rootDIP15AccountKey_0.toString()).to.deep.equal(expectedRootDIP15AccountKey_0); @@ -45,7 +48,7 @@ describe('DerivableKeyChain', function suite() { const contactUniqueId = '0xa137439f36d04a15474ff7423e4b904a14373fafb37a41db74c84f1dbb5c89b5'; // m/9'/5'/15'/0'/0x555d3854c910b7dee436869c4724bed2fe0784e198b8a39f02bbb49d8ebcfc3a'/0xa137439f36d04a15474ff7423e4b904a14373fafb37a41db74c84f1dbb5c89b5'/0 - const DIP15ExtPubKey_0 = derivableKeyChain2.getDIP15ExtendedKey(userUniqueId, contactUniqueId, 0, 0, 'HDPublicKey'); + const DIP15ExtPubKey_0 = derivableKeyChain2.getDIP15ExtendedKey(userUniqueId, contactUniqueId, 0, 0, type='HDPublicKey'); expect(DIP15ExtPubKey_0.toString()).to.equal('xpub6LTkTQFSb8KMgMSz4B6sMZLpkQAY6wSTDprDkHDmLwWLpnjxazuxZn13FrSLKUafitsxuaaffM5a49P6aswhpppWUuYW6eFnwBXshR2W2eY'); expect(DIP15ExtPubKey_0.publicKey.toString()).to.equal('038030c88ab0106e1f4af3b939db2bafc56f892554106f08da1ce1f9ef10f807bd') @@ -54,33 +57,28 @@ describe('DerivableKeyChain', function suite() { expect(DIP15ExtPrivKey_0.privateKey.toString()).to.equal('fac40790776d171ee1db90899b5eb2df2f7d2aaf35ad56f07ffb8ed2c57f8e60') expect(DIP15ExtPrivKey_0.publicKey.toString()).to.equal('038030c88ab0106e1f4af3b939db2bafc56f892554106f08da1ce1f9ef10f807bd') - // This comes from the test factor of DIP-15 const userAhash = "0xa11ce14f698b32e9bb306dba7bbbee831263dcf658abeebb39930460ead117e5"; const userBhash = "0xb0b052ff075c5ca3c16c3e20e9ac8223834475cc1324ab07889cb24ce6a62793"; const DIP15ExtKey_1 = derivableKeyChain.getDIP15ExtendedKey(userAhash, userBhash, 0, 0); expect(DIP15ExtKey_1.privateKey.toString()).to.equal('60581b6dca8244d3fb3cfe619b5a22277e5423b01e5285f356981f247e0f4a60') expect(DIP15ExtKey_1.publicKey.toString()).to.equal('03deaac00f721151307fbc7bf80d7b8afab98c1f026d67e5f56b21e2013f551ce6') }); - - it('should derive from hardened path and get address', () => { + it('should derive from hardened feature path', () => { const hardenedHDKey = derivableKeyChain.getHardenedBIP44HDKey(); const pk2 = derivableKeyChain.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); - // m/44'/1'/0'/0/0 (this is first external address of the account 0) const address = new Dashcore.Address(derivedPk.publicKey.toAddress()).toString(); expect(address).to.equal('yNfUebksUc5HoSfg8gv98ruC3jUNJUM8pT'); }); - it('should get hardened DIP9FeatureHDKey', function () { const hardenedHDKey = derivableKeyChain.getHardenedDIP9FeatureHDKey(); const pk2 = derivableKeyChain.getForPath(`m/9'/1'`).key; expect(pk2.toString()).to.equal(hardenedHDKey.toString()); expect(hardenedHDKey.toString()).to.deep.equal('tprv8fBJjWoGgCpGRCbyzE9RUA59rmoN1RUijhLnXGL4VHnLxvSe523yVg4GrGzbR6TyXtdynAEh5z8UX55EXt2Cb3xjvrsx2PgTY9BHxzFVkWn'); }); - - it('should get key for path using the HDPrivateKey', () => { + it('should get key for path', () => { const derivableKeyChain2 = new DerivableKeyChain({ HDPrivateKey: mnemonicToHDPrivateKey(mnemonic, 'testnet') }); const keyForChild = derivableKeyChain2.getForPath('m/0').key; expect(keyForChild.toString()).to.equal(expectedKeyForChild_0); @@ -90,19 +88,18 @@ describe('DerivableKeyChain', function suite() { const key0 = derivableKeyChain.getForPath('m/0'); derivableKeyChain.getForPath('m/0').isWatched = true key0.isWatched = true; - derivableKeyChain.getForPath('m/1', { isWatched: false }); - derivableKeyChain.getForPath('m/2', { isWatched: true }); + const key1 = derivableKeyChain.getForPath('m/1', { isWatched: true }); + const key2 = derivableKeyChain.getForPath('m/2', { isWatched: true }); const watchedAddresses = derivableKeyChain.getWatchedAddresses(); let expectedWatchedAddresses = [ derivableKeyChain.getForPath('m/0').address.toString(), + derivableKeyChain.getForPath('m/1').address.toString(), derivableKeyChain.getForPath('m/2').address.toString() ]; expect(watchedAddresses).to.deep.equal(expectedWatchedAddresses); }); - it('should get watched addresses', function () { - derivableKeyChain.getForPath('m/1').isWatched = true const watchedAddresses = derivableKeyChain.getWatchedAddresses(); const expectedWatchedAddresses = [ 'ybQDfNwiDjk8ZH5UUmHQzAMEmjbrbK5dAj', @@ -111,28 +108,33 @@ describe('DerivableKeyChain', function suite() { ] expect(watchedAddresses).to.deep.equal(expectedWatchedAddresses); }); - + // it('should get watched public keys', function () { + // const watchedPubKeys = derivableKeyChain.getWatchedPublicKeys(); + // const expectedWatchedPubKeys = [ + // '03e6ab8177a7ca2699da4f83ca3c27768fb88b70ae9d6bde1cba8de88355ccf199', + // '0246a870b65153b98e453ff08f7198d06bce2a790286f44d90929aceafafa0673f', + // '025ad16f78f67801e52abe5b512d66e3896d4c2fa3ca3150349437a5dd13519967' + // ] + // expect(watchedPubKeys).to.deep.equal(expectedWatchedPubKeys) + // }); it('should remove an address from watched addresses', function () { - derivableKeyChain.getForPath('m/0', { isWatched: false }); - derivableKeyChain.getForPath('m/1'); - const data2 = derivableKeyChain.getForPath('m/2'); - data2.isWatched = false; + const data0 = derivableKeyChain.getForPath('m/0', { isWatched: false }); + const data1 = derivableKeyChain.getForPath('m/1'); + const data2 = derivableKeyChain.getForPath('m/2'); + data2.isWatched = false; expect(derivableKeyChain.getWatchedAddresses().length).to.equal(1); }); - it('should get address for path', function (){ const address0_1 = derivableKeyChain.getForPath('m/1').address; expect(address0_1.toString()).to.equal('yhFX5rseJPitV45HUCaa9haeGHtLuooBaq') }) - it('should mark address as used', function () { const address0_0 = derivableKeyChain.getForPath('m/0').address; derivableKeyChain.markAddressAsUsed(address0_0); expect(derivableKeyChain.issuedPaths.get('m/0').isUsed).to.equal(true) }); }); - describe('DerivableKeyChain - HDPublicKey', function suite(){ let hdpubDerivableKeyChain; it('should initiate from a HDPublicKey', function () { @@ -145,22 +147,18 @@ describe('DerivableKeyChain - HDPublicKey', function suite(){ expect(hdpubDerivableKeyChain.keyChainId).to.equal('kc5059442d66'); expect(hdpubDerivableKeyChain.getRootKey().toString()).to.equal(hdPublicKey); }); - it('should derivate', function () { const key0_1 = hdpubDerivableKeyChain.getForPath('m/1').key; expect(key0_1.publicKey.toAddress(hdpubDerivableKeyChain.network).toString()).to.equal('XoL5LcBiDWcj6L7fFwytsFoX5Vz7BVXw9w') }); - it('should get address for path', function (){ const address0_1 = hdpubDerivableKeyChain.getForPath('m/2').address; expect(address0_1.toString()).to.equal('XwAzpxQKbgebaLiadq1c6rDeFJ4FKPUufy') }) }) - describe('DerivableKeyChain - single privateKey', function suite() { this.timeout(10000); - - it('should correctly throw errors out when not a HDPublicKey (privateKey)', () => { + it('should correctly errors out when not a HDPublicKey (privateKey)', () => { const privateKey = Dashcore.PrivateKey().toString(); const network = 'livenet'; const pkDerivableKeyChain = new DerivableKeyChain({ privateKey, network }); @@ -171,7 +169,6 @@ describe('DerivableKeyChain - single privateKey', function suite() { const expectedException1 = 'Wallet is not loaded from a mnemonic or a HDPrivateKey, impossible to derivate keys for path m/0'; expect(() => pkDerivableKeyChain.getForPath('m/0')).to.throw(expectedException1); }); - it('should get private key', () => { const privateKey = Dashcore.PrivateKey().toString(); const pkDerivableKeyChain = new DerivableKeyChain({ privateKey, network: 'livenet' }); diff --git a/packages/wallet-lib/src/types/Identities/Identities.js b/packages/wallet-lib/src/types/Identities/Identities.js index f721356dcce..f457fe9fc99 100644 --- a/packages/wallet-lib/src/types/Identities/Identities.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Identities/methods/getIdentityHDKeyById.js b/packages/wallet-lib/src/types/Identities/methods/getIdentityHDKeyById.js index 374925a14a3..79cd6eedc77 100644 --- a/packages/wallet-lib/src/types/Identities/methods/getIdentityHDKeyById.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Identities/methods/getIdentityHDKeyById.spec.js b/packages/wallet-lib/src/types/Identities/methods/getIdentityHDKeyById.spec.js index e13dc087569..87e60e4d58d 100644 --- a/packages/wallet-lib/src/types/Identities/methods/getIdentityHDKeyById.spec.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Identities/methods/getIdentityIds.js b/packages/wallet-lib/src/types/Identities/methods/getIdentityIds.js index dba62110f7f..e72711adb42 100644 --- a/packages/wallet-lib/src/types/Identities/methods/getIdentityIds.js +++ b/packages/wallet-lib/src/types/Identities/methods/getIdentityIds.js @@ -3,7 +3,10 @@ * @return {string[]} */ function getIdentityIds() { - return this.storage.getIndexedIdentityIds(this.walletId).filter(Boolean); + return this.storage + .getWalletStore(this.walletId) + .getIndexedIdentityIds() + .filter(Boolean); } module.exports = getIdentityIds; diff --git a/packages/wallet-lib/src/types/Identities/methods/getIdentityIds.spec.js b/packages/wallet-lib/src/types/Identities/methods/getIdentityIds.spec.js index df2e887176b..6d0ee52d568 100644 --- a/packages/wallet-lib/src/types/Identities/methods/getIdentityIds.spec.js +++ b/packages/wallet-lib/src/types/Identities/methods/getIdentityIds.spec.js @@ -1,19 +1,24 @@ const { expect } = require('chai'); const mockedStore = require('../../../../fixtures/sirentonight-fullstore-snapshot-1562711703'); const getIdentityIds = require('./getIdentityIds'); +const WalletStore = require("../../WalletStore/WalletStore"); let mockedWallet; let fetchTransactionInfoCalledNb = 0; describe('Wallet#getIdentityIds', function suite() { this.timeout(10000); before(() => { + const walletId = Object.keys(mockedStore.wallets)[0]; + const walletStore = new WalletStore(walletId) + const identityIds = mockedStore.wallets[walletId].identityIds; + identityIds.forEach((id, i) => { + walletStore.insertIdentityIdAtIndex(id, i) + }) + const storageHDW = { - store: mockedStore, - getStore: () => mockedStore, - mappedAddress: {}, - getIndexedIdentityIds: () => mockedStore.wallets[Object.keys(mockedStore.wallets)].identityIds, + getWalletStore: () => walletStore }; - const walletId = Object.keys(mockedStore.wallets)[0]; + mockedWallet = { walletId, index: 0, diff --git a/packages/wallet-lib/src/types/KeyChain/KeyChain.d.ts b/packages/wallet-lib/src/types/KeyChain/KeyChain.d.ts deleted file mode 100644 index 0d774d60b6f..00000000000 --- a/packages/wallet-lib/src/types/KeyChain/KeyChain.d.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {PrivateKey, Network,} from "../types"; -import {HDPrivateKey, HDPublicKey} from "@dashevo/dashcore-lib"; -import {Transaction} from "@dashevo/dashcore-lib/typings/transaction/Transaction"; - -export declare namespace KeyChain { - interface IKeyChainOptions { - network?: Network; - keys?: [Keys] - } -} - -export declare class KeyChain { - constructor(options?: KeyChain.IKeyChainOptions); - network: Network; - keys: [Keys]; - - type: HDKeyTypesParam|PrivateKeyTypeParam; - HDPrivateKey?: HDPrivateKey; - privateKey?: PrivateKey; - - generateKeyForChild(index: number, type?: HDKeyTypesParam): HDPrivateKey|HDPublicKey; - generateKeyForPath(path: string, type?: HDKeyTypesParam): HDPrivateKey|HDPublicKey; - - getDIP15ExtendedKey(userUniqueId: string, contactUniqueId: string, index?: number, accountIndex?: number, type?: HDKeyTypesParam): HDKeyTypes; - getHardenedDIP15AccountKey(index?: number, type?: HDKeyTypesParam): HDKeyTypes; - getHardenedBIP44HDKey(type?: HDKeyTypesParam): HDKeyTypes; - getHardenedDIP9FeatureHDKey(type?: HDKeyTypesParam): HDKeyTypes; - getKeyForChild(index: number, type?: HDKeyTypesParam): HDKeyTypes; - getKeyForPath(path: string, type?: HDKeyTypesParam): HDKeyTypes; - getPrivateKey(): PrivateKey; - - sign(object: Transaction|any, privateKeys:[PrivateKey], sigType: number): any; -} - -type HDKeyTypes = HDPublicKey | HDPrivateKey; - -export declare enum HDKeyTypesParam { - HDPrivateKey="HDPrivateKey", - HDPublicKey="HDPrivateKey", -} -export declare enum PrivateKeyTypeParam { - privateKey='privateKey' -} -export declare interface Keys { - [path: string]: { - path: string - }; -} diff --git a/packages/wallet-lib/src/types/KeyChain/KeyChain.js b/packages/wallet-lib/src/types/KeyChain/KeyChain.js deleted file mode 100644 index 643dcc62737..00000000000 --- a/packages/wallet-lib/src/types/KeyChain/KeyChain.js +++ /dev/null @@ -1,52 +0,0 @@ -const { Networks, HDPrivateKey, HDPublicKey } = require('@dashevo/dashcore-lib'); -const { has } = require('lodash'); - -// eslint-disable-next-line no-underscore-dangle -const _defaultOpts = { - network: Networks.testnet.toString(), - keys: {}, -}; - -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'); - } - if (opts.network) this.network = opts.network; - if (opts.keys) this.keys = { ...opts.keys }; - } -} - -KeyChain.prototype.generateKeyForChild = require('./methods/generateKeyForChild'); -KeyChain.prototype.generateKeyForPath = require('./methods/generateKeyForPath'); -KeyChain.prototype.getDIP15ExtendedKey = require('./methods/getDIP15ExtendedKey'); -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.sign = require('./methods/sign'); - -module.exports = KeyChain; diff --git a/packages/wallet-lib/src/types/KeyChain/KeyChain.spec.js b/packages/wallet-lib/src/types/KeyChain/KeyChain.spec.js deleted file mode 100644 index 3cf9bb0b5a1..00000000000 --- a/packages/wallet-lib/src/types/KeyChain/KeyChain.spec.js +++ /dev/null @@ -1,125 +0,0 @@ -const Dashcore = require('@dashevo/dashcore-lib'); -const { expect } = require('chai'); -const KeyChain = require('./KeyChain'); -const { mnemonicToHDPrivateKey } = require('../../utils/mnemonic'); - -let keychain; -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 expectedRootDIP15AccountKey_0 = 'tprv8hRzmheQujhJN5XP2dj955nAFCKeEoSifJRWuutdbwWRtusdDQ426jbp75EqErUSuTxmPyxYmP1TpcF5qdxGhXLNXRLMGsRLG6NFCv1WnaQ'; -const expectedRootDIP15AccountKey_1 = 'tprv8hRzmheQujhJQyCtFTuUFHxB3Ag5VLB994zhH4CfxbA41cq73HT2mpYq5M33V54oJyn6g514saxxVJB886G55eYX56J6D6x87UNNT6iQHkR'; -const expectedKeyForChild_0 = 'tprv8d4podc2Tg459CH2bwLHXj3vdJFBT2rdsk5Nr1djH7hzHdt5LRdvN6QyFwMiDy7ffRdik7fEVRKKgsHB4F18sh8xF6jFXpKq4sUgGBoSbKw'; -describe('Keychain', function suite() { - this.timeout(10000); - it('should create a keychain', () => { - const expectedException1 = 'Expect privateKey, publicKey, HDPublicKey, HDPrivateKey or Address'; - 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'); - expect(keychain.network.toString()).to.equal('testnet'); - expect(keychain.keys).to.deep.equal({}); - - keychain2 = new KeyChain({ HDPrivateKey: mnemonicToHDPrivateKey(mnemonic2, 'mainnet') }); - }); - it('should get private key', () => { - expect(keychain.getPrivateKey().toString()).to.equal(pk); - }); - it('should generate key for full path', () => { - const path = 'm/44\'/1\'/0\'/0/0'; - const pk2 = keychain.getKeyForPath(path); - 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\''); - expect(pk2.toString()).to.equal(hardenedPk.toString()); - }); - it('should get DIP15 account key', function () { - const rootDIP15AccountKey_0 = keychain.getHardenedDIP15AccountKey(0); - expect(rootDIP15AccountKey_0.toString()).to.deep.equal(expectedRootDIP15AccountKey_0); - const rootDIP15AccountKey_1 = keychain.getHardenedDIP15AccountKey(1); - expect(rootDIP15AccountKey_1.toString()).to.deep.equal(expectedRootDIP15AccountKey_1); - }); - it('should get DIP15 extended key', function () { - const userUniqueId = '0x555d3854c910b7dee436869c4724bed2fe0784e198b8a39f02bbb49d8ebcfc3a'; - const contactUniqueId = '0xa137439f36d04a15474ff7423e4b904a14373fafb37a41db74c84f1dbb5c89b5'; - - // m/9'/5'/15'/0'/0x555d3854c910b7dee436869c4724bed2fe0784e198b8a39f02bbb49d8ebcfc3a'/0xa137439f36d04a15474ff7423e4b904a14373fafb37a41db74c84f1dbb5c89b5'/0 - const DIP15ExtPubKey_0 = keychain2.getDIP15ExtendedKey(userUniqueId, contactUniqueId, 0, 0, type='HDPublicKey'); - expect(DIP15ExtPubKey_0.toString()).to.equal('xpub6LTkTQFSb8KMgMSz4B6sMZLpkQAY6wSTDprDkHDmLwWLpnjxazuxZn13FrSLKUafitsxuaaffM5a49P6aswhpppWUuYW6eFnwBXshR2W2eY'); - expect(DIP15ExtPubKey_0.publicKey.toString()).to.equal('038030c88ab0106e1f4af3b939db2bafc56f892554106f08da1ce1f9ef10f807bd') - - const DIP15ExtPrivKey_0 = keychain2.getDIP15ExtendedKey(userUniqueId, contactUniqueId, 0, 0); - expect(DIP15ExtPrivKey_0.toString()).to.equal('xprvA7UQ3tiYkkm4TsNWx9ZrzRQ6CNL3hUibrbvcwtp9nbyMwzQp3Tbi1ygZQaPoigDhCf8XUjMmGK2NbnB2kLXPYg99Lp6e3iki318sdWcFN3q'); - expect(DIP15ExtPrivKey_0.privateKey.toString()).to.equal('fac40790776d171ee1db90899b5eb2df2f7d2aaf35ad56f07ffb8ed2c57f8e60') - expect(DIP15ExtPrivKey_0.publicKey.toString()).to.equal('038030c88ab0106e1f4af3b939db2bafc56f892554106f08da1ce1f9ef10f807bd') - - const userAhash = "0xa11ce14f698b32e9bb306dba7bbbee831263dcf658abeebb39930460ead117e5"; - const userBhash = "0xb0b052ff075c5ca3c16c3e20e9ac8223834475cc1324ab07889cb24ce6a62793"; - const DIP15ExtKey_1 = keychain.getDIP15ExtendedKey(userAhash, userBhash, 0, 0); - expect(DIP15ExtKey_1.privateKey.toString()).to.equal('60581b6dca8244d3fb3cfe619b5a22277e5423b01e5285f356981f247e0f4a60') - expect(DIP15ExtKey_1.publicKey.toString()).to.equal('03deaac00f721151307fbc7bf80d7b8afab98c1f026d67e5f56b21e2013f551ce6') - - }); - it('should derive from hardened feature path', () => { - const hardenedHDKey = keychain.getHardenedBIP44HDKey(); - const pk2 = keychain.getKeyForPath(`m/44'/1'`); - expect(pk2.toString()).to.equal(hardenedHDKey.toString()); - expect(hardenedHDKey.toString()).to.deep.equal('tprv8dtrJNytYHRiZY585hmHGbguS6VjGpK49puSB7oXZjLHcQfrAzQkF4ZCxM2DkEbyY85J4EYcZ8EjT5ZCU8ozB727TDdodbfXet5GkGau2RQ'); - const derivedPk = hardenedHDKey.deriveChild(0, true).deriveChild(0).deriveChild(0); - const address = new Dashcore.Address(derivedPk.publicKey.toAddress()).toString(); - expect(address).to.equal('yNfUebksUc5HoSfg8gv98ruC3jUNJUM8pT'); - }); - it('should get hardened DIP9FeatureHDKey', function () { - const hardenedHDKey = keychain.getHardenedDIP9FeatureHDKey(); - const pk2 = keychain.getKeyForPath(`m/9'/1'`); - expect(pk2.toString()).to.equal(hardenedHDKey.toString()); - expect(hardenedHDKey.toString()).to.deep.equal('tprv8fBJjWoGgCpGRCbyzE9RUA59rmoN1RUijhLnXGL4VHnLxvSe523yVg4GrGzbR6TyXtdynAEh5z8UX55EXt2Cb3xjvrsx2PgTY9BHxzFVkWn'); - }); - it('should generate key for child', () => { - const keychain2 = new KeyChain({ HDPrivateKey: mnemonicToHDPrivateKey(mnemonic, 'testnet') }); - const keyForChild = keychain2.generateKeyForChild(0); - expect(keyForChild.toString()).to.equal(expectedKeyForChild_0); - }); - - - it('should sign', () => { - - }); -}); -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); - }); -}); -describe('Keychain - single privateKey', function suite() { - this.timeout(10000); - it('should correctly errors out when not a HDPublicKey (privateKey)', () => { - const privateKey = Dashcore.PrivateKey().toString(); - 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); - - 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); - }); - it('should get private key', () => { - const privateKey = Dashcore.PrivateKey().toString(); - const pkKeyChain = new KeyChain({ privateKey, network: 'livenet' }); - expect(pkKeyChain.getPrivateKey().toString()).to.equal(privateKey); - }); -}); diff --git a/packages/wallet-lib/src/types/KeyChain/methods/generateKeyForChild.js b/packages/wallet-lib/src/types/KeyChain/methods/generateKeyForChild.js deleted file mode 100644 index 5d6dcaeba0f..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/KeyChain/methods/generateKeyForPath.js b/packages/wallet-lib/src/types/KeyChain/methods/generateKeyForPath.js deleted file mode 100644 index e266c96d757..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/KeyChain/methods/getDIP15ExtendedKey.js b/packages/wallet-lib/src/types/KeyChain/methods/getDIP15ExtendedKey.js deleted file mode 100644 index a7eed1547dc..00000000000 --- a/packages/wallet-lib/src/types/KeyChain/methods/getDIP15ExtendedKey.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Return the extended key of the relationship between two dashpay contacts. - * @param userUniqueId - Current userID - * @param contactUniqueId - Contact userID - * @param index - the key index. - * @param accountIndex[=0] - the internal wallet account from which derivation is done - * @param type {HDPrivateKey|HDPublicKey} [type=HDPrivateKey] - set the type of returned keys - * @return {HDPrivateKey|HDPublicKey} - */ -function getDIP15ExtendedKey(userUniqueId, contactUniqueId, index = 0, accountIndex = 0, 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'); - } - if (!userUniqueId || !contactUniqueId) throw new Error('Required userUniqueId and contactUniqueId to be defined'); - - // Require a HDPrivateKey for hardened derivation - const extendedPrivateKey = this - .getHardenedDIP15AccountKey(accountIndex, 'HDPrivateKey') - .deriveChild((userUniqueId), true) - .deriveChild((contactUniqueId), true) - .deriveChild(index, false); - - return (type === 'HDPublicKey' ? extendedPrivateKey.hdPublicKey : extendedPrivateKey); -} - -module.exports = getDIP15ExtendedKey; diff --git a/packages/wallet-lib/src/types/KeyChain/methods/getHardenedBIP44HDKey.js b/packages/wallet-lib/src/types/KeyChain/methods/getHardenedBIP44HDKey.js deleted file mode 100644 index 75a6628475f..00000000000 --- a/packages/wallet-lib/src/types/KeyChain/methods/getHardenedBIP44HDKey.js +++ /dev/null @@ -1,12 +0,0 @@ -const { BIP44_TESTNET_ROOT_PATH, BIP44_LIVENET_ROOT_PATH } = require('../../../CONSTANTS'); - -/** - * 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') { - const pathRoot = (this.network.toString() === 'testnet') ? BIP44_TESTNET_ROOT_PATH : BIP44_LIVENET_ROOT_PATH; - return this.generateKeyForPath(pathRoot, type); -} -module.exports = getHardenedBIP44HDKey; diff --git a/packages/wallet-lib/src/types/KeyChain/methods/getHardenedDIP15AccountKey.js b/packages/wallet-lib/src/types/KeyChain/methods/getHardenedDIP15AccountKey.js deleted file mode 100644 index c01f240fc85..00000000000 --- a/packages/wallet-lib/src/types/KeyChain/methods/getHardenedDIP15AccountKey.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Return a safier root path to derivate from - * @param {number} [accountIndex=0] - set the account index - * @param {HDPrivateKey|HDPublicKey} [type=HDPrivateKey] - set the type of returned keys - * @return {HDPrivateKey|HDPublicKey} - */ -function getHardenedDIP15AccountKey(accountIndex = 0, type = 'HDPrivateKey') { - const hardenedFeatureRootKey = this.getHardenedDIP9FeatureHDKey(type); - - // Feature is set to 15' for all DashPay Incoming Funds derivation paths (see DIP15). - const featureKey = hardenedFeatureRootKey.deriveChild(15, true); - return featureKey.deriveChild(accountIndex, true); -} -module.exports = getHardenedDIP15AccountKey; diff --git a/packages/wallet-lib/src/types/KeyChain/methods/getHardenedDIP9FeatureHDKey.js b/packages/wallet-lib/src/types/KeyChain/methods/getHardenedDIP9FeatureHDKey.js deleted file mode 100644 index 06a4733afb0..00000000000 --- a/packages/wallet-lib/src/types/KeyChain/methods/getHardenedDIP9FeatureHDKey.js +++ /dev/null @@ -1,12 +0,0 @@ -const { DIP9_LIVENET_ROOT_PATH, DIP9_TESTNET_ROOT_PATH } = require('../../../CONSTANTS'); - -/** - * 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') { - const pathRoot = (this.network.toString() === 'testnet') ? DIP9_TESTNET_ROOT_PATH : DIP9_LIVENET_ROOT_PATH; - return this.generateKeyForPath(pathRoot, type); -} -module.exports = getHardenedDIP9FeatureHDKey; diff --git a/packages/wallet-lib/src/types/KeyChain/methods/getKeyForChild.js b/packages/wallet-lib/src/types/KeyChain/methods/getKeyForChild.js deleted file mode 100644 index 768f1740d55..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/KeyChain/methods/getKeyForPath.js b/packages/wallet-lib/src/types/KeyChain/methods/getKeyForPath.js deleted file mode 100644 index 4f7bb180d1d..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/KeyChain/methods/getPrivateKey.js b/packages/wallet-lib/src/types/KeyChain/methods/getPrivateKey.js deleted file mode 100644 index 602c287daf7..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/KeyChain/methods/sign.js b/packages/wallet-lib/src/types/KeyChain/methods/sign.js deleted file mode 100644 index b10b561f2c2..00000000000 --- a/packages/wallet-lib/src/types/KeyChain/methods/sign.js +++ /dev/null @@ -1,29 +0,0 @@ -const { - crypto, Transaction, Message, -} = require('@dashevo/dashcore-lib'); - -/** - * Allow to sign any transaction or a transition object from a valid privateKeys list - * @param {Transaction|any} object - * @param {[PrivateKey]} privateKeys - * @param {number} [sigType=crypto.Signature.SIGHASH_ALL] - */ -function sign(object, privateKeys, sigType = crypto.Signature.SIGHASH_ALL) { - const handledTypes = [Transaction.name, Transaction.Payload.SubTxRegisterPayload, Message.name]; - if (!privateKeys) throw new Error('Require one or multiple privateKeys to sign'); - if (!object) throw new Error('Nothing to sign'); - if (!handledTypes.includes(object.constructor.name)) { - throw new Error(`Keychain sign : Unhandled object of type ${object.constructor.name}`); - } - const obj = object.sign(privateKeys, sigType); - - if (obj.isFullySigned && !obj.isFullySigned()) { - throw new Error('Not fully signed transaction'); - } - if (object.constructor.name === 'Message') { - // When signed, message are in string form. - return Message(obj); - } - return obj; -} -module.exports = sign; diff --git a/packages/wallet-lib/src/types/KeyChainStore/KeyChainStore.d.ts b/packages/wallet-lib/src/types/KeyChainStore/KeyChainStore.d.ts new file mode 100644 index 00000000000..7d65d7ba31d --- /dev/null +++ b/packages/wallet-lib/src/types/KeyChainStore/KeyChainStore.d.ts @@ -0,0 +1,20 @@ +import {keyChainId, DerivableKeyChain} from "../DerivableKeyChain/DerivableKeyChain"; + +export declare class KeyChainStore { + constructor(); + + keyChains: Map + masterKeyChainId: keyChainId | null; + + addKeyChain(keychain: DerivableKeyChain, opts?: addKeyChainParam): void; + getKeyChain(keychainId: keyChainId): DerivableKeyChain; + getKeyChains(): Array; + makeChildKeyChainStore(path: string, opts: DerivableKeyChain.IDerivableKeyChainOptions): KeyChainStore; + getMasterKeyChain(): DerivableKeyChain; +} + +export declare interface addKeyChainParam { + isMasterKeyChain?: boolean; +} + + diff --git a/packages/wallet-lib/src/types/KeyChainStore/KeyChainStore.js b/packages/wallet-lib/src/types/KeyChainStore/KeyChainStore.js index fd5c24dfacb..c81c2c0ac2a 100644 --- a/packages/wallet-lib/src/types/KeyChainStore/KeyChainStore.js +++ b/packages/wallet-lib/src/types/KeyChainStore/KeyChainStore.js @@ -1,9 +1,7 @@ class KeyChainStore { constructor() { this.keyChains = new Map(); - this.walletKeyChainId = null; this.masterKeyChainId = null; - this.accountKeyChains = new Map(); } } diff --git a/packages/wallet-lib/src/types/Storage/.eslintrc b/packages/wallet-lib/src/types/Storage/.eslintrc deleted file mode 100644 index a8022ef2b8a..00000000000 --- a/packages/wallet-lib/src/types/Storage/.eslintrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "rules": { - "import/newline-after-import": "off" - } -} diff --git a/packages/wallet-lib/src/types/Storage/Storage.d.ts b/packages/wallet-lib/src/types/Storage/Storage.d.ts deleted file mode 100644 index c41cc83ba66..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/Storage.js b/packages/wallet-lib/src/types/Storage/Storage.js index cf1668582e6..731f0d14d2a 100644 --- a/packages/wallet-lib/src/types/Storage/Storage.js +++ b/packages/wallet-lib/src/types/Storage/Storage.js @@ -1,33 +1,28 @@ const EventEmitter = require('events'); -const { cloneDeep, has } = require('lodash'); - +const { has } = require('lodash'); const CONSTANTS = require('../../CONSTANTS'); -const initialStore = { - wallets: {}, - transactions: {}, - transactionsMetadata: {}, - chains: {}, - instantLocks: {}, -}; -// eslint-disable-next-line no-underscore-dangle -const _defaultOpts = { +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(opts = {}) { super(); - const defaultOpts = JSON.parse(JSON.stringify(_defaultOpts)); + this.wallets = new Map(); + this.chains = new Map(); + this.application = { + blockHeight: 0, + }; - 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') @@ -37,59 +32,17 @@ class Storage extends EventEmitter { 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 = {}; } } -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.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/packages/wallet-lib/src/types/Storage/Storage.spec.js b/packages/wallet-lib/src/types/Storage/Storage.spec.js deleted file mode 100644 index 621a01dc5be..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/initialStore.json b/packages/wallet-lib/src/types/Storage/initialStore.json deleted file mode 100644 index 8b4f9541fa6..00000000000 --- a/packages/wallet-lib/src/types/Storage/initialStore.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "wallets": {}, - "transactions": {}, - "chains": {} -} \ No newline at end of file diff --git a/packages/wallet-lib/src/types/Storage/methods/addNewTxToAddress.js b/packages/wallet-lib/src/types/Storage/methods/addNewTxToAddress.js deleted file mode 100644 index a2dba1b551e..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/addNewTxToAddress.spec.js b/packages/wallet-lib/src/types/Storage/methods/addNewTxToAddress.spec.js deleted file mode 100644 index 03b20df4de9..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/announce.js b/packages/wallet-lib/src/types/Storage/methods/announce.js deleted file mode 100644 index 7f3a5049947..00000000000 --- a/packages/wallet-lib/src/types/Storage/methods/announce.js +++ /dev/null @@ -1,28 +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: - case EVENTS.TX_METADATA: - 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/packages/wallet-lib/src/types/Storage/methods/announce.spec.js b/packages/wallet-lib/src/types/Storage/methods/announce.spec.js deleted file mode 100644 index 55b7e6b5e80..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/calculateDuffBalance.js b/packages/wallet-lib/src/types/Storage/methods/calculateDuffBalance.js deleted file mode 100644 index 8f8e75841b1..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/clearAll.js b/packages/wallet-lib/src/types/Storage/methods/clearAll.js deleted file mode 100644 index c22c665bcd6..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/clearAll.spec.js b/packages/wallet-lib/src/types/Storage/methods/clearAll.spec.js deleted file mode 100644 index de4bfdd8870..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/configure.spec.js b/packages/wallet-lib/src/types/Storage/methods/configure.spec.js deleted file mode 100644 index 186f5adc496..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/createAccount.js b/packages/wallet-lib/src/types/Storage/methods/createAccount.js deleted file mode 100644 index 819a1737239..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/createChain.js b/packages/wallet-lib/src/types/Storage/methods/createChain.js deleted file mode 100644 index 0fea70ed618..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/createChain.spec.js b/packages/wallet-lib/src/types/Storage/methods/createChain.spec.js deleted file mode 100644 index 71d2da156ff..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/createChainStore.js b/packages/wallet-lib/src/types/Storage/methods/createChainStore.js new file mode 100644 index 00000000000..f978e5b9413 --- /dev/null +++ b/packages/wallet-lib/src/types/Storage/methods/createChainStore.js @@ -0,0 +1,28 @@ +const ChainStore = require('../../ChainStore/ChainStore'); +const EVENTS = require('../../../EVENTS'); + +const EVENTS_TO_FORWARD = [ + EVENTS.FETCHED_CONFIRMED_TRANSACTION, +]; + +/** + * Create when does not yet exist a chainStore + * @param network + * @return {boolean} + */ +const createChainStore = function createChain(network) { + if (!this.chains.has(network.toString())) { + const chainStore = new ChainStore(network.toString()); + this.chains.set(network.toString(), chainStore); + + EVENTS_TO_FORWARD.forEach((event) => { + chainStore.on(event, (data) => { + this.emit(event, { type: event, payload: data }); + }); + }); + + return true; + } + return false; +}; +module.exports = createChainStore; diff --git a/packages/wallet-lib/src/types/Storage/methods/createSingleAddress.js b/packages/wallet-lib/src/types/Storage/methods/createSingleAddress.js deleted file mode 100644 index 62421610cd2..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/createWallet.js b/packages/wallet-lib/src/types/Storage/methods/createWallet.js deleted file mode 100644 index 7380cdbc5be..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/createWallet.spec.js b/packages/wallet-lib/src/types/Storage/methods/createWallet.spec.js deleted file mode 100644 index 9b799c12d04..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/createWalletStore.js b/packages/wallet-lib/src/types/Storage/methods/createWalletStore.js new file mode 100644 index 00000000000..b398746ac47 --- /dev/null +++ b/packages/wallet-lib/src/types/Storage/methods/createWalletStore.js @@ -0,0 +1,10 @@ +const WalletStore = require('../../WalletStore/WalletStore'); + +const createWalletStore = function createWallet(walletId = 'squawk7700') { + if (!this.wallets.has(walletId)) { + this.wallets.set(walletId, new WalletStore(walletId)); + return true; + } + return false; +}; +module.exports = createWalletStore; diff --git a/packages/wallet-lib/src/types/Storage/methods/exportAccounts.js b/packages/wallet-lib/src/types/Storage/methods/exportAccounts.js deleted file mode 100644 index 89969a7384c..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/exportChains.js b/packages/wallet-lib/src/types/Storage/methods/exportChains.js deleted file mode 100644 index 0cef2b28d7c..00000000000 --- a/packages/wallet-lib/src/types/Storage/methods/exportChains.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function exportChains() { - return this.store.chains; -}; diff --git a/packages/wallet-lib/src/types/Storage/methods/exportTransactions.js b/packages/wallet-lib/src/types/Storage/methods/exportTransactions.js deleted file mode 100644 index 57daaf8e8e4..00000000000 --- a/packages/wallet-lib/src/types/Storage/methods/exportTransactions.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function exportTransactions() { - return this.store.transactions; -}; diff --git a/packages/wallet-lib/src/types/Storage/methods/exportWallets.js b/packages/wallet-lib/src/types/Storage/methods/exportWallets.js deleted file mode 100644 index b2ba79562bd..00000000000 --- a/packages/wallet-lib/src/types/Storage/methods/exportWallets.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function exportWallets() { - return this.store.wallets; -}; diff --git a/packages/wallet-lib/src/types/Storage/methods/getBlockHeader.js b/packages/wallet-lib/src/types/Storage/methods/getBlockHeader.js deleted file mode 100644 index 4c69a0a4d4d..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/getChainStore.js b/packages/wallet-lib/src/types/Storage/methods/getChainStore.js new file mode 100644 index 00000000000..5255eb71033 --- /dev/null +++ b/packages/wallet-lib/src/types/Storage/methods/getChainStore.js @@ -0,0 +1,4 @@ +function getChainStore(network) { + return this.chains.get(network); +} +module.exports = getChainStore; diff --git a/packages/wallet-lib/src/types/Storage/methods/getIdentityIdByIndex.js b/packages/wallet-lib/src/types/Storage/methods/getIdentityIdByIndex.js deleted file mode 100644 index 111d3a145b5..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/getIndexedIdentityIds.js b/packages/wallet-lib/src/types/Storage/methods/getIndexedIdentityIds.js deleted file mode 100644 index cb82397d31d..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/getInstantLock.js b/packages/wallet-lib/src/types/Storage/methods/getInstantLock.js deleted file mode 100644 index 333bbd8e61d..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/getStore.js b/packages/wallet-lib/src/types/Storage/methods/getStore.js deleted file mode 100644 index 8782c2bcd46..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/getTransaction.js b/packages/wallet-lib/src/types/Storage/methods/getTransaction.js deleted file mode 100644 index 601bcf859ae..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/getTransaction.spec.js b/packages/wallet-lib/src/types/Storage/methods/getTransaction.spec.js deleted file mode 100644 index 4134d582033..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/getTransactionMetadata.js b/packages/wallet-lib/src/types/Storage/methods/getTransactionMetadata.js deleted file mode 100644 index 63626b05866..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/getTransactionMetadata.spec.js b/packages/wallet-lib/src/types/Storage/methods/getTransactionMetadata.spec.js deleted file mode 100644 index 2624263fb8f..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/getWalletStore.js b/packages/wallet-lib/src/types/Storage/methods/getWalletStore.js new file mode 100644 index 00000000000..3f43fc2b25e --- /dev/null +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/importAccounts.js b/packages/wallet-lib/src/types/Storage/methods/importAccounts.js deleted file mode 100644 index 51b373b0876..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/importAccounts.spec.js b/packages/wallet-lib/src/types/Storage/methods/importAccounts.spec.js deleted file mode 100644 index 361f8cfbac8..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/importAddress.js b/packages/wallet-lib/src/types/Storage/methods/importAddress.js deleted file mode 100644 index 1788692122c..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/importAddress.spec.js b/packages/wallet-lib/src/types/Storage/methods/importAddress.spec.js deleted file mode 100644 index 714f3cfd506..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/importAddresses.js b/packages/wallet-lib/src/types/Storage/methods/importAddresses.js deleted file mode 100644 index 5b2287d0ab0..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/importAddresses.spec.js b/packages/wallet-lib/src/types/Storage/methods/importAddresses.spec.js deleted file mode 100644 index 7ad30ca8bf0..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/importBlockHeader.js b/packages/wallet-lib/src/types/Storage/methods/importBlockHeader.js deleted file mode 100644 index a96ca0f1115..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/importChains.js b/packages/wallet-lib/src/types/Storage/methods/importChains.js deleted file mode 100644 index df1afb97899..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/importInstantLock.js b/packages/wallet-lib/src/types/Storage/methods/importInstantLock.js deleted file mode 100644 index 8ce00b63f8c..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/importSingleAddress.js b/packages/wallet-lib/src/types/Storage/methods/importSingleAddress.js deleted file mode 100644 index f408f472be3..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/importSingleAddress.spec.js b/packages/wallet-lib/src/types/Storage/methods/importSingleAddress.spec.js deleted file mode 100644 index 44023d1dbe0..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/importTransaction.js b/packages/wallet-lib/src/types/Storage/methods/importTransaction.js deleted file mode 100644 index af9abcf1003..00000000000 --- a/packages/wallet-lib/src/types/Storage/methods/importTransaction.js +++ /dev/null @@ -1,129 +0,0 @@ -const { Transaction } = require('@dashevo/dashcore-lib'); -const { Output } = Transaction; -const { InvalidDashcoreTransaction } = require('../../../errors'); -const { FETCHED_CONFIRMED_TRANSACTION, TX_METADATA } = 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]); - } - - this.announce(TX_METADATA, { hash: transaction.hash, metadata: transactionMetadata }); - } - } - - // 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; - } else if (addressObject.unconfirmedBalanceSat >= vout.satoshis) { - addressObject.unconfirmedBalanceSat -= vout.satoshis; - 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/packages/wallet-lib/src/types/Storage/methods/importTransaction.spec.js b/packages/wallet-lib/src/types/Storage/methods/importTransaction.spec.js deleted file mode 100644 index 35e7b7b079d..00000000000 --- a/packages/wallet-lib/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.be.oneOf(['TX_METADATA', '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/packages/wallet-lib/src/types/Storage/methods/importTransactions.js b/packages/wallet-lib/src/types/Storage/methods/importTransactions.js deleted file mode 100644 index a2e29244dee..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/importTransactions.spec.js b/packages/wallet-lib/src/types/Storage/methods/importTransactions.spec.js deleted file mode 100644 index 307a5c3263a..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/insertIdentityAtIndex.js b/packages/wallet-lib/src/types/Storage/methods/insertIdentityAtIndex.js deleted file mode 100644 index fd46c1f20ad..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/rehydrateState.js b/packages/wallet-lib/src/types/Storage/methods/rehydrateState.js index 342489cf2e0..a391c9e6bb9 100644 --- a/packages/wallet-lib/src/types/Storage/methods/rehydrateState.js +++ b/packages/wallet-lib/src/types/Storage/methods/rehydrateState.js @@ -1,74 +1,10 @@ const { merge } = require('lodash'); -const { InstantLock, Transaction, BlockHeader } = require('@dashevo/dashcore-lib'); const { hasMethod } = require('../../../utils'); const mergeHelper = (initial = {}, additional = {}) => merge(initial, additional); const { REHYDRATE_STATE_FAILED, REHYDRATE_STATE_SUCCESS } = require('../../../EVENTS'); -const logger = require('../../../logger'); - -const storeTypesSchema = { - transactions: { - '*': Transaction, - }, - instantLocks: { - '*': InstantLock, - }, - wallets: { - '*': { - addresses: { - '*': { - '*': { - utxos: { - // eslint-disable-next-line func-names - '*': function (item) { - // TODO: resolve type inconsistency for address utxos - try { - return new Transaction.UnspentOutput(item); - } catch (e) { - return new Transaction.Output(item); - } - }, - }, - }, - }, - }, - }, - }, - chains: { - '*': { - blockHeaders: { - '*': BlockHeader, - }, - }, - }, -}; - -const castItemTypes = (item, schema) => { - Object.entries(schema).forEach(([schemaKey, schemaValue]) => { - if (schemaValue.constructor.name !== 'Object') { - const Clazz = schemaValue; - if (schemaKey === '*') { - Object.keys(item).forEach((itemKey) => { - // eslint-disable-next-line no-param-reassign - item[itemKey] = new Clazz(item[itemKey]); - }); - } else { - if (!item[schemaKey]) { - throw new Error(`No schema key "${schemaKey}" found for item ${JSON.stringify(item)}`); - } - - // eslint-disable-next-line no-param-reassign - item[schemaKey] = new Clazz(item[schemaKey]); - } - } else if (schemaKey === '*') { - Object.values(item).forEach((itemValue) => castItemTypes(itemValue, schemaValue)); - } else { - castItemTypes(item[schemaKey], schemaValue); - } - }); - - return item; -}; +const WalletStore = require('../../WalletStore/WalletStore'); +const ChainStore = require('../../ChainStore/ChainStore'); /** * Fetch the state from the persistence adapter @@ -77,39 +13,27 @@ const castItemTypes = (item, schema) => { const rehydrateState = async function rehydrateState() { if (this.rehydrate && this.lastRehydrate === null) { try { - const storeItems = { - transactions: null, - wallets: null, - chains: null, - instantLocks: null, - }; - const keys = Object.keys(storeItems); - // Obtain items from storage adapter - for (let i = 0; i < keys.length; i += 1) { - const itemKey = keys[i]; - - let item; - if (this.adapter && hasMethod(this.adapter, 'getItem')) { - // eslint-disable-next-line no-await-in-loop - item = await this.adapter.getItem(itemKey); + if (this.adapter && hasMethod(this.adapter, 'getItem')) { + const wallets = await this.adapter.getItem('wallets'); + if (wallets) { + wallets.forEach((walletState) => { + const walletStore = new WalletStore(); + walletStore.importState(walletState); + this.wallets.set(walletStore.walletId, walletStore); + }); } - storeItems[itemKey] = item || this.store[itemKey]; - } - - // Cast store items to correct data types - try { - castItemTypes(storeItems, storeTypesSchema); - } catch (e) { - logger.error('Error casting storage items types: possibly data schema mismatch'); - throw e; + const chains = await this.adapter.getItem('chains'); + if (chains) { + chains.forEach((chainState) => { + const chainStore = new ChainStore(); + chainStore.importState(chainState); + this.chains.set(chainStore.network, chainStore); + }); + } + this.application = mergeHelper(this.application, await this.adapter.getItem('application')); } - // Merge with the current items in store - Object.keys(storeItems).forEach((itemKey) => { - this.store[itemKey] = mergeHelper(this.store[itemKey], storeItems[itemKey]); - }); - this.lastRehydrate = +new Date(); this.emit(REHYDRATE_STATE_SUCCESS, { type: REHYDRATE_STATE_SUCCESS, payload: null }); } catch (e) { diff --git a/packages/wallet-lib/src/types/Storage/methods/rehydrateState.spec.js b/packages/wallet-lib/src/types/Storage/methods/rehydrateState.spec.js deleted file mode 100644 index 5c59971fa66..00000000000 --- a/packages/wallet-lib/src/types/Storage/methods/rehydrateState.spec.js +++ /dev/null @@ -1,226 +0,0 @@ -const { expect } = require('chai'); -const rehydrateState = require('./rehydrateState'); -const { Transaction, BlockHeader, InstantLock } = require("@dashevo/dashcore-lib"); - -const storeMock = { - "transactions": { - "d37eb9f1b41c45134926d418486f2d8f57778cb949f4069a0b4c91955934913b": { - "hash": "d37eb9f1b41c45134926d418486f2d8f57778cb949f4069a0b4c91955934913b", - "version": 2, - "inputs": [ - { - "prevTxId": "ba657cdff1bdf2010e2b394f7db81aa429a94574c7fdcc5da4a8c5be751bd419", - "outputIndex": 1, - "sequenceNumber": 4294967294, - "script": "483045022100eb7f103a5755e79a25179f289b74033d13e7b5a252a4911bba1c908d0db4361002203a664917ebbb4f5c1d81409898c05f807e23dd324d4458f32a6fc5b42ad8e3830121024b181f43670ebc67826e07885170029e01fd830bfcfa43862d694224d8c7e162", - "scriptString": "72 0x3045022100eb7f103a5755e79a25179f289b74033d13e7b5a252a4911bba1c908d0db4361002203a664917ebbb4f5c1d81409898c05f807e23dd324d4458f32a6fc5b42ad8e38301 33 0x024b181f43670ebc67826e07885170029e01fd830bfcfa43862d694224d8c7e162" - } - ], - "outputs": [ - { - "satoshis": 82716538, - "script": "76a9142f7d620394f5604854e495e6a78773287651faab88ac" - }, - { - "satoshis": 105580000, - "script": "76a91428a46d7ae4d50ec3a5dbb847b01a60818905e98e88ac" - } - ], - "nLockTime": 627066 - }, - "26faad59f6f3aef120323fb3aa9b0a3aaccf56ae770ff83099e3e86324d3a0da": { - "hash": "26faad59f6f3aef120323fb3aa9b0a3aaccf56ae770ff83099e3e86324d3a0da", - "version": 3, - "inputs": [ - { - "prevTxId": "d37eb9f1b41c45134926d418486f2d8f57778cb949f4069a0b4c91955934913b", - "outputIndex": 1, - "sequenceNumber": 4294967295, - "script": "4730440220645a3cfe6798b2d4e5943209c2f241725c0e809c82bd3985743854bbebfeca48022036fd8bf98960d4d1deea37e5cc5833cb64178bc2e27a1628f83250e9e2ec69290121024c61a9a159104ad5e027754b19514347c5ee4a7f5ede8cc379f5f987f2fdb0b0", - "scriptString": "71 0x30440220645a3cfe6798b2d4e5943209c2f241725c0e809c82bd3985743854bbebfeca48022036fd8bf98960d4d1deea37e5cc5833cb64178bc2e27a1628f83250e9e2ec692901 33 0x024c61a9a159104ad5e027754b19514347c5ee4a7f5ede8cc379f5f987f2fdb0b0" - } - ], - "outputs": [ - { - "satoshis": 10000, - "script": "76a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac" - }, - { - "satoshis": 105569753, - "script": "76a91499c256c6f6724b926b7d903b10547bf712faeebd88ac" - } - ], - "nLockTime": 0 - } - }, - "wallets": { - "799ea77395": { - "accounts": { - "m/44'/1'/0'": { - "label": null, - "path": "m/44'/1'/0'", - "network": "testnet", - "blockHeight": 627626, - "blockHash": "00000053b6c3b0b56f64607ad77524922fbb4560306fa31f165ae36e11551d77" - } - }, - "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": "yQ2LrWYLRdUdDnofwqscghwSgCghd2BMPv", - "transactions": [ - "d37eb9f1b41c45134926d418486f2d8f57778cb949f4069a0b4c91955934913b", - "26faad59f6f3aef120323fb3aa9b0a3aaccf56ae770ff83099e3e86324d3a0da" - ], - "balanceSat": 0, - "unconfirmedBalanceSat": 0, - "utxos": { - "adfc0ad1dd803fc1541f87711e794936c3fd99b763d0b26e77dd463425382833-1": { - "address": "yQ2LrWYLRdUdDnofwqscghwSgCghd2BMPv", - "txid": "26faad59f6f3aef120323fb3aa9b0a3aaccf56ae770ff83099e3e86324d3a0da", - "vout": 1, - "scriptPubKey": "76a91421b4db2de99d02a16092fa0aa0f9bb32d87353a588ac", - "amount": 0.82706400 - } - }, - "fetchedLast": 0, - "used": true - } - }, - "internal": { - "m/44'/1'/0'/1/0": { - "path": "m/44'/1'/0'/1/0", - "index": 0, - "address": "yaLT3TjS7jdDVgAjDBCRSm1rHbsMeLXAx4", - "transactions": [ - "adfc0ad1dd803fc1541f87711e794936c3fd99b763d0b26e77dd463425382833" - ], - "balanceSat": 0, - "unconfirmedBalanceSat": 0, - "utxos": { - "adfc0ad1dd803fc1541f87711e794936c3fd99b763d0b26e77dd463425382833-1": { - "address": "yaLT3TjS7jdDVgAjDBCRSm1rHbsMeLXAx4", - "txid": "26faad59f6f3aef120323fb3aa9b0a3aaccf56ae770ff83099e3e86324d3a0da", - "vout": 1, - "scriptPubKey": "76a91421b4db2de99d02a16092fa0aa0f9bb32d87353a588ac", - "amount": 0.0001 - } - }, - "fetchedLast": 0, - "used": true - } - }, - "misc": {} - } - } - }, - "chains": { - "testnet": { - "name": "testnet", - "blockHeaders": { - "000000059885815cfc06ba74b814200d29658394dbe5d1e93948a8587947747b": { - "hash": "000000059885815cfc06ba74b814200d29658394dbe5d1e93948a8587947747b", - "version": 536870912, - "prevHash": "000000c520efd2047f0b6f0c1c75e0382f8a9b7d76bb140bde3ada10c62e8b0d", - "merkleRoot": "ef292bfb7965402e57dfeb4ee8bad0055c216c4c5a4e549a0ac17a393ae8617b", - "time": 1638950949, - "bits": 503385436, - "nonce": 351770 - }, - "000000b7d508273169da2e0f1c167554d3d4643759361ab8ca338cabbbf5dee4": { - "hash": "000000b7d508273169da2e0f1c167554d3d4643759361ab8ca338cabbbf5dee4", - "version": 536870912, - "prevHash": "0000009b0bb17530c41a8200868e61e8a2d927b3cce7cfac14c3f393ffca5b06", - "merkleRoot": "664d055f0194e01ac64db1ace3a720f4580533d6d999b1cfab7740f8b9c4d642", - "time": 1638884848, - "bits": 503373836, - "nonce": 13494 - }, - }, - "mappedBlockHeaderHeights": { - "627561": "00000145d8a9ab1a71ffdcd01657d284c67593b27f9ca244fc202632bad7145d", - "627574": "000000801bafea9630d9c6a341963912e7faff067e79b8b9b57910b62f736d3e", - "627626": "00000053b6c3b0b56f64607ad77524922fbb4560306fa31f165ae36e11551d77" - }, - "blockHeight": 627626, - "fees": { - "minRelay": 1000 - } - } - }, - "instantLocks": { - "d37eb9f1b41c45134926d418486f2d8f57778cb949f4069a0b4c91955934913b": { - "inputs": [ - { - "outpointHash": "3188b2bfb238cb07c1822944088fff59d665af1f00a72d2beee8b88457201beb", - "outpointIndex": 1 - } - ], - "txid": "d37eb9f1b41c45134926d418486f2d8f57778cb949f4069a0b4c91955934913b", - "signature": "0628537fd9b44b5d2441724780183bea34f44e82fea7a7057337c562858bc9dabfe3541c8a7c9bd4677887dfaa2b02b90c483fe223590a05d6ab8670221aacf046731aa462a8e9f08271434ed6cbfaa1ec51c2be71f6e0a7d9612795b1a0f050" - }, - "26faad59f6f3aef120323fb3aa9b0a3aaccf56ae770ff83099e3e86324d3a0da": { - "inputs": [ - { - "outpointHash": "b5cdbdae1f43ef324326b5519e2e670afa9203e2f37115c3c6ac51db968b3726", - "outpointIndex": 1 - } - ], - "txid": "26faad59f6f3aef120323fb3aa9b0a3aaccf56ae770ff83099e3e86324d3a0da", - "signature": "913ab2cf9bb6d918f0d2d12c5fef9ba9652ce93f625cebd83241746b36bdc6505e0385e1875576a92a5d49d74ec8098315e90c8688ccd41ab4fdc8df43bd83fa53081a65651bc30ca2957e4c2e98388465b19fac7b61f7b86393d777c2d26e66" - }, - } -} - - -describe('Storage - rehydrateState', () => { - const storage = { - rehydrate: true, - lastRehydrate: null, - adapter: { - getItem: async (key) => { - return storeMock[key]; - } - }, - store: { - transactions: {}, - wallets: {}, - chains: {}, - instantLocks: {} - }, - emit: () => {} - } - - - it('should rehydrate the state', async () => { - await rehydrateState.call(storage); - Object.values(storage.store.transactions).forEach(tx => { - expect(tx instanceof Transaction).to.be.true; - }); - - Object.values(storage.store.instantLocks).forEach(isLock => { - expect(isLock instanceof InstantLock).to.be.true; - }); - - Object.values(storage.store.wallets).forEach(wallet => { - const { internal, external } = wallet.addresses; - Object.values({ ...internal, ...external }).forEach(address => { - Object.values(address.utxos).forEach(utxo => { - expect(utxo instanceof Transaction.UnspentOutput).to.be.true; - }); - }); - }); - - Object.values(storage.store.chains).forEach(chain => { - Object.values(chain.blockHeaders).forEach(blockHeader => { - expect(blockHeader instanceof BlockHeader).to.be.true; - }) - }) - }); -}); diff --git a/packages/wallet-lib/src/types/Storage/methods/saveState.js b/packages/wallet-lib/src/types/Storage/methods/saveState.js index 2eb95eab9bd..7b6562a61bc 100644 --- a/packages/wallet-lib/src/types/Storage/methods/saveState.js +++ b/packages/wallet-lib/src/types/Storage/methods/saveState.js @@ -8,10 +8,9 @@ const saveState = async function saveState() { if (this.autosave && this.adapter && this.adapter.setItem) { const self = this; try { - await this.adapter.setItem('transactions', { ...self.store.transactions }); - await this.adapter.setItem('wallets', { ...self.store.wallets }); - await this.adapter.setItem('chains', { ...self.store.chains }); - await this.adapter.setItem('instantLocks', { ...self.store.instantLocks }); + await this.adapter.setItem('wallets', [...self.wallets].map(([, walletStore]) => walletStore.exportState())); + await this.adapter.setItem('chains', [...self.chains].map(([, chainStore]) => chainStore.exportState())); + await this.adapter.setItem('application', { ...self.application }); this.lastSave = +new Date(); this.emit(SAVE_STATE_SUCCESS, { type: SAVE_STATE_SUCCESS, payload: this.lastSave }); return true; diff --git a/packages/wallet-lib/src/types/Storage/methods/saveState.spec.js b/packages/wallet-lib/src/types/Storage/methods/saveState.spec.js deleted file mode 100644 index 261a5aa673c..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/searchAddress.js b/packages/wallet-lib/src/types/Storage/methods/searchAddress.js deleted file mode 100644 index b19382c69d6..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/searchAddress.spec.js b/packages/wallet-lib/src/types/Storage/methods/searchAddress.spec.js deleted file mode 100644 index 0d0b92ca73d..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/searchAddressWithTx.spec.js b/packages/wallet-lib/src/types/Storage/methods/searchAddressWithTx.spec.js deleted file mode 100644 index f8df278e8cd..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/searchAddressesWithTx.js b/packages/wallet-lib/src/types/Storage/methods/searchAddressesWithTx.js deleted file mode 100644 index 6aa36d625bb..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/searchBlockHeader.js b/packages/wallet-lib/src/types/Storage/methods/searchBlockHeader.js deleted file mode 100644 index c93463a19cf..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/searchBlockHeader.spec.js b/packages/wallet-lib/src/types/Storage/methods/searchBlockHeader.spec.js deleted file mode 100644 index 137b3b2e902..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/searchTransaction.js b/packages/wallet-lib/src/types/Storage/methods/searchTransaction.js deleted file mode 100644 index 1bd8b729b08..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/searchTransaction.spec.js b/packages/wallet-lib/src/types/Storage/methods/searchTransaction.spec.js deleted file mode 100644 index 831a804be5b..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/searchTransactionMetadata.js b/packages/wallet-lib/src/types/Storage/methods/searchTransactionMetadata.js deleted file mode 100644 index eae1b9627b6..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/searchTransactionMetadata.spec.js b/packages/wallet-lib/src/types/Storage/methods/searchTransactionMetadata.spec.js deleted file mode 100644 index 6cc013b5a0d..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/searchWallet.js b/packages/wallet-lib/src/types/Storage/methods/searchWallet.js deleted file mode 100644 index 9719d057b92..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/searchWallet.spec.js b/packages/wallet-lib/src/types/Storage/methods/searchWallet.spec.js deleted file mode 100644 index 4f40be20c63..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/startWorker.spec.js b/packages/wallet-lib/src/types/Storage/methods/startWorker.spec.js deleted file mode 100644 index 0711f40946e..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/updateAddress.js b/packages/wallet-lib/src/types/Storage/methods/updateAddress.js deleted file mode 100644 index 5bff96feaa5..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/updateAddress.spec.js b/packages/wallet-lib/src/types/Storage/methods/updateAddress.spec.js deleted file mode 100644 index 0de48ab1e3d..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/updateTransaction.js b/packages/wallet-lib/src/types/Storage/methods/updateTransaction.js deleted file mode 100644 index 18c3595050a..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Storage/methods/updateTransaction.spec.js b/packages/wallet-lib/src/types/Storage/methods/updateTransaction.spec.js deleted file mode 100644 index 04c359171fc..00000000000 --- a/packages/wallet-lib/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/packages/wallet-lib/src/types/Wallet/Wallet.js b/packages/wallet-lib/src/types/Wallet/Wallet.js index 220030cf9ca..d730be04b26 100644 --- a/packages/wallet-lib/src/types/Wallet/Wallet.js +++ b/packages/wallet-lib/src/types/Wallet/Wallet.js @@ -88,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; @@ -114,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, }; @@ -137,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, }; } @@ -175,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/packages/wallet-lib/src/types/Wallet/Wallet.spec.js b/packages/wallet-lib/src/types/Wallet/Wallet.spec.js index 5f4a4368e20..a6d416bfbee 100644 --- a/packages/wallet-lib/src/types/Wallet/Wallet.spec.js +++ b/packages/wallet-lib/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); @@ -170,7 +170,7 @@ describe('Wallet - class', function suite() { }); }); describe('Wallet - Get/Create Account', function suite() { - this.timeout(15000); + this.timeout(10000); const wallet1 = new Wallet({ mnemonic: fluidMnemonic.mnemonic, ...mocks }); it('should be able to create/get a wallet', async () => { @@ -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/packages/wallet-lib/src/types/Wallet/methods/exportWallet.js b/packages/wallet-lib/src/types/Wallet/methods/exportWallet.js index f64c4afeca9..39cc12bc41f 100644 --- a/packages/wallet-lib/src/types/Wallet/methods/exportWallet.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Wallet/methods/exportWallet.spec.js b/packages/wallet-lib/src/types/Wallet/methods/exportWallet.spec.js index d7396d6f2c7..b0af3f6c3a5 100644 --- a/packages/wallet-lib/src/types/Wallet/methods/exportWallet.spec.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Wallet/methods/fromAddress.js b/packages/wallet-lib/src/types/Wallet/methods/fromAddress.js index c69c7749b9d..ebf0aba2925 100644 --- a/packages/wallet-lib/src/types/Wallet/methods/fromAddress.js +++ b/packages/wallet-lib/src/types/Wallet/methods/fromAddress.js @@ -1,14 +1,18 @@ const { is } = require('../../../utils'); -const KeyChain = require('../../KeyChain/KeyChain'); +const DerivableKeyChain = require('../../DerivableKeyChain/DerivableKeyChain'); 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 DerivableKeyChain({ address, network }); + this.keyChainStore = new KeyChainStore(); + this.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); }; diff --git a/packages/wallet-lib/src/types/Wallet/methods/fromAddress.spec.js b/packages/wallet-lib/src/types/Wallet/methods/fromAddress.spec.js index 44bd996a4d2..f0839308fc9 100644 --- a/packages/wallet-lib/src/types/Wallet/methods/fromAddress.spec.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Wallet/methods/fromHDPrivateKey.js b/packages/wallet-lib/src/types/Wallet/methods/fromHDPrivateKey.js index da2aba6e047..a90dd2ca516 100644 --- a/packages/wallet-lib/src/types/Wallet/methods/fromHDPrivateKey.js +++ b/packages/wallet-lib/src/types/Wallet/methods/fromHDPrivateKey.js @@ -2,7 +2,8 @@ const { HDPrivateKey } = require('@dashevo/dashcore-lib'); const { is, } = require('../../../utils'); -const KeyChain = require('../../KeyChain/KeyChain'); +const DerivableKeyChain = require('../../DerivableKeyChain/DerivableKeyChain'); +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 DerivableKeyChain({ HDPrivateKey: this.HDPrivateKey }); + this.keyChainStore = new KeyChainStore(); + this.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); }; diff --git a/packages/wallet-lib/src/types/Wallet/methods/fromHDPrivateKey.spec.js b/packages/wallet-lib/src/types/Wallet/methods/fromHDPrivateKey.spec.js index 5c277483190..c947a076b4c 100644 --- a/packages/wallet-lib/src/types/Wallet/methods/fromHDPrivateKey.spec.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Wallet/methods/fromHDPublicKey.js b/packages/wallet-lib/src/types/Wallet/methods/fromHDPublicKey.js index 0631c051151..1a6b5aacdfc 100644 --- a/packages/wallet-lib/src/types/Wallet/methods/fromHDPublicKey.js +++ b/packages/wallet-lib/src/types/Wallet/methods/fromHDPublicKey.js @@ -1,7 +1,8 @@ const Dashcore = require('@dashevo/dashcore-lib'); const { is } = require('../../../utils'); -const KeyChain = require('../../KeyChain/KeyChain'); +const DerivableKeyChain = require('../../DerivableKeyChain/DerivableKeyChain'); 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 DerivableKeyChain({ HDPublicKey: this.HDPublicKey }); + this.keyChainStore = new KeyChainStore(); + this.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); }; diff --git a/packages/wallet-lib/src/types/Wallet/methods/fromHDPublicKey.spec.js b/packages/wallet-lib/src/types/Wallet/methods/fromHDPublicKey.spec.js index 96a1c9cf847..8a8086b7cd9 100644 --- a/packages/wallet-lib/src/types/Wallet/methods/fromHDPublicKey.spec.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Wallet/methods/fromMnemonic.js b/packages/wallet-lib/src/types/Wallet/methods/fromMnemonic.js index 4bdeaefefa8..b0b436fbbf4 100644 --- a/packages/wallet-lib/src/types/Wallet/methods/fromMnemonic.js +++ b/packages/wallet-lib/src/types/Wallet/methods/fromMnemonic.js @@ -2,20 +2,26 @@ const { mnemonicToHDPrivateKey, is, } = require('../../../utils'); -const KeyChain = require('../../KeyChain/KeyChain'); +const DerivableKeyChain = require('../../DerivableKeyChain/DerivableKeyChain'); +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 DerivableKeyChain({ HDPrivateKey: this.HDPrivateKey }); + this.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); }; diff --git a/packages/wallet-lib/src/types/Wallet/methods/fromMnemonic.spec.js b/packages/wallet-lib/src/types/Wallet/methods/fromMnemonic.spec.js index 14e81028618..85f498c6172 100644 --- a/packages/wallet-lib/src/types/Wallet/methods/fromMnemonic.spec.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Wallet/methods/fromPrivateKey.js b/packages/wallet-lib/src/types/Wallet/methods/fromPrivateKey.js index 2f8f919bc7e..60912dcac17 100644 --- a/packages/wallet-lib/src/types/Wallet/methods/fromPrivateKey.js +++ b/packages/wallet-lib/src/types/Wallet/methods/fromPrivateKey.js @@ -1,15 +1,19 @@ const { is } = require('../../../utils'); -const KeyChain = require('../../KeyChain/KeyChain'); +const DerivableKeyChain = require('../../DerivableKeyChain/DerivableKeyChain'); 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 DerivableKeyChain({ privateKey, network }); + this.keyChainStore = new KeyChainStore(); + this.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); }; diff --git a/packages/wallet-lib/src/types/Wallet/methods/fromPrivateKey.spec.js b/packages/wallet-lib/src/types/Wallet/methods/fromPrivateKey.spec.js index c707c2faddc..4b6ca5e89ab 100644 --- a/packages/wallet-lib/src/types/Wallet/methods/fromPrivateKey.spec.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Wallet/methods/fromPublicKey.js b/packages/wallet-lib/src/types/Wallet/methods/fromPublicKey.js index 9e6867ac78f..c86dc6d6e37 100644 --- a/packages/wallet-lib/src/types/Wallet/methods/fromPublicKey.js +++ b/packages/wallet-lib/src/types/Wallet/methods/fromPublicKey.js @@ -1,15 +1,19 @@ const { is } = require('../../../utils'); -const KeyChain = require('../../KeyChain/KeyChain'); +const DerivableKeyChain = require('../../DerivableKeyChain/DerivableKeyChain'); 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 DerivableKeyChain({ publicKey, network }); + this.keyChainStore = new KeyChainStore(); + this.keyChainStore.addKeyChain(keyChain, { isMasterKeyChain: true }); }; diff --git a/packages/wallet-lib/src/types/Wallet/methods/fromPublicKey.spec.js b/packages/wallet-lib/src/types/Wallet/methods/fromPublicKey.spec.js index 8852ee6c6ee..07890f2d45d 100644 --- a/packages/wallet-lib/src/types/Wallet/methods/fromPublicKey.spec.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Wallet/methods/fromSeed.js b/packages/wallet-lib/src/types/Wallet/methods/fromSeed.js index 978adc9ecda..038fc138e6b 100644 --- a/packages/wallet-lib/src/types/Wallet/methods/fromSeed.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Wallet/methods/fromSeed.spec.js b/packages/wallet-lib/src/types/Wallet/methods/fromSeed.spec.js index 75e9d32cda1..a0a9ec827ca 100644 --- a/packages/wallet-lib/src/types/Wallet/methods/fromSeed.spec.js +++ b/packages/wallet-lib/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/packages/wallet-lib/src/types/Wallet/methods/sweepWallet.spec.js b/packages/wallet-lib/src/types/Wallet/methods/sweepWallet.spec.js index 77935054505..21af353bc23 100644 --- a/packages/wallet-lib/src/types/Wallet/methods/sweepWallet.spec.js +++ b/packages/wallet-lib/src/types/Wallet/methods/sweepWallet.spec.js @@ -4,7 +4,7 @@ const sweepWallet = require('./sweepWallet'); const paperWallet = { publicKey: 'ybvbBPisVjiemj4qSg1mzZAzTSAPk64Ppf', - privateKey: 'XE6ZTNwkjyuryGho75fAfCBBtL8rMy9ttLq1ANLF1TmMo2zwZXHq', + privateKey: '53d0f7df9103127f159f939438254011f6fa11df18a843d3962313e38938f020', }; describe('Wallet - sweepWallet', function suite() { @@ -12,11 +12,11 @@ describe('Wallet - sweepWallet', function suite() { let emptyWallet; let emptyAccount; const transportOpts = (process.env.DAPI_SEED) - ? { - seeds: process.env.DAPI_SEED - .split(',') - } - : {} + ? { + seeds: process.env.DAPI_SEED + .split(',') + } + : {} before(async () => { emptyWallet = new Wallet({ privateKey: paperWallet.privateKey, diff --git a/packages/wallet-lib/src/types/WalletStore/WalletStore.d.ts b/packages/wallet-lib/src/types/WalletStore/WalletStore.d.ts new file mode 100644 index 00000000000..91697ef6a8f --- /dev/null +++ b/packages/wallet-lib/src/types/WalletStore/WalletStore.d.ts @@ -0,0 +1,26 @@ +export declare interface WalletStoreState { + mnemonic: string; + paths: Map + identities: Map +} + +type walletId = string; +type exportedState = any; + +export declare class WalletStore { + constructor(walletId: walletId); + + walletId: walletId; + state: WalletStoreState; + + createPathState(path: string): void; + exportState(): exportedState; + getIdentityIdByIndex(identityIndex: number): string; + getIndexedIdentityIds(identityIndex: number): Array; + getPathState(path: string): any; + + importState(exportedState): void; + insertIdentityIdAtIndex(identityId: string, identityIndex: number): void; +} + + diff --git a/packages/wallet-lib/src/utils/calculateDuffBalance.js b/packages/wallet-lib/src/utils/calculateDuffBalance.js new file mode 100644 index 00000000000..82be58fe1e9 --- /dev/null +++ b/packages/wallet-lib/src/utils/calculateDuffBalance.js @@ -0,0 +1,28 @@ +/** + * + * @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}`); + } + }); + return totalSat; +}; diff --git a/packages/wallet-lib/src/utils/categorizeTransactions.js b/packages/wallet-lib/src/utils/categorizeTransactions.js index b13bbfaf483..1b84ac88f41 100644 --- a/packages/wallet-lib/src/utils/categorizeTransactions.js +++ b/packages/wallet-lib/src/utils/categorizeTransactions.js @@ -7,37 +7,52 @@ const { TRANSACTION_HISTORY_TYPES } = require('../CONSTANTS'); // and a sent transaction where our own address is a change... const determineType = (inputsDetection, outputsDetection) => { let type = TRANSACTION_HISTORY_TYPES.UNKNOWN; - if (inputsDetection.hasExternalAddress) { - type = TRANSACTION_HISTORY_TYPES.RECEIVED; - } - if (outputsDetection.hasExternalAddress) { - type = TRANSACTION_HISTORY_TYPES.RECEIVED; - } - if ( - !outputsDetection.hasExternalAddress - && (inputsDetection.hasChangeAddress || inputsDetection.hasExternalAddress) - ) { - type = TRANSACTION_HISTORY_TYPES.SENT; - } - if (inputsDetection.hasExternalAddress && outputsDetection.hasExternalAddress) { - type = TRANSACTION_HISTORY_TYPES.ADDRESS_TRANSFER; - } else if ( - (inputsDetection.hasExternalAddress && outputsDetection.hasOtherAccountAddress) - || (inputsDetection.hasOtherAccountAddress && outputsDetection.hasExternalAddress) + + // We first discriminate with account transfer from or to another account + if (inputsDetection.hasOtherAccountAddress + && !inputsDetection.hasOwnAddress) { + type = TRANSACTION_HISTORY_TYPES.ACCOUNT_TRANSFER; + } else if (inputsDetection.hasOwnAddress + && outputsDetection.hasOtherAccountAddress ) { type = TRANSACTION_HISTORY_TYPES.ACCOUNT_TRANSFER; + } else if (inputsDetection.hasOwnAddress + && !outputsDetection.hasUnknownAddress + && !outputsDetection.hasOtherAccountAddress) { + // Detecting an address transfer is the second element we need to discriminate + type = TRANSACTION_HISTORY_TYPES.ADDRESS_TRANSFER; + } else { + if (inputsDetection.hasExternalAddress) { + type = TRANSACTION_HISTORY_TYPES.RECEIVED; + } + if (outputsDetection.hasExternalAddress && !inputsDetection.hasExternalAddress) { + type = TRANSACTION_HISTORY_TYPES.RECEIVED; + } + if ( + outputsDetection.hasUnknownAddress + && (inputsDetection.hasOwnAddress) + ) { + type = TRANSACTION_HISTORY_TYPES.SENT; + } } + return type; }; -function categorizeTransactions(transactionsWithMetadata, accountStore, accountIndex, walletType, network = 'testnet') { +function categorizeTransactions( + transactionsWithMetadata, + walletStore, + accountIndex, + walletType, + network = 'testnet', +) { 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; @@ -47,19 +62,29 @@ function categorizeTransactions(transactionsWithMetadata, accountStore, accountI let outputsHasChangeAddress = false; let outputsHasExternalAddress = false; let outputsHasOtherAccountAddress = false; + let outputsHasOwnAddress = false; + let outputsHasUnknownAddress = false; let inputsHasChangeAddress = false; let inputsHasExternalAddress = false; let inputsHasOtherAccountAddress = false; + let inputsHasOwnAddress = false; + let inputsHasUnknownAddress = false; // For each vout, we will look at matching known addresses transaction.outputs.forEach((vout) => { 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; + outputsHasOwnAddress = true; + } else if (externalAddressesList.includes(address)) { + outputsHasExternalAddress = true; + outputsHasOwnAddress = true; + } else if (otherAccountAddressesList.includes(address)) { + outputsHasOtherAccountAddress = true; + } else outputsHasUnknownAddress = true; to.push({ address, satoshis, @@ -72,9 +97,15 @@ 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; + inputsHasOwnAddress = true; + } else if (externalAddressesList.includes(address)) { + inputsHasExternalAddress = true; + inputsHasOwnAddress = true; + } else if (otherAccountAddressesList.includes(address)) { + inputsHasOtherAccountAddress = true; + } else inputsHasUnknownAddress = true; from.push({ address, }); @@ -85,10 +116,14 @@ function categorizeTransactions(transactionsWithMetadata, accountStore, accountI hasChangeAddress: inputsHasChangeAddress, hasExternalAddress: inputsHasExternalAddress, hasOtherAccountAddress: inputsHasOtherAccountAddress, + hasOwnAddress: inputsHasOwnAddress, + hasUnknownAddress: inputsHasUnknownAddress, }, { hasChangeAddress: outputsHasChangeAddress, hasExternalAddress: outputsHasExternalAddress, hasOtherAccountAddress: outputsHasOtherAccountAddress, + hasOwnAddress: outputsHasOwnAddress, + hasUnknownAddress: outputsHasUnknownAddress, }); const categorizedTransaction = { @@ -98,8 +133,8 @@ function categorizeTransactions(transactionsWithMetadata, accountStore, accountI type, blockHash: metadata.blockHash, height: metadata.height, - isInstantLocked: metadata.instantLocked, - isChainLocked: metadata.chainLocked, + isInstantLocked: metadata.isInstantLocked, + isChainLocked: metadata.isChainLocked, }; categorizedTransactions.push(categorizedTransaction); }); diff --git a/packages/wallet-lib/src/utils/categorizeTransactions.spec.js b/packages/wallet-lib/src/utils/categorizeTransactions.spec.js index 1d48b25a849..4d9e71c22a4 100644 --- a/packages/wallet-lib/src/utils/categorizeTransactions.spec.js +++ b/packages/wallet-lib/src/utils/categorizeTransactions.spec.js @@ -5,9 +5,10 @@ const {WALLET_TYPES} = require('../CONSTANTS'); const categorizeTransactions = require('./categorizeTransactions'); const transactionsWithMetadataFixtures = require('../../fixtures/wallets/apart-trip-dignity/transactions-with-metadata.json'); -const addressesFixtures = require('../../fixtures/wallets/apart-trip-dignity/addresses.json'); -const walletFixtures = require('../../fixtures/wallets/apart-trip-dignity/wallet.json'); const expectedResults = require('../../fixtures/wallets/apart-trip-dignity/categorizeTransactions.expectedResults'); +const getFixtureHDAccountWithStorage = require("../../fixtures/wallets/apart-trip-dignity/getFixtureAccountWithStorage"); + +const mockedHDAccount = getFixtureHDAccountWithStorage(); const prepareTransactionsWithMetadata = () => { const transactionsWithMetadata = []; @@ -46,14 +47,7 @@ const normalizeResults = (results) =>{ describe('Utils - categorizeTransactions', function suite() { const transactionsWithMetadata = prepareTransactionsWithMetadata(); - const accountStore = { - accounts: walletFixtures.store.accounts, - network: walletFixtures.network, - mnemonic: null, - type: walletFixtures.type, - identityIds: walletFixtures.identityIds, - addresses: addressesFixtures - }; + const accountStore = mockedHDAccount.storage.getWalletStore(mockedHDAccount.walletId); const accountIndex = 0; const walletType = WALLET_TYPES.HDWALLET; diff --git a/packages/wallet-lib/src/utils/classifyAddresses.js b/packages/wallet-lib/src/utils/classifyAddresses.js index 52dfcff9b33..c6f6f55d297 100644 --- a/packages/wallet-lib/src/utils/classifyAddresses.js +++ b/packages/wallet-lib/src/utils/classifyAddresses.js @@ -1,34 +1,49 @@ -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 = 'testnet') { + 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 isHDWallet = [ + WALLET_TYPES.HDWALLET, + WALLET_TYPES.HDPRIVATE, + WALLET_TYPES.HDPUBLIC].includes(walletType); - const otherAccountAddressList = (walletType === WALLET_TYPES.HDWALLET) - ? difference( - [...map(external, addressMappingPredicate), ...map(internal, addressMappingPredicate)], - [...externalAddressList, ...internalAddressList], - ) - : []; + const currentAccountPath = (isHDWallet) ? `${rootPath}/${accountIndex}'` : `m/${accountIndex}`; + + accountsPaths.forEach((accountPath) => { + const isCurrentAccountPath = accountPath === currentAccountPath; + const accountPaths = walletStore.getPathState(accountPath); + + Object.entries(accountPaths.addresses) + .forEach(([path, address]) => { + if (isCurrentAccountPath) { + if (isHDWallet) { + if (path.startsWith('m/0')) externalAddressesList.push(address); + else if (path.startsWith('m/1')) internalAddressesList.push(address); + else miscAddressesList.push(address); + } else { + externalAddressesList.push(address); + } + } else { + otherAccountAddressesList.push(address); + } + }); + }); return { - externalAddressList, - internalAddressList, - otherAccountAddressList, + externalAddressesList, + internalAddressesList, + otherAccountAddressesList, + miscAddressesList, }; } module.exports = classifyAddresses; diff --git a/packages/wallet-lib/src/utils/classifyAddresses.spec.js b/packages/wallet-lib/src/utils/classifyAddresses.spec.js index a0f55d39ee0..d3a1969e8b5 100644 --- a/packages/wallet-lib/src/utils/classifyAddresses.spec.js +++ b/packages/wallet-lib/src/utils/classifyAddresses.spec.js @@ -1,526 +1,22 @@ -const { expect } = require('chai'); -const { WALLET_TYPES } = require('../CONSTANTS'); +const {expect} = require('chai'); +const {WALLET_TYPES} = require('../CONSTANTS'); const classifyAddresses = require('./classifyAddresses'); +const getFixtureHDAccountWithStorage = require("../../fixtures/wallets/apart-trip-dignity/getFixtureAccountWithStorage"); + +const mockedHDAccount = getFixtureHDAccountWithStorage(); describe('Utils - classifyAddresses', function suite() { it('should correctly classify address for HDWallet', function () { const walletType = WALLET_TYPES.HDWALLET; const accountIndex = 0; - const accountStore = { - accounts: { - "m/44'/1'/0'": { - label: null, - path: "m/44'/1'/0'", - network: 'testnet', - blockHeight: 554643, - blockHash: '0000007a84abfe1d2b4201f4844bb1e59f24daf965c928281589269f281abc01' - } - }, - 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: ["1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6"], - balanceSat: 642650000, - unconfirmedBalanceSat: 0, - utxos: { - "1a74dc225b3336c4edb1f94c9ec2ed88fd0ef136866fda26f8a734924407b4d6-1" : {} - }, - fetchedLast: 0, - used: true - }, - "m/44'/1'/0'/0/1": { - path: "m/44'/1'/0'/0/1", - index: 1, - address: 'yMX3ycrLVF2k6YxWQbMoYgs39aeTfY4wrB', - 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: 'ydJGUUmNxdmvyskoZXqtJRqWyqsaFPitGQ', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/0/3": { - path: "m/44'/1'/0'/0/3", - index: 3, - address: 'yhugNjDRVJUL7PK9MqQP9M6M1HJm8zuWJL', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/0/4": { - path: "m/44'/1'/0'/0/4", - index: 4, - address: 'ySgckdRYxpa7Uda8yUNRqjYeuusqLy3AY3', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/0/5": { - path: "m/44'/1'/0'/0/5", - index: 5, - address: 'yS4DeSU3MTBisgL6p8PDRzSxTPN2PUt3vE', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/0/6": { - path: "m/44'/1'/0'/0/6", - index: 6, - address: 'yRZef5UGotGgLMaLYTzhvfknogqMRBkUiX', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/0/7": { - path: "m/44'/1'/0'/0/7", - index: 7, - address: 'yTUw2bGzi9rYs41XH1dxbRqiJoDtwuUcv2', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/0/8": { - path: "m/44'/1'/0'/0/8", - index: 8, - address: 'yNQHAGhNP7UbhxnkZH4muP2oKkuayGEuwX', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/0/9": { - path: "m/44'/1'/0'/0/9", - index: 9, - address: 'yPPDLBDjHctWpMxLiMTJXLngcYPke7YNaY', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/0/10": { - path: "m/44'/1'/0'/0/10", - index: 10, - address: 'ya2vVJAJdZN2We7MYiSjGf9wkdWF6A1RLr', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/0/11": { - path: "m/44'/1'/0'/0/11", - index: 11, - address: 'ya5k2YMjfyfxZoidq4UdQ65jYUXvtVomEv', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/0/12": { - path: "m/44'/1'/0'/0/12", - index: 12, - address: 'yhXZja3Apyp9S32zEVsPqLssNJZLrczxJC', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/0/13": { - path: "m/44'/1'/0'/0/13", - index: 13, - address: 'yhWqJXsp25aZNQHEprebQrqPoANj6A13Aa', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/0/14": { - path: "m/44'/1'/0'/0/14", - index: 14, - address: 'yjV3sKAGsuJHDGyf6HDMNuLfMgGp5pBxRy', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/0/15": { - path: "m/44'/1'/0'/0/15", - index: 15, - address: 'yjPeTiRatdvotxUuPFEPDJc2aF774uMB9J', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/0/16": { - path: "m/44'/1'/0'/0/16", - index: 16, - address: 'yRrhuVw6Vd3NzgYrfqb1oTvdhyxzDT9PGz', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/0/17": { - path: "m/44'/1'/0'/0/17", - index: 17, - address: 'yYpL5JJLVGfJXPE15ZMQzNvUGkD4JY6ETF', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/0/18": { - path: "m/44'/1'/0'/0/18", - index: 18, - address: 'ygcW1365Hs2LSLY5LXnkJAUB94pS4HouNu', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/0/19": { - path: "m/44'/1'/0'/0/19", - index: 19, - address: 'yW8RA7zTUz14sNiGjvFQaNupugwwmE1aQi', - 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: 'yaLhoAZ4iex2zKmfvS9rvEmxXmRiPrjHdD', - 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: 'yTcjWB7v7opDzpfYKpFdFEtEvSKFsh3bW3', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/1/2": { - path: "m/44'/1'/0'/1/2", - index: 2, - address: 'yiDVYtUZ2mKV4teSJzKBArqY4BRsZoFLYs', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/1/3": { - path: "m/44'/1'/0'/1/3", - index: 3, - address: 'ya7Me5KMoSz5x4GGZ1pJGrJjC3yMkDDWDa', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/1/4": { - path: "m/44'/1'/0'/1/4", - index: 4, - address: 'yXwS8mRrrxF3pt1GfG7yGKNpPnD6pdwX3a', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/1/5": { - path: "m/44'/1'/0'/1/5", - index: 5, - address: 'yXd4eqycSaJRhRZxXT3iK5H34af4TV5REE', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/1/6": { - path: "m/44'/1'/0'/1/6", - index: 6, - address: 'yWaDfpToRxHc3qtcd8P1agW4Fvj1ueWgwH', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/1/7": { - path: "m/44'/1'/0'/1/7", - index: 7, - address: 'yPYdu2jDrD3Bai83AdvHTYwpAgAkzMaCcM', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/1/8": { - path: "m/44'/1'/0'/1/8", - index: 8, - address: 'yMEkuZ67vZ6kUgDHVVDSTzwU3GbHoTFwqR', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/1/9": { - path: "m/44'/1'/0'/1/9", - index: 9, - address: 'yYSSMwkEqU4hNF2z5kbVBTDYtgt8dQQYd7', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/1/10": { - path: "m/44'/1'/0'/1/10", - index: 10, - address: 'yfzAa63gQ6arpyBzuqQtZmSnU8HLnJnEan', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/1/11": { - path: "m/44'/1'/0'/1/11", - index: 11, - address: 'ySjTorgG6VVfPiY7TJ2tdU2hkohfFAtzJf', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/1/12": { - path: "m/44'/1'/0'/1/12", - index: 12, - address: 'yMUJsy5HEiQTeDgqxY2zBGEeTCDr4g5V8c', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/1/13": { - path: "m/44'/1'/0'/1/13", - index: 13, - address: 'yWDkvzhKk7BKEU6Ybz1Kyarejzt8zhSqxy', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/1/14": { - path: "m/44'/1'/0'/1/14", - index: 14, - address: 'yTJQE4vYXRdEcPt3nADF36S6FKLkLRG2Ty', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/1/15": { - path: "m/44'/1'/0'/1/15", - index: 15, - address: 'yTjku3TMxJN3uiiSHYB2tQ4wp1rJJ92M2A', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/1/16": { - path: "m/44'/1'/0'/1/16", - index: 16, - address: 'yZ8JgZrpuEr1srdcgLgTAsomtMBgSYwTrW', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/1/17": { - path: "m/44'/1'/0'/1/17", - index: 17, - address: 'yUG2YZrss5JfZrN4AG4RKkkbfYyd4H6TSx', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/1/18": { - path: "m/44'/1'/0'/1/18", - index: 18, - address: 'yiLMqyvjBV3CqkCL8H44bRSUBap7tPCmvo', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - }, - "m/44'/1'/0'/1/19": { - path: "m/44'/1'/0'/1/19", - index: 19, - address: 'yfrCYcuD7ezYnpNAmbPMjrTETjipsNGsEe', - transactions: [], - balanceSat: 0, - unconfirmedBalanceSat: 0, - utxos: {}, - fetchedLast: 0, - used: false - } - }, - misc: {} + const result = classifyAddresses(mockedHDAccount.storage.getWalletStore(mockedHDAccount.walletId), accountIndex, walletType); + const expectedResult = { + "externalAddressesList": ["yTwEca67QSkZ6axGdpNFzWPaCj8zqYybY7", "yercyhdN9oEkZcB9BsW5ktFaDxFEuK6qXN", "ygk3GCSba2J3L9G665Snozhj9HSkh5ByVE", "ybuL6rM6dgrKzCg8s99f3jxGuv5oz5JcDA", "ygHAVkMtYSqoTWHebDv7qkhMV6dHyuRsp2", "yMLhEsiP2ajSh8STmXnNmkWXtoHsmawZxd", "yj8rRKATAUHcAgXvNZekob58xKm2oNyvhv", "yhaAB6e8m3F8zmGX7WAVYa6eEfmSrrnY8x", "yiXh4Yo5djG6QH8WzXkKm5EFzqLRJWakXz", "yQYv3Um6DsdtANo1ZPTUte75wAGMstLRex", "yiYPJmu7eEm1cXUNumQRdjv1fvPhsfgMS4", "yii4aUZhNfL6EWN9KAgAFrJzGJmqHnF4wx", "yLpTquSct2SGz2Ka45uTPDd81Kzro2Jt2k", "yMiJtpzb1Qthy9TGnavsf5NZ6EZZa4j9q3", "yacgSfW7RkwWakEZPg8USAVdzCypiG3vxS", "yVvrmoRPFLy6nUpCQBT8ZExxF5wF3DhiGU", "yaJf2aG6cFUtfv4o6TuEKsh5kr4xq5iAY4", "yfardJQ4ucgWLKQPaRHGMRMbSGm5H4ExJR", "yLSCqx7dcM5JKR2fG7vHbF2axMvuYqomaw", "yVij8XpJ78LM5hepSV1KF7T8vRpUEXCpK5", "ydJpjuJGossAZR7S5oS7cWvjygEwoj8Xwp", "yW3TmWnmhvpxRbgFcQ8oXqDRkn3RhRH6jj", "yRegVX85DThKRkH8C61TtRacfzrkiBfNy5", "yPtDCqDFRe1JuDp8pvdiEMQMz2erGwS3VG", "yM9pSw3L4oBfG7uQL5o522Hu3WTvy9awgZ", "yNC6qYJYungzuk5XUynDFKCn54Dy8ngox4", "yR5KcLr1bceLT4teTk2qoJx6pFLik1zyzL", "yRrKLGJa9JmdjBWvrHtedKjHTao6CRDTKf", "yP5dShZBydpbEzgGoXL6kcjv2KzervRrYB"], + "internalAddressesList": ["yNDpPsJqXKM36zHSNEW7c1zSvNnrZ699FY", "yLk4Hw3w4zDudrDVP6W8J9TggkY57zQUki", "yirJaK8KCE5YAmwvLadizqFw3TCXqBuZXL", "yhdRfg5gNr587dtEC4YYMcSHmLVEGqqtHc", "yYwKP1FQae5kbjXkmuirGx6Xzf8NzHpLqW", "yX9gmsm8aSxZZjYhq4w35aidT7qbhcpNjU", "ybgXCTGMHEBbQeUib8c3xAjtGAc12XtWiU", "yS31WpdMT2b34uL9C37fbUoACHhiupHCyP", "yTSpFqRoX3vyN286AUtKKhgmX5Xb41YKQe", "yQU5YsqN7psTTASuYbcMi7N5nNZGaxXb2X", "yVGGFj9BLgEab5rucSGLC6UGVLQKB4U1wJ", "yQCh5yYCHEbJzgSJE9rdHiqXHidKm3kwr5", "yX7T3Ac3yaLk5CTC5UaR93Fc7SjYkeT5hn", "yXx3WXq8kYNPbYEg5U6bL8Xfih4g5LCYVo", "yYnLMTz3jCi2KKKNuo3TVkEAGyUFg8tgkJ", "yiKa1dA6B4tSTNJqJP9Y5pQfQEffnQQDTL", "yf7vcuDnE9DVhXdMfBMQQTEi43otYQzkWE", "yTmSmocwERCeRHqNNG5SbpYKUra1HTmj8m", "yivUe5NeJsGsREwPQZUGYaTSwWB3E1oLcz", "ygfsZojdfW9UjCRU4ra95Aq6YgCC7UqZFx", "yU9fdXaUVtefwDZvxjJAr9xj1z2MtYi34A", "yXgMN6FgrgZCnTN1vhoZMh8afKMBmi3JC4", "yiqaCbXscvR8y3VFYMzdaKCaAGuDuZxMzt", "ydcgWDxheSxrLAqDBP4JXBndMCzUNf77gq"], + "otherAccountAddressesList": ["yYJmzWey5kNecAThet5BFxAga1F4b4DKQ2", "yNCqctyQaq51WU1hN5aNwsgMsZ5fRiB7GY", "yNPbYz5cZKw2EwxtkL3VSVzPi2FYp9VKjQ", "ybsGWzsnSCAZufgSeUjScVxqEdved99UM2", "yfNHuPojk8XKWP5nuueDptX4nM7qToudgx", "yXxLnDkk6s8h1PSnYaFM6MAyRarc1Kc1rY", "yipResSzN2zUvL7UYkmptKKmQTv7sNssRn", "yZPtNwimHdRiKYbNQW49qezw1Kc1YwUJeT", "yPMjYYfQbga2nBiuqqfUyX41U1vwRZ8fG8", "yLueLWWcLQsaXQ8D5o9tcyo8tfTxMWXvG4", "yN8gzgsc1RVjXThMQT5qZH2jjpnMymz6zP", "yPQLWBNwMdLxUW2oUwHGwQtfyYxD41BARJ", "yg5g2AfWFdwWexWGfbSXYbUHf1y5WWrFPs", "yWyABu4naV1Jzw7w9sn1gqhebPRSkCndsS", "ycuUPzUBjhKyUjezQR1LNot79a6C4aRLaR", "yQ7YjvAXgDAUCekveHVjr6NBveXrUemVno", "yi8bghcw627cMGpuH4bJqH6bqR5ywv1NLH", "yizHu8i2rfwzwBgnJ62s2WUe6wLoDjne6N", "yW1u3tySeUKAKJsz7sjZFyjUiTyKLB6xBv", "yNaSkdy1Q8JNubUdbLMGsGf7sTRofEJYZq", "yXMrw79LPgu78EJsfGGYpm6fXKc1EMnQ49", "yh6Hcyipdvp6WJpQxjNbaXP4kzPQUJpY3n", "yNphpXuaTZRpU9FBh2W7NkUYcr3kBDE8me", "yXFppDT59xYD41mT2pmAdnvr7aZEFdgdrN", "yeKGAiiEHBGRujvLoYewA77jDDpeDamxvF", "yaxTG66CVzKgHhHZXojRHC9ztLTvz3fwdT", "yYw6qU7dwGoELZkSTj3oSKRpM4U8qTMc1U", "yQE2MksEnSfbeNre19oja9Jj8tvpj64C5a", "yaRnvHo8oLvVmv46vMj5XPbDJouQSnmcLT", "yj5ofWf2uYQQkSavYm2WXgu1QkaZCyP3Cm", "yUCjGmEwrHJwNDrE1o2rMre6MkSbiE6yz7", "yfJzd1nE2rEqz5XEurD6vs4ykizwmw9xTv", "yUk8U3jRZMHKVTa1eFDEtZpa1G4E13FP4d", "yMr59YWQFCADq4FbWrtxDUtMwwshSrmAyK", "yetSehBupzGS9yps5ogqARUGmTMAs2xVcQ", "yNcESKLwriNrhM6EyoSpZEXrzdY3uht92T", "yN2FihGU7KdaEspp39bKrhsHypeyeYzoM2", "yirpWLxHuhwFzA6LfUPKUh1Ke9RB9BUjit", "yVDN66vvdshWdNzhUaQNB6xExAHkzs1zj8", "yPzofnEhRVfDisL2nCUJtAoSHkuyMirHZS", "yjG4jiCMNbx3MyFCAsaNaU29CBVmgPf8hS", "yWrEYTtydaZCyJoKE1vzX4e4RXDNPGdnw9", "yM7Smr8HEhbRW4CXga898brATrJSsk1QUh", "yUprvaE5KrKVPgABmGtxba9MmZqDzKQHom"], + "miscAddressesList": [] } - - }; - - const result = classifyAddresses(accountStore.addresses, accountIndex, walletType); - const expectedResult = { - externalAddressList: [ - 'yd1ohc12LgCYp56CDuckTEHwoa6LbPghMd', - 'yMX3ycrLVF2k6YxWQbMoYgs39aeTfY4wrB', - 'ydJGUUmNxdmvyskoZXqtJRqWyqsaFPitGQ', - 'yhugNjDRVJUL7PK9MqQP9M6M1HJm8zuWJL', - 'ySgckdRYxpa7Uda8yUNRqjYeuusqLy3AY3', - 'yS4DeSU3MTBisgL6p8PDRzSxTPN2PUt3vE', - 'yRZef5UGotGgLMaLYTzhvfknogqMRBkUiX', - 'yTUw2bGzi9rYs41XH1dxbRqiJoDtwuUcv2', - 'yNQHAGhNP7UbhxnkZH4muP2oKkuayGEuwX', - 'yPPDLBDjHctWpMxLiMTJXLngcYPke7YNaY', - 'ya2vVJAJdZN2We7MYiSjGf9wkdWF6A1RLr', - 'ya5k2YMjfyfxZoidq4UdQ65jYUXvtVomEv', - 'yhXZja3Apyp9S32zEVsPqLssNJZLrczxJC', - 'yhWqJXsp25aZNQHEprebQrqPoANj6A13Aa', - 'yjV3sKAGsuJHDGyf6HDMNuLfMgGp5pBxRy', - 'yjPeTiRatdvotxUuPFEPDJc2aF774uMB9J', - 'yRrhuVw6Vd3NzgYrfqb1oTvdhyxzDT9PGz', - 'yYpL5JJLVGfJXPE15ZMQzNvUGkD4JY6ETF', - 'ygcW1365Hs2LSLY5LXnkJAUB94pS4HouNu', - 'yW8RA7zTUz14sNiGjvFQaNupugwwmE1aQi' - ], - internalAddressList: [ - 'yaLhoAZ4iex2zKmfvS9rvEmxXmRiPrjHdD', - 'yTcjWB7v7opDzpfYKpFdFEtEvSKFsh3bW3', - 'yiDVYtUZ2mKV4teSJzKBArqY4BRsZoFLYs', - 'ya7Me5KMoSz5x4GGZ1pJGrJjC3yMkDDWDa', - 'yXwS8mRrrxF3pt1GfG7yGKNpPnD6pdwX3a', - 'yXd4eqycSaJRhRZxXT3iK5H34af4TV5REE', - 'yWaDfpToRxHc3qtcd8P1agW4Fvj1ueWgwH', - 'yPYdu2jDrD3Bai83AdvHTYwpAgAkzMaCcM', - 'yMEkuZ67vZ6kUgDHVVDSTzwU3GbHoTFwqR', - 'yYSSMwkEqU4hNF2z5kbVBTDYtgt8dQQYd7', - 'yfzAa63gQ6arpyBzuqQtZmSnU8HLnJnEan', - 'ySjTorgG6VVfPiY7TJ2tdU2hkohfFAtzJf', - 'yMUJsy5HEiQTeDgqxY2zBGEeTCDr4g5V8c', - 'yWDkvzhKk7BKEU6Ybz1Kyarejzt8zhSqxy', - 'yTJQE4vYXRdEcPt3nADF36S6FKLkLRG2Ty', - 'yTjku3TMxJN3uiiSHYB2tQ4wp1rJJ92M2A', - 'yZ8JgZrpuEr1srdcgLgTAsomtMBgSYwTrW', - 'yUG2YZrss5JfZrN4AG4RKkkbfYyd4H6TSx', - 'yiLMqyvjBV3CqkCL8H44bRSUBap7tPCmvo', - 'yfrCYcuD7ezYnpNAmbPMjrTETjipsNGsEe' - ], - otherAccountAddressList: [] - }; expect(result).to.deep.equal(expectedResult); }); }); diff --git a/packages/wallet-lib/src/utils/index.js b/packages/wallet-lib/src/utils/index.js index ac209d5d53d..c23bb6a6e58 100644 --- a/packages/wallet-lib/src/utils/index.js +++ b/packages/wallet-lib/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/packages/wallet-lib/tests/functional/wallet.js b/packages/wallet-lib/tests/functional/wallet.js index 0d2f1f81027..e78856ab4f5 100644 --- a/packages/wallet-lib/tests/functional/wallet.js +++ b/packages/wallet-lib/tests/functional/wallet.js @@ -46,7 +46,8 @@ describe('Wallet-lib - functional', function suite() { expect(newWallet.walletType).to.be.equal('hdwallet'); expect(newWallet.plugins).to.be.deep.equal({}); expect(newWallet.accounts).to.be.deep.equal([]); - expect(newWallet.keyChain.type).to.be.deep.equal('HDPrivateKey'); + expect(newWallet.keyChainStore.getMasterKeyChain().rootKeyType) + .to.be.deep.equal('HDPrivateKey'); expect(newWallet.passphrase).to.be.deep.equal(null); expect(newWallet.allowSensitiveOperations).to.be.deep.equal(false); expect(newWallet.injectDefaultPlugins).to.be.deep.equal(true); @@ -71,7 +72,8 @@ describe('Wallet-lib - functional', function suite() { expect(wallet.walletType).to.be.equal('hdwallet'); expect(wallet.plugins).to.be.deep.equal({}); expect(wallet.accounts).to.be.deep.equal([]); - expect(wallet.keyChain.type).to.be.deep.equal('HDPrivateKey'); + expect(newWallet.keyChainStore.getMasterKeyChain().rootKeyType) + .to.be.deep.equal('HDPrivateKey'); expect(wallet.passphrase).to.be.deep.equal(null); expect(wallet.allowSensitiveOperations).to.be.deep.equal(false); expect(wallet.injectDefaultPlugins).to.be.deep.equal(true); diff --git a/packages/wallet-lib/tests/integration/plugins/Workers/TransactionSyncStreamWorker.spec.js b/packages/wallet-lib/tests/integration/plugins/Workers/TransactionSyncStreamWorker.spec.js index e4b039e7c31..3fe2ae279f5 100644 --- a/packages/wallet-lib/tests/integration/plugins/Workers/TransactionSyncStreamWorker.spec.js +++ b/packages/wallet-lib/tests/integration/plugins/Workers/TransactionSyncStreamWorker.spec.js @@ -55,6 +55,7 @@ describe('TransactionSyncStreamWorker', function suite() { plugins: [worker], allowSensitiveOperations: true, HDPrivateKey: new HDPrivateKey(testHDKey), + network: 'mainnet' }); ({ txStreamMock, transportMock } = await createAndAttachTransportMocksToWallet(wallet, this.sinonSandbox)); @@ -65,12 +66,11 @@ describe('TransactionSyncStreamWorker', function suite() { account = await wallet.getAccount(); storage = account.storage; - walletId = Object.keys(storage.store.wallets)[0]; + walletId = account.walletId; address = account.getAddress(0).address; addressAtIndex19 = account.getAddress(19).address; }); - afterEach(() => { worker.stopWorker(); }) @@ -117,9 +117,8 @@ describe('TransactionSyncStreamWorker', function suite() { await worker.onStart(); - const transactionsInStorage = Object - .values(storage.getStore().transactions) - .map((t) => t.toJSON()); + const transactionsInStorage = Array.from(storage.getChainStore('livenet').state.transactions) + .map(([,t]) => t.transaction.toJSON()); const expectedTransactions = transactionsSent .map((t) => t.toJSON()); @@ -193,15 +192,17 @@ describe('TransactionSyncStreamWorker', function suite() { await worker.onStart(); - const transactionsInStorage = Object - .values(storage.getStore().transactions) - .map((t) => t.toJSON()); + const transactionsInStorage = Array.from(storage.getChainStore('livenet').state.transactions) + .map(([,t]) => t.transaction.toJSON()); const expectedTransactions = transactionsSent .map((t) => t.toJSON()); + const {addresses} = storage.getWalletStore(walletId).state.paths.get(`m/44'/5'/0'`); - const addressesInStorage = storage.store.wallets[walletId].addresses.external; + const addressesInStorage = Object.entries(addresses) + .filter(([path, address])=> path.includes('m/0')) + .map(([path, address])=> address); // We send transaction to index 19, so wallet should generate additional 20 addresses to keep the gap between // the last used address expect(Object.keys(addressesInStorage).length).to.be.equal(40); @@ -218,6 +219,7 @@ describe('TransactionSyncStreamWorker', function suite() { expect(transactionsInStorage.length).to.be.equal(2); expect(transactionsInStorage).to.have.deep.members(expectedTransactions); }); + it('should reconnect to the historical stream if stream is closed due to operational GRPC error', async function () { const lastSavedBlockHeight = 40; const bestBlockHeight = 42; @@ -242,7 +244,11 @@ describe('TransactionSyncStreamWorker', function suite() { await worker.onStart(); - const addressesInStorage = storage.store.wallets[walletId].addresses.external; + const {addresses} = storage.getWalletStore(walletId).state.paths.get(`m/44'/5'/0'`); + + const addressesInStorage = Object.entries(addresses) + .filter(([path, address])=> path.includes('m/0')) + .map(([path, address])=> address); expect(Object.keys(addressesInStorage).length).to.be.equal(20); // It should reconnect after because of the operational error @@ -275,7 +281,11 @@ describe('TransactionSyncStreamWorker', function suite() { await expect(worker.onStart()).to.be.rejectedWith('Some random error'); - const addressesInStorage = storage.store.wallets[walletId].addresses.external; + const {addresses} = storage.getWalletStore(walletId).state.paths.get(`m/44'/5'/0'`); + + const addressesInStorage = Object.entries(addresses) + .filter(([path, address])=> path.includes('m/0')) + .map(([path, address])=> address); expect(Object.keys(addressesInStorage).length).to.be.equal(20); // Shouldn't try to reconnect @@ -287,7 +297,6 @@ describe('TransactionSyncStreamWorker', function suite() { expect(worker.stream).to.be.null; }); }); - describe("#execute", () => { it('should sync incoming transactions and save it to the storage', async function () { const lastSavedBlockHeight = 40; @@ -330,9 +339,8 @@ describe('TransactionSyncStreamWorker', function suite() { await worker.onStop(); - const transactionsInStorage = Object - .values(storage.getStore().transactions) - .map((t) => t.toJSON()); + const transactionsInStorage = Array.from(storage.getChainStore('livenet').state.transactions) + .map(([,t]) => t.transaction.toJSON()); const expectedTransactions = transactionsSent .map((t) => t.toJSON()); @@ -405,14 +413,17 @@ describe('TransactionSyncStreamWorker', function suite() { await worker.onStop(); - const transactionsInStorage = Object - .values(storage.getStore().transactions) - .map((t) => t.toJSON()); + const transactionsInStorage = Array.from(storage.getChainStore('livenet').state.transactions) + .map(([,t]) => t.transaction.toJSON()); const expectedTransactions = transactionsSent .map((t) => t.toJSON()); - const addressesInStorage = storage.store.wallets[walletId].addresses.external; + const {addresses} = storage.getWalletStore(walletId).state.paths.get(`m/44'/5'/0'`); + + const addressesInStorage = Object.entries(addresses) + .filter(([path, address])=> path.includes('m/0')) + .map(([path, address])=> address); // We send transaction to index 19, so wallet should generate additional 20 addresses to keep the gap between // the last used address expect(Object.keys(addressesInStorage).length).to.be.equal(40); @@ -453,7 +464,11 @@ describe('TransactionSyncStreamWorker', function suite() { await worker.onStop(); - const addressesInStorage = storage.store.wallets[walletId].addresses.external; + const {addresses} = storage.getWalletStore(walletId).state.paths.get(`m/44'/5'/0'`); + + const addressesInStorage = Object.entries(addresses) + .filter(([path, address])=> path.includes('m/0')) + .map(([path, address])=> address); expect(Object.keys(addressesInStorage).length).to.be.equal(20); // It should reconnect after the gap limit is reached @@ -486,7 +501,11 @@ describe('TransactionSyncStreamWorker', function suite() { await worker.onStop(); - const addressesInStorage = storage.store.wallets[walletId].addresses.external; + const {addresses} = storage.getWalletStore(walletId).state.paths.get(`m/44'/5'/0'`); + + const addressesInStorage = Object.entries(addresses) + .filter(([path, address])=> path.includes('m/0')) + .map(([path, address])=> address); expect(Object.keys(addressesInStorage).length).to.be.equal(20); // It should reconnect if the server closes the stream @@ -520,7 +539,11 @@ describe('TransactionSyncStreamWorker', function suite() { await expect(worker.incomingSyncPromise).to.be.rejectedWith('Some random error'); - const addressesInStorage = storage.store.wallets[walletId].addresses.external; + const {addresses} = storage.getWalletStore(walletId).state.paths.get(`m/44'/5'/0'`); + + const addressesInStorage = Object.entries(addresses) + .filter(([path, address])=> path.includes('m/0')) + .map(([path, address])=> address); expect(Object.keys(addressesInStorage).length).to.be.equal(20); // Shouldn't try to reconnect @@ -648,17 +671,21 @@ describe('TransactionSyncStreamWorker', function suite() { await worker.onStop(); - const transactionsInStorage = Object - .values(storage.getStore().transactions) - .map((t) => t.toJSON()); + const transactionsInStorage = Array.from(storage.getChainStore('livenet').state.transactions) + .map(([,t]) => t.transaction.toJSON()); const expectedTransactions = transactionsSent .map((t) => t.toJSON()); - const { - external: externalAddressesInStorage, - internal: internalAddressesInStorage - } = storage.store.wallets[walletId].addresses + const {addresses} = storage.getWalletStore(walletId).state.paths.get(`m/44'/5'/0'`); + + const externalAddressesInStorage = Object.entries(addresses) + .filter(([path, address])=> path.includes('m/0')) + .map(([path, address])=> address); + + const internalAddressesInStorage = Object.entries(addresses) + .filter(([path, address])=> path.includes('m/1')) + .map(([path, address])=> address); // We send transaction to index 19, so wallet should generate additional 20 addresses to keep the gap between // the last used address diff --git a/packages/wallet-lib/tests/integration/types/Account.spec.js b/packages/wallet-lib/tests/integration/types/Account.spec.js index 620cce2f413..6aface918c3 100644 --- a/packages/wallet-lib/tests/integration/types/Account.spec.js +++ b/packages/wallet-lib/tests/integration/types/Account.spec.js @@ -50,7 +50,8 @@ describe('Account', function suite() { plugins: [worker], allowSensitiveOperations: true, HDPrivateKey: new HDPrivateKey(testHDKey), - adapter: storageAdapterMock + adapter: storageAdapterMock, + network: 'livenet' }); ({txStreamMock, transportMock} = await createAndAttachTransportMocksToWallet(wallet, this.sinonSandbox)); @@ -58,7 +59,7 @@ describe('Account', function suite() { account = await wallet.getAccount(); storage = account.storage; - walletId = Object.keys(storage.store.wallets)[0]; + walletId = account.walletId; address = account.getAddress(0).address; addressAtIndex19 = account.getAddress(19).address; @@ -74,20 +75,19 @@ describe('Account', function suite() { // Saving state to restore it later await account.storage.saveState(); - // Restoring wallet from the saved state const restoredWallet = new Wallet({ offlineMode: true, plugins: [worker], allowSensitiveOperations: true, HDPrivateKey: new HDPrivateKey(testHDKey), - adapter: storageAdapterMock + adapter: storageAdapterMock, + network: 'livenet' }); const restoredAccount = await restoredWallet.getAccount(); - const utxos = await restoredAccount.getUTXOS(); expect(utxos.length).to.be.equal(1); }); }); -}); \ No newline at end of file +}); From e05f37bb13f66a11646ae44d226d1e63b9d93154 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Tue, 5 Apr 2022 11:45:17 +0200 Subject: [PATCH 3/8] feat(wallet-lib): satoshisBalanceImpact in transaction history (#319) Co-authored-by: markin.io --- .../docs/account/getTransactionHistory.md | 45 ++- .../categorizeTransactions.expectedResults.js | 160 +++++--- .../apart-trip-dignity/chain-store.json | 10 + .../Account/methods/getTransactionHistory.js | 4 + .../methods/getTransactionHistory.spec.js | 361 ++++++++++++++---- .../src/utils/calculateDuffBalance.js | 4 + .../src/utils/categorizeTransactions.js | 119 ++++-- 7 files changed, 535 insertions(+), 168 deletions(-) diff --git a/packages/wallet-lib/docs/account/getTransactionHistory.md b/packages/wallet-lib/docs/account/getTransactionHistory.md index d8ab76c2b58..c3e5773a3ca 100644 --- a/packages/wallet-lib/docs/account/getTransactionHistory.md +++ b/packages/wallet-lib/docs/account/getTransactionHistory.md @@ -9,24 +9,35 @@ Parameters: Returns : sorted and classified transaction history ```js -const transactionHistory = account.getTransactionHistory(); +const transactionHistory = await account.getTransactionHistory(); +``` + +Results in -/** - * [ +```js +[{ + from: [ { address: 'yNCqctyQaq51WU1hN5aNwsgMsZ5fRiB7GY', addressType: 'external' } ], + to: [ { - from: [ { address: 'ySBsUTdfKSzqg5SHDFa6SZLRFzbbaBsrMX' } ], - to: [{ - address: 'yUSaLsYdFxuuWxNJFEvoJNAcJk3KTotLpU', - satoshis: 3999989000 - } - ], - type: 'sent', - time: 1634325172, - txId: '97932c88eda0423578f26d32a0a1ba21b5792721f345c135bc8eb2cb4864184c', - blockHash: '000000f3a03c0c7c89dcd089f87edcb18bdd95051e85bc27e7de73666a071698', - isChainLocked: true, - isInstantLocked: true + address: 'yiXh4Yo5djG6QH8WzXkKm5EFzqLRJWakXz', + satoshis: 1150000000, + addressType: 'otherAccount' + }, + { + address: 'yh6Hcyipdvp6WJpQxjNbaXP4kzPQUJpY3n', + satoshis: 49999753, + addressType: 'internal' } - ] - */ + ], + type: 'account_transfer', + time: 1629236158, + txId: '6f76ca8038c6cb1b373bbbf80698afdc0d638e4a223be12a4feb5fd8e1801135', + blockHash: '000000444b3f2f02085f8befe72da5442c865c290658766cf935e1a71a4f4ba7', + isChainLocked: true, + isInstantLocked: true, + satoshisBalanceImpact: -1150000000, + feeImpact: 247 +}] ``` + +Where `addressType=external|internal|otherAccount|unknown` diff --git a/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/categorizeTransactions.expectedResults.js b/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/categorizeTransactions.expectedResults.js index 14c900525c4..10b6c73b1ca 100644 --- a/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/categorizeTransactions.expectedResults.js +++ b/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/categorizeTransactions.expectedResults.js @@ -1,22 +1,24 @@ const transactionsWithMetadataFixtures = require("./transactions-with-metadata.json"); const expectedResultTx1 = { from: [ - {address: 'ygrRyPRf9vSHnP1ieoRRvY9THtFbTMc66e'}, - {address: 'yhDaDMNRUAB93S2ZcprNLuEGHPG4VT8kYL'}, - {address: 'ygZ5fgrtGQDtwsN8K7sftSNPXN4Srhz99s'}, - {address: 'yb39TanhfUKeqaBtzqDvAE3ad9UsDuj3Fd'}, - {address: 'yToX9gDE6tn2Sv1zhq88WNfJSomeHee3rR'}, - {address: 'yViAv63brJ5kB7Gyc7yX2c7rJ9NuykCzRh'}, - {address: 'yfnJMvdE32izNQP68PhMPiHAeJKYo2PBdH'} + {address: 'ygrRyPRf9vSHnP1ieoRRvY9THtFbTMc66e', addressType: "unknown"}, + {address: 'yhDaDMNRUAB93S2ZcprNLuEGHPG4VT8kYL', addressType: "unknown"}, + {address: 'ygZ5fgrtGQDtwsN8K7sftSNPXN4Srhz99s', addressType: "unknown"}, + {address: 'yb39TanhfUKeqaBtzqDvAE3ad9UsDuj3Fd', addressType: "unknown"}, + {address: 'yToX9gDE6tn2Sv1zhq88WNfJSomeHee3rR', addressType: "unknown"}, + {address: 'yViAv63brJ5kB7Gyc7yX2c7rJ9NuykCzRh', addressType: "unknown"}, + {address: 'yfnJMvdE32izNQP68PhMPiHAeJKYo2PBdH', addressType: "unknown"}, ], to: [ { address: 'ySE2UYPf7PWMJ5oYikSscVifzQEoGiGRmd', - satoshis: 1823313 + satoshis: 1823313, + addressType: "unknown", }, { address: 'yTwEca67QSkZ6axGdpNFzWPaCj8zqYybY7', - satoshis: 187980000 + satoshis: 187980000, + addressType: "external", } ], transaction: transactionsWithMetadataFixtures[2][0], @@ -24,18 +26,22 @@ const expectedResultTx1 = { blockHash: '000001deee9f99e8219a9abcaaea135dbaae8a9b0f1ea214e6b6a37a5c5b115d', height: 555506, isInstantLocked: true, - isChainLocked: true + isChainLocked: true, + satoshisBalanceImpact: 187980000, + feeImpact: 0 } const expectedResultTx2 = { - from: [{address: 'yaLhoAZ4iex2zKmfvS9rvEmxXmRiPrjHdD'}], + from: [{address: 'yaLhoAZ4iex2zKmfvS9rvEmxXmRiPrjHdD', addressType: "unknown"}], to: [ { address: 'yercyhdN9oEkZcB9BsW5ktFaDxFEuK6qXN', - satoshis: 10000000 + satoshis: 10000000, + addressType: "external", }, { address: 'yTcjWB7v7opDzpfYKpFdFEtEvSKFsh3bW3', - satoshis: 532649506 + satoshis: 532649506, + addressType: "unknown", } ], @@ -44,18 +50,22 @@ const expectedResultTx2 = { blockHash: '000000b6006c758eda23ec7e2a640a0bf2c6a0c44827be216faff6bf4fd388e8', height: 555507, isInstantLocked: true, - isChainLocked: true + isChainLocked: true, + satoshisBalanceImpact: 10000000, + feeImpact: 0 } const expectedResultTx3 = { - from: [ { address: 'yTcjWB7v7opDzpfYKpFdFEtEvSKFsh3bW3' } ], + from: [ { address: 'yTcjWB7v7opDzpfYKpFdFEtEvSKFsh3bW3', addressType: "unknown" } ], to: [ { address: 'ygk3GCSba2J3L9G665Snozhj9HSkh5ByVE', - satoshis: 10000000 + satoshis: 10000000, + addressType: "external" }, { address: 'yiDVYtUZ2mKV4teSJzKBArqY4BRsZoFLYs', - satoshis: 522649259 + satoshis: 522649259, + addressType: "unknown" } ], transaction: transactionsWithMetadataFixtures[5][0], @@ -63,21 +73,25 @@ const expectedResultTx3 = { blockHash: '0000012cf6377c6cf2b317a4deed46573c09f04f6880dca731cc9ccea6691e19', height: 555508, isInstantLocked: true, - isChainLocked: true + isChainLocked: true, + satoshisBalanceImpact: 10000000, + feeImpact: 0 } const expectedResultTx4 = { from: [ - { address: 'yXxUiAnB31voBDPqnwxkffcPnUvwJz6a2k' }, - { address: 'yNh6Xzw4rs1kenAo8VWCswdyUnkdYXDZsg' } + { address: 'yXxUiAnB31voBDPqnwxkffcPnUvwJz6a2k', addressType: 'unknown'}, + { address: 'yNh6Xzw4rs1kenAo8VWCswdyUnkdYXDZsg', addressType: 'unknown' } ], to: [ { address: 'yXiTNo71QQAqiw2u1i6vkEEj3m6y4sEGae', - satoshis: 1768694 + satoshis: 1768694, + addressType: "unknown" }, { address: 'yMLhEsiP2ajSh8STmXnNmkWXtoHsmawZxd', - satoshis: 840010000 + satoshis: 840010000, + addressType: "external" } ], transaction: transactionsWithMetadataFixtures[1][0], @@ -85,19 +99,22 @@ const expectedResultTx4 = { blockHash: '00000221952c2a60adcb929de837f659308cb5c6bb7783016479381fb550fbad', height: 557481, isInstantLocked: true, - isChainLocked: true - + isChainLocked: true, + satoshisBalanceImpact: 840010000, + feeImpact: 0 } const expectedResultTx5 = { - from: [{address: 'yP8A3cbdxRtLRduy5mXDsBnJtMzHWs6ZXr'}], + from: [{address: 'yP8A3cbdxRtLRduy5mXDsBnJtMzHWs6ZXr', addressType: 'unknown'}], to: [ { address: 'yY16qMW4TSiYGWUyANYWMSwgwGe36KUQsR', - satoshis: 46810176 + satoshis: 46810176, + addressType: "unknown" }, { address: 'ygHAVkMtYSqoTWHebDv7qkhMV6dHyuRsp2', - satoshis: 729210000 + satoshis: 729210000, + addressType: "external" } ], transaction: transactionsWithMetadataFixtures[0][0], @@ -105,24 +122,28 @@ const expectedResultTx5 = { blockHash: '00000c1e4556add15119392ed36ec6af2640569409abfa23a9972bc3be1b3717', height: 558036, isInstantLocked: true, - isChainLocked: true + isChainLocked: true, + satoshisBalanceImpact: 729210000, + feeImpact: 0 }; const expectedResultTx6 = { from: [ - { address: 'ygHAVkMtYSqoTWHebDv7qkhMV6dHyuRsp2' }, - { address: 'ygk3GCSba2J3L9G665Snozhj9HSkh5ByVE' }, - { address: 'yTwEca67QSkZ6axGdpNFzWPaCj8zqYybY7' }, - { address: 'yercyhdN9oEkZcB9BsW5ktFaDxFEuK6qXN' }, - { address: 'yMLhEsiP2ajSh8STmXnNmkWXtoHsmawZxd' } + { address: 'ygHAVkMtYSqoTWHebDv7qkhMV6dHyuRsp2', addressType: "external" }, + { address: 'ygk3GCSba2J3L9G665Snozhj9HSkh5ByVE', addressType: "external" }, + { address: 'yTwEca67QSkZ6axGdpNFzWPaCj8zqYybY7', addressType: "external" }, + { address: 'yercyhdN9oEkZcB9BsW5ktFaDxFEuK6qXN', addressType: "external" }, + { address: 'yMLhEsiP2ajSh8STmXnNmkWXtoHsmawZxd', addressType: "external" } ], to: [ { address: 'yj8rRKATAUHcAgXvNZekob58xKm2oNyvhv', - satoshis: 1777100000 + satoshis: 1777100000, + addressType: "external", }, { address: 'yNDpPsJqXKM36zHSNEW7c1zSvNnrZ699FY', - satoshis: 99170 + satoshis: 99170, + addressType: "internal", } ], transaction: transactionsWithMetadataFixtures[3][0], @@ -130,22 +151,27 @@ const expectedResultTx6 = { blockHash: '00000084b4d9e887a6ad3f37c576a17d79c35ec9301e55210eded519e8cdcd3a', height: 558102, isInstantLocked: true, - isChainLocked: true + isChainLocked: true, + satoshisBalanceImpact: 0, + feeImpact: 830 }; const expectedResultTx7 = { - from: [ { address: 'yj8rRKATAUHcAgXvNZekob58xKm2oNyvhv' } ], + from: [ { address: 'yj8rRKATAUHcAgXvNZekob58xKm2oNyvhv', addressType: "external" } ], to: [ { address: 'yj8rRKATAUHcAgXvNZekob58xKm2oNyvhv', - satoshis: 1270000000 + satoshis: 1270000000, + addressType: "external", }, { address: 'yhaAB6e8m3F8zmGX7WAVYa6eEfmSrrnY8x', - satoshis: 400000000 + satoshis: 400000000, + addressType: "external", }, { address: 'yLk4Hw3w4zDudrDVP6W8J9TggkY57zQUki', - satoshis: 107099720 + satoshis: 107099720, + addressType: "internal", } ], transaction: transactionsWithMetadataFixtures[6][0], @@ -153,18 +179,22 @@ const expectedResultTx7 = { blockHash: '000001953ea0bbb8ad04a9a1a2a707fef207ad22a712d7d3c619f0f9b63fa98c', height: 558229, isInstantLocked: true, - isChainLocked: true + isChainLocked: true, + satoshisBalanceImpact: 0, + feeImpact: 280 }; const expectedResultTx8 = { - from: [ { address: 'yj8rRKATAUHcAgXvNZekob58xKm2oNyvhv' } ], + from: [ { address: 'yj8rRKATAUHcAgXvNZekob58xKm2oNyvhv', addressType: 'external' } ], to: [ { address: 'yYJmzWey5kNecAThet5BFxAga1F4b4DKQ2', - satoshis: 1260000000 + satoshis: 1260000000, + addressType: "otherAccount", }, { address: 'yirJaK8KCE5YAmwvLadizqFw3TCXqBuZXL', - satoshis: 9999753 + satoshis: 9999753, + addressType: "internal", } ], transaction: transactionsWithMetadataFixtures[7][0], @@ -172,18 +202,22 @@ const expectedResultTx8 = { blockHash: '000000dffb05c071a8c05082a475b7ce9c1e403f3b89895a6c448fe08535a5f5', height: 558230, isInstantLocked: true, - isChainLocked: true + isChainLocked: true, + satoshisBalanceImpact: -1260000000, + feeImpact: 247 }; const expectedResultTx9 = { - from: [ { address: 'yYJmzWey5kNecAThet5BFxAga1F4b4DKQ2' } ], + from: [ { address: 'yYJmzWey5kNecAThet5BFxAga1F4b4DKQ2', addressType: 'otherAccount' } ], to: [ { address: 'yNCqctyQaq51WU1hN5aNwsgMsZ5fRiB7GY', - satoshis: 1200000000 + satoshis: 1200000000, + addressType: "otherAccount", }, { address: 'yXMrw79LPgu78EJsfGGYpm6fXKc1EMnQ49', - satoshis: 59999753 + satoshis: 59999753, + addressType: "otherAccount", } ], transaction: transactionsWithMetadataFixtures[10][0], @@ -191,18 +225,22 @@ const expectedResultTx9 = { blockHash: '0000016fb685b4b1efed743d2263de34a9f8323ed75e732654b1b951c5cb4dde', height: 558236, isInstantLocked: true, - isChainLocked: true + isChainLocked: true, + satoshisBalanceImpact: 0, + feeImpact: 0 }; const expectedResultTx10 = { - from: [ { address: 'yNCqctyQaq51WU1hN5aNwsgMsZ5fRiB7GY' } ], + from: [ { address: 'yNCqctyQaq51WU1hN5aNwsgMsZ5fRiB7GY', addressType: 'otherAccount' } ], to: [ { address: 'yiXh4Yo5djG6QH8WzXkKm5EFzqLRJWakXz', - satoshis: 1150000000 + satoshis: 1150000000, + addressType: "external", }, { address: 'yh6Hcyipdvp6WJpQxjNbaXP4kzPQUJpY3n', - satoshis: 49999753 + satoshis: 49999753, + addressType: "otherAccount", } ], transaction: transactionsWithMetadataFixtures[8][0], @@ -210,22 +248,26 @@ const expectedResultTx10 = { blockHash: '000000444b3f2f02085f8befe72da5442c865c290658766cf935e1a71a4f4ba7', height: 558242, isInstantLocked: true, - isChainLocked: true + isChainLocked: true, + satoshisBalanceImpact: 1150000000, + feeImpact: 0 }; const expectedResultTx11 = { from: [ - { address: 'yirJaK8KCE5YAmwvLadizqFw3TCXqBuZXL' }, - { address: 'yiXh4Yo5djG6QH8WzXkKm5EFzqLRJWakXz' } + { address: 'yirJaK8KCE5YAmwvLadizqFw3TCXqBuZXL', addressType: 'internal' }, + { address: 'yiXh4Yo5djG6QH8WzXkKm5EFzqLRJWakXz', addressType: 'external' } ], to: [ { address: 'yMX3ycrLVF2k6YxWQbMoYgs39aeTfY4wrB', - satoshis: 1000000000 + satoshis: 1000000000, + addressType: "unknown", }, { address: 'yhdRfg5gNr587dtEC4YYMcSHmLVEGqqtHc', - satoshis: 159999359 + satoshis: 159999359, + addressType: "internal", } ], transaction: transactionsWithMetadataFixtures[9][0], @@ -233,7 +275,9 @@ const expectedResultTx11 = { blockHash: '000001f9c5de4d2b258a975bfbf7b9a3346890af6389512bea3cb6926b9be330', height: 558246, isInstantLocked: true, - isChainLocked: true + isChainLocked: true, + satoshisBalanceImpact: -1000000000, + feeImpact: 394 }; module.exports = [ diff --git a/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/chain-store.json b/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/chain-store.json index 15fbaa77a73..3d70eac2f66 100644 --- a/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/chain-store.json +++ b/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/chain-store.json @@ -92,6 +92,15 @@ "isChainLocked": true } }, + "9cd3d44a87a7f99a33aebc6957105d5fb41698ef642189a36bac59ec0b5cd840": { + "transaction": "0300000001338540c64b794f73913f39f2d42d9139ce7c9d1c0ec5317c62ab4a28d6b0376f000000006b483045022100b996d726d224a762acf8ab3e37c085e796b44960b8e9933571ac57750e8ed05102201c6a36d72f16140d6a152be40add102d95a4ac5b177d300b10c277859690a859012103b5614f077d750a1eaffb23ca188dbcc7e267f4b8ffdedf81cdf970643027191bffffffff02008c8647000000001976a91414b05906daab037707927bc6c83900d5dbf2849688ac09869303000000001976a914791e51fff6554c18216c83d9ca81cf30cc66aff388ac00000000", + "metadata": { + "blockHash": "0000016fb685b4b1efed743d2263de34a9f8323ed75e732654b1b951c5cb4dde", + "height": 558236, + "isInstantLocked": true, + "isChainLocked": true + } + }, "6f76ca8038c6cb1b373bbbf80698afdc0d638e4a223be12a4feb5fd8e1801135": { "transaction": "030000000140d85c0bec59ac6ba3892164ef9816b45f5d105769bcae339af9a7874ad4d39c000000006b483045022100eef13b38a771924b1429b119ef27494fe764cf1a7b12962462c4934ba15dd426022037097eaf74e3f3b547bf5e67451c7bf1e2e2a4b7b205850b1315bc4ab983fdf2012103f376b41c9e9ebc3131e33d4de127c57c1bf3ca88f81845595c44f9ac46122677ffffffff02809b8b44000000001976a914f3a39f8266812baa084890d02fc489f2aee8075a88ac89effa02000000001976a914e3dd87e2dd2080c854d0c90abae96d985ae8902288ac00000000", "metadata": { @@ -554,6 +563,7 @@ "yNCqctyQaq51WU1hN5aNwsgMsZ5fRiB7GY": { "address": "yNCqctyQaq51WU1hN5aNwsgMsZ5fRiB7GY", "transactions": [ + "9cd3d44a87a7f99a33aebc6957105d5fb41698ef642189a36bac59ec0b5cd840", "6f76ca8038c6cb1b373bbbf80698afdc0d638e4a223be12a4feb5fd8e1801135" ], "utxos": {}, diff --git a/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.js b/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.js index f2cbdca2c6a..143e687bdba 100644 --- a/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.js +++ b/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.js @@ -52,6 +52,8 @@ function getTransactionHistory() { type, isChainLocked, isInstantLocked, + satoshisBalanceImpact, + feeImpact, } = categorizedTransaction; const blockHash = categorizedTransaction.blockHash !== '' ? categorizedTransaction.blockHash @@ -73,6 +75,8 @@ function getTransactionHistory() { blockHash, isChainLocked, isInstantLocked, + satoshisBalanceImpact, + feeImpact, }; transactionHistory.push(normalizedTransactionHistory); diff --git a/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.spec.js b/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.spec.js index 04e72c5dac6..0078192118b 100644 --- a/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.spec.js +++ b/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.spec.js @@ -15,6 +15,7 @@ const getFixturePrivateAccountWithStorage = require('../../../../fixtures/wallet const normalizedHDStoreFixtures = require('../../../../fixtures/wallets/apart-trip-dignity/store.json'); const normalizedPKStoreFixtures = require('../../../../fixtures/wallets/2a331817b9d6bf85100ef0/store.json'); const CONSTANTS = require("../../../CONSTANTS"); +const getTotalBalance = require("./getTotalBalance"); const normalizedStoreToStore = (normalizedStore) => { const store = { ...normalizedStore @@ -94,6 +95,13 @@ describe('Account - getTransactionHistory', () => { address.balanceSat = 0; }) const transactionHistoryHD = await getTransactionHistory.call(mockedHDSelf); + const balanceImpact = transactionHistoryHD.reduce((acc, item) => { + return acc + item.satoshisBalanceImpact - item.feeImpact + },0); + + const balance = getTotalBalance.call(mockedHDSelf); + expect(balance).to.equal(balanceImpact) + const expectedTransactionHistoryHD = []; expect(transactionHistoryHD).to.deep.equal(expectedTransactionHistoryHD); }); @@ -105,22 +113,39 @@ describe('Account - getTransactionHistory', () => { const transactionHistoryHD = await getTransactionHistory.call(mockedHDSelf); const timeendTs = +new Date(); const calculationTime = timeendTs - timestartTs; + + const balance = getTotalBalance.call(mockedHDSelf); + + const balanceImpact = transactionHistoryHD.reduce((acc, item) => { + return acc + item.satoshisBalanceImpact - item.feeImpact + },0); + + expect(balance).to.equal(balanceImpact) + expect(calculationTime).to.be.below(60 * 1000); const expectedTransactionHistoryHD = [ { from: [ - {address: 'yirJaK8KCE5YAmwvLadizqFw3TCXqBuZXL'}, - {address: 'yiXh4Yo5djG6QH8WzXkKm5EFzqLRJWakXz'} + { + address: 'yirJaK8KCE5YAmwvLadizqFw3TCXqBuZXL', + addressType: 'internal' + }, + { + address: 'yiXh4Yo5djG6QH8WzXkKm5EFzqLRJWakXz', + addressType: 'external' + } ], to: [ { address: 'yMX3ycrLVF2k6YxWQbMoYgs39aeTfY4wrB', - satoshis: 1000000000 + satoshis: 1000000000, + addressType: 'unknown' }, { address: 'yhdRfg5gNr587dtEC4YYMcSHmLVEGqqtHc', - satoshis: 159999359 + satoshis: 159999359, + addressType: 'internal' } ], type: 'sent', @@ -128,18 +153,22 @@ describe('Account - getTransactionHistory', () => { txId: 'e6b6f85a18d77974f376f05d6c96d0fdde990e733664248b1a00391565af6841', blockHash: '000001f9c5de4d2b258a975bfbf7b9a3346890af6389512bea3cb6926b9be330', isChainLocked: true, - isInstantLocked: true + isInstantLocked: true, + satoshisBalanceImpact:-1000000000, + feeImpact: 394 }, { - from: [{address: 'yNCqctyQaq51WU1hN5aNwsgMsZ5fRiB7GY'}], + from: [{address: 'yNCqctyQaq51WU1hN5aNwsgMsZ5fRiB7GY', addressType: 'otherAccount'}], to: [ { address: 'yiXh4Yo5djG6QH8WzXkKm5EFzqLRJWakXz', - satoshis: 1150000000 + satoshis: 1150000000, + addressType: 'external' }, { address: 'yh6Hcyipdvp6WJpQxjNbaXP4kzPQUJpY3n', - satoshis: 49999753 + satoshis: 49999753, + addressType: 'otherAccount' } ], type: 'account_transfer', @@ -147,18 +176,25 @@ describe('Account - getTransactionHistory', () => { txId: '6f76ca8038c6cb1b373bbbf80698afdc0d638e4a223be12a4feb5fd8e1801135', blockHash: '000000444b3f2f02085f8befe72da5442c865c290658766cf935e1a71a4f4ba7', isChainLocked: true, - isInstantLocked: true + isInstantLocked: true, + satoshisBalanceImpact: 1150000000, + feeImpact: 0 }, { - from: [{address: 'yj8rRKATAUHcAgXvNZekob58xKm2oNyvhv'}], + from: [{ + address: 'yj8rRKATAUHcAgXvNZekob58xKm2oNyvhv', + addressType: 'external' + }], to: [ { address: 'yYJmzWey5kNecAThet5BFxAga1F4b4DKQ2', - satoshis: 1260000000 + satoshis: 1260000000, + addressType: 'otherAccount' }, { address: 'yirJaK8KCE5YAmwvLadizqFw3TCXqBuZXL', - satoshis: 9999753 + satoshis: 9999753, + addressType: 'internal' } ], type: 'account_transfer', @@ -166,22 +202,30 @@ describe('Account - getTransactionHistory', () => { txId: '6f37b0d6284aab627c31c50e1c9d7cce39912dd4f2393f91734f794bc6408533', blockHash: '000000dffb05c071a8c05082a475b7ce9c1e403f3b89895a6c448fe08535a5f5', isChainLocked: true, - isInstantLocked: true + isInstantLocked: true, + satoshisBalanceImpact: -1260000000, + feeImpact: 247 }, { - from: [{address: 'yj8rRKATAUHcAgXvNZekob58xKm2oNyvhv'}], + from: [{ + address: 'yj8rRKATAUHcAgXvNZekob58xKm2oNyvhv', + addressType: 'external' + }], to: [ { address: 'yj8rRKATAUHcAgXvNZekob58xKm2oNyvhv', - satoshis: 1270000000 + satoshis: 1270000000, + addressType: 'external' }, { address: 'yhaAB6e8m3F8zmGX7WAVYa6eEfmSrrnY8x', - satoshis: 400000000 + satoshis: 400000000, + addressType: 'external' }, { address: 'yLk4Hw3w4zDudrDVP6W8J9TggkY57zQUki', - satoshis: 107099720 + satoshis: 107099720, + addressType: 'internal' } ], type: 'address_transfer', @@ -189,25 +233,44 @@ describe('Account - getTransactionHistory', () => { txId: 'c3fb3620ebd1c7678879b40df1495cc86a179b5a6f9e48ce0b687a5c6f5a1db5', blockHash: '000001953ea0bbb8ad04a9a1a2a707fef207ad22a712d7d3c619f0f9b63fa98c', isChainLocked: true, - isInstantLocked: true + isInstantLocked: true, + satoshisBalanceImpact: 0, + feeImpact: 280 }, { from: [ - {address: 'ygHAVkMtYSqoTWHebDv7qkhMV6dHyuRsp2'}, - {address: 'ygk3GCSba2J3L9G665Snozhj9HSkh5ByVE'}, - {address: 'yTwEca67QSkZ6axGdpNFzWPaCj8zqYybY7'}, - {address: 'yercyhdN9oEkZcB9BsW5ktFaDxFEuK6qXN'}, - {address: 'yMLhEsiP2ajSh8STmXnNmkWXtoHsmawZxd'} + { + address: 'ygHAVkMtYSqoTWHebDv7qkhMV6dHyuRsp2', + addressType: 'external' + }, + { + address: 'ygk3GCSba2J3L9G665Snozhj9HSkh5ByVE', + addressType: 'external' + }, + { + address: 'yTwEca67QSkZ6axGdpNFzWPaCj8zqYybY7', + addressType: 'external' + }, + { + address: 'yercyhdN9oEkZcB9BsW5ktFaDxFEuK6qXN', + addressType: 'external' + }, + { + address: 'yMLhEsiP2ajSh8STmXnNmkWXtoHsmawZxd', + addressType: 'external' + } ], to: [ { address: 'yj8rRKATAUHcAgXvNZekob58xKm2oNyvhv', - satoshis: 1777100000 + satoshis: 1777100000, + addressType: 'external' }, { address: 'yNDpPsJqXKM36zHSNEW7c1zSvNnrZ699FY', - satoshis: 99170 + satoshis: 99170, + addressType: 'internal' } ], type: 'address_transfer', @@ -215,18 +278,25 @@ describe('Account - getTransactionHistory', () => { txId: 'f230a9414bf577d93d6f7f2515d9b549ede78cfba4168920892970fa8aa1eef8', blockHash: '00000084b4d9e887a6ad3f37c576a17d79c35ec9301e55210eded519e8cdcd3a', isChainLocked: true, - isInstantLocked: true + isInstantLocked: true, + satoshisBalanceImpact: 0, + feeImpact: 830 }, { - from: [{address: 'yP8A3cbdxRtLRduy5mXDsBnJtMzHWs6ZXr'}], + from: [{ + address: 'yP8A3cbdxRtLRduy5mXDsBnJtMzHWs6ZXr', + addressType: "unknown" + }], to: [ { address: 'yY16qMW4TSiYGWUyANYWMSwgwGe36KUQsR', - satoshis: 46810176 + satoshis: 46810176, + addressType: "unknown" }, { address: 'ygHAVkMtYSqoTWHebDv7qkhMV6dHyuRsp2', - satoshis: 729210000 + satoshis: 729210000, + addressType: "external" } ], type: 'received', @@ -234,13 +304,30 @@ describe('Account - getTransactionHistory', () => { txId: '1cbb35edc105918b956838570f122d6f3a1fba2b67467e643e901d09f5f8ac1b', blockHash: '00000c1e4556add15119392ed36ec6af2640569409abfa23a9972bc3be1b3717', isChainLocked: true, - isInstantLocked: true + isInstantLocked: true, + satoshisBalanceImpact: 729210000, + feeImpact: 0 }, { - from: [{address: 'yXxUiAnB31voBDPqnwxkffcPnUvwJz6a2k'},{address: 'yNh6Xzw4rs1kenAo8VWCswdyUnkdYXDZsg'}], + from: [{ + address: 'yXxUiAnB31voBDPqnwxkffcPnUvwJz6a2k', + addressType: "unknown" + }, + { + address: 'yNh6Xzw4rs1kenAo8VWCswdyUnkdYXDZsg', + addressType: "unknown" + }], to: [ - {"address": "yXiTNo71QQAqiw2u1i6vkEEj3m6y4sEGae","satoshis": 1768694}, - {"address": "yMLhEsiP2ajSh8STmXnNmkWXtoHsmawZxd","satoshis": 840010000} + { + "address": "yXiTNo71QQAqiw2u1i6vkEEj3m6y4sEGae", + "satoshis": 1768694, + addressType: "unknown" + }, + { + "address": "yMLhEsiP2ajSh8STmXnNmkWXtoHsmawZxd", + "satoshis": 840010000, + addressType: "external" + } ], time: 1629126597, txId: "eb1a7fc8e3b43d3021653b1176f8f9b41e9667d05b65ee225d14c149a5b14f77", @@ -248,12 +335,25 @@ describe('Account - getTransactionHistory', () => { type: "received", isChainLocked: true, isInstantLocked: true, + satoshisBalanceImpact: 840010000, + feeImpact: 0 }, { - from: [{address: 'yTcjWB7v7opDzpfYKpFdFEtEvSKFsh3bW3'}], + from: [{ + address: 'yTcjWB7v7opDzpfYKpFdFEtEvSKFsh3bW3', + addressType: "unknown" + }], to: [ - {"address": "ygk3GCSba2J3L9G665Snozhj9HSkh5ByVE","satoshis": 10000000}, - {"address": "yiDVYtUZ2mKV4teSJzKBArqY4BRsZoFLYs","satoshis": 522649259} + { + "address": "ygk3GCSba2J3L9G665Snozhj9HSkh5ByVE", + "satoshis": 10000000, + addressType: "external" + }, + { + "address": "yiDVYtUZ2mKV4teSJzKBArqY4BRsZoFLYs", + "satoshis": 522649259, + addressType: "unknown" + } ], time: 1628846998, txId: "7d1b78157f9f2238669f260d95af03aeefc99577ff0cddb91b3e518ee557a2fd", @@ -261,42 +361,87 @@ describe('Account - getTransactionHistory', () => { type: "received", isChainLocked: true, isInstantLocked: true, + satoshisBalanceImpact: 10000000, + feeImpact: 0 }, { - from: [{address: 'yaLhoAZ4iex2zKmfvS9rvEmxXmRiPrjHdD'}], + from: [{ + address: 'yaLhoAZ4iex2zKmfvS9rvEmxXmRiPrjHdD', + addressType: "unknown" + }], to: [ - {"address": "yercyhdN9oEkZcB9BsW5ktFaDxFEuK6qXN","satoshis": 10000000}, - {"address": "yTcjWB7v7opDzpfYKpFdFEtEvSKFsh3bW3","satoshis": 532649506} + { + "address": "yercyhdN9oEkZcB9BsW5ktFaDxFEuK6qXN", + "satoshis": 10000000, + addressType: "external" + }, + { + "address": "yTcjWB7v7opDzpfYKpFdFEtEvSKFsh3bW3", + "satoshis": 532649506, + addressType: "unknown" + } ], type: 'received', time: 1628846768, txId: 'd37b6c7dd449d605bea9997af8bbeed2f3fbbcb23a4068b1f1ad694db801912d', blockHash: '000000b6006c758eda23ec7e2a640a0bf2c6a0c44827be216faff6bf4fd388e8', isChainLocked: true, - isInstantLocked: true + isInstantLocked: true, + satoshisBalanceImpact: 10000000, + feeImpact: 0 }, { from: [ - {address: 'ygrRyPRf9vSHnP1ieoRRvY9THtFbTMc66e'}, - {address: 'yhDaDMNRUAB93S2ZcprNLuEGHPG4VT8kYL'}, - {address: 'ygZ5fgrtGQDtwsN8K7sftSNPXN4Srhz99s'}, - {address: 'yb39TanhfUKeqaBtzqDvAE3ad9UsDuj3Fd'}, - {address: 'yToX9gDE6tn2Sv1zhq88WNfJSomeHee3rR'}, - {address: 'yViAv63brJ5kB7Gyc7yX2c7rJ9NuykCzRh'}, - {address: 'yfnJMvdE32izNQP68PhMPiHAeJKYo2PBdH'}, + { + address: 'ygrRyPRf9vSHnP1ieoRRvY9THtFbTMc66e', + addressType: "unknown" + }, + { + address: 'yhDaDMNRUAB93S2ZcprNLuEGHPG4VT8kYL', + addressType: "unknown" + }, + { + address: 'ygZ5fgrtGQDtwsN8K7sftSNPXN4Srhz99s', + addressType: "unknown" + }, + { + address: 'yb39TanhfUKeqaBtzqDvAE3ad9UsDuj3Fd', + addressType: "unknown" + }, + { + address: 'yToX9gDE6tn2Sv1zhq88WNfJSomeHee3rR', + addressType: "unknown" + }, + { + address: 'yViAv63brJ5kB7Gyc7yX2c7rJ9NuykCzRh', + addressType: "unknown" + }, + { + address: 'yfnJMvdE32izNQP68PhMPiHAeJKYo2PBdH', + addressType: "unknown" + }, ], to: [ - {"address": "ySE2UYPf7PWMJ5oYikSscVifzQEoGiGRmd","satoshis": 1823313}, - {"address": "yTwEca67QSkZ6axGdpNFzWPaCj8zqYybY7","satoshis": 187980000} + { + "address": "ySE2UYPf7PWMJ5oYikSscVifzQEoGiGRmd", + "satoshis": 1823313, + addressType: "unknown" + }, + { + "address": "yTwEca67QSkZ6axGdpNFzWPaCj8zqYybY7", + "satoshis": 187980000, + addressType: "external" + } ], type: 'received', time: 1628846677, txId: 'a43845e580ad01f31bc06ce47ab39674e40316c4c6b765b6e54d6d35777ef456', blockHash: '000001deee9f99e8219a9abcaaea135dbaae8a9b0f1ea214e6b6a37a5c5b115d', isChainLocked: true, - isInstantLocked: true + isInstantLocked: true, + satoshisBalanceImpact: 187980000, + feeImpact: 0 } - ] expect(transactionHistoryHD).to.deep.equal(expectedTransactionHistoryHD); }); @@ -307,17 +452,56 @@ describe('Account - getTransactionHistory', () => { mockedHDSelf.index = 1; mockedHDSelf.accountPath = `m/44'/1'/1'`; const transactionHistoryHD = await getTransactionHistory.call(mockedHDSelf); + + const balance = getTotalBalance.call(mockedHDSelf); + + const balanceImpact = transactionHistoryHD.reduce((acc, item) => { + return acc + item.satoshisBalanceImpact - item.feeImpact + },0); + + expect(balance).to.equal(balanceImpact) + const expectedTransactionHistoryHD = [ { - from: [ { address: 'yNCqctyQaq51WU1hN5aNwsgMsZ5fRiB7GY' } ], + from: [ + { + address: 'yYJmzWey5kNecAThet5BFxAga1F4b4DKQ2', + addressType: 'external', + }, + ], + to: [ + { + address: 'yNCqctyQaq51WU1hN5aNwsgMsZ5fRiB7GY', + satoshis: 1200000000, + addressType: 'external', + }, + { + address: 'yXMrw79LPgu78EJsfGGYpm6fXKc1EMnQ49', + satoshis: 59999753, + addressType: 'internal', + }, + ], + type: 'address_transfer', + time: 9999999999, + txId: '9cd3d44a87a7f99a33aebc6957105d5fb41698ef642189a36bac59ec0b5cd840', + blockHash: '0000016fb685b4b1efed743d2263de34a9f8323ed75e732654b1b951c5cb4dde', + isChainLocked: true, + isInstantLocked: true, + satoshisBalanceImpact: 0, + feeImpact: 247 + }, + { + from: [ { address: 'yNCqctyQaq51WU1hN5aNwsgMsZ5fRiB7GY', addressType: 'external' } ], to: [ { address: 'yiXh4Yo5djG6QH8WzXkKm5EFzqLRJWakXz', - satoshis: 1150000000 + satoshis: 1150000000, + addressType: 'otherAccount' }, { address: 'yh6Hcyipdvp6WJpQxjNbaXP4kzPQUJpY3n', - satoshis: 49999753 + satoshis: 49999753, + addressType: 'internal' } ], type: 'account_transfer', @@ -325,18 +509,22 @@ describe('Account - getTransactionHistory', () => { txId: '6f76ca8038c6cb1b373bbbf80698afdc0d638e4a223be12a4feb5fd8e1801135', blockHash: '000000444b3f2f02085f8befe72da5442c865c290658766cf935e1a71a4f4ba7', isChainLocked: true, - isInstantLocked: true + isInstantLocked: true, + satoshisBalanceImpact: -1150000000, + feeImpact: 247 }, { - from: [ { address: 'yj8rRKATAUHcAgXvNZekob58xKm2oNyvhv' } ], + from: [ { address: 'yj8rRKATAUHcAgXvNZekob58xKm2oNyvhv', addressType: 'otherAccount' } ], to: [ { address: 'yYJmzWey5kNecAThet5BFxAga1F4b4DKQ2', - satoshis: 1260000000 + satoshis: 1260000000, + addressType: 'external' }, { address: 'yirJaK8KCE5YAmwvLadizqFw3TCXqBuZXL', - satoshis: 9999753 + satoshis: 9999753, + addressType: 'otherAccount' } ], type: 'account_transfer', @@ -344,10 +532,12 @@ describe('Account - getTransactionHistory', () => { txId: '6f37b0d6284aab627c31c50e1c9d7cce39912dd4f2393f91734f794bc6408533', blockHash: '000000dffb05c071a8c05082a475b7ce9c1e403f3b89895a6c448fe08535a5f5', isChainLocked: true, - isInstantLocked: true + isInstantLocked: true, + satoshisBalanceImpact: 1260000000, + feeImpact: 0 } ] - expect(transactionHistoryHD.slice(0,5)).to.deep.equal(expectedTransactionHistoryHD); + expect(transactionHistoryHD).to.deep.equal(expectedTransactionHistoryHD); }); it('should correctly compute transaction history for private key based wallet', async function (){ const mockedPKSelf = { @@ -359,17 +549,29 @@ describe('Account - getTransactionHistory', () => { const transactionHistoryPK = await getTransactionHistory.call(mockedPKSelf); + const balanceImpact = transactionHistoryPK.reduce((acc, item) => { + return acc + item.satoshisBalanceImpact - item.feeImpact + },0); + + const balance = getTotalBalance.call(mockedPKSelf); + expect(balance).to.equal(balanceImpact) + const expectedTransactionHistoryPK = [ { - from: [ { address: 'ycDeuTfs4U77bTb5cq17dame28zdWHVYfk' } ], + from: [ { + address: 'ycDeuTfs4U77bTb5cq17dame28zdWHVYfk', + addressType: 'external' + } ], to: [ { address: 'yP8A3cbdxRtLRduy5mXDsBnJtMzHWs6ZXr', - satoshis: 450000 + satoshis: 450000, + addressType: 'unknown' }, { address: 'ycDeuTfs4U77bTb5cq17dame28zdWHVYfk', - satoshis: 8999753 + satoshis: 8999753, + addressType: 'external' } ], type: 'sent', @@ -377,17 +579,24 @@ describe('Account - getTransactionHistory', () => { txId: '47d13f7f713f4258953292c2298c1d91e2d6dee309d689f3c8b44ccf457bab52', blockHash: '0000007b7356e715b43ed7d5b7135fb9a2bf403e079bbcf7faec0f0da5c40117', isChainLocked: true, - isInstantLocked: true + isInstantLocked: true, + satoshisBalanceImpact: -450000, + feeImpact: 247 }, { - from: [ { address: 'ycDeuTfs4U77bTb5cq17dame28zdWHVYfk' } ], + from: [ { + address: 'ycDeuTfs4U77bTb5cq17dame28zdWHVYfk', + addressType: 'external' + } ], to: [ { address: 'ycDeuTfs4U77bTb5cq17dame28zdWHVYfk', + addressType: 'external', satoshis: 9450000 }, { address: 'ycDeuTfs4U77bTb5cq17dame28zdWHVYfk', + addressType: 'external', satoshis: 699999753 } ], @@ -396,21 +605,31 @@ describe('Account - getTransactionHistory', () => { txId: 'd48f415f08fb795d43b216cf56e9ef10e059d4009cfc8fc90edfc0d3850813af', blockHash: '0000018b88fe43d07c3d63050aa82271698dc406dd08388529205dd837bf92dc', isChainLocked: true, - isInstantLocked: true + isInstantLocked: true, + satoshisBalanceImpact: 0, + feeImpact: 247 }, { from: [ - { address: 'yXpVMRLKnH9e9Bdcd68e8iA3rxAerzwKop' }, - { address: 'yeryenDBwJbe7rqdL5uv7iLiJAWSU1iTe2' } + { + address: 'yXpVMRLKnH9e9Bdcd68e8iA3rxAerzwKop', + addressType: 'unknown' + }, + { + address: 'yeryenDBwJbe7rqdL5uv7iLiJAWSU1iTe2', + addressType: 'unknown' + } ], to: [ { address: 'yanVwuG1csehvH7PoWHxmYmjtojXBLnoYP', + addressType: 'unknown', satoshis: 4840346 }, { address: 'ycDeuTfs4U77bTb5cq17dame28zdWHVYfk', - satoshis: 709450000 + satoshis: 709450000, + addressType: 'external' } ], type: 'received', @@ -418,7 +637,9 @@ describe('Account - getTransactionHistory', () => { txId: '0dcdaa9bf5b3596be1bcf22113e39026fd49d24b47190e2c7423be936cb116a7', blockHash: '000000299efeefa87dc15474fd0423c136798975b779a2bb8aa5bb2f50509afb', isChainLocked: true, - isInstantLocked: true + isInstantLocked: true, + satoshisBalanceImpact: 709450000, + feeImpact: 0 } ] diff --git a/packages/wallet-lib/src/utils/calculateDuffBalance.js b/packages/wallet-lib/src/utils/calculateDuffBalance.js index 82be58fe1e9..f32da7d67ea 100644 --- a/packages/wallet-lib/src/utils/calculateDuffBalance.js +++ b/packages/wallet-lib/src/utils/calculateDuffBalance.js @@ -10,6 +10,10 @@ module.exports = function calculateDuffBalance(addresses, chainStore, type = 'to addresses.forEach((address) => { const addressData = chainStore.getAddress(address); + if (!addressData) { + return; + } + switch (type) { case 'total': totalSat += addressData.balanceSat + addressData.unconfirmedBalanceSat; diff --git a/packages/wallet-lib/src/utils/categorizeTransactions.js b/packages/wallet-lib/src/utils/categorizeTransactions.js index 1b84ac88f41..77f162043a3 100644 --- a/packages/wallet-lib/src/utils/categorizeTransactions.js +++ b/packages/wallet-lib/src/utils/categorizeTransactions.js @@ -17,8 +17,8 @@ const determineType = (inputsDetection, outputsDetection) => { ) { type = TRANSACTION_HISTORY_TYPES.ACCOUNT_TRANSFER; } else if (inputsDetection.hasOwnAddress - && !outputsDetection.hasUnknownAddress - && !outputsDetection.hasOtherAccountAddress) { + && !outputsDetection.hasUnknownAddress + && !outputsDetection.hasOtherAccountAddress) { // Detecting an address transfer is the second element we need to discriminate type = TRANSACTION_HISTORY_TYPES.ADDRESS_TRANSFER; } else { @@ -30,7 +30,7 @@ const determineType = (inputsDetection, outputsDetection) => { } if ( outputsDetection.hasUnknownAddress - && (inputsDetection.hasOwnAddress) + && (inputsDetection.hasOwnAddress) ) { type = TRANSACTION_HISTORY_TYPES.SENT; } @@ -71,44 +71,111 @@ function categorizeTransactions( let inputsHasOwnAddress = false; let inputsHasUnknownAddress = false; - // For each vout, we will look at matching known addresses - transaction.outputs.forEach((vout) => { - const { satoshis, script } = vout; - const address = script.toAddress(network).toString(); - if (address) { - if (internalAddressesList.includes(address)) { - outputsHasChangeAddress = true; - outputsHasOwnAddress = true; - } else if (externalAddressesList.includes(address)) { - outputsHasExternalAddress = true; - outputsHasOwnAddress = true; - } else if (otherAccountAddressesList.includes(address)) { - outputsHasOtherAccountAddress = true; - } else outputsHasUnknownAddress = true; - to.push({ - address, - satoshis, - }); - } - }); + /** + * Total duffs amount sent with current account (if any) + * @type {number} + */ + let totalAccountInput = 0; + + /** + * Total duffs amount within the tx outputs + * @type {number} + */ + let totalTxOutput = 0; + + /** + * Output balance impact + * @type {number} + */ + let satoshisBalanceImpact = 0; + + /** + * Fee balance impact (in case TX sent with the current account) + * @type {number} + */ + let feeImpact = 0; + // For each vin, we will look at matching known addresses // In order to know the value in, we would require fetching tx for output of vin info transaction.inputs.forEach((vin) => { const { script } = vin; + + // Ignore coinbase inputs + if (!script) { + return; + } + const address = script.toAddress(network).toString(); + let addressType = 'unknown'; if (address) { if (internalAddressesList.includes(address)) { + addressType = 'internal'; inputsHasChangeAddress = true; inputsHasOwnAddress = true; } else if (externalAddressesList.includes(address)) { + addressType = 'external'; inputsHasExternalAddress = true; inputsHasOwnAddress = true; } else if (otherAccountAddressesList.includes(address)) { + addressType = 'otherAccount'; inputsHasOtherAccountAddress = true; } else inputsHasUnknownAddress = true; + from.push({ address, + addressType, }); + + // Calculates total input amount coming from address belonging to the wallet account + const isSendTx = addressType === 'internal' || addressType === 'external'; + if (isSendTx) { + const { prevTxId, outputIndex } = vin; + const prevTxHash = prevTxId.toString('hex'); + const prevTx = transactionsWithMetadata.find(([tx]) => tx.hash === prevTxHash); + + // Previous tx might not be in the app state because of + // `skipSynchronizationBeforeHeight` option + if (prevTx) { + totalAccountInput += prevTx[0].outputs[outputIndex].satoshis; + } + } + } + }); + + // For each vout, we will look at matching known addresses + transaction.outputs.forEach((vout) => { + const { satoshis, script } = vout; + totalTxOutput += satoshis; + const address = script.toAddress(network).toString(); + let addressType = 'unknown'; + if (address) { + if (internalAddressesList.includes(address)) { + addressType = 'internal'; + outputsHasChangeAddress = true; + outputsHasOwnAddress = true; + } else if (externalAddressesList.includes(address)) { + addressType = 'external'; + outputsHasExternalAddress = true; + outputsHasOwnAddress = true; + } else if (otherAccountAddressesList.includes(address)) { + addressType = 'otherAccount'; + outputsHasOtherAccountAddress = true; + } else outputsHasUnknownAddress = true; + to.push({ + address, + satoshis, + addressType, + }); + + const accountOutput = addressType === 'internal' || addressType === 'external'; + const receivedFromUnknown = accountOutput && totalAccountInput === 0; + const sentToUnknown = !accountOutput && totalAccountInput > 0; + + if (receivedFromUnknown) { + satoshisBalanceImpact += satoshis; + } else if (sentToUnknown) { + satoshisBalanceImpact -= satoshis; + } } }); @@ -126,6 +193,10 @@ function categorizeTransactions( hasUnknownAddress: outputsHasUnknownAddress, }); + if (totalAccountInput > 0) { + feeImpact = totalAccountInput - totalTxOutput; + } + const categorizedTransaction = { from, to, @@ -135,6 +206,8 @@ function categorizeTransactions( height: metadata.height, isInstantLocked: metadata.isInstantLocked, isChainLocked: metadata.isChainLocked, + satoshisBalanceImpact, + feeImpact, }; categorizedTransactions.push(categorizedTransaction); }); From bf393adf04d3d619a544601c869a6800e1ae6323 Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Thu, 14 Apr 2022 00:24:13 +0200 Subject: [PATCH 4/8] feat: adds balance and metadata information from registered identity (#337) * feat: adds balance and metadata information from registered identity * fix: use setter method for identity --- .../SDK/Client/Platform/methods/identities/register.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/register.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/register.ts index fc38f769a68..9fb45c71a38 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/register.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/register.ts @@ -43,5 +43,13 @@ export default async function register( identityIndex, ); + // Current identity object will not have metadata or balance information + const registeredIdentity = await this.identities.get(identity.id.toString()); + + // We cannot just return registeredIdentity as we want to + // keep additional information (assetLockProof and transaction) instance + identity.setMetadata(registeredIdentity.metadata); + identity.setBalance(registeredIdentity.balance); + return identity; } From 066d4b5e0452d28967f7201b892bc9998b4f8f2f Mon Sep 17 00:00:00 2001 From: Alex Werner Date: Thu, 14 Apr 2022 00:24:44 +0200 Subject: [PATCH 5/8] feat: provide transaction history item as a date object (#336) * feat: provide transaction history item as a date object * style: fix lint * docs: update documentation * docs: fix documentation conflict * fix: conflict --- .../docs/account/getTransactionHistory.md | 6 ++-- .../Account/methods/getTransactionHistory.js | 2 +- .../methods/getTransactionHistory.spec.js | 32 +++++++++---------- packages/wallet-lib/src/types/types.d.ts | 2 +- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/wallet-lib/docs/account/getTransactionHistory.md b/packages/wallet-lib/docs/account/getTransactionHistory.md index c3e5773a3ca..2f4ebff80b6 100644 --- a/packages/wallet-lib/docs/account/getTransactionHistory.md +++ b/packages/wallet-lib/docs/account/getTransactionHistory.md @@ -1,12 +1,12 @@ **Usage**: `account.getTransactionHistory()` **Description**: Allow to get the transaction history of an account -Parameters: +Parameters: | parameters | type | required | Description | |-------------------|--------|----------------| -------------------------------------------------| -Returns : sorted and classified transaction history +Returns : sorted and classified transaction history ```js const transactionHistory = await account.getTransactionHistory(); @@ -30,7 +30,7 @@ Results in } ], type: 'account_transfer', - time: 1629236158, + time: Date('2021-08-17T21:35:58.000Z'), txId: '6f76ca8038c6cb1b373bbbf80698afdc0d638e4a223be12a4feb5fd8e1801135', blockHash: '000000444b3f2f02085f8befe72da5442c865c290658766cf935e1a71a4f4ba7', isChainLocked: true, diff --git a/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.js b/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.js index 143e687bdba..71044afa597 100644 --- a/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.js +++ b/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.js @@ -61,7 +61,7 @@ function getTransactionHistory() { // To get time of block, let's find the blockheader. const blockHeader = blockHeaders.get(blockHash); // If it's unconfirmed, we won't have a blockHeader nor it's time. - const time = blockHeader ? blockHeader.time : 9999999999; + const time = blockHeader ? new Date(blockHeader.time * 1e3) : new Date(9999999999 * 1e3); const normalizedTransactionHistory = { // Would require knowing the vout of this vin to determinate inputAmount. diff --git a/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.spec.js b/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.spec.js index 0078192118b..e9fc1adc585 100644 --- a/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.spec.js +++ b/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.spec.js @@ -149,7 +149,7 @@ describe('Account - getTransactionHistory', () => { } ], type: 'sent', - time: 1629237076, + time: new Date(1629237076*1e3), txId: 'e6b6f85a18d77974f376f05d6c96d0fdde990e733664248b1a00391565af6841', blockHash: '000001f9c5de4d2b258a975bfbf7b9a3346890af6389512bea3cb6926b9be330', isChainLocked: true, @@ -172,7 +172,7 @@ describe('Account - getTransactionHistory', () => { } ], type: 'account_transfer', - time: 1629236158, + time: new Date(1629236158*1e3), txId: '6f76ca8038c6cb1b373bbbf80698afdc0d638e4a223be12a4feb5fd8e1801135', blockHash: '000000444b3f2f02085f8befe72da5442c865c290658766cf935e1a71a4f4ba7', isChainLocked: true, @@ -198,7 +198,7 @@ describe('Account - getTransactionHistory', () => { } ], type: 'account_transfer', - time: 1629234873, + time: new Date(1629234873*1e3), txId: '6f37b0d6284aab627c31c50e1c9d7cce39912dd4f2393f91734f794bc6408533', blockHash: '000000dffb05c071a8c05082a475b7ce9c1e403f3b89895a6c448fe08535a5f5', isChainLocked: true, @@ -229,7 +229,7 @@ describe('Account - getTransactionHistory', () => { } ], type: 'address_transfer', - time: 1629234474, + time: new Date(1629234474*1e3), txId: 'c3fb3620ebd1c7678879b40df1495cc86a179b5a6f9e48ce0b687a5c6f5a1db5', blockHash: '000001953ea0bbb8ad04a9a1a2a707fef207ad22a712d7d3c619f0f9b63fa98c', isChainLocked: true, @@ -274,7 +274,7 @@ describe('Account - getTransactionHistory', () => { } ], type: 'address_transfer', - time: 1629216608, + time: new Date(1629216608*1e3), txId: 'f230a9414bf577d93d6f7f2515d9b549ede78cfba4168920892970fa8aa1eef8', blockHash: '00000084b4d9e887a6ad3f37c576a17d79c35ec9301e55210eded519e8cdcd3a', isChainLocked: true, @@ -300,7 +300,7 @@ describe('Account - getTransactionHistory', () => { } ], type: 'received', - time: 1629207543, + time: new Date(1629207543*1e3), txId: '1cbb35edc105918b956838570f122d6f3a1fba2b67467e643e901d09f5f8ac1b', blockHash: '00000c1e4556add15119392ed36ec6af2640569409abfa23a9972bc3be1b3717', isChainLocked: true, @@ -329,7 +329,7 @@ describe('Account - getTransactionHistory', () => { addressType: "external" } ], - time: 1629126597, + time: new Date(1629126597*1e3), txId: "eb1a7fc8e3b43d3021653b1176f8f9b41e9667d05b65ee225d14c149a5b14f77", blockHash: "00000221952c2a60adcb929de837f659308cb5c6bb7783016479381fb550fbad", type: "received", @@ -355,7 +355,7 @@ describe('Account - getTransactionHistory', () => { addressType: "unknown" } ], - time: 1628846998, + time: new Date(1628846998*1e3), txId: "7d1b78157f9f2238669f260d95af03aeefc99577ff0cddb91b3e518ee557a2fd", blockHash: "0000012cf6377c6cf2b317a4deed46573c09f04f6880dca731cc9ccea6691e19", type: "received", @@ -382,7 +382,7 @@ describe('Account - getTransactionHistory', () => { } ], type: 'received', - time: 1628846768, + time: new Date(1628846768*1e3), txId: 'd37b6c7dd449d605bea9997af8bbeed2f3fbbcb23a4068b1f1ad694db801912d', blockHash: '000000b6006c758eda23ec7e2a640a0bf2c6a0c44827be216faff6bf4fd388e8', isChainLocked: true, @@ -434,7 +434,7 @@ describe('Account - getTransactionHistory', () => { } ], type: 'received', - time: 1628846677, + time: new Date(1628846677*1e3), txId: 'a43845e580ad01f31bc06ce47ab39674e40316c4c6b765b6e54d6d35777ef456', blockHash: '000001deee9f99e8219a9abcaaea135dbaae8a9b0f1ea214e6b6a37a5c5b115d', isChainLocked: true, @@ -482,7 +482,7 @@ describe('Account - getTransactionHistory', () => { }, ], type: 'address_transfer', - time: 9999999999, + time: new Date(9999999999*1e3), txId: '9cd3d44a87a7f99a33aebc6957105d5fb41698ef642189a36bac59ec0b5cd840', blockHash: '0000016fb685b4b1efed743d2263de34a9f8323ed75e732654b1b951c5cb4dde', isChainLocked: true, @@ -505,7 +505,7 @@ describe('Account - getTransactionHistory', () => { } ], type: 'account_transfer', - time: 1629236158, + time: new Date(1629236158*1e3), txId: '6f76ca8038c6cb1b373bbbf80698afdc0d638e4a223be12a4feb5fd8e1801135', blockHash: '000000444b3f2f02085f8befe72da5442c865c290658766cf935e1a71a4f4ba7', isChainLocked: true, @@ -528,7 +528,7 @@ describe('Account - getTransactionHistory', () => { } ], type: 'account_transfer', - time: 1629234873, + time: new Date(1629234873*1e3), txId: '6f37b0d6284aab627c31c50e1c9d7cce39912dd4f2393f91734f794bc6408533', blockHash: '000000dffb05c071a8c05082a475b7ce9c1e403f3b89895a6c448fe08535a5f5', isChainLocked: true, @@ -575,7 +575,7 @@ describe('Account - getTransactionHistory', () => { } ], type: 'sent', - time: 1629510092, + time: new Date(1629510092*1e3), txId: '47d13f7f713f4258953292c2298c1d91e2d6dee309d689f3c8b44ccf457bab52', blockHash: '0000007b7356e715b43ed7d5b7135fb9a2bf403e079bbcf7faec0f0da5c40117', isChainLocked: true, @@ -601,7 +601,7 @@ describe('Account - getTransactionHistory', () => { } ], type: 'address_transfer', - time: 1629509216, + time: new Date(1629509216*1e3), txId: 'd48f415f08fb795d43b216cf56e9ef10e059d4009cfc8fc90edfc0d3850813af', blockHash: '0000018b88fe43d07c3d63050aa82271698dc406dd08388529205dd837bf92dc', isChainLocked: true, @@ -633,7 +633,7 @@ describe('Account - getTransactionHistory', () => { } ], type: 'received', - time: 1629503698, + time: new Date(1629503698*1e3), txId: '0dcdaa9bf5b3596be1bcf22113e39026fd49d24b47190e2c7423be936cb116a7', blockHash: '000000299efeefa87dc15474fd0423c136798975b779a2bb8aa5bb2f50509afb', isChainLocked: true, diff --git a/packages/wallet-lib/src/types/types.d.ts b/packages/wallet-lib/src/types/types.d.ts index 103a3e8facb..950c5491b3e 100644 --- a/packages/wallet-lib/src/types/types.d.ts +++ b/packages/wallet-lib/src/types/types.d.ts @@ -47,7 +47,7 @@ export declare type TransactionHistory = T & { satoshis: number }], type: TransactionHistoryType - time: number, + time: Date, txId: string, blockHash: string, isChainLocked: boolean, From 00b52db8fbbcd7bc280bfec3b7b8630a8b4c2cd3 Mon Sep 17 00:00:00 2001 From: Igor Markin Date: Mon, 18 Apr 2022 18:28:45 +0300 Subject: [PATCH 6/8] fix(wallet-lib): persistent storage regression (#302) Co-authored-by: Mikhail Pshenichnikov Co-authored-by: Ivan Shumkov --- .pnp.cjs | 58 +- ...ul-fs-npm-4.2.10-79c70989ca-3f109d70ae.zip | Bin 0 -> 11616 bytes ...h.find-npm-4.6.0-dd2db8c53f-b737f849a4.zip | Bin 0 -> 16782 bytes ...forage-npm-1.1.1-88cd0e2f0f-0cb4e4aa85.zip | Bin 0 -> 11833 bytes ...ckfile-npm-3.2.0-4c500143f0-1be1bb702b.zip | Bin 0 -> 9412 bytes ...locket-npm-1.0.5-6a604ece59-4ea3cba56c.zip | Bin 0 -> 4262 bytes packages/platform-test-suite/.env.example | 3 + .../lib/test/createClientWithFundedWallet.js | 17 +- .../lib/test/createFaucetClient.js | 19 +- packages/platform-test-suite/package.json | 2 + .../core/broadcastTransaction.spec.js | 18 +- .../functional/core/getTransaction.spec.js | 22 +- .../chains/for_wallet_c922713eac.json | 1087 ----------------- .../2a331817b9d6bf85100ef0/chain-store.json | 91 +- .../getFixtureAccountWithStorage.js | 27 +- .../2a331817b9d6bf85100ef0/wallet-store.json | 13 +- .../apart-trip-dignity/chain-store.json | 934 ++------------ .../getFixtureAccountWithStorage.js | 86 +- .../apart-trip-dignity/wallet-store.json | 114 +- .../fixtures/wallets/c922713eac.json | 97 -- packages/wallet-lib/package.json | 2 +- .../src/plugins/Plugins/ChainPlugin.js | 17 +- .../TransactionSyncStreamWorker.js | 22 +- .../methods/getAddressesToSync.js | 2 + .../methods/getLastSyncedBlockHeight.js | 7 +- .../methods/processChunks.js | 14 +- .../methods/setLastSyncedBlockHeight.js | 19 +- .../methods/startHistoricalSync.js | 13 +- .../methods/startIncomingSync.js | 16 +- packages/wallet-lib/src/test/bootstrap.js | 4 + .../src/test/mocks/TransportMock.js | 5 +- .../wallet-lib/src/test/mocks/TxStreamMock.js | 11 + .../test/mocks/createTransactionInAccount.js | 5 +- .../src/test/mocks/mockAccountWithStorage.js | 32 - packages/wallet-lib/src/test/utils.js | 14 + .../wallet-lib/src/types/Account/Account.js | 6 +- .../src/types/Account/_initializeAccount.js | 19 +- .../src/types/Account/_preparePlugins.js | 18 +- .../types/Account/methods/addDefaultPaths.js | 13 + .../types/Account/methods/addPathsToStore.js | 20 + .../methods/createPathsForTransactions.js | 38 + .../types/Account/methods/generateNewPaths.js | 21 + .../methods/getConfirmedBalance.spec.js | 12 +- .../Account/methods/getTotalBalance.spec.js | 12 +- .../Account/methods/getTransaction.spec.js | 100 +- .../methods/getTransactionHistory.spec.js | 77 +- .../src/types/Account/methods/getUTXOS.js | 2 +- .../types/Account/methods/getUTXOS.spec.js | 46 +- .../methods/getUnconfirmedBalance.spec.js | 12 +- .../Account/methods/getUnusedAddress.spec.js | 4 +- .../Account/methods/importTransactions.js | 41 +- .../src/types/ChainStore/ChainStore.js | 30 + .../src/types/ChainStore/ChainStore.spec.js | 116 +- .../types/ChainStore/methods/exportState.js | 45 +- .../types/ChainStore/methods/importAddress.js | 32 +- .../types/ChainStore/methods/importState.js | 47 +- .../ChainStore/methods/importTransaction.js | 7 +- .../DerivableKeyChain/methods/getForPath.js | 2 +- .../wallet-lib/src/types/Storage/Storage.js | 7 + .../src/types/Storage/methods/configure.js | 22 + .../types/Storage/methods/rehydrateState.js | 46 +- .../src/types/Storage/methods/saveState.js | 27 +- .../src/types/Wallet/ChainSyncMediator.js | 43 + .../wallet-lib/src/types/Wallet/Wallet.js | 10 +- .../src/types/Wallet/Wallet.spec.js | 24 +- .../src/types/Wallet/methods/createAccount.js | 5 + .../src/types/Wallet/methods/dumpStorage.js | 26 +- .../src/types/Wallet/methods/getAccount.js | 5 + .../types/Wallet/methods/getAccount.spec.js | 8 +- .../src/types/WalletStore/WalletStore.js | 23 + .../src/types/WalletStore/WalletStore.spec.js | 2 + .../types/WalletStore/methods/exportState.js | 20 +- .../types/WalletStore/methods/importState.js | 12 +- .../src/utils/castStorageItemsTypes.js | 54 + .../src/utils/castStorageItemsTypes.spec.js | 90 ++ .../src/utils/classifyAddresses.spec.js | 7 +- .../wallet-lib/src/utils/sortTransactions.js | 49 + .../TransactionSyncStreamWorker.spec.js | 6 +- .../tests/integration/types/Wallet.spec.js | 331 +++++ scripts/configure_test_suite.sh | 2 + yarn.lock | 61 +- 81 files changed, 1664 insertions(+), 2707 deletions(-) create mode 100644 .yarn/cache/graceful-fs-npm-4.2.10-79c70989ca-3f109d70ae.zip create mode 100644 .yarn/cache/lodash.find-npm-4.6.0-dd2db8c53f-b737f849a4.zip create mode 100644 .yarn/cache/nodeforage-npm-1.1.1-88cd0e2f0f-0cb4e4aa85.zip create mode 100644 .yarn/cache/proper-lockfile-npm-3.2.0-4c500143f0-1be1bb702b.zip create mode 100644 .yarn/cache/slocket-npm-1.0.5-6a604ece59-4ea3cba56c.zip delete mode 100644 packages/wallet-lib/fixtures/chains/for_wallet_c922713eac.json delete mode 100644 packages/wallet-lib/fixtures/wallets/c922713eac.json delete mode 100644 packages/wallet-lib/src/test/mocks/mockAccountWithStorage.js create mode 100644 packages/wallet-lib/src/test/utils.js create mode 100644 packages/wallet-lib/src/types/Account/methods/addDefaultPaths.js create mode 100644 packages/wallet-lib/src/types/Account/methods/addPathsToStore.js create mode 100644 packages/wallet-lib/src/types/Account/methods/createPathsForTransactions.js create mode 100644 packages/wallet-lib/src/types/Account/methods/generateNewPaths.js create mode 100644 packages/wallet-lib/src/types/Wallet/ChainSyncMediator.js create mode 100644 packages/wallet-lib/src/utils/castStorageItemsTypes.js create mode 100644 packages/wallet-lib/src/utils/castStorageItemsTypes.spec.js create mode 100644 packages/wallet-lib/src/utils/sortTransactions.js create mode 100644 packages/wallet-lib/tests/integration/types/Wallet.spec.js diff --git a/.pnp.cjs b/.pnp.cjs index 4572f4413a1..a27df4121e8 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -2758,8 +2758,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["karma-mocha-reporter", "virtual:58fb68f2aed20e5e0f2e48520ab903ae9bb3440369bfd5e912034003cf27c5aae368649fc5620dd2acbed578131f3a0975e75b838d77d12335fb0412e24026c6#npm:2.2.5"], ["karma-sourcemap-loader", "npm:0.3.8"], ["karma-webpack", "virtual:01938c2be4835443e5a304e2b117c575220e96e8b7cedeb0f48d79264590b4c4babc6d1fea6367f522b1ca0149d795b42f2ab89c34a6ffe3c20f0a8cbb8b4453#npm:5.0.0"], + ["localforage", "npm:1.10.0"], ["mocha", "npm:9.1.3"], ["net", "npm:1.0.2"], + ["nodeforage", "npm:1.1.1"], ["os-browserify", "npm:0.3.0"], ["path-browserify", "npm:1.0.1"], ["process", "npm:0.11.10"], @@ -2853,7 +2855,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["karma-mocha-reporter", "virtual:58fb68f2aed20e5e0f2e48520ab903ae9bb3440369bfd5e912034003cf27c5aae368649fc5620dd2acbed578131f3a0975e75b838d77d12335fb0412e24026c6#npm:2.2.5"], ["karma-sourcemap-loader", "npm:0.3.8"], ["karma-webpack", "virtual:45f214395bc38640da4dc5e940482d5df0572c5384e0262802601d1973e71077ec8bbd76b77eafa4c0550b706b664abd84d63fd67a5897139f0b2675530fc84f#npm:5.0.0"], - ["localforage", "npm:1.10.0"], ["lodash", "npm:4.17.21"], ["mocha", "npm:9.1.3"], ["node-inspect-extracted", "npm:1.0.8"], @@ -2864,6 +2865,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["process", "npm:0.11.10"], ["setimmediate", "npm:1.0.5"], ["sinon", "npm:11.1.2"], + ["sinon-chai", "virtual:595d7482cc8ddf98ee6aef33fc48b46393554ab5f17f851ef62e6e39315e53666c3e66226b978689aa0bc7f1e83a03081511a21db1c381362fe67614887077f9#npm:3.7.0"], ["stream-browserify", "npm:3.0.0"], ["stream-http", "npm:3.2.0"], ["string_decoder", "npm:1.3.0"], @@ -10046,6 +10048,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }] ]], ["graceful-fs", [ + ["npm:4.2.10", { + "packageLocation": "./.yarn/cache/graceful-fs-npm-4.2.10-79c70989ca-3f109d70ae.zip/node_modules/graceful-fs/", + "packageDependencies": [ + ["graceful-fs", "npm:4.2.10"] + ], + "linkType": "HARD", + }], ["npm:4.2.9", { "packageLocation": "./.yarn/cache/graceful-fs-npm-4.2.9-ee48e00aaa-68ea4e07ff.zip/node_modules/graceful-fs/", "packageDependencies": [ @@ -12391,6 +12400,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["lodash.find", [ + ["npm:4.6.0", { + "packageLocation": "./.yarn/cache/lodash.find-npm-4.6.0-dd2db8c53f-b737f849a4.zip/node_modules/lodash.find/", + "packageDependencies": [ + ["lodash.find", "npm:4.6.0"] + ], + "linkType": "HARD", + }] + ]], ["lodash.flattendeep", [ ["npm:4.4.0", { "packageLocation": "./.yarn/cache/lodash.flattendeep-npm-4.4.0-26b2b4cbd7-8521c919ac.zip/node_modules/lodash.flattendeep/", @@ -13562,6 +13580,20 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["nodeforage", [ + ["npm:1.1.1", { + "packageLocation": "./.yarn/cache/nodeforage-npm-1.1.1-88cd0e2f0f-0cb4e4aa85.zip/node_modules/nodeforage/", + "packageDependencies": [ + ["nodeforage", "npm:1.1.1"], + ["lodash.find", "npm:4.6.0"], + ["lodash.ismatch", "npm:4.4.0"], + ["lodash.merge", "npm:4.6.2"], + ["proper-lockfile", "npm:3.2.0"], + ["slocket", "npm:1.0.5"] + ], + "linkType": "HARD", + }] + ]], ["nodemon", [ ["npm:2.0.15", { "packageLocation": "./.yarn/unplugged/nodemon-npm-2.0.15-5e88e7aef5/node_modules/nodemon/", @@ -14833,6 +14865,18 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["proper-lockfile", [ + ["npm:3.2.0", { + "packageLocation": "./.yarn/cache/proper-lockfile-npm-3.2.0-4c500143f0-1be1bb702b.zip/node_modules/proper-lockfile/", + "packageDependencies": [ + ["proper-lockfile", "npm:3.2.0"], + ["graceful-fs", "npm:4.2.10"], + ["retry", "npm:0.12.0"], + ["signal-exit", "npm:3.0.7"] + ], + "linkType": "HARD", + }] + ]], ["protobufjs", [ ["https://github.com/jawid-h/protobuf.js.git#commit=d13d5d5688052e366aa2e9169f50dfca376b32cf", { "packageLocation": "./.yarn/unplugged/protobufjs-https-f7cc81dafb/node_modules/protobufjs/", @@ -16196,6 +16240,18 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["slocket", [ + ["npm:1.0.5", { + "packageLocation": "./.yarn/cache/slocket-npm-1.0.5-6a604ece59-4ea3cba56c.zip/node_modules/slocket/", + "packageDependencies": [ + ["slocket", "npm:1.0.5"], + ["bluebird", "npm:3.7.2"], + ["rimraf", "npm:2.7.1"], + ["signal-exit", "npm:3.0.7"] + ], + "linkType": "HARD", + }] + ]], ["smart-buffer", [ ["npm:4.2.0", { "packageLocation": "./.yarn/cache/smart-buffer-npm-4.2.0-5ac3f668bb-b5167a7142.zip/node_modules/smart-buffer/", diff --git a/.yarn/cache/graceful-fs-npm-4.2.10-79c70989ca-3f109d70ae.zip b/.yarn/cache/graceful-fs-npm-4.2.10-79c70989ca-3f109d70ae.zip new file mode 100644 index 0000000000000000000000000000000000000000..2d04255c12ebb5f24b91d910cefb76bca587c3f5 GIT binary patch literal 11616 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 UVE>9i`28CA{hvnsHLL{uKj@bysQ>@~ literal 0 HcmV?d00001 diff --git a/.yarn/cache/lodash.find-npm-4.6.0-dd2db8c53f-b737f849a4.zip b/.yarn/cache/lodash.find-npm-4.6.0-dd2db8c53f-b737f849a4.zip new file mode 100644 index 0000000000000000000000000000000000000000..6b4e3f7c9aeb639a7d3efcf86917eead7668fc69 GIT binary patch literal 16782 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 literal 0 HcmV?d00001 diff --git a/.yarn/cache/slocket-npm-1.0.5-6a604ece59-4ea3cba56c.zip b/.yarn/cache/slocket-npm-1.0.5-6a604ece59-4ea3cba56c.zip new file mode 100644 index 0000000000000000000000000000000000000000..fa991bc4a2879cb8c832c15701466bae4cb2316f GIT binary patch literal 4262 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 */ -async function createClientWithFundedWallet(HDPrivateKey = undefined) { +async function createClientWithFundedWallet(HDPrivateKey = undefined, amount = 40000) { + const useFaucetWalletStorage = process.env.FAUCET_WALLET_USE_STORAGE === "true" const seeds = getDAPISeeds(); const clientOpts = { @@ -29,7 +32,9 @@ async function createClientWithFundedWallet(HDPrivateKey = undefined) { }, }; - const faucetClient = createFaucetClient(); + if (!faucetClient || (faucetClient && useFaucetWalletStorage)) { + faucetClient = createFaucetClient(); + } const walletOptions = { waitForInstantLockTimeout: 120000, @@ -52,10 +57,12 @@ async function createClientWithFundedWallet(HDPrivateKey = undefined) { wallet: walletOptions, }); - const amount = 40000; - await fundWallet(faucetClient.wallet, client.wallet, amount); + if (useFaucetWalletStorage) { + await faucetClient.wallet.disconnect(); + } + return client; } diff --git a/packages/platform-test-suite/lib/test/createFaucetClient.js b/packages/platform-test-suite/lib/test/createFaucetClient.js index dfbac31d17a..b15c59ab143 100644 --- a/packages/platform-test-suite/lib/test/createFaucetClient.js +++ b/packages/platform-test-suite/lib/test/createFaucetClient.js @@ -1,5 +1,16 @@ const Dash = require('dash'); +let storageAdapter; + +if (typeof window === 'undefined') { + // eslint-disable-next-line global-require + const { NodeForage } = require('nodeforage'); + storageAdapter = new NodeForage({ name: `faucet-wallet-${process.env.FAUCET_ADDRESS}` }); +} else { + // eslint-disable-next-line global-require + storageAdapter = require('localforage'); +} + const { contractId } = require('@dashevo/dpns-contract/lib/systemIds'); const getDAPISeeds = require('./getDAPISeeds'); @@ -7,10 +18,6 @@ const getDAPISeeds = require('./getDAPISeeds'); let faucetClient; function createFaucetClient() { - if (faucetClient) { - return faucetClient; - } - const seeds = getDAPISeeds(); const clientOpts = { @@ -27,6 +34,10 @@ function createFaucetClient() { privateKey: process.env.FAUCET_PRIVATE_KEY, }; + if (process.env.FAUCET_WALLET_USE_STORAGE === "true") { + walletOptions.adapter = storageAdapter; + } + if (process.env.SKIP_SYNC_BEFORE_HEIGHT) { walletOptions.unsafeOptions = { skipSynchronizationBeforeHeight: process.env.SKIP_SYNC_BEFORE_HEIGHT, diff --git a/packages/platform-test-suite/package.json b/packages/platform-test-suite/package.json index 57791fb5a1c..4c3e0702e40 100644 --- a/packages/platform-test-suite/package.json +++ b/packages/platform-test-suite/package.json @@ -53,8 +53,10 @@ "karma-mocha-reporter": "^2.2.5", "karma-sourcemap-loader": "^0.3.7", "karma-webpack": "^5.0.0", + "localforage": "^1.10.0", "mocha": "^9.1.2", "net": "^1.0.2", + "nodeforage": "^1.1.1", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "process": "^0.11.10", diff --git a/packages/platform-test-suite/test/functional/core/broadcastTransaction.spec.js b/packages/platform-test-suite/test/functional/core/broadcastTransaction.spec.js index 88dc9d8f1f9..a4a4833c0f2 100644 --- a/packages/platform-test-suite/test/functional/core/broadcastTransaction.spec.js +++ b/packages/platform-test-suite/test/functional/core/broadcastTransaction.spec.js @@ -2,25 +2,29 @@ const { PrivateKey, } = require('@dashevo/dashcore-lib'); -const createFaucetClient = require('../../../lib/test/createFaucetClient'); +const createClientWithFundedWallet = require('../../../lib/test/createClientWithFundedWallet'); describe('Core', () => { describe('broadcastTransaction', () => { - let faucetClient; + let client; - before(() => { - faucetClient = createFaucetClient(); + before(async () => { + client = await createClientWithFundedWallet(); + }); + + after(async () => { + await client.disconnect(); }); it('should sent transaction and return transaction ID', async () => { - const faucetWalletAccount = await faucetClient.getWalletAccount(); + const account = await client.getWalletAccount(); - const transaction = faucetWalletAccount.createTransaction({ + const transaction = account.createTransaction({ recipient: new PrivateKey().toAddress(process.env.NETWORK), satoshis: 10000, }); - const dapiClient = faucetClient.getDAPIClient(); + const dapiClient = client.getDAPIClient(); const transactionId = await dapiClient.core.broadcastTransaction(transaction.toBuffer()); diff --git a/packages/platform-test-suite/test/functional/core/getTransaction.spec.js b/packages/platform-test-suite/test/functional/core/getTransaction.spec.js index 40ff0a1a820..db7fea2ac08 100644 --- a/packages/platform-test-suite/test/functional/core/getTransaction.spec.js +++ b/packages/platform-test-suite/test/functional/core/getTransaction.spec.js @@ -5,32 +5,36 @@ const { const NotFoundError = require('@dashevo/dapi-client/lib/transport/GrpcTransport/errors/NotFoundError'); -const createFaucetClient = require('../../../lib/test/createFaucetClient'); const wait = require('../../../lib/wait'); +const createClientWithFundedWallet = require('../../../lib/test/createClientWithFundedWallet'); describe('Core', () => { describe('getTransaction', () => { - let faucetClient; + let client; - before(() => { - faucetClient = createFaucetClient(); + before(async () => { + client = await createClientWithFundedWallet(); + }); + + after(async () => { + await client.disconnect(); }); it('should respond with a transaction by it\'s ID', async () => { - const faucetWalletAccount = await faucetClient.getWalletAccount(); + const account = await client.getWalletAccount(); await wait(5000); - const transaction = faucetWalletAccount.createTransaction({ + const transaction = account.createTransaction({ recipient: new PrivateKey().toAddress(process.env.NETWORK), satoshis: 10000, }); - await faucetWalletAccount.broadcastTransaction(transaction); + await account.broadcastTransaction(transaction); await wait(5000); - const result = await faucetClient.getDAPIClient().core.getTransaction(transaction.id); + const result = await client.getDAPIClient().core.getTransaction(transaction.id); const receivedTx = new Transaction(result.getTransaction()); expect(receivedTx.hash).to.deep.equal(transaction.id); @@ -40,7 +44,7 @@ describe('Core', () => { const nonExistentId = Buffer.alloc(32).toString('hex'); try { - await faucetClient.getDAPIClient().core.getTransaction(nonExistentId); + await client.getDAPIClient().core.getTransaction(nonExistentId); expect.fail('should throw NotFound'); } catch (e) { diff --git a/packages/wallet-lib/fixtures/chains/for_wallet_c922713eac.json b/packages/wallet-lib/fixtures/chains/for_wallet_c922713eac.json deleted file mode 100644 index fba5af71c98..00000000000 --- a/packages/wallet-lib/fixtures/chains/for_wallet_c922713eac.json +++ /dev/null @@ -1,1087 +0,0 @@ -{ - "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/packages/wallet-lib/fixtures/wallets/2a331817b9d6bf85100ef0/chain-store.json b/packages/wallet-lib/fixtures/wallets/2a331817b9d6bf85100ef0/chain-store.json index 9c9dc1cf05f..84fd8599a97 100644 --- a/packages/wallet-lib/fixtures/wallets/2a331817b9d6bf85100ef0/chain-store.json +++ b/packages/wallet-lib/fixtures/wallets/2a331817b9d6bf85100ef0/chain-store.json @@ -1,67 +1,36 @@ { - "network": "testnet", - "state": { - "fees": { - "minRelay": 1000 + "blockHeaders": { + "0000012464fba1e3c66e678de79e4003bf17c36d5caa689e80fd4711fe620ec1": "00000020dc238f49a4671c66a42eb8312996f69801c4867b50658bf1588e4c402e00000088249fb4facf62de5b7706fb41d25cf7ae6404e387b25bce43d5ca69c621a5a05c2acc6108ca011e20420000", + "000000299efeefa87dc15474fd0423c136798975b779a2bb8aa5bb2f50509afb": "0000002016d86d54b0fb10d74d56687f14f1ff451d1e8e1fd9078afee6c3847eec000000a4c3c2e1b7b03b06ab32eb36b59bbe1ef9bba5b6ba6c18cab1ec64f38b49b7aad2402061bcfb011e7c3a0000", + "0000018b88fe43d07c3d63050aa82271698dc406dd08388529205dd837bf92dc": "00000020a78bb103f356ad73a2da83cfa9138054d170eb86259c6d01ebc7cfa15a01000051066136f040a39786459a111d453331ced2997354230d23d0c171df7c154bb460562061cee0011e07810000", + "0000007b7356e715b43ed7d5b7135fb9a2bf403e079bbcf7faec0f0da5c40117": "0000002011ec1243498c7b866199ea055fc7c899e2efe11585fd8c8099e343331801000025b944e894131cde8e07aaca3a750d7ec707f8ae05b74198391e972f5a8625cbcc59206123e4011eebcb0000" + }, + "transactions": { + "0dcdaa9bf5b3596be1bcf22113e39026fd49d24b47190e2c7423be936cb116a7": "0200000002e56082c04785e77de2dbbfded439dbfc0dde2201fd32e4a32aa425f5769c9e33010000006a4730440220737fb319650cd5c5ff17a1ef0667ec4a2c9d337c25463b8eaa5018ec4ff41cf202201a3f82c7790e0fb07ceddbee7c2ffa1e6de999f5b42b9d3eb64dab2697625a830121038f7857ad3a707c2cb4fa5d7dc270b6e04050dff58613c3a0198a463923c89512fefffffff9c814dd01da4b5b59e06cd1dc3ad8bbfb4af9ddb07d4a3ec4748e91b58d0478000000006a473044022054b284413497f3d8a5909916c298a6585a27e0f20039dbe393634869896293d7022079d252b13434e9bb2bb347f7cb45750500439b652ab28e85573acc5e8c33a60c0121032996947342cd793585d9adc2dc007b89135c764c0c9d528723ad7031fdf2c5aafeffffff029adb4900000000001976a9149eaf940724ad809abdc1f8e32ad7fbd221c742af88ac1059492a000000001976a914ae6994ac03281f1137b91a7e0f050dece040455388ac078c0800", + "d48f415f08fb795d43b216cf56e9ef10e059d4009cfc8fc90edfc0d3850813af": "0300000001a716b16c93be23742c0e19474bd249fd2690e31321f2bce16b59b3f59baacd0d010000006a47304402202c72746fafa9db3ef4ce3b16f2a05bccf2f72b5b2fb18208a10ac563b0e1edc602203371d81765942104bb05acacfe141f204223a1fa426052b55b780e804eb563780121021feafc962128b08d7ef46ce6e4507193d53d3d101d7ad7e6d889fea9b1d4faaaffffffff0210329000000000001976a914ae6994ac03281f1137b91a7e0f050dece040455388ac0926b929000000001976a914ae6994ac03281f1137b91a7e0f050dece040455388ac00000000", + "47d13f7f713f4258953292c2298c1d91e2d6dee309d689f3c8b44ccf457bab52": "0300000001af130885d3c0df0ec98ffc9c00d459e010efe956cf16b2435d79fb085f418fd4000000006b4830450221008d7a45a192df8e1b7d39e1465ddbed40f34793ecfc4c3bbc9c9116fd6d91e3cb0220304abf8405aee39ed7e66fae73f713db5868068990ea48b3865c1bba0ed569fe0121021feafc962128b08d7ef46ce6e4507193d53d3d101d7ad7e6d889fea9b1d4faaaffffffff02d0dd0600000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac49538900000000001976a914ae6994ac03281f1137b91a7e0f050dece040455388ac00000000" + }, + "txMetadata": { + "0dcdaa9bf5b3596be1bcf22113e39026fd49d24b47190e2c7423be936cb116a7": { + "blockHash": "000000299efeefa87dc15474fd0423c136798975b779a2bb8aa5bb2f50509afb", + "height": 560136, + "isInstantLocked": true, + "isChainLocked": true }, - "blockHeight": 640507, - "blockHeaders": { - "0000012464fba1e3c66e678de79e4003bf17c36d5caa689e80fd4711fe620ec1": "00000020dc238f49a4671c66a42eb8312996f69801c4867b50658bf1588e4c402e00000088249fb4facf62de5b7706fb41d25cf7ae6404e387b25bce43d5ca69c621a5a05c2acc6108ca011e20420000", - "000000299efeefa87dc15474fd0423c136798975b779a2bb8aa5bb2f50509afb": "0000002016d86d54b0fb10d74d56687f14f1ff451d1e8e1fd9078afee6c3847eec000000a4c3c2e1b7b03b06ab32eb36b59bbe1ef9bba5b6ba6c18cab1ec64f38b49b7aad2402061bcfb011e7c3a0000", - "0000018b88fe43d07c3d63050aa82271698dc406dd08388529205dd837bf92dc": "00000020a78bb103f356ad73a2da83cfa9138054d170eb86259c6d01ebc7cfa15a01000051066136f040a39786459a111d453331ced2997354230d23d0c171df7c154bb460562061cee0011e07810000", - "0000007b7356e715b43ed7d5b7135fb9a2bf403e079bbcf7faec0f0da5c40117": "0000002011ec1243498c7b866199ea055fc7c899e2efe11585fd8c8099e343331801000025b944e894131cde8e07aaca3a750d7ec707f8ae05b74198391e972f5a8625cbcc59206123e4011eebcb0000" + "d48f415f08fb795d43b216cf56e9ef10e059d4009cfc8fc90edfc0d3850813af": { + "blockHash": "0000018b88fe43d07c3d63050aa82271698dc406dd08388529205dd837bf92dc", + "height": 560169, + "isInstantLocked": true, + "isChainLocked": true }, - "transactions": { - "0dcdaa9bf5b3596be1bcf22113e39026fd49d24b47190e2c7423be936cb116a7": { - "transaction": "0200000002e56082c04785e77de2dbbfded439dbfc0dde2201fd32e4a32aa425f5769c9e33010000006a4730440220737fb319650cd5c5ff17a1ef0667ec4a2c9d337c25463b8eaa5018ec4ff41cf202201a3f82c7790e0fb07ceddbee7c2ffa1e6de999f5b42b9d3eb64dab2697625a830121038f7857ad3a707c2cb4fa5d7dc270b6e04050dff58613c3a0198a463923c89512fefffffff9c814dd01da4b5b59e06cd1dc3ad8bbfb4af9ddb07d4a3ec4748e91b58d0478000000006a473044022054b284413497f3d8a5909916c298a6585a27e0f20039dbe393634869896293d7022079d252b13434e9bb2bb347f7cb45750500439b652ab28e85573acc5e8c33a60c0121032996947342cd793585d9adc2dc007b89135c764c0c9d528723ad7031fdf2c5aafeffffff029adb4900000000001976a9149eaf940724ad809abdc1f8e32ad7fbd221c742af88ac1059492a000000001976a914ae6994ac03281f1137b91a7e0f050dece040455388ac078c0800", - "metadata": { - "blockHash": "000000299efeefa87dc15474fd0423c136798975b779a2bb8aa5bb2f50509afb", - "height": 560136, - "isInstantLocked": true, - "isChainLocked": true - } - }, - "d48f415f08fb795d43b216cf56e9ef10e059d4009cfc8fc90edfc0d3850813af": { - "transaction": "0300000001a716b16c93be23742c0e19474bd249fd2690e31321f2bce16b59b3f59baacd0d010000006a47304402202c72746fafa9db3ef4ce3b16f2a05bccf2f72b5b2fb18208a10ac563b0e1edc602203371d81765942104bb05acacfe141f204223a1fa426052b55b780e804eb563780121021feafc962128b08d7ef46ce6e4507193d53d3d101d7ad7e6d889fea9b1d4faaaffffffff0210329000000000001976a914ae6994ac03281f1137b91a7e0f050dece040455388ac0926b929000000001976a914ae6994ac03281f1137b91a7e0f050dece040455388ac00000000", - "metadata": { - "blockHash": "0000018b88fe43d07c3d63050aa82271698dc406dd08388529205dd837bf92dc", - "height": 560169, - "isInstantLocked": true, - "isChainLocked": true - } - }, - "47d13f7f713f4258953292c2298c1d91e2d6dee309d689f3c8b44ccf457bab52": { - "transaction": "0300000001af130885d3c0df0ec98ffc9c00d459e010efe956cf16b2435d79fb085f418fd4000000006b4830450221008d7a45a192df8e1b7d39e1465ddbed40f34793ecfc4c3bbc9c9116fd6d91e3cb0220304abf8405aee39ed7e66fae73f713db5868068990ea48b3865c1bba0ed569fe0121021feafc962128b08d7ef46ce6e4507193d53d3d101d7ad7e6d889fea9b1d4faaaffffffff02d0dd0600000000001976a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac49538900000000001976a914ae6994ac03281f1137b91a7e0f050dece040455388ac00000000", - "metadata": { - "blockHash": "0000007b7356e715b43ed7d5b7135fb9a2bf403e079bbcf7faec0f0da5c40117", - "height": 560179, - "isInstantLocked": true, - "isChainLocked": true - } - } - }, - "instantLocks": {}, - "addresses": { - "ycDeuTfs4U77bTb5cq17dame28zdWHVYfk": { - "address": "ycDeuTfs4U77bTb5cq17dame28zdWHVYfk", - "transactions": [ - "0dcdaa9bf5b3596be1bcf22113e39026fd49d24b47190e2c7423be936cb116a7", - "d48f415f08fb795d43b216cf56e9ef10e059d4009cfc8fc90edfc0d3850813af", - "47d13f7f713f4258953292c2298c1d91e2d6dee309d689f3c8b44ccf457bab52" - ], - "utxos": { - "d48f415f08fb795d43b216cf56e9ef10e059d4009cfc8fc90edfc0d3850813af-1": { - "satoshis": 699999753, - "script": "76a914ae6994ac03281f1137b91a7e0f050dece040455388ac" - }, - "47d13f7f713f4258953292c2298c1d91e2d6dee309d689f3c8b44ccf457bab52-1": { - "satoshis": 8999753, - "script": "76a914ae6994ac03281f1137b91a7e0f050dece040455388ac" - } - }, - "balanceSat": 708999506, - "unconfirmedBalanceSat": 0 - } + "47d13f7f713f4258953292c2298c1d91e2d6dee309d689f3c8b44ccf457bab52": { + "blockHash": "0000007b7356e715b43ed7d5b7135fb9a2bf403e079bbcf7faec0f0da5c40117", + "height": 560179, + "isInstantLocked": true, + "isChainLocked": true } + }, + "fees": { + "minRelay": -1 } } diff --git a/packages/wallet-lib/fixtures/wallets/2a331817b9d6bf85100ef0/getFixtureAccountWithStorage.js b/packages/wallet-lib/fixtures/wallets/2a331817b9d6bf85100ef0/getFixtureAccountWithStorage.js index 79a622b07de..56ab9ae1c14 100644 --- a/packages/wallet-lib/fixtures/wallets/2a331817b9d6bf85100ef0/getFixtureAccountWithStorage.js +++ b/packages/wallet-lib/fixtures/wallets/2a331817b9d6bf85100ef0/getFixtureAccountWithStorage.js @@ -2,6 +2,10 @@ const walletStoreMock = require('./wallet-store.json'); const chainStoreMock = require('./chain-store.json'); const Storage = require('../../../src/types/Storage/Storage'); const { KeyChainStore, DerivableKeyChain } = require('../../../src/index'); +const createPathsForTransactions = require("../../../src/types/Account/methods/createPathsForTransactions"); +const addPathsToStore = require("../../../src/types/Account/methods/addPathsToStore"); +const generateNewPaths = require("../../../src/types/Account/methods/generateNewPaths"); +const addDefaultPaths = require("../../../src/types/Account/methods/addDefaultPaths"); module.exports = (opts = {}) => { const { walletId } = walletStoreMock; @@ -13,20 +17,33 @@ module.exports = (opts = {}) => { accountPath: 'm/0', network: 'testnet', walletType: 'privateKey', + createPathsForTransactions, + addPathsToStore, + generateNewPaths, + addDefaultPaths, ...opts, }; + mockedAccount.storage.createWalletStore(walletId); + mockedAccount.storage.createChainStore('testnet'); + + const walletStore = mockedAccount.storage.getWalletStore(walletId); + walletStore.importState(walletStoreMock); + walletStore.createPathState(mockedAccount.accountPath); + + mockedAccount.storage.getChainStore('testnet').importState(chainStoreMock); mockedAccount.keyChainStore = new KeyChainStore(); mockedAccount.keyChainStore.addKeyChain(new DerivableKeyChain({ - privateKey: '2a331817b9d6bf85100ef05503d16f9f57c8855dbf13766b2f26c382b716d396', + address: 'ycDeuTfs4U77bTb5cq17dame28zdWHVYfk', lookAheadOpts: { 'm/0': 1, }, }), { isMasterKeyChain: true }); - mockedAccount.storage.createWalletStore(walletId); - mockedAccount.storage.createChainStore('testnet'); - mockedAccount.storage.getWalletStore(walletId).importState(walletStoreMock); - mockedAccount.storage.getChainStore('testnet').importState(chainStoreMock); + + mockedAccount.keyChainStore + .getMasterKeyChain() + .getForPath('0', { isWatched: true }); + mockedAccount.addDefaultPaths() return mockedAccount; }; diff --git a/packages/wallet-lib/fixtures/wallets/2a331817b9d6bf85100ef0/wallet-store.json b/packages/wallet-lib/fixtures/wallets/2a331817b9d6bf85100ef0/wallet-store.json index 3c472465951..23eb12010a7 100644 --- a/packages/wallet-lib/fixtures/wallets/2a331817b9d6bf85100ef0/wallet-store.json +++ b/packages/wallet-lib/fixtures/wallets/2a331817b9d6bf85100ef0/wallet-store.json @@ -1,15 +1,6 @@ { "walletId": "6101b44d50", - "state": { - "mnemonic": null, - "paths": { - "m/0": { - "path": "m/0", - "addresses": { - "0": "ycDeuTfs4U77bTb5cq17dame28zdWHVYfk" - } - } - }, - "identities": {} + "lastKnownBlock": { + "height": 11703 } } diff --git a/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/chain-store.json b/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/chain-store.json index 3d70eac2f66..41fd6246621 100644 --- a/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/chain-store.json +++ b/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/chain-store.json @@ -1,848 +1,100 @@ { - "network": "testnet", - "state": { - "fees": { - "minRelay": 1000 + "blockHeaders": { + "0000022113844aa456c439bf1f7d720c5ee013cd945acad41b6522ddb12102ee": "00000020e621284508549461adaf248278d67709f3df2315a76be01175f7137872010000dd31aa7cb665279529260b154cac878c1af59e81d03209221bcec08a0889da3a7d1ccc616682021efd0d0a00", + "0000009d9bb006df83378ba3b22f6a17e3902658401668fed0b7d4971db91293": "00000020ee0221b1dd22651bd4ca5a94cd13e05e0c727d1fbf39c456a44a8413210200008c3ca551f529a105e132841a22d560d00002e9b2d1fed25bcd75fd8fa222bd44c21ccc61083e021e2cbe0900", + "000001deee9f99e8219a9abcaaea135dbaae8a9b0f1ea214e6b6a37a5c5b115d": "0000002084813d1a5019ed9079e8c7a0d579b685f16e2e0dcaae54d9144aa4a730010000adcba982cc7bea98a786106769a9341a4d3866834d58105be0630b890cba5882553a16613554021ef2800000", + "000000b6006c758eda23ec7e2a640a0bf2c6a0c44827be216faff6bf4fd388e8": "000000205d115b5c7aa3b6e614a21e0f9b8aaeba5d13eaaabc9a9a21e8999feede010000709f82baa9c1f24fec82712e0599152d7e4d077853cf7cba91861ec3eaea22eab03a1661113a021e01da0000", + "0000012cf6377c6cf2b317a4deed46573c09f04f6880dca731cc9ccea6691e19": "00000020e888d34fbff6af6f21be2748c4a0c6f20b0a642a7eec23da8e756c00b6000000766a990ddb0e1be5191eae3ae973e4558a20d9fcdcc5309145ac8edfab8f9966963b1661b82d021e450b0000", + "00000221952c2a60adcb929de837f659308cb5c6bb7783016479381fb550fbad": "0000002031f1bb8d975bf830b0ac702b534e2f66f0edd90fcd99770c8dd4794bd8020000bcaccb7f4cdf155fe1e08ac473530f92fbb4f51d62376f34ca1d882bd810b677c57f1a61e7f5021e53b30000", + "00000c1e4556add15119392ed36ec6af2640569409abfa23a9972bc3be1b3717": "00000020638feafd82aca4e6b2ac1f77876f880e06775d2e6f79a61725f5011c6e0100005ecf6c29dcff20133c5399ef08ea13299fec7788bc03dfb7e05b0695535aa738f7bb1b6174f50e1e95660000", + "00000084b4d9e887a6ad3f37c576a17d79c35ec9301e55210eded519e8cdcd3a": "00000020b0c8a51730dfa39cd12b1f80f55784fd167bf97392d7e8caf5f93053e20000009b43512fc810155df3d5f7d73e7031812845784a6b6ce68f1649dfd8db44d8f660df1b616c8a011e4aab0000", + "000001953ea0bbb8ad04a9a1a2a707fef207ad22a712d7d3c619f0f9b63fa98c": "000000207dca199148d151d71bc35127b31205090d8f242b8a05ad2adea07bc9fe000000b37f61034e6dedf382051227c736ced4b786eb0c6495e6c23526630e38bf6d732a251c61ee09021e6eeb0000", + "000000dffb05c071a8c05082a475b7ce9c1e403f3b89895a6c448fe08535a5f5": "000000208ca93fb6f9f019c6d3d712a722ad07f2fe07a7a2a1a904adb8bba03e95010000d1db1a1802320ab91ddb72525338b3a46059b0d3f3cbed01f550a72577db773cb9261c614d02021eb36b0000", + "000000444b3f2f02085f8befe72da5442c865c290658766cf935e1a71a4f4ba7": "0000002031b3986a9c0814a212264200e2a276c551e5df433c5e147276eab87b0c02000090f859396304db0dd94b9214222d0dd1dfd16d79e4bc610f68ffd18c377f5ef7be2b1c615321021e1d240000", + "000001f9c5de4d2b258a975bfbf7b9a3346890af6389512bea3cb6926b9be330": "00000020478e3710c6030a6d6e9c8588789f530b498db6f8e18b20e9ab23b48bd60100003ffa42f2dc18080e764a4cdd37fccf46533b74287ed7579200f5c937268ab9a9542f1c61803e021efbd80000" + }, + "transactions": { + "a43845e580ad01f31bc06ce47ab39674e40316c4c6b765b6e54d6d35777ef456": "0200000007fd1ef99bc1edb4ab93ba74309da788e4ac460975733f02936a6321620d2a8011000000006b483045022100dfb220a840d597179abdf49692ad64c1c0da785041975b00aee03c9625639cf202204d06eade5cca19fab1e10b1d6e1b67c77626a0e88bb4d5f61bd57293b4b64217012102295ecb812ccf52deaf304bebfe3a59a644f05bac81241ea1e3a2f8750064cbf6feffffff694226ee65ea29ba7ec3e5448464c16f11d5b7564f6b3b5d0425a4c751389519000000006b483045022100beff3263b7c99720e99af9ec146c818701efb0130603f1570f427b74aef8521802202e660bb9f7ea156f91addd5fe47cbd2c2bf388cc6e1eff3a39adffd89d26d346012102c33942799f7cbf4a7d12f1b3e52cb80cc4de083b997d3e63915df9973d5bce2afeffffff963e9964a8394a07dde1dbb1e8b34af0f194def0db77fa69229030fbade2c82d000000006b483045022100ff67776932e7a32520aa131f76bdfd6737650ad3b11edbdf466cca83f691b0e60220633bcbedebacffd53ceb7e9cdbd47928d7c2849f49ac1f8efb9f384c1a4ee46301210371c0bc42e08de059a8829730abb16f3d40cff87e5ad85d65c4a0a949d9c4b524feffffff1fe16550125c3398c25d97308d7ab89bdd3d27a1589c78e97c4823c92723cf40000000006b483045022100cca348c7ab16fac28b3bba502be54a9e3766b7da9821a90605f370b75840569702207d082510aa493988e09da046355b018781718208f8a954e14ea33d608ae59625012103699b9402e109ed9d0c67c6a45be5cf5f1236c44bb9fc4b07a2f3392ba0b64172feffffffce251fb3d87d7df03b0fbd720ce5425ab0ea86d96a4ec658463d9507928bb34b000000006a47304402203ae564ff74b08b1f96bf857f51448434418d747a02039ec1ee109a4f5d8e8106022072f8769bd175416d22f44011f7e67aec301f08573c9937be7e4a09c394c7396601210311bae874933a4503a61d1c8c2e5b57b1a278d28d4892af4bd79ab8a731495265feffffffaf2dfef80d4a1f77c75dfc74e3769d8526c66c467c859b16b3439ab213851eb2000000006a47304402200b49b7059064efb57df453dc2d20002f09b5266bc825760ef81624771f13920802200782616b8c4fb7b5eff94fdf865e6ddc4530d3932b97fdc3a747e8c451f0314c012103a94131f28f8efd67f47f2496ff6e8d9069a3a7df97202a33e90e16f257d03729feffffff344fef05224cca98a16f87515041df61cbca948518761021a286d1a76e2bfdd6000000006a47304402202a24d1123775641269c6f748d3e4dad08a682e4e334a9b73c7df84f6c22e8e7d022022c0cc2225d3f14cb58a6fb3e4bf23c0f33252d9040a9ca9ef66eb17742a476f01210347301de4c9ba7f46b0f27cb82ae70a73749821e2951d3c87c2f0d56648635d1cfeffffff0251d21b00000000001976a91440ca54360086cc0fbd69d862db58ab2b6d22805888ace058340b000000001976a914538da44e7136cc994023d89a7b4b3d02ac0e573988acf1790800", + "d37b6c7dd449d605bea9997af8bbeed2f3fbbcb23a4068b1f1ad694db801912d": "03000000014e74eb53f4b1fa08e1fa02788005b8ef9f70929b3f0668fcb9dc4093b7385af3010000006a473044022071fcd620db9f245fe91c44e9b298ee5a718c2be728958f9e707dd1f785fb05e002207b05d45e4ab78c83f9117032a109d6afa37883ca5b479d2b3110fb0d773d23df0121033883dff35aeac917a26d2cbfb59a365c7ff83256a49c4d6aed8f1c0684605c40ffffffff0280969800000000001976a914cb579d4aa777c3583f61f28425ae3fea5b60d39088ac2296bf1f000000001976a914500ddabedf00296b40842cea428951d57331d5e088ac00000000", + "7d1b78157f9f2238669f260d95af03aeefc99577ff0cddb91b3e518ee557a2fd": "03000000012d9101b84d69adf1b168403ab2bcfbf3d2eebbf87a99a9be05d649d47d6c7bd3010000006a47304402201cc3d6887d5161eba36a5e6fb1ccd8e8f9eeda7fe95b4fb0a1accb99eeba0223022040d0df81fde8f59c807e541ca5bcfc9d7450f76657aeb44c708fa7d65b7d58410121038cdae47fceb5b117cd3ef5bdf8c9f2a83679a9105d012095762067bdb2351ceaffffffff0280969800000000001976a914e00939d2ec2f885f5e7dc7b9f5b06dcf868d0c4b88acabfe261f000000001976a914f03286cbb7954ea6affa9654af6cfe1210dd0c6288ac00000000", + "eb1a7fc8e3b43d3021653b1176f8f9b41e9667d05b65ee225d14c149a5b14f77": "02000000024f5ccb10d1762b155b25a32c1242f72e506ead7dcdffebbb9a57c450eff24306000000006a473044022006d0f91fbc789475f4bc545901b069a5baf657b36914638b7847e61c6ce508a0022061cc6488a5e8e7d142acf015d9447ea2c37b2c44f7ce9822b2b3421e6f200111012103f7626b79771dc4e1928ffbb407aa32f7089bd8deebb7b7393d28397b504f3002feffffff07f289648f6f097dd46857c15d64576658b9f2c693a1472eb8beaa21b21ec020010000006a47304402207ac2b0c2ea3c073db24d893b533d1d5753f35a85c00971e8d0a1d7fdd77c1ffb0220024b43bdd62ee96189e3da40c5725d771c6cb8df0909826538f558fb32c46cf901210308b60306848cbb551d800192ddecfc07bd6cb34eb23df4c53d840a07d1db6e0cfeffffff02f6fc1a00000000001976a9147d03641b70a1883b600f9646081c8bf626504ac288ac10891132000000001976a9140b348dfe637f57295943cfdc2b5c65c79c0da6ba88ac72810800", + "1cbb35edc105918b956838570f122d6f3a1fba2b67467e643e901d09f5f8ac1b": "0200000001194d876938fc6a69f418367e81425640963a2f2569e44b8652fbf98deb49746a000000006b483045022100fc497d765125566b303738841fb04c953fa286e4f531f5fca21df8c2bbdb0b0f02207a57b073f934b47bb1d1b83bb7fedf7a2d52061ea8107d0362d7c2bedfe4b7d401210200669c7e5dd728b676c2c1163ddcfa88e7cd4f01d12f01188b6b32c399c008ccfeffffff024044ca02000000001976a914802950915c17f11b1a677dd7cae5101e376fb34888ac90dc762b000000001976a914daf40881fda36848da6cc430dcbec6da3ea421b088acd3830800", + "f230a9414bf577d93d6f7f2515d9b549ede78cfba4168920892970fa8aa1eef8": "03000000051bacf8f5091d903e647e46672bba1f3a6f2d120f573868958b9105c1ed35bb1c010000006b4830450221008f3e0a1b59bd4072242fafbe994fb264ca3751480628755585de32e7a828b13902205ed37762b260f9ad8ef641084da938b8bd6dd2df0f7d8dca91e7c0ac0d31f4610121039ac6241be1c1f328522f0cd183ccbe78c10ad74d4535bec40f5e8fe082633d3cfffffffffda257e58e513e1bb9dd0cff7795c9efae03af950d269f6638229f7f15781b7d000000006a47304402203773f77a278f4039caa7024373bd5ac8002c12f88ac20b1ac1480ed616c1b561022054811f33ba1403e1fa38839994e97bb4c6540b78b1a2a2d102cccd2f218cd99e012103d59b76bb58143cf8de0ff52007242f7af15f1e6943c40d4d5d5fc5e0aa5141aeffffffff56f47e77356d4de5b665b7c6c41603e47496b37ae46cc01bf301ad80e54538a4010000006a4730440220762743c1cb769a51f6f5e600da17c35cf6ed9d45315e96c96fb203d743118b15022062d5c03716c6a6a9a1bbe9eb139cc500778ef3bf00aa21724e83391edcfcf20b012103e98d3cc012bb72006634b098d678afac500a1b3d3a430ac972075b5fb7153d87ffffffff2d9101b84d69adf1b168403ab2bcfbf3d2eebbf87a99a9be05d649d47d6c7bd3000000006b483045022100d31090741004486f2cb473eb046d058e63f12ff9e101a2f2028d5f0e3371499d022057f7b3f0375590d3961cf8b51bde61a830bb4f29b07d7e48132de7d292306193012103f4991317c773fbb37756fb222909c8bbd44e53f3c4e4f3060edb3df7d8549b24ffffffff774fb1a549c1145d22ee655bd067961eb4f9f876113b6521303db4e3c87f1aeb010000006a473044022064f3b9869041239074dadae73c2d0e78abf100d89f88d4ba33b7b3b83eaf87010220608079322bd051ac7900a3bb96d3bc694f2b8d65850bcfc043ce51e6367c654801210332d0d14a1a28c90c1149b691a675e26f4c8b485b8dcc3624280a9fdc954ef081ffffffff02e064ec69000000001976a914fa49fe511c437a0d4ec01050184bd2d6538b3f0888ac62830100000000001976a91414dfbdcfb48babe7127fa0ee90339c33a46aeda288ac00000000", + "c3fb3620ebd1c7678879b40df1495cc86a179b5a6f9e48ce0b687a5c6f5a1db5": "0300000001f8eea18afa702989208916a4fb8ce7ed49b5d915257f6f3dd977f54b41a930f2000000006a4730440220442af7402fad5756cb4bd1890023369c9b4f63129cc2b6020b3e610cfe0f05f502206d90f315d3467d86b71871b484ab1dfdfdc7fb91cc190f35eb84c34d40490d6b012102ba0588ffd3c838b715d7c79bcf1cff2ba69befd5ea52aa3474d66f094536cac0ffffffff0380a9b24b000000001976a914fa49fe511c437a0d4ec01050184bd2d6538b3f0888ac0084d717000000001976a914e922f6420544f1be0cb593c10535cc3469198bc888ac48366206000000001976a91404a791e67467246c3c0a003007793160387de54288ac00000000", + "6f37b0d6284aab627c31c50e1c9d7cce39912dd4f2393f91734f794bc6408533": "0300000001b51d5a6f5c7a680bce489e6f5a9b176ac85c49f10db4798867c7d1eb2036fbc3000000006a4730440220283fd42353767188532db4a4f1c3d0a9e96e313196ae1310af6d3006c7aa64ff022027fa50cf065c096f146e00516cb3e28a9bb387a6cf1103aae0592d5c882d25e5012102ba0588ffd3c838b715d7c79bcf1cff2ba69befd5ea52aa3474d66f094536cac0ffffffff0200131a4b000000001976a914838112cc6c85e074aa7f373e942c9f5240c3e13a88ac89959800000000001976a914f728c15b9a5fe4e6d7b6ed74b323e23f5c6e303f88ac00000000", + "9cd3d44a87a7f99a33aebc6957105d5fb41698ef642189a36bac59ec0b5cd840" : "0300000001338540c64b794f73913f39f2d42d9139ce7c9d1c0ec5317c62ab4a28d6b0376f000000006b483045022100b996d726d224a762acf8ab3e37c085e796b44960b8e9933571ac57750e8ed05102201c6a36d72f16140d6a152be40add102d95a4ac5b177d300b10c277859690a859012103b5614f077d750a1eaffb23ca188dbcc7e267f4b8ffdedf81cdf970643027191bffffffff02008c8647000000001976a91414b05906daab037707927bc6c83900d5dbf2849688ac09869303000000001976a914791e51fff6554c18216c83d9ca81cf30cc66aff388ac00000000", + "6f76ca8038c6cb1b373bbbf80698afdc0d638e4a223be12a4feb5fd8e1801135": "030000000140d85c0bec59ac6ba3892164ef9816b45f5d105769bcae339af9a7874ad4d39c000000006b483045022100eef13b38a771924b1429b119ef27494fe764cf1a7b12962462c4934ba15dd426022037097eaf74e3f3b547bf5e67451c7bf1e2e2a4b7b205850b1315bc4ab983fdf2012103f376b41c9e9ebc3131e33d4de127c57c1bf3ca88f81845595c44f9ac46122677ffffffff02809b8b44000000001976a914f3a39f8266812baa084890d02fc489f2aee8075a88ac89effa02000000001976a914e3dd87e2dd2080c854d0c90abae96d985ae8902288ac00000000", + "e6b6f85a18d77974f376f05d6c96d0fdde990e733664248b1a00391565af6841": "0300000002338540c64b794f73913f39f2d42d9139ce7c9d1c0ec5317c62ab4a28d6b0376f010000006a47304402204de0c38c97e07cddaa0da91563b9ae7620c593c18fe146dde8429b662e542b4902203eeb557d6553dbac0d4f813c07d5ad6ecdd9c27e2e228aaba8c727943676f62b0121038ada8b4de6d21a29ab12401e70d7f44566dbd224a056c857f273b48adf8b0cd2ffffffff351180e1d85feb4f2ae13b224a8e630ddcaf9806f8bb3b371bcbc63880ca766f000000006b483045022100b9a1ff2866f2795fead698f7b16f26c42cc91ef4efda6203e147d8fe910b31cb02203c9c846cea5efa369f2f0e015952bd5992a884a0d79b2c237bcaa842a83f0efa0121033f532214f69c414bc1742367df5cd1195c64a5ee08455d0aad17f6de72e9eaadffffffff0200ca9a3b000000001976a9140d2a064dc57ccd2270a436a871f277bbb7b9ca2088ac7f658909000000001976a914e9c12479daba9d989cedba69adb56a5a50fe500288ac00000000" + }, + "txMetadata": { + "a43845e580ad01f31bc06ce47ab39674e40316c4c6b765b6e54d6d35777ef456": { + "blockHash": "000001deee9f99e8219a9abcaaea135dbaae8a9b0f1ea214e6b6a37a5c5b115d", + "height": 555506, + "isInstantLocked": true, + "isChainLocked": true }, - "blockHeight": 640476, - "blockHeaders": { - "0000022113844aa456c439bf1f7d720c5ee013cd945acad41b6522ddb12102ee": "00000020e621284508549461adaf248278d67709f3df2315a76be01175f7137872010000dd31aa7cb665279529260b154cac878c1af59e81d03209221bcec08a0889da3a7d1ccc616682021efd0d0a00", - "0000009d9bb006df83378ba3b22f6a17e3902658401668fed0b7d4971db91293": "00000020ee0221b1dd22651bd4ca5a94cd13e05e0c727d1fbf39c456a44a8413210200008c3ca551f529a105e132841a22d560d00002e9b2d1fed25bcd75fd8fa222bd44c21ccc61083e021e2cbe0900", - "000001deee9f99e8219a9abcaaea135dbaae8a9b0f1ea214e6b6a37a5c5b115d": "0000002084813d1a5019ed9079e8c7a0d579b685f16e2e0dcaae54d9144aa4a730010000adcba982cc7bea98a786106769a9341a4d3866834d58105be0630b890cba5882553a16613554021ef2800000", - "000000b6006c758eda23ec7e2a640a0bf2c6a0c44827be216faff6bf4fd388e8": "000000205d115b5c7aa3b6e614a21e0f9b8aaeba5d13eaaabc9a9a21e8999feede010000709f82baa9c1f24fec82712e0599152d7e4d077853cf7cba91861ec3eaea22eab03a1661113a021e01da0000", - "0000012cf6377c6cf2b317a4deed46573c09f04f6880dca731cc9ccea6691e19": "00000020e888d34fbff6af6f21be2748c4a0c6f20b0a642a7eec23da8e756c00b6000000766a990ddb0e1be5191eae3ae973e4558a20d9fcdcc5309145ac8edfab8f9966963b1661b82d021e450b0000", - "00000221952c2a60adcb929de837f659308cb5c6bb7783016479381fb550fbad": "0000002031f1bb8d975bf830b0ac702b534e2f66f0edd90fcd99770c8dd4794bd8020000bcaccb7f4cdf155fe1e08ac473530f92fbb4f51d62376f34ca1d882bd810b677c57f1a61e7f5021e53b30000", - "00000c1e4556add15119392ed36ec6af2640569409abfa23a9972bc3be1b3717": "00000020638feafd82aca4e6b2ac1f77876f880e06775d2e6f79a61725f5011c6e0100005ecf6c29dcff20133c5399ef08ea13299fec7788bc03dfb7e05b0695535aa738f7bb1b6174f50e1e95660000", - "00000084b4d9e887a6ad3f37c576a17d79c35ec9301e55210eded519e8cdcd3a": "00000020b0c8a51730dfa39cd12b1f80f55784fd167bf97392d7e8caf5f93053e20000009b43512fc810155df3d5f7d73e7031812845784a6b6ce68f1649dfd8db44d8f660df1b616c8a011e4aab0000", - "000001953ea0bbb8ad04a9a1a2a707fef207ad22a712d7d3c619f0f9b63fa98c": "000000207dca199148d151d71bc35127b31205090d8f242b8a05ad2adea07bc9fe000000b37f61034e6dedf382051227c736ced4b786eb0c6495e6c23526630e38bf6d732a251c61ee09021e6eeb0000", - "000000dffb05c071a8c05082a475b7ce9c1e403f3b89895a6c448fe08535a5f5": "000000208ca93fb6f9f019c6d3d712a722ad07f2fe07a7a2a1a904adb8bba03e95010000d1db1a1802320ab91ddb72525338b3a46059b0d3f3cbed01f550a72577db773cb9261c614d02021eb36b0000", - "000000444b3f2f02085f8befe72da5442c865c290658766cf935e1a71a4f4ba7": "0000002031b3986a9c0814a212264200e2a276c551e5df433c5e147276eab87b0c02000090f859396304db0dd94b9214222d0dd1dfd16d79e4bc610f68ffd18c377f5ef7be2b1c615321021e1d240000", - "000001f9c5de4d2b258a975bfbf7b9a3346890af6389512bea3cb6926b9be330": "00000020478e3710c6030a6d6e9c8588789f530b498db6f8e18b20e9ab23b48bd60100003ffa42f2dc18080e764a4cdd37fccf46533b74287ed7579200f5c937268ab9a9542f1c61803e021efbd80000" + "d37b6c7dd449d605bea9997af8bbeed2f3fbbcb23a4068b1f1ad694db801912d": { + "blockHash": "000000b6006c758eda23ec7e2a640a0bf2c6a0c44827be216faff6bf4fd388e8", + "height": 555507, + "isInstantLocked": true, + "isChainLocked": true }, - "transactions": { - "a43845e580ad01f31bc06ce47ab39674e40316c4c6b765b6e54d6d35777ef456": { - "transaction": "0200000007fd1ef99bc1edb4ab93ba74309da788e4ac460975733f02936a6321620d2a8011000000006b483045022100dfb220a840d597179abdf49692ad64c1c0da785041975b00aee03c9625639cf202204d06eade5cca19fab1e10b1d6e1b67c77626a0e88bb4d5f61bd57293b4b64217012102295ecb812ccf52deaf304bebfe3a59a644f05bac81241ea1e3a2f8750064cbf6feffffff694226ee65ea29ba7ec3e5448464c16f11d5b7564f6b3b5d0425a4c751389519000000006b483045022100beff3263b7c99720e99af9ec146c818701efb0130603f1570f427b74aef8521802202e660bb9f7ea156f91addd5fe47cbd2c2bf388cc6e1eff3a39adffd89d26d346012102c33942799f7cbf4a7d12f1b3e52cb80cc4de083b997d3e63915df9973d5bce2afeffffff963e9964a8394a07dde1dbb1e8b34af0f194def0db77fa69229030fbade2c82d000000006b483045022100ff67776932e7a32520aa131f76bdfd6737650ad3b11edbdf466cca83f691b0e60220633bcbedebacffd53ceb7e9cdbd47928d7c2849f49ac1f8efb9f384c1a4ee46301210371c0bc42e08de059a8829730abb16f3d40cff87e5ad85d65c4a0a949d9c4b524feffffff1fe16550125c3398c25d97308d7ab89bdd3d27a1589c78e97c4823c92723cf40000000006b483045022100cca348c7ab16fac28b3bba502be54a9e3766b7da9821a90605f370b75840569702207d082510aa493988e09da046355b018781718208f8a954e14ea33d608ae59625012103699b9402e109ed9d0c67c6a45be5cf5f1236c44bb9fc4b07a2f3392ba0b64172feffffffce251fb3d87d7df03b0fbd720ce5425ab0ea86d96a4ec658463d9507928bb34b000000006a47304402203ae564ff74b08b1f96bf857f51448434418d747a02039ec1ee109a4f5d8e8106022072f8769bd175416d22f44011f7e67aec301f08573c9937be7e4a09c394c7396601210311bae874933a4503a61d1c8c2e5b57b1a278d28d4892af4bd79ab8a731495265feffffffaf2dfef80d4a1f77c75dfc74e3769d8526c66c467c859b16b3439ab213851eb2000000006a47304402200b49b7059064efb57df453dc2d20002f09b5266bc825760ef81624771f13920802200782616b8c4fb7b5eff94fdf865e6ddc4530d3932b97fdc3a747e8c451f0314c012103a94131f28f8efd67f47f2496ff6e8d9069a3a7df97202a33e90e16f257d03729feffffff344fef05224cca98a16f87515041df61cbca948518761021a286d1a76e2bfdd6000000006a47304402202a24d1123775641269c6f748d3e4dad08a682e4e334a9b73c7df84f6c22e8e7d022022c0cc2225d3f14cb58a6fb3e4bf23c0f33252d9040a9ca9ef66eb17742a476f01210347301de4c9ba7f46b0f27cb82ae70a73749821e2951d3c87c2f0d56648635d1cfeffffff0251d21b00000000001976a91440ca54360086cc0fbd69d862db58ab2b6d22805888ace058340b000000001976a914538da44e7136cc994023d89a7b4b3d02ac0e573988acf1790800", - "metadata": { - "blockHash": "000001deee9f99e8219a9abcaaea135dbaae8a9b0f1ea214e6b6a37a5c5b115d", - "height": 555506, - "isInstantLocked": true, - "isChainLocked": true - } - }, - "d37b6c7dd449d605bea9997af8bbeed2f3fbbcb23a4068b1f1ad694db801912d": { - "transaction": "03000000014e74eb53f4b1fa08e1fa02788005b8ef9f70929b3f0668fcb9dc4093b7385af3010000006a473044022071fcd620db9f245fe91c44e9b298ee5a718c2be728958f9e707dd1f785fb05e002207b05d45e4ab78c83f9117032a109d6afa37883ca5b479d2b3110fb0d773d23df0121033883dff35aeac917a26d2cbfb59a365c7ff83256a49c4d6aed8f1c0684605c40ffffffff0280969800000000001976a914cb579d4aa777c3583f61f28425ae3fea5b60d39088ac2296bf1f000000001976a914500ddabedf00296b40842cea428951d57331d5e088ac00000000", - "metadata": { - "blockHash": "000000b6006c758eda23ec7e2a640a0bf2c6a0c44827be216faff6bf4fd388e8", - "height": 555507, - "isInstantLocked": true, - "isChainLocked": true - } - }, - "7d1b78157f9f2238669f260d95af03aeefc99577ff0cddb91b3e518ee557a2fd": { - "transaction": "03000000012d9101b84d69adf1b168403ab2bcfbf3d2eebbf87a99a9be05d649d47d6c7bd3010000006a47304402201cc3d6887d5161eba36a5e6fb1ccd8e8f9eeda7fe95b4fb0a1accb99eeba0223022040d0df81fde8f59c807e541ca5bcfc9d7450f76657aeb44c708fa7d65b7d58410121038cdae47fceb5b117cd3ef5bdf8c9f2a83679a9105d012095762067bdb2351ceaffffffff0280969800000000001976a914e00939d2ec2f885f5e7dc7b9f5b06dcf868d0c4b88acabfe261f000000001976a914f03286cbb7954ea6affa9654af6cfe1210dd0c6288ac00000000", - "metadata": { - "blockHash": "0000012cf6377c6cf2b317a4deed46573c09f04f6880dca731cc9ccea6691e19", - "height": 555508, - "isInstantLocked": true, - "isChainLocked": true - } - }, - "eb1a7fc8e3b43d3021653b1176f8f9b41e9667d05b65ee225d14c149a5b14f77": { - "transaction": "02000000024f5ccb10d1762b155b25a32c1242f72e506ead7dcdffebbb9a57c450eff24306000000006a473044022006d0f91fbc789475f4bc545901b069a5baf657b36914638b7847e61c6ce508a0022061cc6488a5e8e7d142acf015d9447ea2c37b2c44f7ce9822b2b3421e6f200111012103f7626b79771dc4e1928ffbb407aa32f7089bd8deebb7b7393d28397b504f3002feffffff07f289648f6f097dd46857c15d64576658b9f2c693a1472eb8beaa21b21ec020010000006a47304402207ac2b0c2ea3c073db24d893b533d1d5753f35a85c00971e8d0a1d7fdd77c1ffb0220024b43bdd62ee96189e3da40c5725d771c6cb8df0909826538f558fb32c46cf901210308b60306848cbb551d800192ddecfc07bd6cb34eb23df4c53d840a07d1db6e0cfeffffff02f6fc1a00000000001976a9147d03641b70a1883b600f9646081c8bf626504ac288ac10891132000000001976a9140b348dfe637f57295943cfdc2b5c65c79c0da6ba88ac72810800", - "metadata": { - "blockHash": "00000221952c2a60adcb929de837f659308cb5c6bb7783016479381fb550fbad", - "height": 557481, - "isInstantLocked": true, - "isChainLocked": true - } - }, - "1cbb35edc105918b956838570f122d6f3a1fba2b67467e643e901d09f5f8ac1b": { - "transaction": "0200000001194d876938fc6a69f418367e81425640963a2f2569e44b8652fbf98deb49746a000000006b483045022100fc497d765125566b303738841fb04c953fa286e4f531f5fca21df8c2bbdb0b0f02207a57b073f934b47bb1d1b83bb7fedf7a2d52061ea8107d0362d7c2bedfe4b7d401210200669c7e5dd728b676c2c1163ddcfa88e7cd4f01d12f01188b6b32c399c008ccfeffffff024044ca02000000001976a914802950915c17f11b1a677dd7cae5101e376fb34888ac90dc762b000000001976a914daf40881fda36848da6cc430dcbec6da3ea421b088acd3830800", - "metadata": { - "blockHash": "00000c1e4556add15119392ed36ec6af2640569409abfa23a9972bc3be1b3717", - "height": 558036, - "isInstantLocked": true, - "isChainLocked": true - } - }, - "f230a9414bf577d93d6f7f2515d9b549ede78cfba4168920892970fa8aa1eef8": { - "transaction": "03000000051bacf8f5091d903e647e46672bba1f3a6f2d120f573868958b9105c1ed35bb1c010000006b4830450221008f3e0a1b59bd4072242fafbe994fb264ca3751480628755585de32e7a828b13902205ed37762b260f9ad8ef641084da938b8bd6dd2df0f7d8dca91e7c0ac0d31f4610121039ac6241be1c1f328522f0cd183ccbe78c10ad74d4535bec40f5e8fe082633d3cfffffffffda257e58e513e1bb9dd0cff7795c9efae03af950d269f6638229f7f15781b7d000000006a47304402203773f77a278f4039caa7024373bd5ac8002c12f88ac20b1ac1480ed616c1b561022054811f33ba1403e1fa38839994e97bb4c6540b78b1a2a2d102cccd2f218cd99e012103d59b76bb58143cf8de0ff52007242f7af15f1e6943c40d4d5d5fc5e0aa5141aeffffffff56f47e77356d4de5b665b7c6c41603e47496b37ae46cc01bf301ad80e54538a4010000006a4730440220762743c1cb769a51f6f5e600da17c35cf6ed9d45315e96c96fb203d743118b15022062d5c03716c6a6a9a1bbe9eb139cc500778ef3bf00aa21724e83391edcfcf20b012103e98d3cc012bb72006634b098d678afac500a1b3d3a430ac972075b5fb7153d87ffffffff2d9101b84d69adf1b168403ab2bcfbf3d2eebbf87a99a9be05d649d47d6c7bd3000000006b483045022100d31090741004486f2cb473eb046d058e63f12ff9e101a2f2028d5f0e3371499d022057f7b3f0375590d3961cf8b51bde61a830bb4f29b07d7e48132de7d292306193012103f4991317c773fbb37756fb222909c8bbd44e53f3c4e4f3060edb3df7d8549b24ffffffff774fb1a549c1145d22ee655bd067961eb4f9f876113b6521303db4e3c87f1aeb010000006a473044022064f3b9869041239074dadae73c2d0e78abf100d89f88d4ba33b7b3b83eaf87010220608079322bd051ac7900a3bb96d3bc694f2b8d65850bcfc043ce51e6367c654801210332d0d14a1a28c90c1149b691a675e26f4c8b485b8dcc3624280a9fdc954ef081ffffffff02e064ec69000000001976a914fa49fe511c437a0d4ec01050184bd2d6538b3f0888ac62830100000000001976a91414dfbdcfb48babe7127fa0ee90339c33a46aeda288ac00000000", - "metadata": { - "blockHash": "00000084b4d9e887a6ad3f37c576a17d79c35ec9301e55210eded519e8cdcd3a", - "height": 558102, - "isInstantLocked": true, - "isChainLocked": true - } - }, - "c3fb3620ebd1c7678879b40df1495cc86a179b5a6f9e48ce0b687a5c6f5a1db5": { - "transaction": "0300000001f8eea18afa702989208916a4fb8ce7ed49b5d915257f6f3dd977f54b41a930f2000000006a4730440220442af7402fad5756cb4bd1890023369c9b4f63129cc2b6020b3e610cfe0f05f502206d90f315d3467d86b71871b484ab1dfdfdc7fb91cc190f35eb84c34d40490d6b012102ba0588ffd3c838b715d7c79bcf1cff2ba69befd5ea52aa3474d66f094536cac0ffffffff0380a9b24b000000001976a914fa49fe511c437a0d4ec01050184bd2d6538b3f0888ac0084d717000000001976a914e922f6420544f1be0cb593c10535cc3469198bc888ac48366206000000001976a91404a791e67467246c3c0a003007793160387de54288ac00000000", - "metadata": { - "blockHash": "000001953ea0bbb8ad04a9a1a2a707fef207ad22a712d7d3c619f0f9b63fa98c", - "height": 558229, - "isInstantLocked": true, - "isChainLocked": true - } - }, - "6f37b0d6284aab627c31c50e1c9d7cce39912dd4f2393f91734f794bc6408533": { - "transaction": "0300000001b51d5a6f5c7a680bce489e6f5a9b176ac85c49f10db4798867c7d1eb2036fbc3000000006a4730440220283fd42353767188532db4a4f1c3d0a9e96e313196ae1310af6d3006c7aa64ff022027fa50cf065c096f146e00516cb3e28a9bb387a6cf1103aae0592d5c882d25e5012102ba0588ffd3c838b715d7c79bcf1cff2ba69befd5ea52aa3474d66f094536cac0ffffffff0200131a4b000000001976a914838112cc6c85e074aa7f373e942c9f5240c3e13a88ac89959800000000001976a914f728c15b9a5fe4e6d7b6ed74b323e23f5c6e303f88ac00000000", - "metadata": { - "blockHash": "000000dffb05c071a8c05082a475b7ce9c1e403f3b89895a6c448fe08535a5f5", - "height": 558230, - "isInstantLocked": true, - "isChainLocked": true - } - }, - "9cd3d44a87a7f99a33aebc6957105d5fb41698ef642189a36bac59ec0b5cd840": { - "transaction": "0300000001338540c64b794f73913f39f2d42d9139ce7c9d1c0ec5317c62ab4a28d6b0376f000000006b483045022100b996d726d224a762acf8ab3e37c085e796b44960b8e9933571ac57750e8ed05102201c6a36d72f16140d6a152be40add102d95a4ac5b177d300b10c277859690a859012103b5614f077d750a1eaffb23ca188dbcc7e267f4b8ffdedf81cdf970643027191bffffffff02008c8647000000001976a91414b05906daab037707927bc6c83900d5dbf2849688ac09869303000000001976a914791e51fff6554c18216c83d9ca81cf30cc66aff388ac00000000", - "metadata": { - "blockHash": "0000016fb685b4b1efed743d2263de34a9f8323ed75e732654b1b951c5cb4dde", - "height": 558236, - "isInstantLocked": true, - "isChainLocked": true - } - }, - "6f76ca8038c6cb1b373bbbf80698afdc0d638e4a223be12a4feb5fd8e1801135": { - "transaction": "030000000140d85c0bec59ac6ba3892164ef9816b45f5d105769bcae339af9a7874ad4d39c000000006b483045022100eef13b38a771924b1429b119ef27494fe764cf1a7b12962462c4934ba15dd426022037097eaf74e3f3b547bf5e67451c7bf1e2e2a4b7b205850b1315bc4ab983fdf2012103f376b41c9e9ebc3131e33d4de127c57c1bf3ca88f81845595c44f9ac46122677ffffffff02809b8b44000000001976a914f3a39f8266812baa084890d02fc489f2aee8075a88ac89effa02000000001976a914e3dd87e2dd2080c854d0c90abae96d985ae8902288ac00000000", - "metadata": { - "blockHash": "000000444b3f2f02085f8befe72da5442c865c290658766cf935e1a71a4f4ba7", - "height": 558242, - "isInstantLocked": true, - "isChainLocked": true - } - }, - "e6b6f85a18d77974f376f05d6c96d0fdde990e733664248b1a00391565af6841": { - "transaction": "0300000002338540c64b794f73913f39f2d42d9139ce7c9d1c0ec5317c62ab4a28d6b0376f010000006a47304402204de0c38c97e07cddaa0da91563b9ae7620c593c18fe146dde8429b662e542b4902203eeb557d6553dbac0d4f813c07d5ad6ecdd9c27e2e228aaba8c727943676f62b0121038ada8b4de6d21a29ab12401e70d7f44566dbd224a056c857f273b48adf8b0cd2ffffffff351180e1d85feb4f2ae13b224a8e630ddcaf9806f8bb3b371bcbc63880ca766f000000006b483045022100b9a1ff2866f2795fead698f7b16f26c42cc91ef4efda6203e147d8fe910b31cb02203c9c846cea5efa369f2f0e015952bd5992a884a0d79b2c237bcaa842a83f0efa0121033f532214f69c414bc1742367df5cd1195c64a5ee08455d0aad17f6de72e9eaadffffffff0200ca9a3b000000001976a9140d2a064dc57ccd2270a436a871f277bbb7b9ca2088ac7f658909000000001976a914e9c12479daba9d989cedba69adb56a5a50fe500288ac00000000", - "metadata": { - "blockHash": "000001f9c5de4d2b258a975bfbf7b9a3346890af6389512bea3cb6926b9be330", - "height": 558246, - "isInstantLocked": true, - "isChainLocked": true - } - } + "7d1b78157f9f2238669f260d95af03aeefc99577ff0cddb91b3e518ee557a2fd": { + "blockHash": "0000012cf6377c6cf2b317a4deed46573c09f04f6880dca731cc9ccea6691e19", + "height": 555508, + "isInstantLocked": true, + "isChainLocked": true }, - "instantLocks": {}, - "addresses": { - "yTwEca67QSkZ6axGdpNFzWPaCj8zqYybY7": { - "address": "yTwEca67QSkZ6axGdpNFzWPaCj8zqYybY7", - "transactions": [ - "a43845e580ad01f31bc06ce47ab39674e40316c4c6b765b6e54d6d35777ef456", - "f230a9414bf577d93d6f7f2515d9b549ede78cfba4168920892970fa8aa1eef8" - ], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yercyhdN9oEkZcB9BsW5ktFaDxFEuK6qXN": { - "address": "yercyhdN9oEkZcB9BsW5ktFaDxFEuK6qXN", - "transactions": [ - "d37b6c7dd449d605bea9997af8bbeed2f3fbbcb23a4068b1f1ad694db801912d", - "f230a9414bf577d93d6f7f2515d9b549ede78cfba4168920892970fa8aa1eef8" - ], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "ygk3GCSba2J3L9G665Snozhj9HSkh5ByVE": { - "address": "ygk3GCSba2J3L9G665Snozhj9HSkh5ByVE", - "transactions": [ - "7d1b78157f9f2238669f260d95af03aeefc99577ff0cddb91b3e518ee557a2fd", - "f230a9414bf577d93d6f7f2515d9b549ede78cfba4168920892970fa8aa1eef8" - ], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "ybuL6rM6dgrKzCg8s99f3jxGuv5oz5JcDA": { - "address": "ybuL6rM6dgrKzCg8s99f3jxGuv5oz5JcDA", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "ygHAVkMtYSqoTWHebDv7qkhMV6dHyuRsp2": { - "address": "ygHAVkMtYSqoTWHebDv7qkhMV6dHyuRsp2", - "transactions": [ - "1cbb35edc105918b956838570f122d6f3a1fba2b67467e643e901d09f5f8ac1b", - "f230a9414bf577d93d6f7f2515d9b549ede78cfba4168920892970fa8aa1eef8" - ], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yMLhEsiP2ajSh8STmXnNmkWXtoHsmawZxd": { - "address": "yMLhEsiP2ajSh8STmXnNmkWXtoHsmawZxd", - "transactions": [ - "eb1a7fc8e3b43d3021653b1176f8f9b41e9667d05b65ee225d14c149a5b14f77", - "f230a9414bf577d93d6f7f2515d9b549ede78cfba4168920892970fa8aa1eef8" - ], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yj8rRKATAUHcAgXvNZekob58xKm2oNyvhv": { - "address": "yj8rRKATAUHcAgXvNZekob58xKm2oNyvhv", - "transactions": [ - "f230a9414bf577d93d6f7f2515d9b549ede78cfba4168920892970fa8aa1eef8", - "c3fb3620ebd1c7678879b40df1495cc86a179b5a6f9e48ce0b687a5c6f5a1db5", - "6f37b0d6284aab627c31c50e1c9d7cce39912dd4f2393f91734f794bc6408533" - ], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yhaAB6e8m3F8zmGX7WAVYa6eEfmSrrnY8x": { - "address": "yhaAB6e8m3F8zmGX7WAVYa6eEfmSrrnY8x", - "transactions": [ - "c3fb3620ebd1c7678879b40df1495cc86a179b5a6f9e48ce0b687a5c6f5a1db5" - ], - "utxos": { - "c3fb3620ebd1c7678879b40df1495cc86a179b5a6f9e48ce0b687a5c6f5a1db5-1": { - "satoshis": 400000000, - "script": "76a914e922f6420544f1be0cb593c10535cc3469198bc888ac" - } - }, - "balanceSat": 400000000, - "unconfirmedBalanceSat": 0 - }, - "yiXh4Yo5djG6QH8WzXkKm5EFzqLRJWakXz": { - "address": "yiXh4Yo5djG6QH8WzXkKm5EFzqLRJWakXz", - "transactions": [ - "6f76ca8038c6cb1b373bbbf80698afdc0d638e4a223be12a4feb5fd8e1801135", - "e6b6f85a18d77974f376f05d6c96d0fdde990e733664248b1a00391565af6841" - ], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yQYv3Um6DsdtANo1ZPTUte75wAGMstLRex": { - "address": "yQYv3Um6DsdtANo1ZPTUte75wAGMstLRex", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yiYPJmu7eEm1cXUNumQRdjv1fvPhsfgMS4": { - "address": "yiYPJmu7eEm1cXUNumQRdjv1fvPhsfgMS4", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yii4aUZhNfL6EWN9KAgAFrJzGJmqHnF4wx": { - "address": "yii4aUZhNfL6EWN9KAgAFrJzGJmqHnF4wx", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yLpTquSct2SGz2Ka45uTPDd81Kzro2Jt2k": { - "address": "yLpTquSct2SGz2Ka45uTPDd81Kzro2Jt2k", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yMiJtpzb1Qthy9TGnavsf5NZ6EZZa4j9q3": { - "address": "yMiJtpzb1Qthy9TGnavsf5NZ6EZZa4j9q3", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yacgSfW7RkwWakEZPg8USAVdzCypiG3vxS": { - "address": "yacgSfW7RkwWakEZPg8USAVdzCypiG3vxS", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yVvrmoRPFLy6nUpCQBT8ZExxF5wF3DhiGU": { - "address": "yVvrmoRPFLy6nUpCQBT8ZExxF5wF3DhiGU", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yaJf2aG6cFUtfv4o6TuEKsh5kr4xq5iAY4": { - "address": "yaJf2aG6cFUtfv4o6TuEKsh5kr4xq5iAY4", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yfardJQ4ucgWLKQPaRHGMRMbSGm5H4ExJR": { - "address": "yfardJQ4ucgWLKQPaRHGMRMbSGm5H4ExJR", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yLSCqx7dcM5JKR2fG7vHbF2axMvuYqomaw": { - "address": "yLSCqx7dcM5JKR2fG7vHbF2axMvuYqomaw", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yVij8XpJ78LM5hepSV1KF7T8vRpUEXCpK5": { - "address": "yVij8XpJ78LM5hepSV1KF7T8vRpUEXCpK5", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yNDpPsJqXKM36zHSNEW7c1zSvNnrZ699FY": { - "address": "yNDpPsJqXKM36zHSNEW7c1zSvNnrZ699FY", - "transactions": [ - "f230a9414bf577d93d6f7f2515d9b549ede78cfba4168920892970fa8aa1eef8" - ], - "utxos": { - "f230a9414bf577d93d6f7f2515d9b549ede78cfba4168920892970fa8aa1eef8-1": { - "satoshis": 99170, - "script": "76a91414dfbdcfb48babe7127fa0ee90339c33a46aeda288ac" - } - }, - "balanceSat": 99170, - "unconfirmedBalanceSat": 0 - }, - "yLk4Hw3w4zDudrDVP6W8J9TggkY57zQUki": { - "address": "yLk4Hw3w4zDudrDVP6W8J9TggkY57zQUki", - "transactions": [ - "c3fb3620ebd1c7678879b40df1495cc86a179b5a6f9e48ce0b687a5c6f5a1db5" - ], - "utxos": { - "c3fb3620ebd1c7678879b40df1495cc86a179b5a6f9e48ce0b687a5c6f5a1db5-2": { - "satoshis": 107099720, - "script": "76a91404a791e67467246c3c0a003007793160387de54288ac" - } - }, - "balanceSat": 107099720, - "unconfirmedBalanceSat": 0 - }, - "yirJaK8KCE5YAmwvLadizqFw3TCXqBuZXL": { - "address": "yirJaK8KCE5YAmwvLadizqFw3TCXqBuZXL", - "transactions": [ - "6f37b0d6284aab627c31c50e1c9d7cce39912dd4f2393f91734f794bc6408533", - "e6b6f85a18d77974f376f05d6c96d0fdde990e733664248b1a00391565af6841" - ], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yhdRfg5gNr587dtEC4YYMcSHmLVEGqqtHc": { - "address": "yhdRfg5gNr587dtEC4YYMcSHmLVEGqqtHc", - "transactions": [ - "e6b6f85a18d77974f376f05d6c96d0fdde990e733664248b1a00391565af6841" - ], - "utxos": { - "e6b6f85a18d77974f376f05d6c96d0fdde990e733664248b1a00391565af6841-1": { - "satoshis": 159999359, - "script": "76a914e9c12479daba9d989cedba69adb56a5a50fe500288ac" - } - }, - "balanceSat": 159999359, - "unconfirmedBalanceSat": 0 - }, - "yYwKP1FQae5kbjXkmuirGx6Xzf8NzHpLqW": { - "address": "yYwKP1FQae5kbjXkmuirGx6Xzf8NzHpLqW", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yX9gmsm8aSxZZjYhq4w35aidT7qbhcpNjU": { - "address": "yX9gmsm8aSxZZjYhq4w35aidT7qbhcpNjU", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "ybgXCTGMHEBbQeUib8c3xAjtGAc12XtWiU": { - "address": "ybgXCTGMHEBbQeUib8c3xAjtGAc12XtWiU", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yS31WpdMT2b34uL9C37fbUoACHhiupHCyP": { - "address": "yS31WpdMT2b34uL9C37fbUoACHhiupHCyP", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yTSpFqRoX3vyN286AUtKKhgmX5Xb41YKQe": { - "address": "yTSpFqRoX3vyN286AUtKKhgmX5Xb41YKQe", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yQU5YsqN7psTTASuYbcMi7N5nNZGaxXb2X": { - "address": "yQU5YsqN7psTTASuYbcMi7N5nNZGaxXb2X", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yVGGFj9BLgEab5rucSGLC6UGVLQKB4U1wJ": { - "address": "yVGGFj9BLgEab5rucSGLC6UGVLQKB4U1wJ", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yQCh5yYCHEbJzgSJE9rdHiqXHidKm3kwr5": { - "address": "yQCh5yYCHEbJzgSJE9rdHiqXHidKm3kwr5", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yX7T3Ac3yaLk5CTC5UaR93Fc7SjYkeT5hn": { - "address": "yX7T3Ac3yaLk5CTC5UaR93Fc7SjYkeT5hn", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yXx3WXq8kYNPbYEg5U6bL8Xfih4g5LCYVo": { - "address": "yXx3WXq8kYNPbYEg5U6bL8Xfih4g5LCYVo", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yYnLMTz3jCi2KKKNuo3TVkEAGyUFg8tgkJ": { - "address": "yYnLMTz3jCi2KKKNuo3TVkEAGyUFg8tgkJ", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yiKa1dA6B4tSTNJqJP9Y5pQfQEffnQQDTL": { - "address": "yiKa1dA6B4tSTNJqJP9Y5pQfQEffnQQDTL", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yf7vcuDnE9DVhXdMfBMQQTEi43otYQzkWE": { - "address": "yf7vcuDnE9DVhXdMfBMQQTEi43otYQzkWE", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yTmSmocwERCeRHqNNG5SbpYKUra1HTmj8m": { - "address": "yTmSmocwERCeRHqNNG5SbpYKUra1HTmj8m", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yivUe5NeJsGsREwPQZUGYaTSwWB3E1oLcz": { - "address": "yivUe5NeJsGsREwPQZUGYaTSwWB3E1oLcz", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "ygfsZojdfW9UjCRU4ra95Aq6YgCC7UqZFx": { - "address": "ygfsZojdfW9UjCRU4ra95Aq6YgCC7UqZFx", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "ydJpjuJGossAZR7S5oS7cWvjygEwoj8Xwp": { - "address": "ydJpjuJGossAZR7S5oS7cWvjygEwoj8Xwp", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yW3TmWnmhvpxRbgFcQ8oXqDRkn3RhRH6jj": { - "address": "yW3TmWnmhvpxRbgFcQ8oXqDRkn3RhRH6jj", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yRegVX85DThKRkH8C61TtRacfzrkiBfNy5": { - "address": "yRegVX85DThKRkH8C61TtRacfzrkiBfNy5", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yPtDCqDFRe1JuDp8pvdiEMQMz2erGwS3VG": { - "address": "yPtDCqDFRe1JuDp8pvdiEMQMz2erGwS3VG", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yM9pSw3L4oBfG7uQL5o522Hu3WTvy9awgZ": { - "address": "yM9pSw3L4oBfG7uQL5o522Hu3WTvy9awgZ", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yNC6qYJYungzuk5XUynDFKCn54Dy8ngox4": { - "address": "yNC6qYJYungzuk5XUynDFKCn54Dy8ngox4", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yR5KcLr1bceLT4teTk2qoJx6pFLik1zyzL": { - "address": "yR5KcLr1bceLT4teTk2qoJx6pFLik1zyzL", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yU9fdXaUVtefwDZvxjJAr9xj1z2MtYi34A": { - "address": "yU9fdXaUVtefwDZvxjJAr9xj1z2MtYi34A", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yRrKLGJa9JmdjBWvrHtedKjHTao6CRDTKf": { - "address": "yRrKLGJa9JmdjBWvrHtedKjHTao6CRDTKf", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yXgMN6FgrgZCnTN1vhoZMh8afKMBmi3JC4": { - "address": "yXgMN6FgrgZCnTN1vhoZMh8afKMBmi3JC4", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yiqaCbXscvR8y3VFYMzdaKCaAGuDuZxMzt": { - "address": "yiqaCbXscvR8y3VFYMzdaKCaAGuDuZxMzt", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yP5dShZBydpbEzgGoXL6kcjv2KzervRrYB": { - "address": "yP5dShZBydpbEzgGoXL6kcjv2KzervRrYB", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "ydcgWDxheSxrLAqDBP4JXBndMCzUNf77gq": { - "address": "ydcgWDxheSxrLAqDBP4JXBndMCzUNf77gq", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yYJmzWey5kNecAThet5BFxAga1F4b4DKQ2": { - "address": "yYJmzWey5kNecAThet5BFxAga1F4b4DKQ2", - "transactions": [ - "6f37b0d6284aab627c31c50e1c9d7cce39912dd4f2393f91734f794bc6408533" - ], - "utxos": { - "6f37b0d6284aab627c31c50e1c9d7cce39912dd4f2393f91734f794bc6408533-0": { - "satoshis": 1260000000, - "script": "76a914838112cc6c85e074aa7f373e942c9f5240c3e13a88ac" - } - }, - "balanceSat": 1260000000, - "unconfirmedBalanceSat": 0 - }, - "yNCqctyQaq51WU1hN5aNwsgMsZ5fRiB7GY": { - "address": "yNCqctyQaq51WU1hN5aNwsgMsZ5fRiB7GY", - "transactions": [ - "9cd3d44a87a7f99a33aebc6957105d5fb41698ef642189a36bac59ec0b5cd840", - "6f76ca8038c6cb1b373bbbf80698afdc0d638e4a223be12a4feb5fd8e1801135" - ], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yNPbYz5cZKw2EwxtkL3VSVzPi2FYp9VKjQ": { - "address": "yNPbYz5cZKw2EwxtkL3VSVzPi2FYp9VKjQ", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "ybsGWzsnSCAZufgSeUjScVxqEdved99UM2": { - "address": "ybsGWzsnSCAZufgSeUjScVxqEdved99UM2", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yfNHuPojk8XKWP5nuueDptX4nM7qToudgx": { - "address": "yfNHuPojk8XKWP5nuueDptX4nM7qToudgx", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yXxLnDkk6s8h1PSnYaFM6MAyRarc1Kc1rY": { - "address": "yXxLnDkk6s8h1PSnYaFM6MAyRarc1Kc1rY", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yipResSzN2zUvL7UYkmptKKmQTv7sNssRn": { - "address": "yipResSzN2zUvL7UYkmptKKmQTv7sNssRn", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yZPtNwimHdRiKYbNQW49qezw1Kc1YwUJeT": { - "address": "yZPtNwimHdRiKYbNQW49qezw1Kc1YwUJeT", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yPMjYYfQbga2nBiuqqfUyX41U1vwRZ8fG8": { - "address": "yPMjYYfQbga2nBiuqqfUyX41U1vwRZ8fG8", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yLueLWWcLQsaXQ8D5o9tcyo8tfTxMWXvG4": { - "address": "yLueLWWcLQsaXQ8D5o9tcyo8tfTxMWXvG4", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yN8gzgsc1RVjXThMQT5qZH2jjpnMymz6zP": { - "address": "yN8gzgsc1RVjXThMQT5qZH2jjpnMymz6zP", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yPQLWBNwMdLxUW2oUwHGwQtfyYxD41BARJ": { - "address": "yPQLWBNwMdLxUW2oUwHGwQtfyYxD41BARJ", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yg5g2AfWFdwWexWGfbSXYbUHf1y5WWrFPs": { - "address": "yg5g2AfWFdwWexWGfbSXYbUHf1y5WWrFPs", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yWyABu4naV1Jzw7w9sn1gqhebPRSkCndsS": { - "address": "yWyABu4naV1Jzw7w9sn1gqhebPRSkCndsS", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "ycuUPzUBjhKyUjezQR1LNot79a6C4aRLaR": { - "address": "ycuUPzUBjhKyUjezQR1LNot79a6C4aRLaR", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yQ7YjvAXgDAUCekveHVjr6NBveXrUemVno": { - "address": "yQ7YjvAXgDAUCekveHVjr6NBveXrUemVno", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yi8bghcw627cMGpuH4bJqH6bqR5ywv1NLH": { - "address": "yi8bghcw627cMGpuH4bJqH6bqR5ywv1NLH", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yizHu8i2rfwzwBgnJ62s2WUe6wLoDjne6N": { - "address": "yizHu8i2rfwzwBgnJ62s2WUe6wLoDjne6N", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yW1u3tySeUKAKJsz7sjZFyjUiTyKLB6xBv": { - "address": "yW1u3tySeUKAKJsz7sjZFyjUiTyKLB6xBv", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yNaSkdy1Q8JNubUdbLMGsGf7sTRofEJYZq": { - "address": "yNaSkdy1Q8JNubUdbLMGsGf7sTRofEJYZq", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yXMrw79LPgu78EJsfGGYpm6fXKc1EMnQ49": { - "address": "yXMrw79LPgu78EJsfGGYpm6fXKc1EMnQ49", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yh6Hcyipdvp6WJpQxjNbaXP4kzPQUJpY3n": { - "address": "yh6Hcyipdvp6WJpQxjNbaXP4kzPQUJpY3n", - "transactions": [ - "6f76ca8038c6cb1b373bbbf80698afdc0d638e4a223be12a4feb5fd8e1801135" - ], - "utxos": { - "6f76ca8038c6cb1b373bbbf80698afdc0d638e4a223be12a4feb5fd8e1801135-1": { - "satoshis": 49999753, - "script": "76a914e3dd87e2dd2080c854d0c90abae96d985ae8902288ac" - } - }, - "balanceSat": 49999753, - "unconfirmedBalanceSat": 0 - }, - "yNphpXuaTZRpU9FBh2W7NkUYcr3kBDE8me": { - "address": "yNphpXuaTZRpU9FBh2W7NkUYcr3kBDE8me", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yXFppDT59xYD41mT2pmAdnvr7aZEFdgdrN": { - "address": "yXFppDT59xYD41mT2pmAdnvr7aZEFdgdrN", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yeKGAiiEHBGRujvLoYewA77jDDpeDamxvF": { - "address": "yeKGAiiEHBGRujvLoYewA77jDDpeDamxvF", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yaxTG66CVzKgHhHZXojRHC9ztLTvz3fwdT": { - "address": "yaxTG66CVzKgHhHZXojRHC9ztLTvz3fwdT", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yYw6qU7dwGoELZkSTj3oSKRpM4U8qTMc1U": { - "address": "yYw6qU7dwGoELZkSTj3oSKRpM4U8qTMc1U", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yQE2MksEnSfbeNre19oja9Jj8tvpj64C5a": { - "address": "yQE2MksEnSfbeNre19oja9Jj8tvpj64C5a", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yaRnvHo8oLvVmv46vMj5XPbDJouQSnmcLT": { - "address": "yaRnvHo8oLvVmv46vMj5XPbDJouQSnmcLT", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yj5ofWf2uYQQkSavYm2WXgu1QkaZCyP3Cm": { - "address": "yj5ofWf2uYQQkSavYm2WXgu1QkaZCyP3Cm", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yUCjGmEwrHJwNDrE1o2rMre6MkSbiE6yz7": { - "address": "yUCjGmEwrHJwNDrE1o2rMre6MkSbiE6yz7", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yfJzd1nE2rEqz5XEurD6vs4ykizwmw9xTv": { - "address": "yfJzd1nE2rEqz5XEurD6vs4ykizwmw9xTv", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yUk8U3jRZMHKVTa1eFDEtZpa1G4E13FP4d": { - "address": "yUk8U3jRZMHKVTa1eFDEtZpa1G4E13FP4d", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yMr59YWQFCADq4FbWrtxDUtMwwshSrmAyK": { - "address": "yMr59YWQFCADq4FbWrtxDUtMwwshSrmAyK", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yetSehBupzGS9yps5ogqARUGmTMAs2xVcQ": { - "address": "yetSehBupzGS9yps5ogqARUGmTMAs2xVcQ", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yNcESKLwriNrhM6EyoSpZEXrzdY3uht92T": { - "address": "yNcESKLwriNrhM6EyoSpZEXrzdY3uht92T", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yN2FihGU7KdaEspp39bKrhsHypeyeYzoM2": { - "address": "yN2FihGU7KdaEspp39bKrhsHypeyeYzoM2", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yirpWLxHuhwFzA6LfUPKUh1Ke9RB9BUjit": { - "address": "yirpWLxHuhwFzA6LfUPKUh1Ke9RB9BUjit", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yVDN66vvdshWdNzhUaQNB6xExAHkzs1zj8": { - "address": "yVDN66vvdshWdNzhUaQNB6xExAHkzs1zj8", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - }, - "yPzofnEhRVfDisL2nCUJtAoSHkuyMirHZS": { - "address": "yPzofnEhRVfDisL2nCUJtAoSHkuyMirHZS", - "transactions": [], - "utxos": {}, - "balanceSat": 0, - "unconfirmedBalanceSat": 0 - } + "eb1a7fc8e3b43d3021653b1176f8f9b41e9667d05b65ee225d14c149a5b14f77": { + "blockHash": "00000221952c2a60adcb929de837f659308cb5c6bb7783016479381fb550fbad", + "height": 557481, + "isInstantLocked": true, + "isChainLocked": true + }, + "1cbb35edc105918b956838570f122d6f3a1fba2b67467e643e901d09f5f8ac1b": { + "blockHash": "00000c1e4556add15119392ed36ec6af2640569409abfa23a9972bc3be1b3717", + "height": 558036, + "isInstantLocked": true, + "isChainLocked": true + }, + "f230a9414bf577d93d6f7f2515d9b549ede78cfba4168920892970fa8aa1eef8": { + "blockHash": "00000084b4d9e887a6ad3f37c576a17d79c35ec9301e55210eded519e8cdcd3a", + "height": 558102, + "isInstantLocked": true, + "isChainLocked": true + }, + "c3fb3620ebd1c7678879b40df1495cc86a179b5a6f9e48ce0b687a5c6f5a1db5": { + "blockHash": "000001953ea0bbb8ad04a9a1a2a707fef207ad22a712d7d3c619f0f9b63fa98c", + "height": 558229, + "isInstantLocked": true, + "isChainLocked": true + }, + "6f37b0d6284aab627c31c50e1c9d7cce39912dd4f2393f91734f794bc6408533": { + "blockHash": "000000dffb05c071a8c05082a475b7ce9c1e403f3b89895a6c448fe08535a5f5", + "height": 558230, + "isInstantLocked": true, + "isChainLocked": true + }, + "9cd3d44a87a7f99a33aebc6957105d5fb41698ef642189a36bac59ec0b5cd840": { + "blockHash": "0000016fb685b4b1efed743d2263de34a9f8323ed75e732654b1b951c5cb4dde", + "height": 558236, + "isInstantLocked": true, + "isChainLocked": true + }, + "6f76ca8038c6cb1b373bbbf80698afdc0d638e4a223be12a4feb5fd8e1801135": { + "blockHash": "000000444b3f2f02085f8befe72da5442c865c290658766cf935e1a71a4f4ba7", + "height": 558242, + "isInstantLocked": true, + "isChainLocked": true + }, + "e6b6f85a18d77974f376f05d6c96d0fdde990e733664248b1a00391565af6841": { + "blockHash": "000001f9c5de4d2b258a975bfbf7b9a3346890af6389512bea3cb6926b9be330", + "height": 558246, + "isInstantLocked": true, + "isChainLocked": true } + }, + "fees": { + "minRelay": -1 } } diff --git a/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/getFixtureAccountWithStorage.js b/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/getFixtureAccountWithStorage.js index 46955463144..9f678c9cc9a 100644 --- a/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/getFixtureAccountWithStorage.js +++ b/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/getFixtureAccountWithStorage.js @@ -2,32 +2,88 @@ const walletStoreMock = require('./wallet-store.json'); const chainStoreMock = require('./chain-store.json'); const Storage = require('../../../src/types/Storage/Storage'); const { KeyChainStore, DerivableKeyChain } = require('../../../src/index'); +const createPathsForTransactions = require('../../../src/types/Account/methods/createPathsForTransactions'); +const addPathsToStore = require("../../../src/types/Account/methods/addPathsToStore"); +const generateNewPaths = require("../../../src/types/Account/methods/generateNewPaths"); +const addDefaultPaths = require("../../../src/types/Account/methods/addDefaultPaths"); module.exports = (opts = {}) => { const { walletId } = walletStoreMock; - const mockedAccount = { + const mockedWallet = { + walletId, + storage: new Storage(), + keyChainStore: null + } + + mockedWallet.storage.createWalletStore(walletId); + mockedWallet.storage.createChainStore('testnet'); + + mockedWallet.keyChainStore = new KeyChainStore(); + mockedWallet.keyChainStore.addKeyChain(new DerivableKeyChain({ + mnemonic: 'apart trip dignity try point rocket damp reflect raw ten normal young', + }), { isMasterKeyChain: true }); + + const walletStore = mockedWallet.storage.getWalletStore(walletId); + walletStore.importState(walletStoreMock); + const chainStore = mockedWallet.storage.getChainStore('testnet'); + chainStore.importState(chainStoreMock); + + const mockedAccount0 = { walletId, index: 0, - storage: new Storage(), + storage: mockedWallet.storage, accountPath: "m/44'/1'/0'", network: 'testnet', walletType: 'hdwallet', ...opts, + addDefaultPaths, + createPathsForTransactions, + generateNewPaths, + addPathsToStore, + keyChainStore: null }; - mockedAccount.keyChainStore = new KeyChainStore(); - mockedAccount.keyChainStore.addKeyChain(new DerivableKeyChain({ - mnemonic: 'apart trip dignity try point rocket damp reflect raw ten normal young', - 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); + // This account is not participating directly in the mock. + // However, we must take it into consideration having in mind that it participates in account_transfer actions + const mockedAccount1 = { + walletId, + network: 'testnet', + index: 1, + accountPath: "m/44'/1'/1'", + keyChainStore: null, + storage: mockedWallet.storage, + addDefaultPaths, + addPathsToStore, + }; + + const accounts = [mockedAccount0, mockedAccount1]; + /** + * Fill path states for both accounts in wallet store + */ + accounts.forEach(account => { + walletStore.createPathState(account.accountPath); + }) + + /** + * Initialize key chain stores and default derivation paths for accounts + */ + accounts.forEach(account => { + account.keyChainStore = mockedWallet.keyChainStore + .makeChildKeyChainStore(account.accountPath, { + lookAheadOpts: { + paths: { + 'm/0': 20, + 'm/1': 20, + } + } + }); + + account.addDefaultPaths() + }) + + + mockedAccount0.createPathsForTransactions() - return mockedAccount; + return mockedAccount0; }; diff --git a/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/wallet-store.json b/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/wallet-store.json index e6858bf2dc3..6846110a1f6 100644 --- a/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/wallet-store.json +++ b/packages/wallet-lib/fixtures/wallets/apart-trip-dignity/wallet-store.json @@ -1,116 +1,6 @@ { "walletId": "d6143ef4e6", - "state": { - "mnemonic": null, - "paths": { - "m/44'/1'/1'": { - "path": "m/44'/1'/1'", - "addresses": { - "m/0/0": "yYJmzWey5kNecAThet5BFxAga1F4b4DKQ2", - "m/0/1": "yNCqctyQaq51WU1hN5aNwsgMsZ5fRiB7GY", - "m/0/2": "yNPbYz5cZKw2EwxtkL3VSVzPi2FYp9VKjQ", - "m/0/3": "ybsGWzsnSCAZufgSeUjScVxqEdved99UM2", - "m/0/4": "yfNHuPojk8XKWP5nuueDptX4nM7qToudgx", - "m/0/5": "yXxLnDkk6s8h1PSnYaFM6MAyRarc1Kc1rY", - "m/0/6": "yipResSzN2zUvL7UYkmptKKmQTv7sNssRn", - "m/0/7": "yZPtNwimHdRiKYbNQW49qezw1Kc1YwUJeT", - "m/0/8": "yPMjYYfQbga2nBiuqqfUyX41U1vwRZ8fG8", - "m/0/9": "yLueLWWcLQsaXQ8D5o9tcyo8tfTxMWXvG4", - "m/0/10": "yN8gzgsc1RVjXThMQT5qZH2jjpnMymz6zP", - "m/0/11": "yPQLWBNwMdLxUW2oUwHGwQtfyYxD41BARJ", - "m/0/12": "yg5g2AfWFdwWexWGfbSXYbUHf1y5WWrFPs", - "m/0/13": "yWyABu4naV1Jzw7w9sn1gqhebPRSkCndsS", - "m/0/14": "ycuUPzUBjhKyUjezQR1LNot79a6C4aRLaR", - "m/0/15": "yQ7YjvAXgDAUCekveHVjr6NBveXrUemVno", - "m/0/16": "yi8bghcw627cMGpuH4bJqH6bqR5ywv1NLH", - "m/0/17": "yizHu8i2rfwzwBgnJ62s2WUe6wLoDjne6N", - "m/0/18": "yW1u3tySeUKAKJsz7sjZFyjUiTyKLB6xBv", - "m/0/19": "yNaSkdy1Q8JNubUdbLMGsGf7sTRofEJYZq", - "m/1/0": "yXMrw79LPgu78EJsfGGYpm6fXKc1EMnQ49", - "m/1/1": "yh6Hcyipdvp6WJpQxjNbaXP4kzPQUJpY3n", - "m/1/2": "yNphpXuaTZRpU9FBh2W7NkUYcr3kBDE8me", - "m/1/3": "yXFppDT59xYD41mT2pmAdnvr7aZEFdgdrN", - "m/1/4": "yeKGAiiEHBGRujvLoYewA77jDDpeDamxvF", - "m/1/5": "yaxTG66CVzKgHhHZXojRHC9ztLTvz3fwdT", - "m/1/6": "yYw6qU7dwGoELZkSTj3oSKRpM4U8qTMc1U", - "m/1/7": "yQE2MksEnSfbeNre19oja9Jj8tvpj64C5a", - "m/1/8": "yaRnvHo8oLvVmv46vMj5XPbDJouQSnmcLT", - "m/1/9": "yj5ofWf2uYQQkSavYm2WXgu1QkaZCyP3Cm", - "m/1/10": "yUCjGmEwrHJwNDrE1o2rMre6MkSbiE6yz7", - "m/1/11": "yfJzd1nE2rEqz5XEurD6vs4ykizwmw9xTv", - "m/1/12": "yUk8U3jRZMHKVTa1eFDEtZpa1G4E13FP4d", - "m/1/13": "yMr59YWQFCADq4FbWrtxDUtMwwshSrmAyK", - "m/1/14": "yetSehBupzGS9yps5ogqARUGmTMAs2xVcQ", - "m/1/15": "yNcESKLwriNrhM6EyoSpZEXrzdY3uht92T", - "m/1/16": "yN2FihGU7KdaEspp39bKrhsHypeyeYzoM2", - "m/1/17": "yirpWLxHuhwFzA6LfUPKUh1Ke9RB9BUjit", - "m/1/18": "yVDN66vvdshWdNzhUaQNB6xExAHkzs1zj8", - "m/1/19": "yPzofnEhRVfDisL2nCUJtAoSHkuyMirHZS", - "m/0/20": "yjG4jiCMNbx3MyFCAsaNaU29CBVmgPf8hS", - "m/0/21": "yWrEYTtydaZCyJoKE1vzX4e4RXDNPGdnw9", - "m/1/20": "yM7Smr8HEhbRW4CXga898brATrJSsk1QUh", - "m/1/21": "yUprvaE5KrKVPgABmGtxba9MmZqDzKQHom" - } - }, - "m/44'/1'/0'": { - "path": "m/44'/1'/0'", - "addresses": { - "m/0/0": "yTwEca67QSkZ6axGdpNFzWPaCj8zqYybY7", - "m/0/1": "yercyhdN9oEkZcB9BsW5ktFaDxFEuK6qXN", - "m/0/2": "ygk3GCSba2J3L9G665Snozhj9HSkh5ByVE", - "m/0/3": "ybuL6rM6dgrKzCg8s99f3jxGuv5oz5JcDA", - "m/0/4": "ygHAVkMtYSqoTWHebDv7qkhMV6dHyuRsp2", - "m/0/5": "yMLhEsiP2ajSh8STmXnNmkWXtoHsmawZxd", - "m/0/6": "yj8rRKATAUHcAgXvNZekob58xKm2oNyvhv", - "m/0/7": "yhaAB6e8m3F8zmGX7WAVYa6eEfmSrrnY8x", - "m/0/8": "yiXh4Yo5djG6QH8WzXkKm5EFzqLRJWakXz", - "m/0/9": "yQYv3Um6DsdtANo1ZPTUte75wAGMstLRex", - "m/0/10": "yiYPJmu7eEm1cXUNumQRdjv1fvPhsfgMS4", - "m/0/11": "yii4aUZhNfL6EWN9KAgAFrJzGJmqHnF4wx", - "m/0/12": "yLpTquSct2SGz2Ka45uTPDd81Kzro2Jt2k", - "m/0/13": "yMiJtpzb1Qthy9TGnavsf5NZ6EZZa4j9q3", - "m/0/14": "yacgSfW7RkwWakEZPg8USAVdzCypiG3vxS", - "m/0/15": "yVvrmoRPFLy6nUpCQBT8ZExxF5wF3DhiGU", - "m/0/16": "yaJf2aG6cFUtfv4o6TuEKsh5kr4xq5iAY4", - "m/0/17": "yfardJQ4ucgWLKQPaRHGMRMbSGm5H4ExJR", - "m/0/18": "yLSCqx7dcM5JKR2fG7vHbF2axMvuYqomaw", - "m/0/19": "yVij8XpJ78LM5hepSV1KF7T8vRpUEXCpK5", - "m/1/0": "yNDpPsJqXKM36zHSNEW7c1zSvNnrZ699FY", - "m/1/1": "yLk4Hw3w4zDudrDVP6W8J9TggkY57zQUki", - "m/1/2": "yirJaK8KCE5YAmwvLadizqFw3TCXqBuZXL", - "m/1/3": "yhdRfg5gNr587dtEC4YYMcSHmLVEGqqtHc", - "m/1/4": "yYwKP1FQae5kbjXkmuirGx6Xzf8NzHpLqW", - "m/1/5": "yX9gmsm8aSxZZjYhq4w35aidT7qbhcpNjU", - "m/1/6": "ybgXCTGMHEBbQeUib8c3xAjtGAc12XtWiU", - "m/1/7": "yS31WpdMT2b34uL9C37fbUoACHhiupHCyP", - "m/1/8": "yTSpFqRoX3vyN286AUtKKhgmX5Xb41YKQe", - "m/1/9": "yQU5YsqN7psTTASuYbcMi7N5nNZGaxXb2X", - "m/1/10": "yVGGFj9BLgEab5rucSGLC6UGVLQKB4U1wJ", - "m/1/11": "yQCh5yYCHEbJzgSJE9rdHiqXHidKm3kwr5", - "m/1/12": "yX7T3Ac3yaLk5CTC5UaR93Fc7SjYkeT5hn", - "m/1/13": "yXx3WXq8kYNPbYEg5U6bL8Xfih4g5LCYVo", - "m/1/14": "yYnLMTz3jCi2KKKNuo3TVkEAGyUFg8tgkJ", - "m/1/15": "yiKa1dA6B4tSTNJqJP9Y5pQfQEffnQQDTL", - "m/1/16": "yf7vcuDnE9DVhXdMfBMQQTEi43otYQzkWE", - "m/1/17": "yTmSmocwERCeRHqNNG5SbpYKUra1HTmj8m", - "m/1/18": "yivUe5NeJsGsREwPQZUGYaTSwWB3E1oLcz", - "m/1/19": "ygfsZojdfW9UjCRU4ra95Aq6YgCC7UqZFx", - "m/1/20": "yU9fdXaUVtefwDZvxjJAr9xj1z2MtYi34A", - "m/1/21": "yXgMN6FgrgZCnTN1vhoZMh8afKMBmi3JC4", - "m/1/22": "yiqaCbXscvR8y3VFYMzdaKCaAGuDuZxMzt", - "m/0/20": "ydJpjuJGossAZR7S5oS7cWvjygEwoj8Xwp", - "m/0/21": "yW3TmWnmhvpxRbgFcQ8oXqDRkn3RhRH6jj", - "m/0/22": "yRegVX85DThKRkH8C61TtRacfzrkiBfNy5", - "m/0/23": "yPtDCqDFRe1JuDp8pvdiEMQMz2erGwS3VG", - "m/0/24": "yM9pSw3L4oBfG7uQL5o522Hu3WTvy9awgZ", - "m/0/25": "yNC6qYJYungzuk5XUynDFKCn54Dy8ngox4", - "m/0/26": "yR5KcLr1bceLT4teTk2qoJx6pFLik1zyzL", - "m/0/27": "yRrKLGJa9JmdjBWvrHtedKjHTao6CRDTKf", - "m/0/28": "yP5dShZBydpbEzgGoXL6kcjv2KzervRrYB", - "m/1/23": "ydcgWDxheSxrLAqDBP4JXBndMCzUNf77gq" - } - } - }, - "identities": {} + "lastKnownBlock": { + "height": 11703 } } diff --git a/packages/wallet-lib/fixtures/wallets/c922713eac.json b/packages/wallet-lib/fixtures/wallets/c922713eac.json deleted file mode 100644 index 3fac8277713..00000000000 --- a/packages/wallet-lib/fixtures/wallets/c922713eac.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "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/packages/wallet-lib/package.json b/packages/wallet-lib/package.json index 19690131fc1..56947172d9d 100644 --- a/packages/wallet-lib/package.json +++ b/packages/wallet-lib/package.json @@ -73,7 +73,6 @@ "karma-mocha-reporter": "^2.2.5", "karma-sourcemap-loader": "^0.3.7", "karma-webpack": "^5.0.0", - "localforage": "^1.7.3", "mocha": "^9.1.2", "node-inspect-extracted": "^1.0.8", "nyc": "^15.1.0", @@ -81,6 +80,7 @@ "path-browserify": "^1.0.1", "process": "^0.11.10", "sinon": "^11.1.2", + "sinon-chai": "^3.7.0", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", "string_decoder": "^1.3.0", diff --git a/packages/wallet-lib/src/plugins/Plugins/ChainPlugin.js b/packages/wallet-lib/src/plugins/Plugins/ChainPlugin.js index eccbfd61873..b7e5b19a37f 100644 --- a/packages/wallet-lib/src/plugins/Plugins/ChainPlugin.js +++ b/packages/wallet-lib/src/plugins/Plugins/ChainPlugin.js @@ -2,6 +2,7 @@ const logger = require('../../logger'); const { StandardPlugin } = require('..'); const EVENTS = require('../../EVENTS'); const { dashToDuffs } = require('../../utils'); +const ChainSyncMediator = require('../../types/Wallet/ChainSyncMediator'); const defaultOpts = { firstExecutionRequired: true, @@ -20,6 +21,7 @@ class ChainPlugin extends StandardPlugin { 'transport', 'fetchStatus', 'walletId', + 'chainSyncMediator', ], }; super(Object.assign(params, opts)); @@ -35,13 +37,12 @@ class ChainPlugin extends StandardPlugin { const self = this; const { network } = this.storage.application; const chainStore = this.storage.getChainStore(network); + const walletStore = this.storage.getWalletStore(this.walletId); if (!this.isSubscribedToBlocks) { self.transport.on(EVENTS.BLOCK, async (ev) => { 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 chainStore.importBlockHeader(block.header); }); self.transport.on(EVENTS.BLOCKHEIGHT_CHANGED, async (ev) => { const { payload: blockheight } = ev; @@ -51,6 +52,14 @@ class ChainPlugin extends StandardPlugin { }); chainStore.state.blockHeight = blockheight; + + // Update last known block for the wallet only if we are in the state of the incoming sync. + // (During the historical sync, it is populated from transactions metadata) + if (this.chainSyncMediator.state === ChainSyncMediator.STATES.CONTINUOUS_SYNC) { + walletStore.updateLastKnownBlock(blockheight); + this.storage.scheduleStateSave(); + } + logger.debug(`ChainPlugin - setting chain blockheight ${blockheight}`); }); await self.transport.subscribeToBlocks(); @@ -81,13 +90,11 @@ class ChainPlugin extends StandardPlugin { chainStore.state.fees.minRelay = dashToDuffs(relay); } - const bestBlock = await this.transport.getBlockHeaderByHeight(blocks); - await chainStore.importBlockHeader(bestBlock); - return true; } async onStart() { + this.chainSyncMediator.state = ChainSyncMediator.STATES.CHAIN_STATUS_SYNC; await this.execStatusFetch(); await this.execBlockListener(); } diff --git a/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js index f623a080e7d..84f85dd9d8f 100644 --- a/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js +++ b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker.js @@ -9,6 +9,7 @@ const Worker = require('../../Worker'); const isBrowser = require('../../../utils/isBrowser'); const logger = require('../../../logger'); +const ChainSyncMediator = require('../../../types/Wallet/ChainSyncMediator'); class TransactionSyncStreamWorker extends Worker { constructor(options) { @@ -32,6 +33,7 @@ class TransactionSyncStreamWorker extends Worker { 'index', 'BIP44PATH', 'walletType', + 'chainSyncMediator', ], ...options, }); @@ -41,6 +43,7 @@ class TransactionSyncStreamWorker extends Worker { this.incomingSyncPromise = null; this.pendingRequest = {}; this.delayedRequests = {}; + this.lastSyncedBlockHeight = -1; } /** @@ -140,18 +143,27 @@ class TransactionSyncStreamWorker extends Worker { if (skipSynchronization) { logger.debug('TransactionSyncStreamWorker - Wallet created from a new mnemonic. Sync from the best block height.'); const bestBlockHeight = this.storage.getChainStore(this.network.toString()).state.blockHeight; - this.setLastSyncedBlockHeight(bestBlockHeight); + this.setLastSyncedBlockHeight(bestBlockHeight, true); return; } - if (skipSynchronizationBeforeHeight) { + const { lastKnownBlock } = this.storage.getWalletStore(this.walletId).state; + const skipSyncBefore = typeof skipSynchronizationBeforeHeight === 'number' + ? skipSynchronizationBeforeHeight + : parseInt(skipSynchronizationBeforeHeight, 10); + + if (skipSyncBefore > lastKnownBlock.height) { this.setLastSyncedBlockHeight( skipSynchronizationBeforeHeight, ); + } else if (lastKnownBlock.height !== -1) { + this.setLastSyncedBlockHeight(lastKnownBlock.height); } + this.chainSyncMediator.state = ChainSyncMediator.STATES.HISTORICAL_SYNC; // We first need to sync up initial historical transactions await this.startHistoricalSync(this.network); + await this.storage.saveState(); } /** @@ -165,8 +177,12 @@ class TransactionSyncStreamWorker extends Worker { // We shouldn't block workers execution process with transaction syncing // it should proceed in background + this.chainSyncMediator.state = ChainSyncMediator.STATES.CONTINUOUS_SYNC; // noinspection ES6MissingAwait - this.incomingSyncPromise = this.startIncomingSync(); + this.incomingSyncPromise = this.startIncomingSync().catch((e) => { + logger.error('Error syncing incoming transactions', e); + this.emit('error', e); + }); } /** diff --git a/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js index e87d86e14aa..ec35d12f238 100644 --- a/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js +++ b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/getAddressesToSync.js @@ -1,3 +1,5 @@ +// TODO: consider obtaining addresses only for current account +// instead of the whole keychain module.exports = function getAddressesToSync() { return this.keyChainStore.getKeyChains() .map((keychain) => keychain.getWatchedAddresses()) diff --git a/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/getLastSyncedBlockHeight.js b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/getLastSyncedBlockHeight.js index 85e0a4aea13..632a517f31d 100644 --- a/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/getLastSyncedBlockHeight.js +++ b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/getLastSyncedBlockHeight.js @@ -3,10 +3,5 @@ * @return {number} */ module.exports = function getLastSyncedBlockHeight() { - let { blockHeight } = this.storage.application; - - // Fix Genesis issue on DCore - if (blockHeight === 0) blockHeight = 1; - - return blockHeight; + return this.lastSyncedBlockHeight; }; diff --git a/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/processChunks.js b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/processChunks.js index f0b5975942b..9a6a904b810 100644 --- a/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/processChunks.js +++ b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/processChunks.js @@ -33,7 +33,7 @@ async function processChunks(dataChunk) { // When a transaction exist, there is multiple things we need to do : // 1) The transaction itself needs to be imported - const addressesGeneratedCount = await self + const { addressesGenerated: addressesGeneratedCount } = await self .importTransactions(addressesTransactionsWithoutMetadata); // 2) Transaction metadata need to be fetched and imported as well. // as such event might happen in the future @@ -50,7 +50,17 @@ async function processChunks(dataChunk) { .all(awaitingMetadataPromises) .then(async (transactionsWithMetadata) => { // Import into account - await self.importTransactions(transactionsWithMetadata); + const { mostRecentHeight } = await self.importTransactions(transactionsWithMetadata); + + if (mostRecentHeight !== -1) { + this.setLastSyncedBlockHeight(mostRecentHeight, true); + } + + // Schedule save state after all chain data has been imported + this.storage.scheduleStateSave(); + }) + .catch((err) => { + logger.error('Error while importing transactions', err); }); self.hasReachedGapLimit = self.hasReachedGapLimit || addressesGeneratedCount > 0; diff --git a/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/setLastSyncedBlockHeight.js b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/setLastSyncedBlockHeight.js index c54075b44e4..209bfe84b0f 100644 --- a/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/setLastSyncedBlockHeight.js +++ b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/setLastSyncedBlockHeight.js @@ -2,11 +2,22 @@ * Set last synced block height * * @param {number} blockHeight + * @param {boolean} [updateWalletState=false] * @return {number} */ -module.exports = function setLastSyncedBlockHeight(blockHeight) { - const applicationStore = this.storage.application; - applicationStore.blockHeight = blockHeight; +module.exports = function setLastSyncedBlockHeight(blockHeight, updateWalletState = false) { + if (this.lastSyncedBlockHeight >= blockHeight) { + return this.lastSyncedBlockHeight; + } - return applicationStore.blockHeight; + this.lastSyncedBlockHeight = blockHeight; + + // TODO: consider getting rid of a side effect of storage update to make this a pure function + if (updateWalletState) { + const walletStore = this.storage.getWalletStore(this.walletId); + walletStore.updateLastKnownBlock(blockHeight); + this.storage.scheduleStateSave(); + } + + return blockHeight; }; diff --git a/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/startHistoricalSync.js b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/startHistoricalSync.js index 5baf4d478ba..1586d382361 100644 --- a/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/startHistoricalSync.js +++ b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/startHistoricalSync.js @@ -16,20 +16,15 @@ const GRPC_RETRY_ERRORS = [ * @return {Promise} */ module.exports = async function startHistoricalSync(network) { - const lastSyncedBlockHash = this.getLastSyncedBlockHash(); const bestBlockHeight = await this.getBestBlockHeightFromTransport(); const lastSyncedBlockHeight = await this.getLastSyncedBlockHeight(); - const count = bestBlockHeight - lastSyncedBlockHeight || 1; + const fromBlockHeight = lastSyncedBlockHeight > 0 ? lastSyncedBlockHeight : 1; + const count = bestBlockHeight - fromBlockHeight || 1; const start = +new Date(); try { const options = { count, network }; - // If there's no blocks synced, start from height 0, otherwise from the last block hash. - if (lastSyncedBlockHash == null) { - options.fromBlockHeight = lastSyncedBlockHeight; - } else { - options.fromBlockHash = lastSyncedBlockHash; - } + options.fromBlockHeight = lastSyncedBlockHeight > 0 ? lastSyncedBlockHeight : 1; logger.debug(`TransactionSyncStreamWorker - HistoricalSync - Started from ${options.fromBlockHash || options.fromBlockHeight}, count: ${count}`); const gapLimitIsReached = await this.syncUpToTheGapLimit(options); @@ -60,7 +55,7 @@ module.exports = async function startHistoricalSync(network) { }); } - this.setLastSyncedBlockHeight(bestBlockHeight); + this.setLastSyncedBlockHeight(bestBlockHeight, true); logger.debug(`TransactionSyncStreamWorker - HistoricalSync - Synchronized ${count} in ${+new Date() - start}ms`); }; diff --git a/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/startIncomingSync.js b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/startIncomingSync.js index 221d9b244b6..66a911c063d 100644 --- a/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/startIncomingSync.js +++ b/packages/wallet-lib/src/plugins/Workers/TransactionSyncStreamWorker/methods/startIncomingSync.js @@ -12,27 +12,19 @@ const GRPC_RETRY_ERRORS = [ module.exports = async function startIncomingSync() { const { network } = this; - const lastSyncedBlockHash = await this.getLastSyncedBlockHash(); const lastSyncedBlockHeight = await this.getLastSyncedBlockHeight(); const count = 0; try { const options = { count, network }; - // If there's no blocks synced, start from height 0, otherwise from the last block hash. - if (lastSyncedBlockHash == null) { - options.fromBlockHeight = lastSyncedBlockHeight; - } else { - options.fromBlockHash = lastSyncedBlockHash; - } + options.fromBlockHeight = lastSyncedBlockHeight > 0 ? lastSyncedBlockHeight : 1; await this.syncUpToTheGapLimit(options); // The method above resolves only in two cases: the limit is reached or the server is closed. // In both cases, the stream needs to be restarted, unless syncIncomingTransactions is // set to false, which is signalling the worker not to restart stream. if (this.syncIncomingTransactions) { - logger.debug('TransactionSyncStreamWorker - IncomingSync - Restarted from', (lastSyncedBlockHash) - ? `hash: ${lastSyncedBlockHash}` - : `height: ${lastSyncedBlockHeight}`); + logger.debug(`TransactionSyncStreamWorker - IncomingSync - Restarted from height: ${lastSyncedBlockHeight}`); await startIncomingSync.call(this); } @@ -40,9 +32,7 @@ module.exports = async function startIncomingSync() { this.stream = null; if (GRPC_RETRY_ERRORS.includes(e.code)) { - logger.debug('TransactionSyncStreamWorker - IncomingSync - Restarted from', (lastSyncedBlockHash) - ? `hash: ${lastSyncedBlockHash}` - : `height: ${lastSyncedBlockHeight}`); + logger.debug(`TransactionSyncStreamWorker - IncomingSync - Restarted from height: ${lastSyncedBlockHeight}`); if (this.syncIncomingTransactions) { await startIncomingSync.call(this); diff --git a/packages/wallet-lib/src/test/bootstrap.js b/packages/wallet-lib/src/test/bootstrap.js index 7ed8f32f31e..57620157fd1 100644 --- a/packages/wallet-lib/src/test/bootstrap.js +++ b/packages/wallet-lib/src/test/bootstrap.js @@ -1,6 +1,10 @@ +const { use } = require('chai'); const path = require('path'); const dotenvSafe = require('dotenv-safe'); const sinon = require('sinon'); +const sinonChai = require('sinon-chai'); + +use(sinonChai); dotenvSafe.config({ path: path.resolve(__dirname, '..', '..', '.env'), diff --git a/packages/wallet-lib/src/test/mocks/TransportMock.js b/packages/wallet-lib/src/test/mocks/TransportMock.js index e1c51bbdfb6..9d32756698a 100644 --- a/packages/wallet-lib/src/test/mocks/TransportMock.js +++ b/packages/wallet-lib/src/test/mocks/TransportMock.js @@ -1,7 +1,9 @@ +const EventEmitter = require('events'); const getStatus = require('../../transport/FixtureTransport/methods/getStatus'); -class TransportMock { +class TransportMock extends EventEmitter { constructor(sinonSandbox, transactionStreamMock) { + super(); this.sinonSandbox = sinonSandbox; this.getBestBlockHeight = sinonSandbox.stub().returns(42); @@ -16,7 +18,6 @@ class TransportMock { bits: 503385436, nonce: 351770, }); - this.on = sinonSandbox.stub(); this.subscribeToBlocks = sinonSandbox.stub(); this.getIdentityIdsByPublicKeyHash = sinonSandbox.stub().returns([null]); this.sendTransaction = sinonSandbox.stub(); diff --git a/packages/wallet-lib/src/test/mocks/TxStreamMock.js b/packages/wallet-lib/src/test/mocks/TxStreamMock.js index f4fd1ca2e8c..bcde9fe36f9 100644 --- a/packages/wallet-lib/src/test/mocks/TxStreamMock.js +++ b/packages/wallet-lib/src/test/mocks/TxStreamMock.js @@ -1,4 +1,5 @@ const EventEmitter = require('events'); +const TxStreamDataResponseMock = require('./TxStreamDataResponseMock'); class TxStreamMock extends EventEmitter { constructor() { @@ -20,6 +21,16 @@ class TxStreamMock extends EventEmitter { this.emit('end'); this.removeAllListeners(); } + + sendTransactions(transactions) { + this.emit(TxStreamMock.EVENTS.data, new TxStreamDataResponseMock({ + rawTransactions: transactions.map((tx) => tx.toBuffer()), + })); + } + + finish() { + this.emit(TxStreamMock.EVENTS.end); + } } TxStreamMock.EVENTS = { diff --git a/packages/wallet-lib/src/test/mocks/createTransactionInAccount.js b/packages/wallet-lib/src/test/mocks/createTransactionInAccount.js index 52496b44e31..3efca85848d 100644 --- a/packages/wallet-lib/src/test/mocks/createTransactionInAccount.js +++ b/packages/wallet-lib/src/test/mocks/createTransactionInAccount.js @@ -16,7 +16,10 @@ async function createTransactionInAccount(account) { }]) .to(account.getAddress(10).address, 100000); - await account.importTransactions([[walletTransaction.serialize(true)]]); + await account.importTransactions([[walletTransaction.serialize(true), { + height: 100, + blockHash: '0000000000000000000000000000000000000000000000000000000000000000', + }]]); // console.log(account.storage.wallets.get('361032c8a0').state.paths) return walletTransaction; } diff --git a/packages/wallet-lib/src/test/mocks/mockAccountWithStorage.js b/packages/wallet-lib/src/test/mocks/mockAccountWithStorage.js deleted file mode 100644 index 1e5b300ddf6..00000000000 --- a/packages/wallet-lib/src/test/mocks/mockAccountWithStorage.js +++ /dev/null @@ -1,32 +0,0 @@ -const walletStoreMock = require('../../../fixtures/wallets/c922713eac.json'); -const chainStoreMock = require('../../../fixtures/chains/for_wallet_c922713eac.json'); -const Storage = require('../../types/Storage/Storage'); -const { KeyChainStore, DerivableKeyChain } = 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 DerivableKeyChain({ - 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/packages/wallet-lib/src/test/utils.js b/packages/wallet-lib/src/test/utils.js new file mode 100644 index 00000000000..14fd883c7c3 --- /dev/null +++ b/packages/wallet-lib/src/test/utils.js @@ -0,0 +1,14 @@ +const waitOneTick = () => new Promise((resolve) => { + if (typeof setImmediate === 'undefined') { + setTimeout(resolve, 10); + } else { + setImmediate(resolve); + } +}); + +const wait = (timeout) => new Promise(((resolve) => setTimeout(resolve, timeout))); + +module.exports = { + waitOneTick, + wait, +}; diff --git a/packages/wallet-lib/src/types/Account/Account.js b/packages/wallet-lib/src/types/Account/Account.js index f2a7d6ee900..e726584c2ad 100644 --- a/packages/wallet-lib/src/types/Account/Account.js +++ b/packages/wallet-lib/src/types/Account/Account.js @@ -58,6 +58,7 @@ class Account extends EventEmitter { this.walletId = wallet.walletId; this.identities = wallet.identities; + this.chainSyncMediator = wallet.chainSyncMediator; this.state = { isInitialized: false, @@ -396,7 +397,10 @@ Account.prototype.hasPlugins = require('./methods/hasPlugins'); Account.prototype.injectPlugin = require('./methods/injectPlugin'); Account.prototype.importTransactions = require('./methods/importTransactions'); Account.prototype.importBlockHeader = require('./methods/importBlockHeader'); - +Account.prototype.createPathsForTransactions = require('./methods/createPathsForTransactions'); +Account.prototype.generateNewPaths = require('./methods/generateNewPaths'); +Account.prototype.addPathsToStore = require('./methods/addPathsToStore'); +Account.prototype.addDefaultPaths = require('./methods/addDefaultPaths'); Account.prototype.sign = require('./methods/sign'); module.exports = Account; diff --git a/packages/wallet-lib/src/types/Account/_initializeAccount.js b/packages/wallet-lib/src/types/Account/_initializeAccount.js index adee1ea8a00..ee9daa9d7c4 100644 --- a/packages/wallet-lib/src/types/Account/_initializeAccount.js +++ b/packages/wallet-lib/src/types/Account/_initializeAccount.js @@ -6,20 +6,11 @@ const preparePlugins = require('./_preparePlugins'); async function _initializeAccount(account, userUnsafePlugins) { const self = account; - const accountStore = account.storage - .getWalletStore(account.walletId) - .getPathState(account.accountPath); + account.addDefaultPaths(); - 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()); - }); + // Issue additional derivation paths in case we have transactions in the store + // at the moment of initialization (from persistent storage) + account.createPathsForTransactions(); // We run faster in offlineMode to speed up the process when less happens. const readinessIntervalTime = (account.offlineMode) ? 50 : 200; @@ -53,13 +44,11 @@ async function _initializeAccount(account, userUnsafePlugins) { watchedPlugins.forEach((pluginName) => { const watchedPlugin = account.plugins.watchers[pluginName]; if (watchedPlugin.ready === true && !watchedPlugin.announced) { - logger.debug(`Initializing - ${readyPlugins}/${watchedPlugins.length} plugins`); readyPlugins += 1; watchedPlugin.announced = true; logger.debug(`Initialized ${pluginName} - ${readyPlugins}/${watchedPlugins.length} plugins`); } }); - logger.debug(`Initializing - ${readyPlugins}/${watchedPlugins.length} plugins`); if (readyPlugins === watchedPlugins.length) { // At this stage, our worker are initialized sendInitialized(); diff --git a/packages/wallet-lib/src/types/Account/_preparePlugins.js b/packages/wallet-lib/src/types/Account/_preparePlugins.js index 5814071b78c..470f9c8255a 100644 --- a/packages/wallet-lib/src/types/Account/_preparePlugins.js +++ b/packages/wallet-lib/src/types/Account/_preparePlugins.js @@ -1,9 +1,25 @@ const sortPlugins = require('./_sortPlugins'); +const logger = require('../../logger'); const preparePlugins = function preparePlugins(account, userUnsafePlugins) { function reducer(accumulatorPromise, [plugin, allowSensitiveOperation, awaitOnInjection]) { return accumulatorPromise - .then(() => account.injectPlugin(plugin, allowSensitiveOperation, awaitOnInjection)); + .then(async () => { + try { + await account.injectPlugin( + plugin, + allowSensitiveOperation, + awaitOnInjection, + ); + } catch (e) { + logger.error('Error injecting plugin', e); + this.emit('error', e, { + type: 'plugin', + pluginType: 'plugin', + pluginName: plugin.name, + }); + } + }); } return new Promise((resolve, reject) => { diff --git a/packages/wallet-lib/src/types/Account/methods/addDefaultPaths.js b/packages/wallet-lib/src/types/Account/methods/addDefaultPaths.js new file mode 100644 index 00000000000..f9428ce0158 --- /dev/null +++ b/packages/wallet-lib/src/types/Account/methods/addDefaultPaths.js @@ -0,0 +1,13 @@ +/** + * Adds info about default derivation paths to the wallet and chain stores + */ +function addDefaultPaths() { + const defaultPaths = this.keyChainStore + .getMasterKeyChain() + .getIssuedPaths(); + + // Add default keychain paths to the account and chain store + this.addPathsToStore(defaultPaths, true); +} + +module.exports = addDefaultPaths; diff --git a/packages/wallet-lib/src/types/Account/methods/addPathsToStore.js b/packages/wallet-lib/src/types/Account/methods/addPathsToStore.js new file mode 100644 index 00000000000..d8bd5e5146c --- /dev/null +++ b/packages/wallet-lib/src/types/Account/methods/addPathsToStore.js @@ -0,0 +1,20 @@ +/** + * Adds info about derivation paths to the wallet and chain stores + * @param paths - list of new derivation paths + * @param refreshUTXOState - a flag to trigger side effect in importAddress function + */ +function addPathsToStore(paths, refreshUTXOState = true) { + const accountStore = this.storage + .getWalletStore(this.walletId) + .getPathState(this.accountPath); + + const chainStore = this.storage.getChainStore(this.network); + + paths.forEach((path, i, self) => { + accountStore.addresses[path.path] = path.address.toString(); + const reconsiderTransactions = refreshUTXOState && i === self.length - 1; + chainStore.importAddress(path.address.toString(), reconsiderTransactions); + }); +} + +module.exports = addPathsToStore; diff --git a/packages/wallet-lib/src/types/Account/methods/createPathsForTransactions.js b/packages/wallet-lib/src/types/Account/methods/createPathsForTransactions.js new file mode 100644 index 00000000000..22fb045aae8 --- /dev/null +++ b/packages/wallet-lib/src/types/Account/methods/createPathsForTransactions.js @@ -0,0 +1,38 @@ +const sortTransactions = require('../../../utils/sortTransactions'); + +/** + * Function goes through all transactions, and ensures address gap + * having in mind addresses already used by the account. + */ +function createPathsForTransactions() { + const chainStore = this.storage.getChainStore(this.network); + const transactions = [...chainStore.getTransactions().values()]; + + const sortedTransactions = sortTransactions(transactions); + + sortedTransactions.forEach((transaction, i, self) => { + // Update the state of UTXO for a given transaction + const { inputs, outputs } = transaction; + + const affectedAddresses = []; + [...inputs, ...outputs].forEach((element) => { + if (element.script) { + const address = element.script.toAddress(this.network).toString(); + if (chainStore.getAddress(address)) { + affectedAddresses.push(address); + } + } + }); + + // Generate new addresses in case the current set reached it's limit + // and add them to store + const paths = this.generateNewPaths(affectedAddresses); + + if (paths && paths.length) { + const refreshUTXOState = i === self.length - 1; + this.addPathsToStore(paths, refreshUTXOState); + } + }); +} + +module.exports = createPathsForTransactions; diff --git a/packages/wallet-lib/src/types/Account/methods/generateNewPaths.js b/packages/wallet-lib/src/types/Account/methods/generateNewPaths.js new file mode 100644 index 00000000000..b43ad7362f6 --- /dev/null +++ b/packages/wallet-lib/src/types/Account/methods/generateNewPaths.js @@ -0,0 +1,21 @@ +/** + * Marks addresses as used and generates new ones if needed + * @param {string[]} addresses + */ +function generateNewPaths(addresses) { + let issuedPaths = []; + const keyChains = this.keyChainStore.getKeyChains(); + + addresses.forEach((address) => { + keyChains.forEach((keyChain) => { + const keyChainIssuedPaths = keyChain.markAddressAsUsed(address); + if (keyChainIssuedPaths.length > 0) { + issuedPaths = issuedPaths.concat(keyChainIssuedPaths); + } + }); + }); + + return issuedPaths; +} + +module.exports = generateNewPaths; diff --git a/packages/wallet-lib/src/types/Account/methods/getConfirmedBalance.spec.js b/packages/wallet-lib/src/types/Account/methods/getConfirmedBalance.spec.js index d8c6f4b6590..0c0c0e0d294 100644 --- a/packages/wallet-lib/src/types/Account/methods/getConfirmedBalance.spec.js +++ b/packages/wallet-lib/src/types/Account/methods/getConfirmedBalance.spec.js @@ -2,24 +2,24 @@ const { expect } = require('chai'); const getTotalBalance = require('./getTotalBalance'); const getConfirmedBalance = require('./getConfirmedBalance'); const getUnconfirmedBalance = require('./getUnconfirmedBalance'); -const mockAccountWithStorage = require("../../../test/mocks/mockAccountWithStorage"); +const getFixtureHDAccountWithStorage = require("../../../../fixtures/wallets/apart-trip-dignity/getFixtureAccountWithStorage"); let mockedAccount; describe('Account - getTotalBalance', function suite() { this.timeout(10000); before(() => { - mockedAccount = mockAccountWithStorage() + mockedAccount = getFixtureHDAccountWithStorage(); }); it('should correctly get the balance', () => { const balance = getTotalBalance.call(mockedAccount); - expect(balance).to.equal(224108673); + expect(balance).to.equal(667198249); }); it('should correctly get the balance confirmed only', () => { const balance = getConfirmedBalance.call(mockedAccount); - expect(balance).to.equal(224108673); + expect(balance).to.equal(667198249); }); // TODO: file looks like a complete duplicate of the getTotalBalance.spec.js @@ -29,8 +29,8 @@ describe('Account - getTotalBalance', function suite() { const balanceUnconfDash = getUnconfirmedBalance.call(mockedAccount, false); const balanceConfDash = getConfirmedBalance.call(mockedAccount, false); - expect(balanceTotalDash).to.equal(2.24108673); + expect(balanceTotalDash).to.equal(6.67198249); expect(balanceUnconfDash).to.equal(0); - expect(balanceConfDash).to.equal(2.24108673); + expect(balanceConfDash).to.equal(6.67198249); }); }); diff --git a/packages/wallet-lib/src/types/Account/methods/getTotalBalance.spec.js b/packages/wallet-lib/src/types/Account/methods/getTotalBalance.spec.js index 0368595db06..443e93a2fa9 100644 --- a/packages/wallet-lib/src/types/Account/methods/getTotalBalance.spec.js +++ b/packages/wallet-lib/src/types/Account/methods/getTotalBalance.spec.js @@ -2,29 +2,29 @@ const { expect } = require('chai'); const getTotalBalance = require('./getTotalBalance'); const getConfirmedBalance = require('./getConfirmedBalance'); const getUnconfirmedBalance = require('./getUnconfirmedBalance'); -const mockAccountWithStorage = require("../../../test/mocks/mockAccountWithStorage"); +const getFixtureHDAccountWithStorage = require("../../../../fixtures/wallets/apart-trip-dignity/getFixtureAccountWithStorage"); let mockedAccount; describe('Account - getTotalBalance', function suite() { this.timeout(10000); before(() => { - mockedAccount = mockAccountWithStorage() + mockedAccount = getFixtureHDAccountWithStorage(); }); it('should correctly get the balance',() => { const balance = getTotalBalance.call(mockedAccount); - expect(balance).to.equal(224108673); + expect(balance).to.equal(667198249); }); it('should correctly get the balance confirmed only', () => { const balance = getConfirmedBalance.call(mockedAccount); - expect(balance).to.equal(224108673); + expect(balance).to.equal(667198249); }); 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(balanceTotalDash).to.equal(6.67198249); expect(balanceUnconfDash).to.equal(0); - expect(balanceConfDash).to.equal(2.24108673); + expect(balanceConfDash).to.equal(6.67198249); }); }); diff --git a/packages/wallet-lib/src/types/Account/methods/getTransaction.spec.js b/packages/wallet-lib/src/types/Account/methods/getTransaction.spec.js index ea65ce935f1..06f1c1d0d92 100644 --- a/packages/wallet-lib/src/types/Account/methods/getTransaction.spec.js +++ b/packages/wallet-lib/src/types/Account/methods/getTransaction.spec.js @@ -1,31 +1,31 @@ const { expect } = require('chai'); const getTransaction = require('./getTransaction'); -const mockAccountWithStorage = require("../../../test/mocks/mockAccountWithStorage"); +const getFixtureHDAccountWithStorage = require("../../../../fixtures/wallets/apart-trip-dignity/getFixtureAccountWithStorage"); let mockedAccount; let fetchTransactionInfoCalledNb = 0; describe('Account - getTransaction', function suite() { this.timeout(10000); before(() => { - mockedAccount = mockAccountWithStorage({ - transport: { - getTransaction: () => { - fetchTransactionInfoCalledNb += 1; - return null - }, - } - }); + mockedAccount = getFixtureHDAccountWithStorage(); + + mockedAccount.transport = { + getTransaction: () => { + fetchTransactionInfoCalledNb += 1; + return null + }, + } }); it('should correctly get a existing transaction', async () => { - const tx = await getTransaction.call(mockedAccount, 'c8f0d780e6cedb9c37724f98cc3fecd6d5ad314db28e3cd439184bf25196ceb4'); + const tx = await getTransaction.call(mockedAccount, 'a43845e580ad01f31bc06ce47ab39674e40316c4c6b765b6e54d6d35777ef456'); expect(tx.transaction.toObject()).to.deep.equal(expectedTx); expect(tx.metadata).to.deep.equal({ - blockHash: '000001810cf3b49ed94ae033f007923d3c243077f5f9e24b559b536087e8b960', - height: 615751, - isInstantLocked: null, - isChainLocked: null + "blockHash": "000001deee9f99e8219a9abcaaea135dbaae8a9b0f1ea214e6b6a37a5c5b115d", + "height": 555506, + "isInstantLocked": true, + "isChainLocked": true }); }); @@ -38,27 +38,69 @@ describe('Account - getTransaction', function suite() { }); const expectedTx = { - hash: 'c8f0d780e6cedb9c37724f98cc3fecd6d5ad314db28e3cd439184bf25196ceb4', - version: 3, - inputs: [ + "hash": "a43845e580ad01f31bc06ce47ab39674e40316c4c6b765b6e54d6d35777ef456", + "version": 2, + "inputs": [ + { + "prevTxId": "11802a0d6221636a93023f73750946ace488a79d3074ba93abb4edc19bf91efd", + "outputIndex": 0, + "sequenceNumber": 4294967294, + "script": "483045022100dfb220a840d597179abdf49692ad64c1c0da785041975b00aee03c9625639cf202204d06eade5cca19fab1e10b1d6e1b67c77626a0e88bb4d5f61bd57293b4b64217012102295ecb812ccf52deaf304bebfe3a59a644f05bac81241ea1e3a2f8750064cbf6", + "scriptString": "72 0x3045022100dfb220a840d597179abdf49692ad64c1c0da785041975b00aee03c9625639cf202204d06eade5cca19fab1e10b1d6e1b67c77626a0e88bb4d5f61bd57293b4b6421701 33 0x02295ecb812ccf52deaf304bebfe3a59a644f05bac81241ea1e3a2f8750064cbf6" + }, + { + "prevTxId": "19953851c7a425045d3b6b4f56b7d5116fc1648444e5c37eba29ea65ee264269", + "outputIndex": 0, + "sequenceNumber": 4294967294, + "script": "483045022100beff3263b7c99720e99af9ec146c818701efb0130603f1570f427b74aef8521802202e660bb9f7ea156f91addd5fe47cbd2c2bf388cc6e1eff3a39adffd89d26d346012102c33942799f7cbf4a7d12f1b3e52cb80cc4de083b997d3e63915df9973d5bce2a", + "scriptString": "72 0x3045022100beff3263b7c99720e99af9ec146c818701efb0130603f1570f427b74aef8521802202e660bb9f7ea156f91addd5fe47cbd2c2bf388cc6e1eff3a39adffd89d26d34601 33 0x02c33942799f7cbf4a7d12f1b3e52cb80cc4de083b997d3e63915df9973d5bce2a" + }, + { + "prevTxId": "2dc8e2adfb30902269fa77dbf0de94f1f04ab3e8b1dbe1dd074a39a864993e96", + "outputIndex": 0, + "sequenceNumber": 4294967294, + "script": "483045022100ff67776932e7a32520aa131f76bdfd6737650ad3b11edbdf466cca83f691b0e60220633bcbedebacffd53ceb7e9cdbd47928d7c2849f49ac1f8efb9f384c1a4ee46301210371c0bc42e08de059a8829730abb16f3d40cff87e5ad85d65c4a0a949d9c4b524", + "scriptString": "72 0x3045022100ff67776932e7a32520aa131f76bdfd6737650ad3b11edbdf466cca83f691b0e60220633bcbedebacffd53ceb7e9cdbd47928d7c2849f49ac1f8efb9f384c1a4ee46301 33 0x0371c0bc42e08de059a8829730abb16f3d40cff87e5ad85d65c4a0a949d9c4b524" + }, + { + "prevTxId": "40cf2327c923487ce9789c58a1273ddd9bb87a8d30975dc298335c125065e11f", + "outputIndex": 0, + "sequenceNumber": 4294967294, + "script": "483045022100cca348c7ab16fac28b3bba502be54a9e3766b7da9821a90605f370b75840569702207d082510aa493988e09da046355b018781718208f8a954e14ea33d608ae59625012103699b9402e109ed9d0c67c6a45be5cf5f1236c44bb9fc4b07a2f3392ba0b64172", + "scriptString": "72 0x3045022100cca348c7ab16fac28b3bba502be54a9e3766b7da9821a90605f370b75840569702207d082510aa493988e09da046355b018781718208f8a954e14ea33d608ae5962501 33 0x03699b9402e109ed9d0c67c6a45be5cf5f1236c44bb9fc4b07a2f3392ba0b64172" + }, + { + "prevTxId": "4bb38b9207953d4658c64e6ad986eab05a42e50c72bd0f3bf07d7dd8b31f25ce", + "outputIndex": 0, + "sequenceNumber": 4294967294, + "script": "47304402203ae564ff74b08b1f96bf857f51448434418d747a02039ec1ee109a4f5d8e8106022072f8769bd175416d22f44011f7e67aec301f08573c9937be7e4a09c394c7396601210311bae874933a4503a61d1c8c2e5b57b1a278d28d4892af4bd79ab8a731495265", + "scriptString": "71 0x304402203ae564ff74b08b1f96bf857f51448434418d747a02039ec1ee109a4f5d8e8106022072f8769bd175416d22f44011f7e67aec301f08573c9937be7e4a09c394c7396601 33 0x0311bae874933a4503a61d1c8c2e5b57b1a278d28d4892af4bd79ab8a731495265" + }, + { + "prevTxId": "b21e8513b29a43b3169b857c466cc626859d76e374fc5dc7771f4a0df8fe2daf", + "outputIndex": 0, + "sequenceNumber": 4294967294, + "script": "47304402200b49b7059064efb57df453dc2d20002f09b5266bc825760ef81624771f13920802200782616b8c4fb7b5eff94fdf865e6ddc4530d3932b97fdc3a747e8c451f0314c012103a94131f28f8efd67f47f2496ff6e8d9069a3a7df97202a33e90e16f257d03729", + "scriptString": "71 0x304402200b49b7059064efb57df453dc2d20002f09b5266bc825760ef81624771f13920802200782616b8c4fb7b5eff94fdf865e6ddc4530d3932b97fdc3a747e8c451f0314c01 33 0x03a94131f28f8efd67f47f2496ff6e8d9069a3a7df97202a33e90e16f257d03729" + }, { - prevTxId: 'a43f20bcb46fef22926745a27ca38f63c773f2c7c4cbb55aaf184b58b3755965', - outputIndex: 0, - sequenceNumber: 4294967295, - script: '48304502210080185b616b2f8cb013e264b66146954cdc1597053ea8238467b896413b836039022071e71147dc45fc2dbde666c2774912c5bfd00b761268a3a4f8951375eb895c310121029671ae86f7eeb5c127568fddc977a1cce1f76ad64efa83c8ec9fcaef08ea9738', - scriptString: '72 0x304502210080185b616b2f8cb013e264b66146954cdc1597053ea8238467b896413b836039022071e71147dc45fc2dbde666c2774912c5bfd00b761268a3a4f8951375eb895c3101 33 0x029671ae86f7eeb5c127568fddc977a1cce1f76ad64efa83c8ec9fcaef08ea9738' + "prevTxId": "d6fd2b6ea7d186a2211076188594cacb61df415051876fa198ca4c2205ef4f34", + "outputIndex": 0, + "sequenceNumber": 4294967294, + "script": "47304402202a24d1123775641269c6f748d3e4dad08a682e4e334a9b73c7df84f6c22e8e7d022022c0cc2225d3f14cb58a6fb3e4bf23c0f33252d9040a9ca9ef66eb17742a476f01210347301de4c9ba7f46b0f27cb82ae70a73749821e2951d3c87c2f0d56648635d1c", + "scriptString": "71 0x304402202a24d1123775641269c6f748d3e4dad08a682e4e334a9b73c7df84f6c22e8e7d022022c0cc2225d3f14cb58a6fb3e4bf23c0f33252d9040a9ca9ef66eb17742a476f01 33 0x0347301de4c9ba7f46b0f27cb82ae70a73749821e2951d3c87c2f0d56648635d1c" } ], - outputs: [ + "outputs": [ { - satoshis: 10000, - script: '76a9141ec5c66e9789c655ae068d35088b4073345fe0b088ac' + "satoshis": 1823313, + "script": "76a91440ca54360086cc0fbd69d862db58ab2b6d22805888ac" }, { - satoshis: 224508306, - script: '76a9147f6f4280f91e00126d927466cd48629439b763fa88ac' + "satoshis": 187980000, + "script": "76a914538da44e7136cc994023d89a7b4b3d02ac0e573988ac" } ], - nLockTime: 0 -}; + "nLockTime": 555505 +} diff --git a/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.spec.js b/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.spec.js index e9fc1adc585..7caae444622 100644 --- a/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.spec.js +++ b/packages/wallet-lib/src/types/Account/methods/getTransactionHistory.spec.js @@ -1,79 +1,12 @@ const {expect} = require('chai'); -const {Transaction, BlockHeader} = require('@dashevo/dashcore-lib'); -const {WALLET_TYPES} = require('../../../CONSTANTS'); const getTransactions = require('./getTransactions'); const getTransactionHistory = require('./getTransactionHistory'); -const mockedStoreHDWallet = require('../../../../fixtures/duringdevelop-fullstore-snapshot-1548538361'); -const mockedStoreSingleAddress = require('../../../../fixtures/da07-fullstore-snapshot-1548533266'); - +const getTotalBalance = require('./getTotalBalance'); const getFixtureHDAccountWithStorage = require('../../../../fixtures/wallets/apart-trip-dignity/getFixtureAccountWithStorage'); const getFixturePrivateAccountWithStorage = require('../../../../fixtures/wallets/2a331817b9d6bf85100ef0/getFixtureAccountWithStorage'); - -const normalizedHDStoreFixtures = require('../../../../fixtures/wallets/apart-trip-dignity/store.json'); -const normalizedPKStoreFixtures = require('../../../../fixtures/wallets/2a331817b9d6bf85100ef0/store.json'); -const CONSTANTS = require("../../../CONSTANTS"); -const getTotalBalance = require("./getTotalBalance"); -const normalizedStoreToStore = (normalizedStore) => { - const store = { - ...normalizedStore - }; - - for (let walletId in store.wallets) { - for (let addressType in store.wallets[walletId].addresses) { - for (let path in store.wallets[walletId].addresses[addressType]) { - for (let utxo in store.wallets[walletId].addresses[addressType][path].utxos) { - store.wallets[walletId].addresses[addressType][path].utxos[utxo] = new Transaction.Output(store.wallets[walletId].addresses[addressType][path].utxos[utxo]); - } - } - } - } - - for (let transactionHash in store.transactions) { - store.transactions[transactionHash] = new Transaction(store.transactions[transactionHash]); - } - - for (let blockHeaderHash in store.chains['testnet'].blockHeaders) { - store.chains['testnet'].blockHeaders[blockHeaderHash] = new BlockHeader(store.chains['testnet'].blockHeaders[blockHeaderHash]) - } - return store; -} -const mockedEmptySelf = { - getTransactions, - network: 'testnet', - walletId: 'd6143ef4e6', - walletType: CONSTANTS.WALLET_TYPES.HDWALLET, - index: 0, - storage: { - store: { - transactions: {}, - wallets: { - 'd6143ef4e6': { - "addresses": { - "external": {}, - "internal": {}, - "misc": {} - } - - } - }, - "chains": { - "testnet": { - name: "testnet", - blockHeaders: {}, - mappedBlockHeaderHeights: {}, - blockHeight: 0 - } - }, - }, - getStore: () => { - return mockedSelf.storage.store; - } - } -} - const mockedHDAccount = getFixtureHDAccountWithStorage(); mockedHDAccount.getTransactions = getTransactions; @@ -83,7 +16,7 @@ mockedPKAccount.getTransactions = getTransactions; describe('Account - getTransactionHistory', () => { it('should return empty array on no transaction history', async function () { const mockedHDSelf = { - ...getFixtureHDAccountWithStorage(), + ...getFixtureHDAccountWithStorage() } mockedHDSelf.getTransactions = getTransactions; const chainStore = mockedHDSelf.storage.getChainStore('testnet') @@ -105,6 +38,7 @@ describe('Account - getTransactionHistory', () => { const expectedTransactionHistoryHD = []; expect(transactionHistoryHD).to.deep.equal(expectedTransactionHistoryHD); }); + it('should return valid transaction for HDWallet', async function () { const mockedHDSelf = { ...mockedHDAccount @@ -539,13 +473,10 @@ describe('Account - getTransactionHistory', () => { ] expect(transactionHistoryHD).to.deep.equal(expectedTransactionHistoryHD); }); - it('should correctly compute transaction history for private key based wallet', async function (){ + it('should correctly compute transaction history for single address based wallet', async function (){ const mockedPKSelf = { ...mockedPKAccount } - // mockedPKSelf.storage.store = normalizedStoreToStore(normalizedPKStoreFixtures) - // mockedPKSelf.walletType = 'single_address'; - // mockedPKSelf.walletId = '6101b44d50'; const transactionHistoryPK = await getTransactionHistory.call(mockedPKSelf); diff --git a/packages/wallet-lib/src/types/Account/methods/getUTXOS.js b/packages/wallet-lib/src/types/Account/methods/getUTXOS.js index 292798cc173..0e46d619842 100644 --- a/packages/wallet-lib/src/types/Account/methods/getUTXOS.js +++ b/packages/wallet-lib/src/types/Account/methods/getUTXOS.js @@ -20,7 +20,7 @@ function getUTXOS(options = { const chainStore = this.storage.getChainStore(network); const accountState = this.storage.getWalletStore(walletId).getPathState(this.accountPath); - const currentBlockHeight = chainStore.blockHeight; + const currentBlockHeight = chainStore.state.blockHeight; Object.values(accountState.addresses).forEach((address) => { const addressData = chainStore.getAddress(address); diff --git a/packages/wallet-lib/src/types/Account/methods/getUTXOS.spec.js b/packages/wallet-lib/src/types/Account/methods/getUTXOS.spec.js index 0e2d8fbb77f..906c907ac18 100644 --- a/packages/wallet-lib/src/types/Account/methods/getUTXOS.spec.js +++ b/packages/wallet-lib/src/types/Account/methods/getUTXOS.spec.js @@ -1,13 +1,13 @@ const { expect } = require('chai'); const Dashcore = require('@dashevo/dashcore-lib'); const getUTXOS = require('./getUTXOS'); -const mockAccountWithStorage = require("../../../test/mocks/mockAccountWithStorage"); +const getFixtureHDAccountWithStorage = require("../../../../fixtures/wallets/apart-trip-dignity/getFixtureAccountWithStorage"); describe('Account - getUTXOS', function suite() { this.timeout(10000); it('should return empty UTXOs list for new account', () => { - const mockedAccount = mockAccountWithStorage(); + const mockedAccount = getFixtureHDAccountWithStorage(); const { walletId, accountPath, network } = mockedAccount; // Wipe transactions and addresses from the storage to simulate empty UTXOs @@ -23,17 +23,43 @@ describe('Account - getUTXOS', function suite() { }) it('should get the proper UTXOS list', () => { - const mockedAccount = mockAccountWithStorage(); + const mockedAccount = getFixtureHDAccountWithStorage(); const utxos = getUTXOS.call(mockedAccount); - expect(utxos).to.be.deep.equal([new Dashcore.Transaction.UnspentOutput( + const expectedUtxos = [ { - address: new Dashcore.Address('yMEnFG5TBqEZXYXTg3PhENtZgGbwhw6qbX'), - txId: '33b14c6bc960c5717d734d5a15dc86b2060bf6e746cc509863344204d356cee4', - outputIndex: 1, - script: new Dashcore.Script('76a9140a163cfcba43b87e58b1996f61376d7bd8d9805288ac'), - satoshis: 224108673, + "address": "yhaAB6e8m3F8zmGX7WAVYa6eEfmSrrnY8x", + "txid": "c3fb3620ebd1c7678879b40df1495cc86a179b5a6f9e48ce0b687a5c6f5a1db5", + "vout": 1, + "scriptPubKey": "76a914e922f6420544f1be0cb593c10535cc3469198bc888ac", + "amount": 4 }, - )]); + { + "address": "yhdRfg5gNr587dtEC4YYMcSHmLVEGqqtHc", + "txid": "e6b6f85a18d77974f376f05d6c96d0fdde990e733664248b1a00391565af6841", + "vout": 1, + "scriptPubKey": "76a914e9c12479daba9d989cedba69adb56a5a50fe500288ac", + "amount": 1.59999359 + }, + { + "address": "yLk4Hw3w4zDudrDVP6W8J9TggkY57zQUki", + "txid": "c3fb3620ebd1c7678879b40df1495cc86a179b5a6f9e48ce0b687a5c6f5a1db5", + "vout": 2, + "scriptPubKey": "76a91404a791e67467246c3c0a003007793160387de54288ac", + "amount": 1.0709972 + }, + { + "address": "yNDpPsJqXKM36zHSNEW7c1zSvNnrZ699FY", + "txid": "f230a9414bf577d93d6f7f2515d9b549ede78cfba4168920892970fa8aa1eef8", + "vout": 1, + "scriptPubKey": "76a91414dfbdcfb48babe7127fa0ee90339c33a46aeda288ac", + "amount": 0.0009917 + } + ]; + + utxos.forEach((utxo, i) => { + expect(utxo).to.be.instanceOf(Dashcore.Transaction.UnspentOutput); + expect(utxo.toObject()).to.be.deep.equal(expectedUtxos[i]); + }) }); }); diff --git a/packages/wallet-lib/src/types/Account/methods/getUnconfirmedBalance.spec.js b/packages/wallet-lib/src/types/Account/methods/getUnconfirmedBalance.spec.js index ad562c6bb2a..1494975803b 100644 --- a/packages/wallet-lib/src/types/Account/methods/getUnconfirmedBalance.spec.js +++ b/packages/wallet-lib/src/types/Account/methods/getUnconfirmedBalance.spec.js @@ -2,23 +2,23 @@ const { expect } = require('chai'); const getTotalBalance = require('./getTotalBalance'); const getConfirmedBalance = require('./getConfirmedBalance'); const getUnconfirmedBalance = require('./getUnconfirmedBalance'); -const mockAccountWithStorage = require("../../../test/mocks/mockAccountWithStorage"); +const getFixtureHDAccountWithStorage = require("../../../../fixtures/wallets/apart-trip-dignity/getFixtureAccountWithStorage"); let mockedAccount; describe('Account - getUnconfirmedBalance', function suite() { this.timeout(10000); before(() => { - mockedAccount = mockAccountWithStorage() + mockedAccount = getFixtureHDAccountWithStorage(); }); it('should correctly get the balance', () => { const balance = getTotalBalance.call(mockedAccount); - expect(balance).to.equal(224108673); + expect(balance).to.equal(667198249); }); it('should correctly get the balance confirmed only', () => { const balance = getConfirmedBalance.call(mockedAccount); - expect(balance).to.equal(224108673); + expect(balance).to.equal(667198249); }); // TODO: file looks like a complete duplicate of the getTotalBalance.spec.js @@ -28,8 +28,8 @@ describe('Account - getUnconfirmedBalance', function suite() { const balanceUnconfDash = getUnconfirmedBalance.call(mockedAccount, false); const balanceConfDash = getConfirmedBalance.call(mockedAccount, false); - expect(balanceTotalDash).to.equal(2.24108673); + expect(balanceTotalDash).to.equal(6.67198249); expect(balanceUnconfDash).to.equal(0); - expect(balanceConfDash).to.equal(2.24108673); + expect(balanceConfDash).to.equal(6.67198249); }); }); diff --git a/packages/wallet-lib/src/types/Account/methods/getUnusedAddress.spec.js b/packages/wallet-lib/src/types/Account/methods/getUnusedAddress.spec.js index 1f94b0442b3..fb1be0ba5b1 100644 --- a/packages/wallet-lib/src/types/Account/methods/getUnusedAddress.spec.js +++ b/packages/wallet-lib/src/types/Account/methods/getUnusedAddress.spec.js @@ -14,13 +14,13 @@ describe('Account - getUnusedAddress', function suite() { const unusedAddressInternal = getUnusedAddress.call(mockedHDSelf, 'internal'); expect(unusedAddressExternal).to.be.deep.equal({ - address: 'yTrBDbJ6635RuBMRYGvUs2s68jwhpSMyQD', + address: 'ybuL6rM6dgrKzCg8s99f3jxGuv5oz5JcDA', index: 3, path: 'm/0/3' }); expect(unusedAddressInternal).to.be.deep.equal({ - address: 'ycxSmwSVQsRmuq2ivzU6NowuB11Wi9kioj', + address: 'yYwKP1FQae5kbjXkmuirGx6Xzf8NzHpLqW', path: 'm/1/4', index: 4 }); diff --git a/packages/wallet-lib/src/types/Account/methods/importTransactions.js b/packages/wallet-lib/src/types/Account/methods/importTransactions.js index 42c5f167884..05ae5ff70ae 100644 --- a/packages/wallet-lib/src/types/Account/methods/importTransactions.js +++ b/packages/wallet-lib/src/types/Account/methods/importTransactions.js @@ -3,53 +3,40 @@ const logger = require('../../../logger'); /** * Import transactions and always keep a number of unused addresses up to gap * - * @param transactions - * @returns {Promise} + * @param transactionsWithMayBeMetadata + * @returns {Promise<{ addressesGenerated: number, mostRecentHeight: number}>} */ module.exports = async function importTransactions(transactionsWithMayBeMetadata) { const { storage, network, - walletId, - accountPath, - keyChainStore, } = this; let addressesGenerated = 0; const chainStore = storage.getChainStore(network); - const accountStore = storage - .getWalletStore(walletId) - .getPathState(accountPath); - const masterKeyChain = keyChainStore.getMasterKeyChain(); - const keyChains = keyChainStore.getKeyChains(); + let mostRecentHeight = -1; 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; + if (metadata && metadata.height > mostRecentHeight) { + mostRecentHeight = metadata.height; + } + + const normalizedTransaction = chainStore.importTransaction(transaction, metadata); // Affected addresses might not be from our master keychain (account) - const affectedAddressesData = chainStore.importTransaction(transaction, metadata); + const affectedAddressesData = chainStore.considerTransaction(normalizedTransaction.hash); const affectedAddresses = Object.keys(affectedAddressesData); logger.silly(`Account.importTransactions - Import ${transaction.hash} to chainStore. ${affectedAddresses.length} addresses affected.`); - affectedAddresses.forEach((address) => { - keyChains.forEach((keyChain) => { - const issuedPaths = keyChain.markAddressAsUsed(address); - if (issuedPaths) { - addressesGenerated += issuedPaths.length; - 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()); - }); - } - }); - }); + const newPaths = this.generateNewPaths(affectedAddresses); + addressesGenerated += newPaths.length; + this.addPathsToStore(newPaths); }); + logger.silly(`Account.importTransactions(len: ${transactionsWithMayBeMetadata.length})`); - return addressesGenerated; + return { addressesGenerated, mostRecentHeight }; }; diff --git a/packages/wallet-lib/src/types/ChainStore/ChainStore.js b/packages/wallet-lib/src/types/ChainStore/ChainStore.js index e1ebed074d5..0835e2b8614 100644 --- a/packages/wallet-lib/src/types/ChainStore/ChainStore.js +++ b/packages/wallet-lib/src/types/ChainStore/ChainStore.js @@ -1,5 +1,29 @@ const EventEmitter = require('events'); +const { + Transaction, BlockHeader, +} = require('@dashevo/dashcore-lib'); + +const SCHEMA = { + blockHeaders: { + '*': (hex) => new BlockHeader(Buffer.from(hex, 'hex')), + }, + transactions: { + '*': Transaction, + }, + txMetadata: { + '*': { + blockHash: 'string', + height: 'number', + isChainLocked: 'boolean', + isInstantLocked: 'boolean', + }, + }, + fees: { + minRelay: 'number', + }, +}; + /** * ChainStore holds any information that is relatives to a specific network. * Information such as blockHeaders, transactions, instantLocks. @@ -21,8 +45,14 @@ class ChainStore extends EventEmitter { addresses: new Map(), }; } + + getTransactions() { + return this.state.transactions; + } } +ChainStore.prototype.SCHEMA = SCHEMA; + ChainStore.prototype.considerTransaction = require('./methods/considerTransaction'); ChainStore.prototype.exportState = require('./methods/exportState'); diff --git a/packages/wallet-lib/src/types/ChainStore/ChainStore.spec.js b/packages/wallet-lib/src/types/ChainStore/ChainStore.spec.js index 5a738fb5268..484ea85a414 100644 --- a/packages/wallet-lib/src/types/ChainStore/ChainStore.spec.js +++ b/packages/wallet-lib/src/types/ChainStore/ChainStore.spec.js @@ -1,101 +1,97 @@ -const {Transaction, BlockHeader} = require('@dashevo/dashcore-lib'); +const { Transaction, BlockHeader } = require('@dashevo/dashcore-lib'); +const { expect } = require('chai'); const ChainStore = require('./ChainStore'); -const {expect} = require('chai'); -const fixtures1 = require('../../../fixtures/wallets/2a331817b9d6bf85100ef0/chain-store.json') -const fixtures2 = require('../../../fixtures/wallets/apart-trip-dignity/chain-store.json') +const fixtures1 = require('../../../fixtures/wallets/2a331817b9d6bf85100ef0/chain-store.json'); -describe('ChainStore - class', function suite() { +describe('ChainStore - class', () => { let testnetChainStore; - let mainnetChainStore; - it('should create a new chain store', function () { + + it('should create a new chain store', () => { testnetChainStore = new ChainStore('testnet'); - mainnetChainStore = new ChainStore('mainnet'); + expect(new ChainStore()).to.deep.equal(testnetChainStore); expect(testnetChainStore.state).to.exist; expect(testnetChainStore.state.blockHeight).to.equal(0); - expect(testnetChainStore.state.fees).to.deep.equal({minRelay: -1}); + expect(testnetChainStore.state.fees).to.deep.equal({ minRelay: -1 }); expect(testnetChainStore.state.blockHeaders).to.deep.equal(new Map()); expect(testnetChainStore.state.transactions).to.deep.equal(new Map()); expect(testnetChainStore.state.instantLocks).to.deep.equal(new Map()); expect(testnetChainStore.state.addresses).to.deep.equal(new Map()); }); - it('should be able to import transactions with metadata', function () { - let {transactions} = fixtures1.state; + it('should be able to import transactions with metadata', () => { + const { transactions, txMetadata } = fixtures1; - const tx1 = new Transaction(transactions.d48f415f08fb795d43b216cf56e9ef10e059d4009cfc8fc90edfc0d3850813af.transaction); - const meta1 = transactions.d48f415f08fb795d43b216cf56e9ef10e059d4009cfc8fc90edfc0d3850813af.metadata; + const tx1 = new Transaction( + transactions.d48f415f08fb795d43b216cf56e9ef10e059d4009cfc8fc90edfc0d3850813af, + ); + const meta1 = txMetadata.d48f415f08fb795d43b216cf56e9ef10e059d4009cfc8fc90edfc0d3850813af; testnetChainStore.importTransaction(tx1, meta1); + testnetChainStore.considerTransaction(tx1.hash); const storedTransactionData = testnetChainStore.getTransaction('d48f415f08fb795d43b216cf56e9ef10e059d4009cfc8fc90edfc0d3850813af'); - expect(storedTransactionData.transaction.toString()).to.equal(tx1.toString()) - expect(storedTransactionData.metadata).to.deep.equal(meta1) + expect(storedTransactionData.transaction.toString()).to.equal(tx1.toString()); + expect(storedTransactionData.metadata).to.deep.equal(meta1); }); - it('should be able to import transaction without metadata', function () { - let {transactions} = fixtures1.state; + it('should be able to import transaction without metadata', () => { + const { transactions } = fixtures1; - const tx1 = new Transaction(transactions['0dcdaa9bf5b3596be1bcf22113e39026fd49d24b47190e2c7423be936cb116a7'].transaction); + const tx1 = new Transaction(transactions['0dcdaa9bf5b3596be1bcf22113e39026fd49d24b47190e2c7423be936cb116a7']); testnetChainStore.importTransaction(tx1); + testnetChainStore.considerTransaction(tx1.hash); const storedTransactionData = testnetChainStore.getTransaction('0dcdaa9bf5b3596be1bcf22113e39026fd49d24b47190e2c7423be936cb116a7'); - expect(storedTransactionData.transaction.toString()).to.equal(tx1.toString()) + expect(storedTransactionData.transaction.toString()).to.equal(tx1.toString()); expect(storedTransactionData.metadata).to.deep.equal({ blockHash: null, height: null, - isInstantLocked: null, - isChainLocked: null - }) + isInstantLocked: false, + isChainLocked: false, + }); }); - it('should update metadata', function () { - let {transactions} = fixtures1.state; + it('should update metadata', () => { + const { transactions, txMetadata } = fixtures1; - const tx1 = new Transaction(transactions['0dcdaa9bf5b3596be1bcf22113e39026fd49d24b47190e2c7423be936cb116a7'].transaction); - const meta1 = transactions['0dcdaa9bf5b3596be1bcf22113e39026fd49d24b47190e2c7423be936cb116a7'].metadata; + const tx1 = new Transaction(transactions['0dcdaa9bf5b3596be1bcf22113e39026fd49d24b47190e2c7423be936cb116a7']); + const meta1 = txMetadata['0dcdaa9bf5b3596be1bcf22113e39026fd49d24b47190e2c7423be936cb116a7']; testnetChainStore.importTransaction(tx1, meta1); + testnetChainStore.considerTransaction(tx1.hash); const storedTransactionData = testnetChainStore.getTransaction('0dcdaa9bf5b3596be1bcf22113e39026fd49d24b47190e2c7423be936cb116a7'); - expect(storedTransactionData.metadata).to.deep.equal(meta1) + expect(storedTransactionData.metadata).to.deep.equal(meta1); }); - it('should be able to import and get a blockheader', function () { - let {blockHeaders} = fixtures1.state; + it('should be able to import and get a blockheader', () => { + const { blockHeaders } = fixtures1; - const blockheaders1 = new BlockHeader.fromString(blockHeaders['0000012464fba1e3c66e678de79e4003bf17c36d5caa689e80fd4711fe620ec1']); + const blockheaders1 = BlockHeader.fromString( + blockHeaders['0000012464fba1e3c66e678de79e4003bf17c36d5caa689e80fd4711fe620ec1'], + ); testnetChainStore.importBlockHeader(blockheaders1); const storedTransactionData = testnetChainStore.getBlockHeader('0000012464fba1e3c66e678de79e4003bf17c36d5caa689e80fd4711fe620ec1'); - expect(storedTransactionData.toString()).to.equal(blockheaders1.toString()) + expect(storedTransactionData.toString()).to.equal(blockheaders1.toString()); }); - it('should be able to import addresses', function () { - const { addresses, transactions } = fixtures1.state; - - testnetChainStore.importAddress('ycDeuTfs4U77bTb5cq17dame28zdWHVYfk') - - const tx1 = new Transaction(transactions['47d13f7f713f4258953292c2298c1d91e2d6dee309d689f3c8b44ccf457bab52'].transaction); - const meta1 = transactions['47d13f7f713f4258953292c2298c1d91e2d6dee309d689f3c8b44ccf457bab52'].metadata; - testnetChainStore.importTransaction(tx1, meta1); - - const addr = testnetChainStore.getAddress('ycDeuTfs4U77bTb5cq17dame28zdWHVYfk'); - expect(addr).to.deep.equal(addresses['ycDeuTfs4U77bTb5cq17dame28zdWHVYfk']) - }); - it('should export and import state', function () { + it('should export and import state', () => { const exportedState = testnetChainStore.exportState(); const importedChainStore = new ChainStore(); importedChainStore.importState(exportedState); - expect(importedChainStore.network).to.equal(testnetChainStore.network) - expect(importedChainStore.state.fees).to.deep.equal(testnetChainStore.state.fees) - expect(importedChainStore.state.blockHeight).to.deep.equal(testnetChainStore.state.blockHeight) - expect(importedChainStore.state.blockHeaders).to.deep.equal(testnetChainStore.state.blockHeaders) - expect(importedChainStore.state.instantLocks).to.deep.equal(testnetChainStore.state.instantLocks) - expect([...importedChainStore.state.addresses]).to.deep.equal([...testnetChainStore.state.addresses]) - - const importedTransactionsState = [...importedChainStore.state.transactions]; - const exportedTransactionsState = exportedState.state.transactions; - - importedTransactionsState.forEach(([hash, {transaction, metadata}]) => { - expect(exportedTransactionsState[hash]).to.deep.equal({ - transaction: transaction.toString(), - metadata - }) - }) + expect(importedChainStore.state.blockHeaders) + .to.deep.equal(testnetChainStore.state.blockHeaders); + expect(importedChainStore.state.instantLocks) + .to.deep.equal(testnetChainStore.state.instantLocks); + + const expectedTransactions = testnetChainStore.state.transactions; + const importedTransactions = importedChainStore.state.transactions; + + expect(importedTransactions.size).to.equal(expectedTransactions.size); + + Array.from(expectedTransactions.keys()).forEach((txHash) => { + expect(importedTransactions.has(txHash)).to.equal(true); + expect(importedTransactions.get(txHash).transaction.toString()) + .to.equal(expectedTransactions.get(txHash).transaction.toString()); + + expect(importedTransactions.get(txHash).metadata) + .to.deep.equal(expectedTransactions.get(txHash).metadata); + }); }); }); diff --git a/packages/wallet-lib/src/types/ChainStore/methods/exportState.js b/packages/wallet-lib/src/types/ChainStore/methods/exportState.js index e82e918806b..9d70d9811d2 100644 --- a/packages/wallet-lib/src/types/ChainStore/methods/exportState.js +++ b/packages/wallet-lib/src/types/ChainStore/methods/exportState.js @@ -1,44 +1,37 @@ function exportState() { - const { network, state } = this; + const { state } = this; const { - fees, - blockHeight, blockHeaders, transactions, - instantLocks, - addresses, + blockHeight, + fees, } = state; const serializedState = { - network, - state: { - fees, - blockHeight, - blockHeaders: {}, - transactions: {}, - instantLocks: {}, - addresses: {}, - }, + blockHeaders: {}, + transactions: {}, + txMetadata: {}, + fees: {}, }; + let reorgSafeHeight = Infinity; + + if (blockHeight) { + reorgSafeHeight = blockHeight - 6; + } + [...blockHeaders.entries()].forEach(([blockHeaderHash, blockHeader]) => { - serializedState.state.blockHeaders[blockHeaderHash] = blockHeader.toString(); + serializedState.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(); + if (metadata && metadata.height && metadata.height <= reorgSafeHeight) { + serializedState.transactions[transactionHash] = transaction.toString(); + serializedState.txMetadata[transactionHash] = metadata; + } }); - [...addresses.entries()].forEach(([address, addressObj]) => { - serializedState.state.addresses[address] = addressObj; - }); + serializedState.fees.minRelay = fees.minRelay; return serializedState; } diff --git a/packages/wallet-lib/src/types/ChainStore/methods/importAddress.js b/packages/wallet-lib/src/types/ChainStore/methods/importAddress.js index ff504321308..0b5d6ca27d4 100644 --- a/packages/wallet-lib/src/types/ChainStore/methods/importAddress.js +++ b/packages/wallet-lib/src/types/ChainStore/methods/importAddress.js @@ -1,8 +1,13 @@ const logger = require('../../../logger'); +const sortTransactions = require('../../../utils/sortTransactions'); -function importAddress(address) { +function importAddress(address, reconsiderTransactions = true) { logger.silly(`ChainStore - import address ${address}`); - if (this.state.addresses.has(address.toString())) throw new Error('Address is already inserted'); + + if (this.state.addresses.has(address.toString())) { + return; + } + this.state.addresses.set(address.toString(), { address: address.toString(), transactions: [], @@ -11,17 +16,20 @@ function importAddress(address) { unconfirmedBalanceSat: 0, }); - // We need to consider all previous transactions - const transactions = [...this.state.transactions]; - const sortedTransactions = transactions.sort((a, b) => { - const heightA = a[1].metadata.height; - const heightB = b[1].metadata.height; - return heightA - heightB; - }); + // TODO: Consider refactoring + // this code might engage into a cyclic recursive chain of side effects + // of uncertain complexity + // (importAddress -> considerTransaction -> importAddress -> ...) + if (reconsiderTransactions) { + // We need to consider all previous transactions + const transactions = [...this.state.transactions.values()]; - sortedTransactions.forEach(([transactionHash]) => { - this.considerTransaction(transactionHash); - }); + const sortedTransactions = sortTransactions(transactions); + + sortedTransactions.forEach((transaction) => { + this.considerTransaction(transaction.hash); + }); + } } module.exports = importAddress; diff --git a/packages/wallet-lib/src/types/ChainStore/methods/importState.js b/packages/wallet-lib/src/types/ChainStore/methods/importState.js index 64460e0954f..f9f2a84c271 100644 --- a/packages/wallet-lib/src/types/ChainStore/methods/importState.js +++ b/packages/wallet-lib/src/types/ChainStore/methods/importState.js @@ -1,42 +1,23 @@ -const { - BlockHeader, - Transaction, - InstantLock, -} = require('@dashevo/dashcore-lib'); +const castStorageItemsTypes = require('../../../utils/castStorageItemsTypes'); + +function importState(rawState) { + const state = castStorageItemsTypes(rawState, this.SCHEMA); -function importState(state) { const { - network, - state: { - fees, - blockHeight, - blockHeaders, - transactions, - instantLocks, - addresses, - }, + blockHeaders, + transactions, + txMetadata, } = 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((blockHeader) => { + this.importBlockHeader(blockHeader); }); - 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.importInstantLock(new InstantLock(Buffer.from(serializedInstantLock, 'hex'))); + Object.keys(transactions).forEach((hash) => { + const tx = transactions[hash]; + const metadata = txMetadata[hash]; + this.importTransaction(tx, metadata); }); } + module.exports = importState; diff --git a/packages/wallet-lib/src/types/ChainStore/methods/importTransaction.js b/packages/wallet-lib/src/types/ChainStore/methods/importTransaction.js index d78c11ba5b8..2c29f7acc2b 100644 --- a/packages/wallet-lib/src/types/ChainStore/methods/importTransaction.js +++ b/packages/wallet-lib/src/types/ChainStore/methods/importTransaction.js @@ -13,11 +13,12 @@ function importTransaction(transaction, metadata = {}) { metadata: { blockHash: metadata.blockHash || null, height: metadata.height || null, - isInstantLocked: metadata.isInstantLocked || null, - isChainLocked: metadata.isChainLocked || null, + isInstantLocked: metadata.isInstantLocked || false, + isChainLocked: metadata.isChainLocked || false, }, }); - return this.considerTransaction(normalizedTransaction.hash); + + return normalizedTransaction; } module.exports = importTransaction; diff --git a/packages/wallet-lib/src/types/DerivableKeyChain/methods/getForPath.js b/packages/wallet-lib/src/types/DerivableKeyChain/methods/getForPath.js index 1388abdf05f..571a307004e 100644 --- a/packages/wallet-lib/src/types/DerivableKeyChain/methods/getForPath.js +++ b/packages/wallet-lib/src/types/DerivableKeyChain/methods/getForPath.js @@ -30,7 +30,7 @@ function getForPath(path, opts = {}) { key, isUsed, isWatched, - address: key.publicKey.toAddress(this.network), + address: this.rootKeyType === 'address' ? key : key.publicKey.toAddress(this.network), issuedTime: +new Date(), }; diff --git a/packages/wallet-lib/src/types/Storage/Storage.js b/packages/wallet-lib/src/types/Storage/Storage.js index 731f0d14d2a..6736969ef9b 100644 --- a/packages/wallet-lib/src/types/Storage/Storage.js +++ b/packages/wallet-lib/src/types/Storage/Storage.js @@ -17,6 +17,8 @@ const defaultOpts = { class Storage extends EventEmitter { constructor(opts = {}) { super(); + this.currentWalletId = ''; + this.currentNetwork = ''; this.wallets = new Map(); this.chains = new Map(); this.application = { @@ -32,6 +34,11 @@ class Storage extends EventEmitter { this.lastRehydrate = null; this.lastSave = null; this.lastModified = null; + this.configured = false; + } + + scheduleStateSave() { + this.lastModified = Date.now(); } } diff --git a/packages/wallet-lib/src/types/Storage/methods/configure.js b/packages/wallet-lib/src/types/Storage/methods/configure.js index d5e3de81454..328f0564ae6 100644 --- a/packages/wallet-lib/src/types/Storage/methods/configure.js +++ b/packages/wallet-lib/src/types/Storage/methods/configure.js @@ -2,6 +2,9 @@ const { has } = require('lodash'); const configureAdapter = require('../_configureAdapter'); const getDefaultAdapter = require('../_getDefaultAdapter'); const { CONFIGURED } = require('../../../EVENTS'); +const logger = require('../../../logger'); + +const CURRENT_VERSION = 1; /** * To be called after instantialization as it contains all the async logic / test of adapters @@ -13,6 +16,24 @@ module.exports = async function configure(opts = {}) { this.autosave = has(opts, 'autosave') ? opts.autosave : this.autosave; this.adapter = await configureAdapter((opts.adapter) ? opts.adapter : await getDefaultAdapter()); + const version = await this.adapter.getItem('version'); + + if (!version) { + await this.adapter.setItem('version', CURRENT_VERSION); + } else if (version !== CURRENT_VERSION) { + logger.warn('Storage version mismatch, resyncing from start'); + await this.adapter.setItem('wallets', null); + await this.adapter.setItem('chains', null); + + await this.adapter.setItem('version', CURRENT_VERSION); + } + + this.createWalletStore(opts.walletId); + this.createChainStore(opts.network); + + this.currentWalletId = opts.walletId; + this.currentNetwork = opts.network; + if (this.rehydrate) { await this.rehydrateState(); } @@ -21,5 +42,6 @@ module.exports = async function configure(opts = {}) { this.startWorker(); } + this.configured = true; this.emit(CONFIGURED, { type: CONFIGURED, payload: null }); }; diff --git a/packages/wallet-lib/src/types/Storage/methods/rehydrateState.js b/packages/wallet-lib/src/types/Storage/methods/rehydrateState.js index a391c9e6bb9..0926c99a360 100644 --- a/packages/wallet-lib/src/types/Storage/methods/rehydrateState.js +++ b/packages/wallet-lib/src/types/Storage/methods/rehydrateState.js @@ -1,10 +1,8 @@ -const { merge } = require('lodash'); const { hasMethod } = require('../../../utils'); -const mergeHelper = (initial = {}, additional = {}) => merge(initial, additional); const { REHYDRATE_STATE_FAILED, REHYDRATE_STATE_SUCCESS } = require('../../../EVENTS'); -const WalletStore = require('../../WalletStore/WalletStore'); -const ChainStore = require('../../ChainStore/ChainStore'); + +const logger = require('../../../logger'); /** * Fetch the state from the persistence adapter @@ -16,27 +14,45 @@ const rehydrateState = async function rehydrateState() { if (this.adapter && hasMethod(this.adapter, 'getItem')) { const wallets = await this.adapter.getItem('wallets'); if (wallets) { - wallets.forEach((walletState) => { - const walletStore = new WalletStore(); - walletStore.importState(walletState); - this.wallets.set(walletStore.walletId, walletStore); - }); + try { + Object.keys(wallets).forEach((walletId) => { + const walletStore = this.getWalletStore(walletId); + + if (walletStore) { + walletStore.importState(wallets[walletId]); + } + }); + } catch (e) { + logger.error('Error importing wallets storage, resyncing from start', e); + + this.adapter.setItem('wallets', null); + this.adapter.setItem('chains˚', null); + } } const chains = await this.adapter.getItem('chains'); if (chains) { - chains.forEach((chainState) => { - const chainStore = new ChainStore(); - chainStore.importState(chainState); - this.chains.set(chainStore.network, chainStore); - }); + try { + Object.keys(chains).forEach((chainNetwork) => { + const chainStore = this.getChainStore(chainNetwork); + + if (chainStore) { + chainStore.importState(chains[chainNetwork]); + } + }); + } catch (e) { + logger.error('Error importing chains storage, resyncing from start', e); + + this.adapter.setItem('wallets', null); + this.adapter.setItem('chains', null); + } } - this.application = mergeHelper(this.application, await this.adapter.getItem('application')); } this.lastRehydrate = +new Date(); this.emit(REHYDRATE_STATE_SUCCESS, { type: REHYDRATE_STATE_SUCCESS, payload: null }); } catch (e) { + logger.error('Error rehydrating storage state', e); this.emit(REHYDRATE_STATE_FAILED, { type: REHYDRATE_STATE_FAILED, payload: e }); throw e; } diff --git a/packages/wallet-lib/src/types/Storage/methods/saveState.js b/packages/wallet-lib/src/types/Storage/methods/saveState.js index 7b6562a61bc..4094e61ef1b 100644 --- a/packages/wallet-lib/src/types/Storage/methods/saveState.js +++ b/packages/wallet-lib/src/types/Storage/methods/saveState.js @@ -8,9 +8,30 @@ const saveState = async function saveState() { if (this.autosave && this.adapter && this.adapter.setItem) { const self = this; try { - await this.adapter.setItem('wallets', [...self.wallets].map(([, walletStore]) => walletStore.exportState())); - await this.adapter.setItem('chains', [...self.chains].map(([, chainStore]) => chainStore.exportState())); - await this.adapter.setItem('application', { ...self.application }); + const currentChainHeight = this.getChainStore(this.currentNetwork).state.blockHeight; + + const serializedWallets = [...self.wallets].reduce((acc, [walletId, walletStore]) => { + let walletStoreState; + if (walletId === this.currentWalletId) { + // For current wallet we need to take into account the current chain height + walletStoreState = walletStore.exportState(currentChainHeight); + } else { + // Others stay unaffected + walletStoreState = walletStore.exportState(); + } + + acc[walletId] = walletStoreState; + return acc; + }, {}); + + const serializedChains = [...self.chains].reduce((acc, [chainId, chainStore]) => { + acc[chainId] = chainStore.exportState(); + return acc; + }, {}); + + await this.adapter.setItem('wallets', serializedWallets); + await this.adapter.setItem('chains', serializedChains); + this.lastSave = +new Date(); this.emit(SAVE_STATE_SUCCESS, { type: SAVE_STATE_SUCCESS, payload: this.lastSave }); return true; diff --git a/packages/wallet-lib/src/types/Wallet/ChainSyncMediator.js b/packages/wallet-lib/src/types/Wallet/ChainSyncMediator.js new file mode 100644 index 00000000000..b2f778b4507 --- /dev/null +++ b/packages/wallet-lib/src/types/Wallet/ChainSyncMediator.js @@ -0,0 +1,43 @@ +/* eslint-disable no-underscore-dangle */ + +const STATES = { + OFFLINE: 'OFFLINE', + CHAIN_STATUS_SYNC: 'CHAIN_STATUS_SYNC', + HISTORICAL_SYNC: 'HISTORICAL_SYNC', + CONTINUOUS_SYNC: 'CONTINUOUS_SYNC', +}; + +/** + * The class responsible for communication of + * the chain sync state between plugins + * @class ChainSyncMediator + */ +class ChainSyncMediator { + constructor() { + this._state = STATES.OFFLINE; + } + + /** + * Changes the state of the chain sync + * @param {string} state + */ + set state(state) { + if (!STATES[state]) { + throw new Error('Invalid state'); + } + + this._state = state; + } + + /** + * Returns the current state of the chain sync + * @returns {string} + */ + get state() { + return this._state; + } +} + +ChainSyncMediator.STATES = STATES; + +module.exports = ChainSyncMediator; diff --git a/packages/wallet-lib/src/types/Wallet/Wallet.js b/packages/wallet-lib/src/types/Wallet/Wallet.js index d730be04b26..8378baf87a6 100644 --- a/packages/wallet-lib/src/types/Wallet/Wallet.js +++ b/packages/wallet-lib/src/types/Wallet/Wallet.js @@ -30,6 +30,7 @@ const fromHDPrivateKey = require('./methods/fromHDPrivateKey'); const generateNewWalletId = require('./methods/generateNewWalletId'); const createTransportFromOptions = require('../../transport/createTransportFromOptions'); +const ChainSyncMediator = require('./ChainSyncMediator'); /** * Instantiate a basic Wallet object, @@ -116,14 +117,13 @@ class Wallet extends EventEmitter { autosave: true, }); + this.storage.application.network = this.network; this.storage.configure({ adapter: opts.adapter, + walletId: this.walletId, + network: this.network, }); - 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 @@ -181,6 +181,8 @@ class Wallet extends EventEmitter { const Identities = require('../Identities/Identities'); this.identities = new Identities(this); this.savedBackup = false; // TODO: When true, we delete mnemonic from internals + + this.chainSyncMediator = new ChainSyncMediator(); } } diff --git a/packages/wallet-lib/src/types/Wallet/Wallet.spec.js b/packages/wallet-lib/src/types/Wallet/Wallet.spec.js index a6d416bfbee..87f509728c5 100644 --- a/packages/wallet-lib/src/types/Wallet/Wallet.spec.js +++ b/packages/wallet-lib/src/types/Wallet/Wallet.spec.js @@ -220,7 +220,7 @@ describe('Wallet - Get/Create Account', function suite() { walletTestnet.disconnect(); }); }); - it('should be able to create an account at a specific index', (done) => { + it('should be able to create an account at a specific index', async () => { const network = Dashcore.Networks.testnet.toString(); const passphrase = 'Evolution'; const config = { @@ -230,18 +230,18 @@ describe('Wallet - Get/Create Account', function suite() { }; const walletTestnet = new Wallet(Object.assign(config, mocks)); - walletTestnet.createAccount().then(async (account)=>{ - // eslint-disable-next-line no-unused-expressions - expect(account).to.exist; - expect(account.BIP44PATH.split('/')[3]).to.equal('0\''); - expect(account.index).to.equal(0); + const account = await walletTestnet.createAccount(); - const accountSpecificIndex = await walletTestnet.createAccount({ index: 42 }); - expect(accountSpecificIndex.BIP44PATH.split('/')[3]).to.equal('42\''); - expect(accountSpecificIndex.index).to.equal(42); - walletTestnet.disconnect(); - done(); - }) + // eslint-disable-next-line no-unused-expressions + expect(account).to.exist; + expect(account.BIP44PATH.split('/')[3]).to.equal('0\''); + expect(account.index).to.equal(0); + + const accountSpecificIndex = await walletTestnet.createAccount({ index: 42 }); + + expect(accountSpecificIndex.BIP44PATH.split('/')[3]).to.equal('42\''); + expect(accountSpecificIndex.index).to.equal(42); + walletTestnet.disconnect(); }); it('should not leak', () => { const mockOpts1 = { }; diff --git a/packages/wallet-lib/src/types/Wallet/methods/createAccount.js b/packages/wallet-lib/src/types/Wallet/methods/createAccount.js index f4147d876c7..8710c43fda8 100644 --- a/packages/wallet-lib/src/types/Wallet/methods/createAccount.js +++ b/packages/wallet-lib/src/types/Wallet/methods/createAccount.js @@ -1,10 +1,15 @@ const { WALLET_TYPES } = require('../../../CONSTANTS'); +const EVENTS = require('../../../EVENTS'); /** * Will derivate to a new account. * @param {object} accountOpts - options to pass, will autopopulate some * @return {Account} - account object */ async function createAccount(accountOpts) { + if (!this.storage.configured) { + await new Promise((resolve) => this.storage.once(EVENTS.CONFIGURED, resolve)); + } + /** * Wallet.createAccount calls Account that depends on Wallet. * In order to avoid a cyclic dependency issue we put this require here and diff --git a/packages/wallet-lib/src/types/Wallet/methods/dumpStorage.js b/packages/wallet-lib/src/types/Wallet/methods/dumpStorage.js index 72c002f12a7..ac141828187 100644 --- a/packages/wallet-lib/src/types/Wallet/methods/dumpStorage.js +++ b/packages/wallet-lib/src/types/Wallet/methods/dumpStorage.js @@ -14,7 +14,31 @@ function dumpStorage(options) { const dumpOptions = options !== null && typeof options === 'object' ? Object.assign(defaultOptions, options) : defaultOptions; - const storageDump = JSON.stringify(this.storage.store); + + const storage = { chains: {}, wallets: {} }; + + this.storage.wallets.forEach((wallet) => { + storage.wallets[wallet.walletId] = wallet.state; + }); + + this.storage.chains.forEach((chain) => { + storage.chains[chain.network] = chain.state; + }); + + const storageDump = JSON.stringify(storage, (jsonKey, jsonValue) => { + if (jsonValue instanceof Map) { + const object = {}; + + // eslint-disable-next-line no-restricted-syntax + for (const [key, value] of jsonValue.entries()) { + object[key] = value; + } + + return object; + } + + return jsonValue; + }); if (dumpOptions.log) { // Add a linebreak to the log message for the ease of copying of the diff --git a/packages/wallet-lib/src/types/Wallet/methods/getAccount.js b/packages/wallet-lib/src/types/Wallet/methods/getAccount.js index e66dad25f73..241dbe964a0 100644 --- a/packages/wallet-lib/src/types/Wallet/methods/getAccount.js +++ b/packages/wallet-lib/src/types/Wallet/methods/getAccount.js @@ -1,5 +1,6 @@ const _ = require('lodash'); const { is } = require('../../../utils'); +const EVENTS = require('../../../EVENTS'); /** * Get a specific account per accounts index @@ -9,6 +10,10 @@ const { is } = require('../../../utils'); */ async function getAccount(accountOpts = {}) { + if (!this.storage.configured) { + await new Promise((resolve) => this.storage.once(EVENTS.CONFIGURED, resolve)); + } + if (is.num(accountOpts)) { throw new Error('getAccount expected index integer to be a property of accountOptions'); } diff --git a/packages/wallet-lib/src/types/Wallet/methods/getAccount.spec.js b/packages/wallet-lib/src/types/Wallet/methods/getAccount.spec.js index 536f8b03ee4..597d690f930 100644 --- a/packages/wallet-lib/src/types/Wallet/methods/getAccount.spec.js +++ b/packages/wallet-lib/src/types/Wallet/methods/getAccount.spec.js @@ -13,7 +13,9 @@ describe('Wallet - getAccount', function suite() { let timesAttachEventsCalled = 0; const mockOpts = { accounts: [], - storage: {}, + storage: { + configured: true + }, walletType: WALLET_TYPES.HDWALLET, createAccount: (opts = { index: 0 }) => { timesCreateAccountCalled += 1; @@ -33,7 +35,9 @@ describe('Wallet - getAccount', function suite() { let timesCreateAccountCalled = 0; const mockOpts1 = { accounts: [], - storage: {}, + storage: { + configured: true + }, walletType: WALLET_TYPES.HDWALLET, createAccount: (opts = { index: 0 }) => { timesCreateAccountCalled += 1; diff --git a/packages/wallet-lib/src/types/WalletStore/WalletStore.js b/packages/wallet-lib/src/types/WalletStore/WalletStore.js index b105d6792b8..e963c1dbc80 100644 --- a/packages/wallet-lib/src/types/WalletStore/WalletStore.js +++ b/packages/wallet-lib/src/types/WalletStore/WalletStore.js @@ -1,3 +1,9 @@ +const SCHEMA = { + lastKnownBlock: { + height: 'number', + }, +}; + class WalletStore { constructor(walletId) { this.walletId = walletId; @@ -6,10 +12,27 @@ class WalletStore { mnemonic: null, paths: new Map(), identities: new Map(), + lastKnownBlock: { + height: -1, + }, }; } + + /** + * Updates last known block value + * @param height - height of a last known block + */ + updateLastKnownBlock(height) { + if (this.state.lastKnownBlock.height >= height) { + return; + } + + this.state.lastKnownBlock.height = height; + } } +WalletStore.prototype.SCHEMA = SCHEMA; + WalletStore.prototype.createPathState = require('./methods/createPathState'); WalletStore.prototype.exportState = require('./methods/exportState'); WalletStore.prototype.getIdentityIdByIndex = require('./methods/getIdentityIdByIndex'); diff --git a/packages/wallet-lib/src/types/WalletStore/WalletStore.spec.js b/packages/wallet-lib/src/types/WalletStore/WalletStore.spec.js index 0d5d8f4d41b..27b21230c2f 100644 --- a/packages/wallet-lib/src/types/WalletStore/WalletStore.spec.js +++ b/packages/wallet-lib/src/types/WalletStore/WalletStore.spec.js @@ -6,6 +6,8 @@ describe('WalletStore - Class', ()=> { describe('simple usage', () => { it('should create a walletStore', function () { walletStore = new WalletStore('squawk7700'); + walletStore.state.lastKnownBlock.height = 100; + expect(walletStore.walletId).to.equal('squawk7700'); }); it('should create path state', function () { diff --git a/packages/wallet-lib/src/types/WalletStore/methods/exportState.js b/packages/wallet-lib/src/types/WalletStore/methods/exportState.js index 03b37ea6667..7819cd3bdb0 100644 --- a/packages/wallet-lib/src/types/WalletStore/methods/exportState.js +++ b/packages/wallet-lib/src/types/WalletStore/methods/exportState.js @@ -1,15 +1,17 @@ -const { cloneDeep } = require('lodash'); +function exportState(chainHeight) { + let { lastKnownBlock: { height } } = this.state; -function exportState() { - const { walletId } = this; - const { mnemonic, paths, identities } = this.state; + /* + * If we have chain height provided, we must set last known block to + * chainHeight - 6 to avoid reorgs + */ + if (chainHeight && height > chainHeight - 6) { + height = chainHeight - 6; + } return { - walletId, - state: { - mnemonic, - paths: cloneDeep(Object.fromEntries(paths)), - identities: cloneDeep(Object.fromEntries(identities)), + lastKnownBlock: { + height, }, }; } diff --git a/packages/wallet-lib/src/types/WalletStore/methods/importState.js b/packages/wallet-lib/src/types/WalletStore/methods/importState.js index e4eb709107c..92af05dea14 100644 --- a/packages/wallet-lib/src/types/WalletStore/methods/importState.js +++ b/packages/wallet-lib/src/types/WalletStore/methods/importState.js @@ -1,9 +1,9 @@ -const { cloneDeep } = require('lodash'); +const castStorageItemsTypes = require('../../../utils/castStorageItemsTypes'); -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))); +function importState(rawState) { + const state = castStorageItemsTypes(rawState, this.SCHEMA); + + this.state.lastKnownBlock = state.lastKnownBlock; } + module.exports = importState; diff --git a/packages/wallet-lib/src/utils/castStorageItemsTypes.js b/packages/wallet-lib/src/utils/castStorageItemsTypes.js new file mode 100644 index 00000000000..44232a62dbf --- /dev/null +++ b/packages/wallet-lib/src/utils/castStorageItemsTypes.js @@ -0,0 +1,54 @@ +const castStorageItemsTypes = (originalItem, schema) => { + if (!schema) { + throw new Error('Schema is undefined'); + } + + return Object.entries(schema).reduce((acc, next) => { + const [schemaKey, schemaValue] = next; + const result = {}; + + if (schemaKey !== '*' && originalItem[schemaKey] === undefined) { + throw new Error(`No item found for schema key "${schemaKey}" in item ${JSON.stringify(originalItem)}`); + } + + if (schemaValue.constructor.name !== 'Object') { + let castItem; + + if (typeof schemaValue === 'string') { + castItem = (itemToCast) => { + // eslint-disable-next-line valid-typeof + if (typeof itemToCast !== schemaValue) { + throw new Error(`Value "${itemToCast}" is not of type "${schemaValue}"`); + } + return itemToCast; + }; + } else if (typeof schemaValue === 'function') { + castItem = schemaValue; + } else { + castItem = (itemToCast) => { + const Clazz = schemaValue; + return new Clazz(itemToCast); + }; + } + + if (schemaKey === '*') { + Object.keys(originalItem).forEach((itemKey) => { + result[itemKey] = castItem(originalItem[itemKey]); + }); + } else { + result[schemaKey] = castItem(originalItem[schemaKey]); + } + } else if (schemaKey === '*') { + Object + .entries(originalItem) + .forEach(([key, value]) => { + result[key] = castStorageItemsTypes(value, schemaValue); + }, {}); + } else { + result[schemaKey] = castStorageItemsTypes(originalItem[schemaKey], schemaValue); + } + + return { ...acc, ...result }; + }, {}); +}; +module.exports = castStorageItemsTypes; diff --git a/packages/wallet-lib/src/utils/castStorageItemsTypes.spec.js b/packages/wallet-lib/src/utils/castStorageItemsTypes.spec.js new file mode 100644 index 00000000000..a5e96e6b29b --- /dev/null +++ b/packages/wallet-lib/src/utils/castStorageItemsTypes.spec.js @@ -0,0 +1,90 @@ +const _ = require('lodash'); +const {expect} = require('chai'); +const ChainStore = require('../../src/types/ChainStore/ChainStore'); +const castItemTypes = require('./castStorageItemsTypes'); +const {BlockHeader, Transaction} = require('@dashevo/dashcore-lib') +const WalletStore = require("../types/WalletStore/WalletStore"); + +const mockChainStorage = { + "blockHeaders": { + "fakeBlockHash": "000000206ff6709b4816a98a7601bc9626a597191fe4f788228a037de3bd839e811f4913c49de57ac9553f0cd2529c94eaa9781273f315a792c4148eb7e3756c9cd7e1ce40285562ffff7f2000000000" + }, + "transactions": { + "fakeTxHash": "0300000001e569f827418be2e49f5ae4a34d30ff3bc5abc723f72b0e10712598dff1e70689010000006b483045022100f9aebe9bcfaa8208f1486ad4d15f65730b5adc0cb02c1d2bd8836a4582e2ea7f02200250bbfe524f70324417237549f421c2c3584d6b532194dbac8043e46900753701210387f3d1ff9e6a06db60bd61d0757002836407e8a3b1094b446690d13392c3fb9affffffff02e8030000000000001976a9148d0ba6247ad4988ba70fdcb56bb5f45b6e49423d88aca351c253040000001976a91471a97f71915e7c1d4460ad520511761b06534d8088ac00000000" + }, + "instantLocks": {}, + "txMetadata": { + "fakeTxHash": { + "blockHash": "0eca27f921836079a85f41b134679cd557fab1f66b2e60013b873eb56e7b3f2d", + "height": 5409, + "isInstantLocked": true, + "isChainLocked": true + } + }, + "fees": { + "minRelay": -1 + } +} + +const mockWalletStorage = { + "lastKnownBlock": { + "height": 11703 + } +} + +describe('Utils - castStorageItemsTypes', function suite() { + it('should proceed with valid schema', function () { + const chainStore = castItemTypes(mockChainStorage, ChainStore.prototype.SCHEMA) + + expect(chainStore.blockHeaders.fakeBlockHash instanceof BlockHeader).to.be.true + expect(chainStore.transactions.fakeTxHash instanceof Transaction).to.be.true + expect(chainStore.txMetadata.fakeTxHash.isInstantLocked).to.be.true + expect(chainStore.txMetadata.fakeTxHash.isChainLocked).to.be.true + expect(typeof chainStore.txMetadata.fakeTxHash.height).to.be.equal('number') + + const walletStore = castItemTypes(mockWalletStorage, WalletStore.prototype.SCHEMA) + + expect(walletStore.lastKnownBlock.height).to.be.equal(11703) + }); + + it('should throw if no schema passed', function () { + expect(() => castItemTypes(mockChainStorage, null)) + .to.throw(Error, 'Schema is undefined') + }); + + it('should throw if invalid primitive value passed', function () { + const mockWalletStorageWithWrongType = _.cloneDeep(mockWalletStorage) + mockWalletStorageWithWrongType.lastKnownBlock.height = '11703' + expect(() => castItemTypes(mockWalletStorageWithWrongType, WalletStore.prototype.SCHEMA)) + .to.throw(Error, 'Value "11703" is not of type "number"'); + }); + + it('should throw if invalid object value passed', function () { + const mockChainStorageWithUnknownKeys = _.cloneDeep(mockChainStorage) + mockChainStorageWithUnknownKeys.txMetadata.unknownKey = true + + expect(() => castItemTypes(mockChainStorageWithUnknownKeys, ChainStore.prototype.SCHEMA)) + .to.throw('No item found for schema key "blockHash" in item true') + }); + + it('should throw if invalid uniform object with primitives passed', function () { + const schema = { + '*': 'boolean' + } + const items = { + '1': true, + '2': 'false' + } + + expect(() => castItemTypes(items, schema)) + .to.throw(Error, 'Value "false" is not of type "boolean"'); + }); + + it('should throw if some of the keys are missing from the storage', function () { + const mockWalletChainStorageWithMissingKeys = _.cloneDeep(mockChainStorage) + mockWalletChainStorageWithMissingKeys.txMetadata = undefined + + expect(() => castItemTypes(mockWalletChainStorageWithMissingKeys, ChainStore.prototype.SCHEMA)) + .to.throw(Error, 'No item found for schema key "txMetadata" in item'); + }); +}); diff --git a/packages/wallet-lib/src/utils/classifyAddresses.spec.js b/packages/wallet-lib/src/utils/classifyAddresses.spec.js index d3a1969e8b5..0e7c9e83b84 100644 --- a/packages/wallet-lib/src/utils/classifyAddresses.spec.js +++ b/packages/wallet-lib/src/utils/classifyAddresses.spec.js @@ -13,10 +13,13 @@ describe('Utils - classifyAddresses', function suite() { const expectedResult = { "externalAddressesList": ["yTwEca67QSkZ6axGdpNFzWPaCj8zqYybY7", "yercyhdN9oEkZcB9BsW5ktFaDxFEuK6qXN", "ygk3GCSba2J3L9G665Snozhj9HSkh5ByVE", "ybuL6rM6dgrKzCg8s99f3jxGuv5oz5JcDA", "ygHAVkMtYSqoTWHebDv7qkhMV6dHyuRsp2", "yMLhEsiP2ajSh8STmXnNmkWXtoHsmawZxd", "yj8rRKATAUHcAgXvNZekob58xKm2oNyvhv", "yhaAB6e8m3F8zmGX7WAVYa6eEfmSrrnY8x", "yiXh4Yo5djG6QH8WzXkKm5EFzqLRJWakXz", "yQYv3Um6DsdtANo1ZPTUte75wAGMstLRex", "yiYPJmu7eEm1cXUNumQRdjv1fvPhsfgMS4", "yii4aUZhNfL6EWN9KAgAFrJzGJmqHnF4wx", "yLpTquSct2SGz2Ka45uTPDd81Kzro2Jt2k", "yMiJtpzb1Qthy9TGnavsf5NZ6EZZa4j9q3", "yacgSfW7RkwWakEZPg8USAVdzCypiG3vxS", "yVvrmoRPFLy6nUpCQBT8ZExxF5wF3DhiGU", "yaJf2aG6cFUtfv4o6TuEKsh5kr4xq5iAY4", "yfardJQ4ucgWLKQPaRHGMRMbSGm5H4ExJR", "yLSCqx7dcM5JKR2fG7vHbF2axMvuYqomaw", "yVij8XpJ78LM5hepSV1KF7T8vRpUEXCpK5", "ydJpjuJGossAZR7S5oS7cWvjygEwoj8Xwp", "yW3TmWnmhvpxRbgFcQ8oXqDRkn3RhRH6jj", "yRegVX85DThKRkH8C61TtRacfzrkiBfNy5", "yPtDCqDFRe1JuDp8pvdiEMQMz2erGwS3VG", "yM9pSw3L4oBfG7uQL5o522Hu3WTvy9awgZ", "yNC6qYJYungzuk5XUynDFKCn54Dy8ngox4", "yR5KcLr1bceLT4teTk2qoJx6pFLik1zyzL", "yRrKLGJa9JmdjBWvrHtedKjHTao6CRDTKf", "yP5dShZBydpbEzgGoXL6kcjv2KzervRrYB"], "internalAddressesList": ["yNDpPsJqXKM36zHSNEW7c1zSvNnrZ699FY", "yLk4Hw3w4zDudrDVP6W8J9TggkY57zQUki", "yirJaK8KCE5YAmwvLadizqFw3TCXqBuZXL", "yhdRfg5gNr587dtEC4YYMcSHmLVEGqqtHc", "yYwKP1FQae5kbjXkmuirGx6Xzf8NzHpLqW", "yX9gmsm8aSxZZjYhq4w35aidT7qbhcpNjU", "ybgXCTGMHEBbQeUib8c3xAjtGAc12XtWiU", "yS31WpdMT2b34uL9C37fbUoACHhiupHCyP", "yTSpFqRoX3vyN286AUtKKhgmX5Xb41YKQe", "yQU5YsqN7psTTASuYbcMi7N5nNZGaxXb2X", "yVGGFj9BLgEab5rucSGLC6UGVLQKB4U1wJ", "yQCh5yYCHEbJzgSJE9rdHiqXHidKm3kwr5", "yX7T3Ac3yaLk5CTC5UaR93Fc7SjYkeT5hn", "yXx3WXq8kYNPbYEg5U6bL8Xfih4g5LCYVo", "yYnLMTz3jCi2KKKNuo3TVkEAGyUFg8tgkJ", "yiKa1dA6B4tSTNJqJP9Y5pQfQEffnQQDTL", "yf7vcuDnE9DVhXdMfBMQQTEi43otYQzkWE", "yTmSmocwERCeRHqNNG5SbpYKUra1HTmj8m", "yivUe5NeJsGsREwPQZUGYaTSwWB3E1oLcz", "ygfsZojdfW9UjCRU4ra95Aq6YgCC7UqZFx", "yU9fdXaUVtefwDZvxjJAr9xj1z2MtYi34A", "yXgMN6FgrgZCnTN1vhoZMh8afKMBmi3JC4", "yiqaCbXscvR8y3VFYMzdaKCaAGuDuZxMzt", "ydcgWDxheSxrLAqDBP4JXBndMCzUNf77gq"], - "otherAccountAddressesList": ["yYJmzWey5kNecAThet5BFxAga1F4b4DKQ2", "yNCqctyQaq51WU1hN5aNwsgMsZ5fRiB7GY", "yNPbYz5cZKw2EwxtkL3VSVzPi2FYp9VKjQ", "ybsGWzsnSCAZufgSeUjScVxqEdved99UM2", "yfNHuPojk8XKWP5nuueDptX4nM7qToudgx", "yXxLnDkk6s8h1PSnYaFM6MAyRarc1Kc1rY", "yipResSzN2zUvL7UYkmptKKmQTv7sNssRn", "yZPtNwimHdRiKYbNQW49qezw1Kc1YwUJeT", "yPMjYYfQbga2nBiuqqfUyX41U1vwRZ8fG8", "yLueLWWcLQsaXQ8D5o9tcyo8tfTxMWXvG4", "yN8gzgsc1RVjXThMQT5qZH2jjpnMymz6zP", "yPQLWBNwMdLxUW2oUwHGwQtfyYxD41BARJ", "yg5g2AfWFdwWexWGfbSXYbUHf1y5WWrFPs", "yWyABu4naV1Jzw7w9sn1gqhebPRSkCndsS", "ycuUPzUBjhKyUjezQR1LNot79a6C4aRLaR", "yQ7YjvAXgDAUCekveHVjr6NBveXrUemVno", "yi8bghcw627cMGpuH4bJqH6bqR5ywv1NLH", "yizHu8i2rfwzwBgnJ62s2WUe6wLoDjne6N", "yW1u3tySeUKAKJsz7sjZFyjUiTyKLB6xBv", "yNaSkdy1Q8JNubUdbLMGsGf7sTRofEJYZq", "yXMrw79LPgu78EJsfGGYpm6fXKc1EMnQ49", "yh6Hcyipdvp6WJpQxjNbaXP4kzPQUJpY3n", "yNphpXuaTZRpU9FBh2W7NkUYcr3kBDE8me", "yXFppDT59xYD41mT2pmAdnvr7aZEFdgdrN", "yeKGAiiEHBGRujvLoYewA77jDDpeDamxvF", "yaxTG66CVzKgHhHZXojRHC9ztLTvz3fwdT", "yYw6qU7dwGoELZkSTj3oSKRpM4U8qTMc1U", "yQE2MksEnSfbeNre19oja9Jj8tvpj64C5a", "yaRnvHo8oLvVmv46vMj5XPbDJouQSnmcLT", "yj5ofWf2uYQQkSavYm2WXgu1QkaZCyP3Cm", "yUCjGmEwrHJwNDrE1o2rMre6MkSbiE6yz7", "yfJzd1nE2rEqz5XEurD6vs4ykizwmw9xTv", "yUk8U3jRZMHKVTa1eFDEtZpa1G4E13FP4d", "yMr59YWQFCADq4FbWrtxDUtMwwshSrmAyK", "yetSehBupzGS9yps5ogqARUGmTMAs2xVcQ", "yNcESKLwriNrhM6EyoSpZEXrzdY3uht92T", "yN2FihGU7KdaEspp39bKrhsHypeyeYzoM2", "yirpWLxHuhwFzA6LfUPKUh1Ke9RB9BUjit", "yVDN66vvdshWdNzhUaQNB6xExAHkzs1zj8", "yPzofnEhRVfDisL2nCUJtAoSHkuyMirHZS", "yjG4jiCMNbx3MyFCAsaNaU29CBVmgPf8hS", "yWrEYTtydaZCyJoKE1vzX4e4RXDNPGdnw9", "yM7Smr8HEhbRW4CXga898brATrJSsk1QUh", "yUprvaE5KrKVPgABmGtxba9MmZqDzKQHom"], + "otherAccountAddressesList": ["yYJmzWey5kNecAThet5BFxAga1F4b4DKQ2", "yNCqctyQaq51WU1hN5aNwsgMsZ5fRiB7GY", "yNPbYz5cZKw2EwxtkL3VSVzPi2FYp9VKjQ", "ybsGWzsnSCAZufgSeUjScVxqEdved99UM2", "yfNHuPojk8XKWP5nuueDptX4nM7qToudgx", "yXxLnDkk6s8h1PSnYaFM6MAyRarc1Kc1rY", "yipResSzN2zUvL7UYkmptKKmQTv7sNssRn", "yZPtNwimHdRiKYbNQW49qezw1Kc1YwUJeT", "yPMjYYfQbga2nBiuqqfUyX41U1vwRZ8fG8", "yLueLWWcLQsaXQ8D5o9tcyo8tfTxMWXvG4", "yN8gzgsc1RVjXThMQT5qZH2jjpnMymz6zP", "yPQLWBNwMdLxUW2oUwHGwQtfyYxD41BARJ", "yg5g2AfWFdwWexWGfbSXYbUHf1y5WWrFPs", "yWyABu4naV1Jzw7w9sn1gqhebPRSkCndsS", "ycuUPzUBjhKyUjezQR1LNot79a6C4aRLaR", "yQ7YjvAXgDAUCekveHVjr6NBveXrUemVno", "yi8bghcw627cMGpuH4bJqH6bqR5ywv1NLH", "yizHu8i2rfwzwBgnJ62s2WUe6wLoDjne6N", "yW1u3tySeUKAKJsz7sjZFyjUiTyKLB6xBv", "yNaSkdy1Q8JNubUdbLMGsGf7sTRofEJYZq", "yXMrw79LPgu78EJsfGGYpm6fXKc1EMnQ49", "yh6Hcyipdvp6WJpQxjNbaXP4kzPQUJpY3n", "yNphpXuaTZRpU9FBh2W7NkUYcr3kBDE8me", "yXFppDT59xYD41mT2pmAdnvr7aZEFdgdrN", "yeKGAiiEHBGRujvLoYewA77jDDpeDamxvF", "yaxTG66CVzKgHhHZXojRHC9ztLTvz3fwdT", "yYw6qU7dwGoELZkSTj3oSKRpM4U8qTMc1U", "yQE2MksEnSfbeNre19oja9Jj8tvpj64C5a", "yaRnvHo8oLvVmv46vMj5XPbDJouQSnmcLT", "yj5ofWf2uYQQkSavYm2WXgu1QkaZCyP3Cm", "yUCjGmEwrHJwNDrE1o2rMre6MkSbiE6yz7", "yfJzd1nE2rEqz5XEurD6vs4ykizwmw9xTv", "yUk8U3jRZMHKVTa1eFDEtZpa1G4E13FP4d", "yMr59YWQFCADq4FbWrtxDUtMwwshSrmAyK", "yetSehBupzGS9yps5ogqARUGmTMAs2xVcQ", "yNcESKLwriNrhM6EyoSpZEXrzdY3uht92T", "yN2FihGU7KdaEspp39bKrhsHypeyeYzoM2", "yirpWLxHuhwFzA6LfUPKUh1Ke9RB9BUjit", "yVDN66vvdshWdNzhUaQNB6xExAHkzs1zj8", "yPzofnEhRVfDisL2nCUJtAoSHkuyMirHZS"], "miscAddressesList": [] } - expect(result).to.deep.equal(expectedResult); + expect(result.externalAddressesList).to.deep.equal(expectedResult.externalAddressesList); + expect(result.internalAddressesList).to.deep.equal(expectedResult.internalAddressesList); + expect(result.otherAccountAddressesList).to.deep.equal(expectedResult.otherAccountAddressesList); + expect(result.miscAddressesList).to.deep.equal(expectedResult.miscAddressesList); }); }); diff --git a/packages/wallet-lib/src/utils/sortTransactions.js b/packages/wallet-lib/src/utils/sortTransactions.js new file mode 100644 index 00000000000..d64e19c8ce8 --- /dev/null +++ b/packages/wallet-lib/src/utils/sortTransactions.js @@ -0,0 +1,49 @@ +/** + * @typedef TxMetadata + * @property {number} height + * @property {string} blockHash + * @property {boolean} isChainLocked + * @property {boolean} isInstantLocked + */ + +/** + * Sorts transactions by height taking into account prevTx linkage within the same height + * @typedef sortTransactions + * @param {{ transaction: Transaction, metadata: TxMetadata }} txsWithMetadata + * @returns {Transaction[]} + */ +const sortTransactions = (txsWithMetadata) => { + const transactionsByHeight = txsWithMetadata.reduce((acc, { transaction, metadata }) => { + const { height } = metadata; + + if (!acc[height]) { + acc[height] = []; + } + + acc[height].push(transaction); + + return acc; + }, {}); + + return Object.keys(transactionsByHeight) + .sort((a, b) => parseInt(a, 10) - parseInt(b, 10)) + .reduce((acc, height) => { + transactionsByHeight[height].sort((a, b) => { + // const prevTxHashBuffer = Buffer.alloc(32); + const prevTxHashes = new Set(); + b.inputs.forEach((input) => { + if (input.prevTxId.readUInt32BE() !== 0) { + prevTxHashes.add(input.prevTxId.toString('hex')); + } + }); + + if (prevTxHashes.has(a.hash)) { + return -1; + } + return 0; + }); + return acc.concat(transactionsByHeight[height]); + }, []); +}; + +module.exports = sortTransactions; diff --git a/packages/wallet-lib/tests/integration/plugins/Workers/TransactionSyncStreamWorker.spec.js b/packages/wallet-lib/tests/integration/plugins/Workers/TransactionSyncStreamWorker.spec.js index 3fe2ae279f5..0b9ee6fd2a9 100644 --- a/packages/wallet-lib/tests/integration/plugins/Workers/TransactionSyncStreamWorker.spec.js +++ b/packages/wallet-lib/tests/integration/plugins/Workers/TransactionSyncStreamWorker.spec.js @@ -213,7 +213,7 @@ describe('TransactionSyncStreamWorker', function suite() { expect(account.transport.subscribeToTransactionsWithProofs.firstCall.args[1]).to.be.deep.equal({ fromBlockHeight: 40, count: 2}); // 20 more of external, since the last address is used. expect(account.transport.subscribeToTransactionsWithProofs.secondCall.args[0].length).to.be.equal(60); - expect(account.transport.subscribeToTransactionsWithProofs.secondCall.args[1]).to.be.deep.equal({ fromBlockHash: '0000025d24ebe65454bd51a61bab94095a6ad1df996be387e31495f764d8e2d9', count: 2}); + expect(account.transport.subscribeToTransactionsWithProofs.secondCall.args[1]).to.be.deep.equal({ fromBlockHeight: 42, count: 1}); expect(worker.stream).to.be.null; expect(transactionsInStorage.length).to.be.equal(2); @@ -434,7 +434,7 @@ describe('TransactionSyncStreamWorker', function suite() { expect(account.transport.subscribeToTransactionsWithProofs.firstCall.args[1]).to.be.deep.equal({ fromBlockHeight: 40, count: 0}); // 20 more of external, since the last address is used. expect(account.transport.subscribeToTransactionsWithProofs.secondCall.args[0].length).to.be.equal(60); - expect(account.transport.subscribeToTransactionsWithProofs.secondCall.args[1]).to.be.deep.equal({ fromBlockHash: '0000025d24ebe65454bd51a61bab94095a6ad1df996be387e31495f764d8e2d9', count: 0}); + expect(account.transport.subscribeToTransactionsWithProofs.secondCall.args[1]).to.be.deep.equal({ fromBlockHeight: 42, count: 0}); expect(worker.stream).to.be.null; expect(transactionsInStorage.length).to.be.equal(2); @@ -698,7 +698,7 @@ describe('TransactionSyncStreamWorker', function suite() { expect(account.transport.subscribeToTransactionsWithProofs.firstCall.args[0].length).to.be.equal(40); // 20 more of external, since the last address is used, Merkle Block received expect(account.transport.subscribeToTransactionsWithProofs.secondCall.args[0].length).to.be.equal(60); - expect(account.transport.subscribeToTransactionsWithProofs.secondCall.args[1]).to.be.deep.equal({ fromBlockHash: '0000025d24ebe65454bd51a61bab94095a6ad1df996be387e31495f764d8e2d9', count: 0}); + expect(account.transport.subscribeToTransactionsWithProofs.secondCall.args[1]).to.be.deep.equal({ fromBlockHeight: 42, count: 0}); expect(worker.stream).to.be.null; expect(transactionsInStorage.length).to.be.equal(2); expect(transactionsInStorage).to.have.deep.members(expectedTransactions); diff --git a/packages/wallet-lib/tests/integration/types/Wallet.spec.js b/packages/wallet-lib/tests/integration/types/Wallet.spec.js new file mode 100644 index 00000000000..dcfec8d8346 --- /dev/null +++ b/packages/wallet-lib/tests/integration/types/Wallet.spec.js @@ -0,0 +1,331 @@ +const { + HDPrivateKey, + Transaction, + BlockHeader, + PrivateKey +} = require('@dashevo/dashcore-lib'); + +const { expect } = require('chai'); + +const { Wallet, EVENTS } = require("../../../src"); +const TransactionSyncStreamWorker = require("../../../src/plugins/Workers/TransactionSyncStreamWorker/TransactionSyncStreamWorker"); +const ChainPlugin = require("../../../src/plugins/Plugins/ChainPlugin"); +const LocalForageAdapterMock = require("../../../src/test/mocks/LocalForageAdapterMock"); +const createAndAttachTransportMocksToWallet = require("../../../src/test/mocks/createAndAttachTransportMocksToWallet"); +const {waitOneTick} = require("../../../src/test/utils"); + +describe('Wallet', () => { + describe('Storage', () => { + let wallet; + let txStreamMock; + let txStreamWorker; + let chainPlugin; + let transportMock; + let bestBlockHeight = 42; + let storageAdapterMock = new LocalForageAdapterMock(); + + beforeEach(async function() { + const testHDKey = "xprv9s21ZrQH143K4PgfRZPuYjYUWRZkGfEPuWTEUESMoEZLC274ntC4G49qxgZJEPgmujsmY52eVggtwZgJPrWTMXmbYgqDVySWg46XzbGXrSZ"; + txStreamWorker = new TransactionSyncStreamWorker({ executeOnStart: false }); + chainPlugin = new ChainPlugin({ executeOnStart: false }); + + wallet = new Wallet({ + offlineMode: true, + plugins: [chainPlugin, txStreamWorker], + allowSensitiveOperations: true, + HDPrivateKey: new HDPrivateKey(testHDKey), + adapter: storageAdapterMock, + network: 'livenet' + }); + + ({ txStreamMock, transportMock } = await createAndAttachTransportMocksToWallet(wallet, this.sinonSandbox)); + + transportMock.getStatus.returns({ + chain: { blocksCount: bestBlockHeight }, + network: { fee: 237 } + }) + + transportMock.sendTransaction.callsFake((tx) => { + txStreamMock.sendTransactions([new Transaction(tx)]) + }) + + await chainPlugin.onStart() + }) + + /** + * In this scenario we have a fresh wallet that receives a funding transaction + * and sends a transaction on his own. + * Points to check: + * - subscr + */ + it('should fill the storage for a fresh wallet', async function() { + const account = await wallet.getAccount(); + const { address: addressToFund } = account.getUnusedAddress(); + + /** Define a scenario */ + const scenario = { + transactions: { + fundingTx: new Transaction().to(addressToFund, 10000), + }, + blockHeaders: [ + new BlockHeader({ + version: 1, + prevHash: '0000000000000000000000000000000000000000000000000000000000000000', + merkleRoot: '0000000000000000000000000000000000000000000000000000000000000000', + time: Date.now() / 1000, + bits: 0, + nonce: 0, + }), + new BlockHeader({ + version: 1, + prevHash: '0000000000000000000000000000000000000000000000000000000000000001', + merkleRoot: '0000000000000000000000000000000000000000000000000000000000000000', + time: Date.now() / 1000, + bits: 1, + nonce: 1, + }) + ], + metadata: {} + } + + transportMock.getBestBlockHeight.returns(bestBlockHeight); + transportMock.getTransaction.callsFake(async (hash) => scenario.metadata[hash]) + transportMock.getBlockHeaderByHash.callsFake(async hash => scenario.blockHeaders.find(header => header.hash === hash)) + + Object.assign(scenario.metadata, { + [scenario.transactions.fundingTx.hash]: { + transaction: scenario.transactions.fundingTx, + height: 10, + blockHash: scenario.blockHeaders[0].hash + } + }) + + /** Start transactions sync plugin */ + txStreamWorker.onStart(); + await waitOneTick(); + + /** Ensure proper transport arguments */ + expect(transportMock.subscribeToTransactionsWithProofs.firstCall.args[1]) + .to.deep.equal({ fromBlockHeight: 1, count: 41 }); + + /** Send first funding transaction to the wallet */ + const { fundingTx } = scenario.transactions; + txStreamMock.sendTransactions([fundingTx]); + await wallet.storage.saveState(); + + /** Ensure that storage has no items for transactions without the metadata */ + let chainStoreState = storageAdapterMock.getItem('chains')[wallet.network]; + let walletStoreState = storageAdapterMock.getItem('wallets')[wallet.walletId] + expect(chainStoreState.transactions).to.be.empty; + expect(chainStoreState.txMetadata).to.be.empty; + expect(chainStoreState.blockHeaders).to.be.empty; + expect(walletStoreState.lastKnownBlock.height).to.equal(-1) + + /** Wait for transactions metadata */ + await waitOneTick(); + + /** + * Simulate block height change to ensure that this value is not + * affecting WalletStore.state.lastKnownBlock, because we still in the phase of historical sync + */ + transportMock.emit(EVENTS.BLOCKHEIGHT_CHANGED, { payload: (bestBlockHeight = 43) }) + await waitOneTick(); + await wallet.storage.saveState(); + + /** + * Ensure that chain items for fundingTx have been propagated + * alongside with the lastKnownBlock + */ + chainStoreState = storageAdapterMock.getItem('chains')[wallet.network]; + walletStoreState = storageAdapterMock.getItem('wallets')[wallet.walletId] + expect(chainStoreState.transactions[fundingTx.hash]).to.exist; + expect(chainStoreState.txMetadata[fundingTx.hash]).to.exist + expect(chainStoreState.blockHeaders[scenario.blockHeaders[0].hash]).to.exist; + expect(walletStoreState.lastKnownBlock.height).to.equal(10) + + /** End historical sync */ + txStreamMock.finish(); + await waitOneTick(); + + /** + * Ensure that reorg safe height (chain height - 6) is set as last known block + * after historical sync is finished + */ + await wallet.storage.saveState(); + walletStoreState = storageAdapterMock.getItem('wallets')[wallet.walletId] + expect(walletStoreState.lastKnownBlock.height).to.equal(37) + + /** Start continuous sync */ + txStreamWorker.execute() + await waitOneTick(); + + /** Ensure proper transport arguments */ + expect(transportMock.subscribeToTransactionsWithProofs.lastCall.args[1]) + .to.deep.equal({ fromBlockHeight: 42, count: 0 }); + + /** Broadcast transaction from the wallet */ + const sendTx = account.createTransaction({ + recipient: new PrivateKey().toAddress(), + satoshis: 1000 + }); + await account.broadcastTransaction(sendTx) + + Object.assign(scenario.metadata, { + [sendTx.hash]: { + transaction: sendTx, + height: 44, + blockHash: scenario.blockHeaders[1].hash + } + }) + + transportMock.emit(EVENTS.BLOCKHEIGHT_CHANGED, { payload: (bestBlockHeight = 44) }) + await waitOneTick(); + + /** + * Ensure that reorg safe height (chain height - 6) is set as last known block height + * and sent transaction hasn't been saved because it's still not reorg-safe + * */ + await wallet.storage.saveState(); + walletStoreState = storageAdapterMock.getItem('wallets')[wallet.walletId] + expect(Object.keys(chainStoreState.transactions)).to.have.lengthOf(1) + expect(Object.keys(chainStoreState.txMetadata)).to.have.lengthOf(1) + expect(Object.keys(chainStoreState.blockHeaders)).to.have.lengthOf(1) + expect(walletStoreState.lastKnownBlock.height).to.equal(38) + + /** + * Emit one more BLOCKHEIGHT_CHANGE event to ensure that previously considered + * reorg unsafe items were saved + */ + transportMock.emit(EVENTS.BLOCKHEIGHT_CHANGED, { payload: (bestBlockHeight = 50) }) + await waitOneTick(); + + await wallet.storage.saveState(); + chainStoreState = storageAdapterMock.getItem('chains')[wallet.network]; + walletStoreState = storageAdapterMock.getItem('wallets')[wallet.walletId] + + /** + * Ensure that storage have been updated with the latest + * transactions and relevant chain data which now considered reorg safe + */ + expect(Object.keys(chainStoreState.transactions)).to.have.lengthOf(2) + expect(Object.keys(chainStoreState.txMetadata)).to.have.lengthOf(2) + expect(Object.keys(chainStoreState.blockHeaders)).to.have.lengthOf(2) + expect(walletStoreState.lastKnownBlock.height).to.equal(44) + + /** Update chain height */ + bestBlockHeight = 52; + }) + + /** + * In this scenario we have a wallet that picks part of the data from the storage + * and then sends a new transaction to the network + */ + it('should ensure synchronization from last known block for wallet with storage', async () => { + const scenario = { + blockHeaders: [ + new BlockHeader({ + version: 1, + prevHash: '0000000000000000000000000000000000000000000000000000000000000002', + merkleRoot: '0000000000000000000000000000000000000000000000000000000000000000', + time: Date.now() / 1000, + bits: 0, + nonce: 0, + }), + ], + metadata: {} + } + + transportMock.getTransaction.callsFake(async (hash) => scenario.metadata[hash]) + transportMock.getBestBlockHeight.returns(bestBlockHeight); + transportMock.getBlockHeaderByHash + .callsFake(async hash => scenario.blockHeaders.find(header => header.hash === hash)) + + /** Initialize account */ + const account = await wallet.getAccount(); + + const walletStore = account.storage.getWalletStore(wallet.walletId); + const chainStore = account.storage.getChainStore(wallet.network); + + /** Ensure that storage contains transaction and relevant chain data */ + expect(chainStore.state.transactions.size).to.equal(2); + expect(chainStore.state.blockHeaders.size).to.equal(2) + expect(walletStore.state.lastKnownBlock.height).to.equal(44) + + /** Start transactions sync plugin */ + txStreamWorker.onStart(); + await waitOneTick(); + + /** Ensure that historical synchronization starts from last known block */ + expect(transportMock.subscribeToTransactionsWithProofs.lastCall.args[1]) + .to.deep.equal({ fromBlockHeight: 44, count: 8 }); + + /** End historical sync */ + txStreamMock.finish(); + await waitOneTick(); + + /** Ensure that reorg-safe block set as last known block */ + await wallet.storage.saveState(); + let walletStoreState = storageAdapterMock.getItem('wallets')[wallet.walletId] + expect(walletStoreState.lastKnownBlock.height).to.equal(46) + + /** Start continuous sync */ + txStreamWorker.execute() + await waitOneTick(); + + /** Ensure proper transport arguments */ + expect(transportMock.subscribeToTransactionsWithProofs.lastCall.args[1]) + .to.deep.equal({ fromBlockHeight: 52, count: 0 }); + + /** Broadcast transaction from the wallet */ + const sendTx = account.createTransaction({ + recipient: new PrivateKey().toAddress(), + satoshis: 1000 + }); + await account.broadcastTransaction(sendTx) + + Object.assign(scenario.metadata, { + [sendTx.hash]: { + transaction: sendTx, + height: 53, + blockHash: scenario.blockHeaders[0].hash + } + }) + + /** Wait for sendTx metadata arrives to the storage */ + await waitOneTick(); + + /** + * Ensure that storage still in reorg-safe state + */ + await wallet.storage.saveState(); + walletStoreState = storageAdapterMock.getItem('wallets')[wallet.walletId] + let chainStoreState = storageAdapterMock.getItem('chains')[wallet.network]; + + expect(Object.keys(chainStoreState.transactions)).to.have.lengthOf(2) + expect(Object.keys(chainStoreState.txMetadata)).to.have.lengthOf(2) + expect(Object.keys(chainStoreState.blockHeaders)).to.have.lengthOf(3) + expect(walletStoreState.lastKnownBlock.height).to.equal(46) + + + /** + * Emit one more BLOCKHEIGHT_CHANGE event to ensure that previously considered + * reorg unsafe items were saved + */ + transportMock.emit(EVENTS.BLOCKHEIGHT_CHANGED, { payload: (bestBlockHeight = 59) }) + await waitOneTick(); + + await wallet.storage.saveState(); + chainStoreState = storageAdapterMock.getItem('chains')[wallet.network]; + walletStoreState = storageAdapterMock.getItem('wallets')[wallet.walletId] + + /** + * Ensure that storage have been updated with the latest + * transactions and relevant chain data which now considered reorg safe + */ + expect(Object.keys(chainStoreState.transactions)).to.have.lengthOf(3) + expect(Object.keys(chainStoreState.txMetadata)).to.have.lengthOf(3) + expect(Object.keys(chainStoreState.blockHeaders)).to.have.lengthOf(3) + expect(walletStoreState.lastKnownBlock.height).to.equal(53) + }) + }) +}) diff --git a/scripts/configure_test_suite.sh b/scripts/configure_test_suite.sh index ed7191bf89e..4d59af83538 100755 --- a/scripts/configure_test_suite.sh +++ b/scripts/configure_test_suite.sh @@ -27,6 +27,7 @@ MINT_FILE_PATH=${PATH_TO_PROJECT_ROOT}/logs/mint.log yarn dashmate wallet mint --verbose --config=local_seed 100 | tee "${MINT_FILE_PATH}" FAUCET_ADDRESS=$(grep -m 1 "Address:" "${MINT_FILE_PATH}" | awk '{printf $3}') FAUCET_PRIVATE_KEY=$(grep -m 1 "Private key:" "${MINT_FILE_PATH}" | awk '{printf $4}') +FAUCET_WALLET_USE_STORAGE=true # check variables are not empty if [ -z "$FAUCET_ADDRESS" ] || \ @@ -50,6 +51,7 @@ touch ${TEST_ENV_FILE_PATH} echo "DAPI_SEED=127.0.0.1 FAUCET_ADDRESS=${FAUCET_ADDRESS} FAUCET_PRIVATE_KEY=${FAUCET_PRIVATE_KEY} +FAUCET_WALLET_USE_STORAGE=${FAUCET_WALLET_USE_STORAGE} DPNS_OWNER_PRIVATE_KEY=${DPNS_OWNER_PRIVATE_KEY} FEATURE_FLAGS_OWNER_PRIVATE_KEY=${FEATURE_FLAGS_OWNER_PRIVATE_KEY} DASHPAY_OWNER_PRIVATE_KEY=${DASHPAY_OWNER_PRIVATE_KEY} diff --git a/yarn.lock b/yarn.lock index baa714dc8d3..8714b10097a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1736,8 +1736,10 @@ __metadata: karma-mocha-reporter: ^2.2.5 karma-sourcemap-loader: ^0.3.7 karma-webpack: ^5.0.0 + localforage: ^1.10.0 mocha: ^9.1.2 net: ^1.0.2 + nodeforage: ^1.1.1 os-browserify: ^0.3.0 path-browserify: ^1.0.1 process: ^0.11.10 @@ -1835,7 +1837,6 @@ __metadata: karma-mocha-reporter: ^2.2.5 karma-sourcemap-loader: ^0.3.7 karma-webpack: ^5.0.0 - localforage: ^1.7.3 lodash: ^4.17.19 mocha: ^9.1.2 node-inspect-extracted: ^1.0.8 @@ -1846,6 +1847,7 @@ __metadata: process: ^0.11.10 setimmediate: ^1.0.5 sinon: ^11.1.2 + sinon-chai: ^3.7.0 stream-browserify: ^3.0.0 stream-http: ^3.2.0 string_decoder: ^1.3.0 @@ -4085,7 +4087,7 @@ __metadata: languageName: node linkType: hard -"bluebird@npm:^3.7.2": +"bluebird@npm:^3.4.7, bluebird@npm:^3.7.2": version: 3.7.2 resolution: "bluebird@npm:3.7.2" checksum: 869417503c722e7dc54ca46715f70e15f4d9c602a423a02c825570862d12935be59ed9c7ba34a9b31f186c017c23cac6b54e35446f8353059c101da73eac22ef @@ -7821,6 +7823,13 @@ fsevents@~2.3.2: languageName: node linkType: hard +"graceful-fs@npm:^4.1.11": + version: 4.2.10 + resolution: "graceful-fs@npm:4.2.10" + checksum: 3f109d70ae123951905d85032ebeae3c2a5a7a997430df00ea30df0e3a6c60cf6689b109654d6fdacd28810a053348c4d14642da1d075049e6be1ba5216218da + languageName: node + linkType: hard + "graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.5, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6": version: 4.2.9 resolution: "graceful-fs@npm:4.2.9" @@ -9734,7 +9743,7 @@ fsevents@~2.3.2: languageName: node linkType: hard -"localforage@npm:^1.7.3": +"localforage@npm:^1.10.0": version: 1.10.0 resolution: "localforage@npm:1.10.0" dependencies: @@ -9799,6 +9808,13 @@ fsevents@~2.3.2: languageName: node linkType: hard +"lodash.find@npm:^4.6.0": + version: 4.6.0 + resolution: "lodash.find@npm:4.6.0" + checksum: b737f849a4fe36f5c3664ea636780dda2fde18335021faf80cdfdcb300ed75441da6f55cfd6de119092d8bb2ddbc4433f4a8de4b99c0b9c8640465b0901c717c + languageName: node + linkType: hard + "lodash.flattendeep@npm:^4.4.0": version: 4.4.0 resolution: "lodash.flattendeep@npm:4.4.0" @@ -9834,7 +9850,7 @@ fsevents@~2.3.2: languageName: node linkType: hard -"lodash.merge@npm:^4.6.2": +"lodash.merge@npm:^4.6.1, lodash.merge@npm:^4.6.2": version: 4.6.2 resolution: "lodash.merge@npm:4.6.2" checksum: ad580b4bdbb7ca1f7abf7e1bce63a9a0b98e370cf40194b03380a46b4ed799c9573029599caebc1b14e3f24b111aef72b96674a56cfa105e0f5ac70546cdc005 @@ -10837,6 +10853,19 @@ fsevents@~2.3.2: languageName: node linkType: hard +"nodeforage@npm:^1.1.1": + version: 1.1.1 + resolution: "nodeforage@npm:1.1.1" + dependencies: + lodash.find: ^4.6.0 + lodash.ismatch: ^4.4.0 + lodash.merge: ^4.6.1 + proper-lockfile: ^3.2.0 + slocket: ^1.0.5 + checksum: 0cb4e4aa853cb9cd81ec2c3aabb2a9032b68ec067cef0bc31b7f7b70522a0081983f730fecc9053d6911cb0114edc2d54201c296b5acf9ea85bf96cc016cbb22 + languageName: node + linkType: hard + "nodemon@npm:^2.0.4": version: 2.0.15 resolution: "nodemon@npm:2.0.15" @@ -11983,6 +12012,17 @@ fsevents@~2.3.2: languageName: node linkType: hard +"proper-lockfile@npm:^3.2.0": + version: 3.2.0 + resolution: "proper-lockfile@npm:3.2.0" + dependencies: + graceful-fs: ^4.1.11 + retry: ^0.12.0 + signal-exit: ^3.0.2 + checksum: 1be1bb702b9d47bdf18d75f22578f51370781feba7d2617f70ff8c66a86bcfa6e55b4f69c57fc326380110f2d1ffdb6e54a4900814bf156c04ee4eb2d3c065aa + languageName: node + linkType: hard + "protobufjs@github:jawid-h/protobuf.js#fix/buffer-conversion": version: 6.10.2 resolution: "protobufjs@https://github.com/jawid-h/protobuf.js.git#commit=d13d5d5688052e366aa2e9169f50dfca376b32cf" @@ -12742,7 +12782,7 @@ fsevents@~2.3.2: languageName: node linkType: hard -"rimraf@npm:^2.6.3": +"rimraf@npm:^2.5.4, rimraf@npm:^2.6.3": version: 2.7.1 resolution: "rimraf@npm:2.7.1" dependencies: @@ -13187,6 +13227,17 @@ fsevents@~2.3.2: languageName: node linkType: hard +"slocket@npm:^1.0.5": + version: 1.0.5 + resolution: "slocket@npm:1.0.5" + dependencies: + bluebird: ^3.4.7 + rimraf: ^2.5.4 + signal-exit: ^3.0.2 + checksum: 4ea3cba56c38325ce190ffa96ef0122ede78846ce12b82c810b247fd52549cfb962fe10bcc629ee14be315db085ff5ebb1059c3ccd1e52251a5b0be66f0cf0d7 + languageName: node + linkType: hard + "smart-buffer@npm:^4.1.0": version: 4.2.0 resolution: "smart-buffer@npm:4.2.0" From 8570a6829b9778dccbbbeb4b83b2f5cd911053a6 Mon Sep 17 00:00:00 2001 From: Mikhail Pshenichnikov Date: Tue, 19 Apr 2022 17:16:09 +0400 Subject: [PATCH 7/8] fix(wallet-lib): optimize storage version check (#348) --- .../src/types/Storage/methods/configure.js | 12 ++++++++---- .../src/types/Storage/methods/rehydrateState.js | 6 +++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/wallet-lib/src/types/Storage/methods/configure.js b/packages/wallet-lib/src/types/Storage/methods/configure.js index 328f0564ae6..e068f8bdae2 100644 --- a/packages/wallet-lib/src/types/Storage/methods/configure.js +++ b/packages/wallet-lib/src/types/Storage/methods/configure.js @@ -1,4 +1,5 @@ const { has } = require('lodash'); +const InMem = require('../../../adapters/InMem'); const configureAdapter = require('../_configureAdapter'); const getDefaultAdapter = require('../_getDefaultAdapter'); const { CONFIGURED } = require('../../../EVENTS'); @@ -18,12 +19,15 @@ module.exports = async function configure(opts = {}) { const version = await this.adapter.getItem('version'); - if (!version) { - await this.adapter.setItem('version', CURRENT_VERSION); - } else if (version !== CURRENT_VERSION) { - logger.warn('Storage version mismatch, resyncing from start'); + if (!(this.adapter instanceof InMem) && version !== CURRENT_VERSION) { + if (typeof version === 'number') { + logger.warn('Storage version mismatch, resyncing from start'); + } + await this.adapter.setItem('wallets', null); await this.adapter.setItem('chains', null); + await this.adapter.setItem('transactions', null); + await this.adapter.setItem('instantLocks', null); await this.adapter.setItem('version', CURRENT_VERSION); } diff --git a/packages/wallet-lib/src/types/Storage/methods/rehydrateState.js b/packages/wallet-lib/src/types/Storage/methods/rehydrateState.js index 0926c99a360..ac696070410 100644 --- a/packages/wallet-lib/src/types/Storage/methods/rehydrateState.js +++ b/packages/wallet-lib/src/types/Storage/methods/rehydrateState.js @@ -26,7 +26,9 @@ const rehydrateState = async function rehydrateState() { logger.error('Error importing wallets storage, resyncing from start', e); this.adapter.setItem('wallets', null); - this.adapter.setItem('chains˚', null); + this.adapter.setItem('chains', null); + this.adapter.setItem('transactions', null); + this.adapter.setItem('instantLocks', null); } } @@ -45,6 +47,8 @@ const rehydrateState = async function rehydrateState() { this.adapter.setItem('wallets', null); this.adapter.setItem('chains', null); + this.adapter.setItem('transactions', null); + this.adapter.setItem('instantLocks', null); } } } From 8a9b526fa92697c2ae4f88f81140d5dae7acbe9d Mon Sep 17 00:00:00 2001 From: "markin.io" Date: Wed, 27 Apr 2022 10:38:01 +0400 Subject: [PATCH 8/8] fix(wallet-lib): inconsistencies after cherry-picks --- .../Client/Platform/methods/identities/register.ts | 2 +- packages/platform-test-suite/test/e2e/wallet.spec.js | 2 +- .../test/functional/platform/Identity.spec.js | 11 ++++++----- packages/wallet-lib/src/types/Account/Account.js | 11 +++++++---- .../types/ChainStore/methods/considerTransaction.js | 1 + .../src/types/Storage/methods/createChainStore.js | 1 + .../Workers/TransactionSyncStreamWorker.spec.js | 2 +- 7 files changed, 18 insertions(+), 12 deletions(-) diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/register.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/register.ts index 9fb45c71a38..f5983d98c9a 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/register.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/identities/register.ts @@ -44,7 +44,7 @@ export default async function register( ); // Current identity object will not have metadata or balance information - const registeredIdentity = await this.identities.get(identity.id.toString()); + const registeredIdentity = await this.identities.get(identity.getId().toString()); // We cannot just return registeredIdentity as we want to // keep additional information (assetLockProof and transaction) instance diff --git a/packages/platform-test-suite/test/e2e/wallet.spec.js b/packages/platform-test-suite/test/e2e/wallet.spec.js index 6ae32f1869b..e138c8467ba 100644 --- a/packages/platform-test-suite/test/e2e/wallet.spec.js +++ b/packages/platform-test-suite/test/e2e/wallet.spec.js @@ -36,7 +36,7 @@ describe('e2e', () => { mnemonic = emptyWallet.wallet.exportWallet(); const { storage } = fundedWallet.wallet; - emptyWalletHeight = storage.store.chains[storage.network].blockHeight; + emptyWalletHeight = storage.getChainStore(storage.application.network).state.blockHeight; }); // Skip test if any prior test in this describe failed diff --git a/packages/platform-test-suite/test/functional/platform/Identity.spec.js b/packages/platform-test-suite/test/functional/platform/Identity.spec.js index 69de310a30e..43c2aa2e788 100644 --- a/packages/platform-test-suite/test/functional/platform/Identity.spec.js +++ b/packages/platform-test-suite/test/functional/platform/Identity.spec.js @@ -117,11 +117,12 @@ describe('Platform', () => { await wait(5000); } - walletAccount.storage.insertIdentityIdAtIndex( - walletAccount.walletId, - identityOne.getId().toString(), - identityOneIndex, - ); + walletAccount.storage + .getWalletStore(walletAccount.walletId) + .insertIdentityIdAtIndex( + identityOne.getId().toString(), + identityOneIndex, + ); // Creating transition that tries to spend the same transaction const { diff --git a/packages/wallet-lib/src/types/Account/Account.js b/packages/wallet-lib/src/types/Account/Account.js index e726584c2ad..e8e9e30f786 100644 --- a/packages/wallet-lib/src/types/Account/Account.js +++ b/packages/wallet-lib/src/types/Account/Account.js @@ -277,7 +277,8 @@ class Account extends EventEmitter { */ waitForInstantLock(transactionHash, timeout = this.waitForInstantLockTimeout) { // Return instant lock immediately if already exists - const instantLock = this.storage.getInstantLock(transactionHash); + const chainStore = this.storage.getChainStore(this.network); + const instantLock = chainStore.getInstantLock(transactionHash); if (instantLock != null) { return { promise: Promise.resolve(instantLock), @@ -327,10 +328,12 @@ class Account extends EventEmitter { */ waitForTxMetadata(transactionHash, timeout = this.waitForTxMetadataTimeout) { // Return tx metadata immediately if already exists - const { transactionsMetadata } = this.storage; - if (transactionsMetadata && transactionsMetadata[transactionHash]) { + const chainStore = this.storage.getChainStore(this.network); + const txWithMetadata = chainStore.getTransaction(transactionHash); + + if (txWithMetadata && txWithMetadata.metadata && txWithMetadata.metadata.height) { return { - promise: Promise.resolve(transactionsMetadata[transactionHash]), + promise: Promise.resolve(txWithMetadata.metadata), cancel: () => {}, }; } diff --git a/packages/wallet-lib/src/types/ChainStore/methods/considerTransaction.js b/packages/wallet-lib/src/types/ChainStore/methods/considerTransaction.js index bbfd5e895a8..0a897c8acc8 100644 --- a/packages/wallet-lib/src/types/ChainStore/methods/considerTransaction.js +++ b/packages/wallet-lib/src/types/ChainStore/methods/considerTransaction.js @@ -84,6 +84,7 @@ function considerTransaction(transactionHash) { } }); }); + this.emit(EVENTS.TX_METADATA, { hash: transaction.hash, metadata }); } // TODO: restore EVENTS.FETCHED_UNCONFIRMED_TRANSACTION diff --git a/packages/wallet-lib/src/types/Storage/methods/createChainStore.js b/packages/wallet-lib/src/types/Storage/methods/createChainStore.js index f978e5b9413..890ac45c070 100644 --- a/packages/wallet-lib/src/types/Storage/methods/createChainStore.js +++ b/packages/wallet-lib/src/types/Storage/methods/createChainStore.js @@ -3,6 +3,7 @@ const EVENTS = require('../../../EVENTS'); const EVENTS_TO_FORWARD = [ EVENTS.FETCHED_CONFIRMED_TRANSACTION, + EVENTS.TX_METADATA, ]; /** diff --git a/packages/wallet-lib/tests/integration/plugins/Workers/TransactionSyncStreamWorker.spec.js b/packages/wallet-lib/tests/integration/plugins/Workers/TransactionSyncStreamWorker.spec.js index 0b9ee6fd2a9..97a567a8670 100644 --- a/packages/wallet-lib/tests/integration/plugins/Workers/TransactionSyncStreamWorker.spec.js +++ b/packages/wallet-lib/tests/integration/plugins/Workers/TransactionSyncStreamWorker.spec.js @@ -735,7 +735,7 @@ describe('TransactionSyncStreamWorker', function suite() { const { promise: transaction2Promise } = account.waitForInstantLock(transactions[2].hash, 1000); await expect(transaction2Promise).to.eventually - .be.rejectedWith('InstantLock waiting period for transaction 256d5b3bf6d8869f5cc882ae070af9b648fa0f512bfa2b6f07b35d55e160a16c timed out'); + .be.rejectedWith('InstantLock waiting period for transaction 823c272fc1694b571805d2bc2f8936597ee52de638a0ca5323233c239fd3e8c4 timed out'); }); it('should start from the height specified in `skipSynchronizationBeforeHeight` options', async function () { const bestBlockHeight = 42;