// Use strict "use strict"; // Classes // Wallets class class Wallets { // Public // Constructor constructor(torProxy, node, listener, settings, application, message, transactions, prices) { // Set node this.node = node; // Set listener this.listener = listener; // Set settings this.settings = settings; // Set application this.application = application; // Set transactions this.transactions = transactions; // Set prices this.prices = prices; // Set wallets this.wallets = {}; // Set unverify address suffixes on close this.unverifyAddressSuffixesOnClose = false; // Set is syncing this.isSyncing = false; // Set stop syncing this.stopSyncing = false; // Set ignore synced status this.ignoreSyncedStatus = false; // Set recent heights this.recentHeights = new RecentHeights(this.node); // Set API this.api = new Api(torProxy, this.node, this.transactions, this, this.settings, message, application, this.prices); // Set number of confirmations to setting's default value this.numberOfConfirmations = new BigNumber(Wallets.SETTINGS_NUMBER_OF_CONFIRMATIONS_DEFAULT_VALUE); // Set password to no password this.password = Wallets.NO_PASSWORD; // Set exclusive hardware lock this.exclusiveHardwareLock = false; // Set exclusive hardware lock release event index this.exclusiveHardwareLockReleaseEventIndex = 0; // Set self var self = this; // Check if hardware wallets are supported if(HardwareWallet.isSupported() === true) { // Check if USB is supported if("usb" in navigator === true) { // USB connect event $(navigator["usb"]).on("connect", function(event) { // Obtain exclusive hardware lock self.obtainExclusiveHardwareLock().then(function() { // Check if device isn't opened if(event["originalEvent"]["device"]["opened"] === false) { // Initialize hardware wallet found var hardwareWalletFound = false; // Go through all wallets for(var keyPath in self.wallets) { if(self.wallets.hasOwnProperty(keyPath) === true) { // Get wallet var wallet = self.wallets[keyPath]; // Check if wallet is open, it's a hardware wallet, and its hardware isn't connected if(wallet.isOpen() === true && wallet.getHardwareType() !== Wallet.NO_HARDWARE_TYPE && wallet.isHardwareConnected() === false) { // Set hardware wallet found hardwareWalletFound = true; // Break break; } } } // Check if a hardware wallet was found if(hardwareWalletFound === true) { // Create hardware wallet var hardwareWallet = new HardwareWallet(self.application); // Connect to hardware wallet hardwareWallet.connect(event["originalEvent"]["device"], true).then(function() { // Initialize connect wallets to hardware var connectWalletsToHardware = []; // Go through all wallets for(var keyPath in self.wallets) { if(self.wallets.hasOwnProperty(keyPath) === true) { // Get wallet let wallet = self.wallets[keyPath]; // Check if wallet is a hardware wallet if(wallet.getHardwareType() !== Wallet.NO_HARDWARE_TYPE) { // Append connecting wallet to hardware to list connectWalletsToHardware.push(new Promise(function(resolve, reject) { // Return connecting wallet to the applicable hardware wallet return wallet.connectToApplicableHardware([hardwareWallet]).then(function() { // Resolve resolve(); // Catch errors }).catch(function(error) { // Reject error reject(error); }); })); } } } // Connect wallets to hardware Promise.allSettled(connectWalletsToHardware).then(function() { // Check if hardware wallet isn't in use if(hardwareWallet.getInUse() === false) { // Close hardware wallet hardwareWallet.close(); } // Release exclusive hardware lock self.releaseExclusiveHardwareLock(); }); // Catch errors }).catch(function(error) { // Release exclusive hardware lock self.releaseExclusiveHardwareLock(); }); } // Otherwise else { // Release exclusive hardware lock self.releaseExclusiveHardwareLock(); } } // Otherwise else { // Release exclusive hardware lock self.releaseExclusiveHardwareLock(); } }); }); } } // Create database Database.createDatabase(function(database, currentVersion, databaseTransaction) { // Create or get wallets object store var walletsObjectStore = (currentVersion === Database.NO_CURRENT_VERSION) ? database.createObjectStore(Wallets.OBJECT_STORE_NAME, { // Auto increment "autoIncrement": true }) : databaseTransaction.objectStore(Wallets.OBJECT_STORE_NAME); // Check if no database version exists if(currentVersion === Database.NO_CURRENT_VERSION) { // Create index to search wallets object store by wallet type, network type, and address suffix walletsObjectStore.createIndex(Wallets.DATABASE_WALLET_TYPE_NETWORK_TYPE_AND_ADDRESS_SUFFIX_NAME, [ // Wallet type Database.toKeyPath(Wallets.DATABASE_WALLET_TYPE_NAME), // Network type Database.toKeyPath(Wallets.DATABASE_NETWORK_TYPE_NAME), // Address suffix Database.toKeyPath(Wallets.DATABASE_ADDRESS_SUFFIX_NAME) ], { // Unique "unique": true }); // Create index to search wallets object store by wallet type, network type, and order walletsObjectStore.createIndex(Wallets.DATABASE_WALLET_TYPE_NETWORK_TYPE_AND_ORDER_NAME, [ // Wallet type Database.toKeyPath(Wallets.DATABASE_WALLET_TYPE_NAME), // Network type Database.toKeyPath(Wallets.DATABASE_NETWORK_TYPE_NAME), // Order Database.toKeyPath(Wallets.DATABASE_ORDER_NAME) ], { // Unique "unique": true }); // Create index to search wallets object store by wallet type and network type walletsObjectStore.createIndex(Wallets.DATABASE_WALLET_TYPE_AND_NETWORK_TYPE_NAME, [ // Wallet type Database.toKeyPath(Wallets.DATABASE_WALLET_TYPE_NAME), // Network type Database.toKeyPath(Wallets.DATABASE_NETWORK_TYPE_NAME) ], { // Unique "unique": false }); } }); // Once database is initialized Database.onceInitialized(function() { // Return promise return new Promise(function(resolve, reject) { // Return getting all wallets with the wallet type and network type in the database return Database.getResults(Wallets.OBJECT_STORE_NAME, Database.GET_ALL_RESULTS, Database.GET_ALL_RESULTS, Wallets.DATABASE_WALLET_TYPE_AND_NETWORK_TYPE_NAME, IDBKeyRange.only([ // Wallet type lower bound Consensus.getWalletType(), // Network type lower bound Consensus.getNetworkType() ])).then(function(results) { // Go through all wallets for(var i = 0; i < results["length"]; ++i) { // Get wallet from result var wallet = Wallets.getWalletFromResult(results[i]); // Append wallet to list of wallets self.wallets[wallet.getKeyPath()] = wallet; } // Return creating settings return Promise.all([ // Number of confirmations setting self.settings.createValue(Wallets.SETTINGS_NUMBER_OF_CONFIRMATIONS_NAME, Wallets.SETTINGS_NUMBER_OF_CONFIRMATIONS_DEFAULT_VALUE) ]).then(function() { // Initialize settings var settings = [ // Number of confirmations setting Wallets.SETTINGS_NUMBER_OF_CONFIRMATIONS_NAME ]; // Return getting settings' values return Promise.all(settings.map(function(setting) { // Return getting setting's value return self.settings.getValue(setting); })).then(function(settingValues) { // Set number of confirmations to setting's value self.numberOfConfirmations = new BigNumber(settingValues[settings.indexOf(Wallets.SETTINGS_NUMBER_OF_CONFIRMATIONS_NAME)]); // Resolve resolve(); // Catch errors }).catch(function(error) { // Reject reject(); }); // Catch errors }).catch(function(error) { // Reject reject(); }); // Catch errors }).catch(function(error) { // Reject reject(); }); }); }); // Settings change event $(this.settings).on(Settings.CHANGE_EVENT, function(event, setting) { // Check what setting was changes switch(setting[Settings.DATABASE_SETTING_NAME]) { // Number of confirmations setting case Wallets.SETTINGS_NUMBER_OF_CONFIRMATIONS_NAME: // Set number of confirmations to setting's value self.numberOfConfirmations = new BigNumber(setting[Settings.DATABASE_VALUE_NAME]); // Break break; } }); // Node connection warning or close event $(this.node).on(Node.CONNECTION_WARNING_EVENT + " " + Node.CONNECTION_CLOSE_EVENT, function() { // Sync failed self.syncFailed(); // Node height change event }).on(Node.HEIGHT_CHANGE_EVENT, function(event, height, lastBlockHash, ignoreSyncedStatus) { // Check if node is synced if(height.isEqualTo(Consensus.FIRST_BLOCK_HEIGHT) === false) { // Create a database transaction Database.createTransaction(Wallets.OBJECT_STORE_NAME, Database.READ_AND_WRITE_MODE, Database.STRICT_DURABILITY).then(function(databaseTransaction) { // Initialize set synced heights var setSyncedHeights = []; // Initialize changed wallets var changedWallets = []; // Go through all wallets for(var keyPath in self.wallets) { if(self.wallets.hasOwnProperty(keyPath) === true) { // Get wallet let wallet = self.wallets[keyPath]; // Add to set synced heights setSyncedHeights.push(new Promise(function(resolve, reject) { // Check if wallet exists if(self.walletExists(wallet.getKeyPath()) === true) { // Check if wallet's synced height is current height if(wallet.getSyncedHeight() === Wallet.CURRENT_HEIGHT) { // Return saving wallet return self.saveWallet(wallet, function() { // Check if wallet's synced height is still the current height if(wallet.getSyncedHeight() === Wallet.CURRENT_HEIGHT) { // Return return { // New synced height value [Wallets.NEW_SYNCED_HEIGHT_VALUE]: height.minus(1) }; } // Otherwise else { // Return nothing return {}; } }, databaseTransaction).then(function(newValues) { // Check if wallet's new synced height was saved if(Wallets.NEW_SYNCED_HEIGHT_VALUE in newValues === true) { // Append changed wallet to list changedWallets.push(wallet); } // Resolve resolve(); // Catch errors }).catch(function(error) { // Reject error reject(error); }); } // Otherwise else { // Resolve resolve(); } } // Otherwise else { // Resolve resolve(); } })); } } // When all set synced heights are done Promise.all(setSyncedHeights).then(function() { // Commit database transaction Database.commitTransaction(databaseTransaction).then(function() { // Go through all changed wallets for(var i = 0; i < changedWallets["length"]; ++i) { // Get wallet var wallet = changedWallets[i]; // Set wallet's synced height to the node's current height wallet.setSyncedHeight(height.minus(1)); // Update wallet's starting sync height wallet.setStartingSyncHeight(wallet.getSyncedHeight()); } // Start syncing and ignore synced status if specified self.startSyncing(typeof ignoreSyncedStatus !== "undefined" && ignoreSyncedStatus === true); // Catch errors }).catch(function() { // Abort database transaction and catch errors Database.abortTransaction(databaseTransaction).catch(function() { // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); // Catch errors }).catch(function() { // Abort database transaction and catch errors Database.abortTransaction(databaseTransaction).catch(function() { // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); // Catch errors }).catch(function() { }); } }); // Listener request receive event $(this.listener).on(Listener.REQUEST_RECEIVE_EVENT, function(event, interaction, promiseResolve = undefined, promiseReject = undefined) { // Get interaction's wallet var getInteractionsWallet = function() { // Return promise return new Promise(function(resolve, reject) { // Check if interaction uses a URL if(interaction.getUrl() !== Interaction.NO_URL) { // Return getting wallet with interaction's URL return self.getWalletFromAddressSuffix(interaction.getUrl()).then(function(wallet) { // Resolve wallet resolve(wallet); // Catch errors }).catch(function(error) { // Reject error reject(error); }); } // Otherwise check if interaction uses a wallet key path else if(interaction.getWalletKeyPath() !== Interaction.NO_WALLET_KEY_PATH) { // Check if wallet with the interaction's wallet key path doesn't exist if(self.walletExists(interaction.getWalletKeyPath()) === false) { // Reject error reject(Language.getDefaultTranslation('The wallet doesn\'t exist.')); } // Otherwise else { // Resolve wallet resolve(self.wallets[interaction.getWalletKeyPath()]); } } // Otherwise else { // Reject error reject(Language.getDefaultTranslation('The wallet doesn\'t exist.')); } }); }; // Get interaction's wallet getInteractionsWallet().then(function(wallet) { // Get receiving as file var receivingAsFile = typeof promiseResolve !== "undefined"; // Obtain wallet's exclusive transactions lock and make it high priority if receiving as file self.transactions.obtainWalletsExclusiveTransactionsLock(wallet.getKeyPath(), receivingAsFile === true).then(function() { // Check if wallet exists if(self.walletExists(wallet.getKeyPath()) === true) { // Get interaction's type var type = interaction.getType(); // Get interaction's data var data = interaction.getData(); // Get wallet's last identifier var lastIdentifier = wallet.getLastIdentifier(); // Get the APIs response to the request self.api.getResponse(interaction.getApi(), wallet, type, data, (receivingAsFile === true) ? false : true, (receivingAsFile === true) ? true : false, (receivingAsFile === true) ? Common.NO_CANCEL_OCCURRED : function() { // Return if interaction is canceled return interaction.isCanceled() === true; }).then(function(response) { // Check if wallet exists if(self.walletExists(wallet.getKeyPath()) === true) { // Respond to interaction interaction.respond(response[Api.RESPONSE_RESPONSE_INDEX]).then(function() { // Set timestamp var timestamp = Date.now(); // Get prices var prices = self.prices.getPrices(); // Check if currency was received if(response[Api.RESPONSE_METHOD_INDEX] === Api.RECEIVE_TRANSACTION_METHOD) { // Get slate var slate = response[Api.RESPONSE_ADDITIONAL_DATA_INDEX][Api.RECEIVE_TRANSACTION_ADDITIONAL_DATA_SLATE_INDEX]; // Get commit var commit = response[Api.RESPONSE_ADDITIONAL_DATA_INDEX][Api.RECEIVE_TRANSACTION_ADDITIONAL_DATA_COMMIT_INDEX]; // Get identifier var identifier = response[Api.RESPONSE_ADDITIONAL_DATA_INDEX][Api.RECEIVE_TRANSACTION_ADDITIONAL_DATA_IDENTIFIER_INDEX]; // Get switch type var switchType = response[Api.RESPONSE_ADDITIONAL_DATA_INDEX][Api.RECEIVE_TRANSACTION_ADDITIONAL_DATA_SWITCH_TYPE_INDEX]; // Check if slate's height isn't known if(slate.getHeight() === Slate.UNKNOWN_HEIGHT) { // Set spendable height to the slate's lock height added to the number of confirmation if it exists var spendableHeight = (slate.getLockHeight().isEqualTo(Slate.NO_LOCK_HEIGHT) === false) ? slate.getLockHeight().plus(self.numberOfConfirmations.minus(1)) : Transaction.UNKNOWN_SPENDABLE_HEIGHT; } // Otherwise else { // Set spendable height to the slate's height added to the number of confirmations var spendableHeight = slate.getHeight().plus(self.numberOfConfirmations.minus(1)); // Check if the slate's lock height added to the number of confirmation is greater than the spendable height if(slate.getLockHeight().isEqualTo(Slate.NO_LOCK_HEIGHT) === false && slate.getLockHeight().plus(self.numberOfConfirmations.minus(1)).isGreaterThan(spendableHeight) === true) { // Set the spendable height to the slate's lock height added to the number of confirmation spendableHeight = slate.getLockHeight().plus(self.numberOfConfirmations.minus(1)); } } // Set file response var fileResponse = (receivingAsFile === true) ? ((typeof response[Api.RESPONSE_RESPONSE_INDEX]["result"]["Ok"] === "string") ? response[Api.RESPONSE_RESPONSE_INDEX]["result"]["Ok"] : JSONBigNumber.stringify(response[Api.RESPONSE_RESPONSE_INDEX]["result"]["Ok"])) : Transaction.UNUSED_FILE_RESPONSE; // Try try { // Create transaction var transaction = new Transaction(wallet.getWalletType(), wallet.getNetworkType(), commit, wallet.getKeyPath(), true, timestamp, timestamp, (slate.getHeight() === Slate.UNKNOWN_HEIGHT) ? Transaction.UNKNOWN_HEIGHT : slate.getHeight(), (slate.getLockHeight().isEqualTo(Slate.NO_LOCK_HEIGHT) === false) ? slate.getLockHeight() : Transaction.NO_LOCK_HEIGHT, false, Transaction.STATUS_UNCONFIRMED, slate.getAmount(), false, slate.getExcess(), identifier, switchType, true, slate.getOffsetExcess(), slate.getId(), (slate.getParticipant(SlateParticipant.SENDER_ID).getMessage() !== SlateParticipant.NO_MESSAGE) ? slate.getParticipant(SlateParticipant.SENDER_ID).getMessage() : Transaction.NO_MESSAGE, (slate.getTimeToLiveCutOffHeight() !== Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT) ? slate.getTimeToLiveCutOffHeight() : Transaction.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT, false, Transaction.NO_CONFIRMED_TIMESTAMP, slate.getFee(), (slate.getSenderAddress() !== Slate.NO_SENDER_ADDRESS) ? slate.getSenderAddress() : Transaction.NO_SENDER_ADDRESS, (slate.getReceiverAddress() !== Slate.NO_RECEIVER_ADDRESS) ? slate.getReceiverAddress() : Transaction.NO_RECEIVER_ADDRESS, (slate.getReceiverSignature() !== Slate.NO_RECEIVER_SIGNATURE) ? slate.getReceiverSignature() : Transaction.NO_RECEIVER_SIGNATURE, Transaction.UNUSED_DESTINATION, spendableHeight, self.numberOfConfirmations, Transaction.UNUSED_SPENT_OUTPUTS, Transaction.UNUSED_CHANGE_OUTPUTS, false, Transaction.UNKNOWN_REBROADCAST_MESSAGE, fileResponse, (prices !== Prices.NO_PRICES_FOUND) ? prices : Transaction.UNKNOWN_PRICES_WHEN_RECORDED); } // Catch errors catch(error) { // Check if wallet exists and wallet's last identifier changed if(self.walletExists(wallet.getKeyPath()) === true && wallet.getLastIdentifier() !== Wallet.NO_LAST_IDENTIFIER && (lastIdentifier === Wallet.NO_LAST_IDENTIFIER || wallet.getLastIdentifier().equalsValue(lastIdentifier) === false)) { // Save wallet self.saveWallet(wallet).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Trigger a fatal error new FatalError(FatalError.UNKNOWN_ERROR); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); } // Otherwise else { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Trigger a fatal error new FatalError(FatalError.UNKNOWN_ERROR); } // Return return; } // Create a database transaction Database.createTransaction([ // Wallets object store Wallets.OBJECT_STORE_NAME, // Transactions object store Transactions.OBJECT_STORE_NAME, ], Database.READ_AND_WRITE_MODE, Database.STRICT_DURABILITY).then(function(databaseTransaction) { // Save transactions self.transactions.saveTransactions([transaction], databaseTransaction).then(function() { // Check if wallet exists if(self.walletExists(wallet.getKeyPath()) === true) { // Save wallet self.saveWallet(wallet, function() { // Return return { // New unconfirmed amount value [Wallets.NEW_UNCONFIRMED_AMOUNT_VALUE]: wallet.getUnconfirmedAmount().plus(slate.getAmount()) }; }, databaseTransaction).then(function(newValues) { // Check if wallet exists if(self.walletExists(wallet.getKeyPath()) === true) { // Commit database transaction Database.commitTransaction(databaseTransaction).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Update wallet's unconfirmed amount wallet.setUnconfirmedAmount(newValues[Wallets.NEW_UNCONFIRMED_AMOUNT_VALUE]); // Check if wallet exists if(self.walletExists(wallet.getKeyPath()) === true) { // Check if promise resolve exists if(typeof promiseResolve !== "undefined") { // Resolve promise promiseResolve([ // Wallet wallet, // Amount slate.getAmount().dividedBy(Consensus.VALUE_NUMBER_BASE), // Currency Consensus.CURRENCY_NAME, // Message slate.getParticipant(SlateParticipant.SENDER_ID).getMessage(), // Receiver address slate.getReceiverAddress(), // File response fileResponse, // ID slate.getId() ]); } // Otherwise else { // Trigger currency receive event $(self).trigger(Wallets.CURRENCY_RECEIVE_EVENT, [ // Wallet wallet, // Amount slate.getAmount().dividedBy(Consensus.VALUE_NUMBER_BASE), // Currency Consensus.CURRENCY_NAME, // Message slate.getParticipant(SlateParticipant.SENDER_ID).getMessage(), // Receiver address slate.getReceiverAddress(), // File response fileResponse, // ID slate.getId() ]); } // Trigger change event $(document).trigger(Wallets.CHANGE_EVENT, [ // Key path wallet.getKeyPath(), // Change Wallets.UNCONFIRMED_AMOUNT_CHANGED, // New value newValues[Wallets.NEW_UNCONFIRMED_AMOUNT_VALUE] ]); // Trigger transactions change event $(self.transactions).trigger(Transactions.CHANGE_EVENT, [ // Transactions [transaction] ]); } // Otherwise else { // Check if promise reject exists if(typeof promiseReject !== "undefined") { // Reject promise promiseReject(Listener.NOT_FOUND_RESPONSE); } } // Catch errors }).catch(function(error) { // Abort database transaction and catch errors Database.abortTransaction(databaseTransaction).catch(function() { // Finally }).finally(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Check if promise reject exists if(typeof promiseReject !== "undefined") { // Reject promise promiseReject(error); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); } // Otherwise else { // Abort database transaction Database.abortTransaction(databaseTransaction).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Check if promise reject exists if(typeof promiseReject !== "undefined") { // Reject promise promiseReject(Listener.NOT_FOUND_RESPONSE); } // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Check if promise reject exists if(typeof promiseReject !== "undefined") { // Reject promise promiseReject(Listener.NOT_FOUND_RESPONSE); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); } // Catch errors }).catch(function(error) { // Abort database transaction and catch errors Database.abortTransaction(databaseTransaction).catch(function() { // Finally }).finally(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Check if promise reject exists if(typeof promiseReject !== "undefined") { // Reject promise promiseReject(error); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); } // Otherwise else { // Abort database transaction Database.abortTransaction(databaseTransaction).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Check if promise reject exists if(typeof promiseReject !== "undefined") { // Reject promise promiseReject(Listener.NOT_FOUND_RESPONSE); } // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Check if promise reject exists if(typeof promiseReject !== "undefined") { // Reject promise promiseReject(Listener.NOT_FOUND_RESPONSE); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); } // Catch errors }).catch(function(error) { // Abort database transaction and catch errors Database.abortTransaction(databaseTransaction).catch(function() { // Finally }).finally(function() { // Check if wallet exists and wallet's last identifier changed if(self.walletExists(wallet.getKeyPath()) === true && wallet.getLastIdentifier() !== Wallet.NO_LAST_IDENTIFIER && (lastIdentifier === Wallet.NO_LAST_IDENTIFIER || wallet.getLastIdentifier().equalsValue(lastIdentifier) === false)) { // Save wallet and catch errors self.saveWallet(wallet).catch(function() { // Finally }).finally(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Check if promise reject exists if(typeof promiseReject !== "undefined") { // Reject promise promiseReject(error); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); } // Otherwise else { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Check if promise reject exists if(typeof promiseReject !== "undefined") { // Reject promise promiseReject(error); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); } }); }); // Catch errors }).catch(function(error) { // Check if wallet exists and wallet's last identifier changed if(self.walletExists(wallet.getKeyPath()) === true && wallet.getLastIdentifier() !== Wallet.NO_LAST_IDENTIFIER && (lastIdentifier === Wallet.NO_LAST_IDENTIFIER || wallet.getLastIdentifier().equalsValue(lastIdentifier) === false)) { // Save wallet self.saveWallet(wallet).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Check if promise reject exists if(typeof promiseReject !== "undefined") { // Reject promise promiseReject(error); } // Catch errors }).catch(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Check if promise reject exists if(typeof promiseReject !== "undefined") { // Reject promise promiseReject(error); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); } // Otherwise else { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Check if promise reject exists if(typeof promiseReject !== "undefined") { // Reject promise promiseReject(error); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); } }); } // Otherwise else { // Check if wallet exists and wallet's last identifier changed if(self.walletExists(wallet.getKeyPath()) === true && wallet.getLastIdentifier() !== Wallet.NO_LAST_IDENTIFIER && (lastIdentifier === Wallet.NO_LAST_IDENTIFIER || wallet.getLastIdentifier().equalsValue(lastIdentifier) === false)) { // Save wallet self.saveWallet(wallet).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Check if promise resolve exists if(typeof promiseResolve !== "undefined") { // Resolve promise promiseResolve(); } // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Check if promise reject exists if(typeof promiseReject !== "undefined") { // Reject promise promiseReject(error); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); } // Otherwise else { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Check if promise resolve exists if(typeof promiseResolve !== "undefined") { // Resolve promise promiseResolve(); } } } // Catch errors }).catch(function(error) { // Check if wallet exists and wallet's last identifier changed if(self.walletExists(wallet.getKeyPath()) === true && wallet.getLastIdentifier() !== Wallet.NO_LAST_IDENTIFIER && (lastIdentifier === Wallet.NO_LAST_IDENTIFIER || wallet.getLastIdentifier().equalsValue(lastIdentifier) === false)) { // Save wallet self.saveWallet(wallet).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Check if promise reject exists if(typeof promiseReject !== "undefined") { // Reject promise promiseReject(error); } // Catch errors }).catch(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Check if promise reject exists if(typeof promiseReject !== "undefined") { // Reject promise promiseReject(error); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); } // Otherwise else { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Check if promise reject exists if(typeof promiseReject !== "undefined") { // Reject promise promiseReject(error); } } }); } // Otherwise else { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Check if promise reject exists if(typeof promiseReject !== "undefined") { // Reject promise promiseReject(Listener.NOT_FOUND_RESPONSE); } // Cancel interaction with not found response and catch errors interaction.cancel(Listener.NOT_FOUND_RESPONSE).catch(function(error) { }); } // Catch errors }).catch(function(error) { // Check if canceled or user rejected on hardware wallet if(error === Common.CANCELED_ERROR || error === HardwareWallet.USER_REJECTED_ERROR) { // Check if data is a buffer if(data instanceof Uint8Array === true) { // Set listener error to JSON-RPC internal error error response var listenerError = JsonRpc.createErrorResponse(JsonRpc.INTERNAL_ERROR_ERROR, JSONBigNumber.parse((new TextDecoder("utf-8", {"fatal": true})).decode(data))); } // Otherwise else { // Set listener error to JSON-RPC internal error error response var listenerError = JsonRpc.createErrorResponse(JsonRpc.INTERNAL_ERROR_ERROR, data); } } // Otherwise else { // Set listener error to error var listenerError = error; } // Check if wallet exists and wallet's last identifier changed if(self.walletExists(wallet.getKeyPath()) === true && wallet.getLastIdentifier() !== Wallet.NO_LAST_IDENTIFIER && (lastIdentifier === Wallet.NO_LAST_IDENTIFIER || wallet.getLastIdentifier().equalsValue(lastIdentifier) === false)) { // Save wallet self.saveWallet(wallet).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Check if promise reject exists if(typeof promiseReject !== "undefined") { // Reject promise promiseReject(error); } // Cancel interaction with listener error and catch errors interaction.cancel(listenerError).catch(function(error) { }); // Catch errors }).catch(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Check if promise reject exists if(typeof promiseReject !== "undefined") { // Reject promise promiseReject(error); } // Cancel interaction with listener error and catch errors interaction.cancel(listenerError).catch(function(error) { // Finally }).finally(function() { // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); } // Otherwise else { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Check if promise reject exists if(typeof promiseReject !== "undefined") { // Reject promise promiseReject(error); } // Cancel interaction with listener error and catch errors interaction.cancel(listenerError).catch(function(error) { }); } }); } // Otherwise else { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(wallet.getKeyPath()); // Check if promise reject exists if(typeof promiseReject !== "undefined") { // Reject promise promiseReject(Listener.NOT_FOUND_RESPONSE); } // Cancel interaction with not found response and catch errors interaction.cancel(Listener.NOT_FOUND_RESPONSE).catch(function(error) { }); } }); // Catch errors }).catch(function(error) { // Check if promise reject exists if(typeof promiseReject !== "undefined") { // Reject promise promiseReject(Listener.NOT_FOUND_RESPONSE); } // Cancel interaction with not found response and catch errors interaction.cancel(Listener.NOT_FOUND_RESPONSE).catch(function(error) { }); }); // Listener connection open event }).on(Listener.CONNECTION_OPEN_EVENT, function() { // Go through all wallets for(var keyPath in self.wallets) if(self.wallets.hasOwnProperty(keyPath) === true) { // Get wallet var wallet = self.wallets[keyPath]; // Check if wallet has an address suffix if(wallet.getAddressSuffix() !== Wallet.NO_ADDRESS_SUFFIX) // Verify wallet's address suffix self.verifyAddressSuffix(wallet.getKeyPath()); } // Set unverify address suffixes on close self.unverifyAddressSuffixesOnClose = true; // Listener connection close or settings change event }).on(Listener.CONNECTION_CLOSE_EVENT + " " + Listener.SETTINGS_CHANGE_EVENT, function() { // Check if unverify address suffixes on close if(self.unverifyAddressSuffixesOnClose === true) { // Clear unverify address suffixes on close self.unverifyAddressSuffixesOnClose = false; // Go through all wallets for(var keyPath in self.wallets) if(self.wallets.hasOwnProperty(keyPath) === true) { // Get wallet var wallet = self.wallets[keyPath]; // Set that wallet's address suffix isn't verified wallet.setAddressSuffixVerified(false); } } }); // Document wallet hardware type change event $(document).on(Wallet.HARDWARE_TYPE_CHANGE_EVENT, function(event, keyPath, newHardwareType) { // Check if wallet exists if(self.walletExists(keyPath.toFixed()) === true) { // Get wallet var wallet = self.wallets[keyPath.toFixed()]; // Save wallet self.saveWallet(wallet, function() { // Return return { // New hardware type value [Wallets.NEW_HARDWARE_TYPE_VALUE]: newHardwareType }; }).then(function(newValues) { // Check if wallet exists if(self.walletExists(keyPath) === true) { // Trigger change event $(document).trigger(Wallets.CHANGE_EVENT, [ // Key path keyPath, // Change Wallets.HARDWARE_TYPE_CHANGED, // New value newValues[Wallets.NEW_HARDWARE_TYPE_VALUE] ]); } // Catch errors }).catch(function(error) { // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); } }); } // Start syncing startSyncing(ignoreSyncedStatus = false) { // Check if node is connected if(this.node.isConnected() === true) { // Clear stop syncing this.stopSyncing = false; // Sync this.sync(false, ignoreSyncedStatus); } // Otherwise else { // Sync failed this.syncFailed(); } } // Exist exist() { // Return if at least one wallet exists return Object.keys(this.wallets)["length"] !== 0; } // Create create(name = Wallet.NO_NAME, type = Consensus.getWalletType(), networkType = Consensus.getNetworkType(), syncingStatus = Wallet.STATUS_SYNCING, hardwareWallet = Wallet.NO_HARDWARE_WALLET, passphrase = Wallet.NO_PASSPHRASE, useBip39 = false, bip39Salt = Wallet.NO_BIP39_SALT, startSyncing = false, syncHeight = Wallet.CURRENT_HEIGHT) { // Set self var self = this; // Return promise return new Promise(function(resolve, reject) { // Check if locked if(self.isLocked() === true) { // Reject error reject(Language.getDefaultTranslation('The wallets are locked.')); } // Otherwise else { // Create new wallet var wallet = new Wallet(); // Get node's current height var currentHeight = self.node.getCurrentHeight().getHeight(); // Return initializing wallet return wallet.initialize(name, self.password, type, networkType, (syncHeight === Wallet.CURRENT_HEIGHT && currentHeight !== Node.UNKNOWN_HEIGHT && currentHeight.isEqualTo(Consensus.FIRST_BLOCK_HEIGHT) === false) ? currentHeight : syncHeight, syncingStatus, hardwareWallet, passphrase, useBip39, bip39Salt, Object.keys(self.wallets).map(function(keyPath) { // Return wallet return self.wallets[keyPath]; })).then(function() { // Return saving wallet return self.saveWallet(wallet).then(function() { // Request promise return new Promise(function(resolve, reject) { // Check is storage manager is supported if(typeof navigator === "object" && navigator !== null && "storage" in navigator === true) { // Return requesting for storage to be persistent return navigator["storage"].persist().then(function() { // Resolve resolve(); // Catch errors }).catch(function(error) { // Resolve resolve(); }); } // Otherwise else { // Resolve resolve(); } }).then(function() { // Append wallet to list of wallets self.wallets[wallet.getKeyPath()] = wallet; // Check if start syncing if(startSyncing === true) { // Start syncing self.startSyncing(); } // Resolve wallet resolve(wallet); }); // Catch errors }).catch(function(error) { // Close wallet wallet.close(); // Reject error reject(Language.getDefaultTranslation('The database failed.')); }); // Catch errors }).catch(function(error) { // Reject error reject(error); }); } }); } // Unlock unlock(password, connectHardwareWallets = false) { // Lock this.lock(false); // Set self var self = this; // Return promise return new Promise(function(resolve, reject) { // Check if wallets don't exist if(self.exist() === false) { // Set password self.password = (new TextEncoder()).encode(password); // Resolve resolve(); } // Otherwise else { // Initialize opening wallets var openingWallets = []; // Initialize password candidate var passwordCandidate = (new TextEncoder()).encode(password); // Go through all wallets for(var keyPath in self.wallets) { if(self.wallets.hasOwnProperty(keyPath) === true) { // Get wallet let wallet = self.wallets[keyPath]; // Append to opening wallets openingWallets.push(new Promise(function(resolve, reject) { // Return opening wallet return wallet.open(passwordCandidate).then(function() { // Resolve resolve(); // Catch errors }).catch(function(error) { // Reject error reject(error); }); })); } } // Return checking if opening all wallets was successful return Promise.all(openingWallets).then(function() { // Set password self.password = passwordCandidate; // Check if connecting hardware wallets if(connectHardwareWallets === true) { // Connect to hardware wallets and catch errors self.connectToHardwareWallets().catch(function(error) { }); // Resolve resolve(); } // Otherwise else { // Resolve resolve(); } // Catch errors }).catch(function(error) { // Securely clear password candidate passwordCandidate.fill(0); // Reject error reject(error); }); } }); } // Is password isPassword(password) { // Check if locked if(this.isLocked() === true) { // Throw error throw Language.getDefaultTranslation('The wallets are locked.'); } // Otherwise else { // Initialize password candidate var passwordCandidate = (new TextEncoder()).encode(password); // Set result to if password is correct var result = Common.arraysAreEqualTimingSafe(passwordCandidate, this.password) === true; // Securely clear password candidate passwordCandidate.fill(0); // Return result return result; } } // Change password changePassword(currentPassword, newPassword) { // Set self var self = this; // Return promise return new Promise(function(resolve, reject) { // Check if locked if(self.isLocked() === true) { // Reject error reject(Language.getDefaultTranslation('The wallets are locked.')); } // Otherwise check if current password is incorrect else if(self.isPassword(currentPassword) === false) { // Reject error reject(Language.getDefaultTranslation('Incorrect password.')); } // Otherwise else { // Initialize password candidate var passwordCandidate = (new TextEncoder()).encode(newPassword); // Initialize new encryption values var newEncryptionValues = {}; // Initialize encrypt wallets var encryptWallets = []; // Go through all wallets for(let keyPath in self.wallets) { if(self.wallets.hasOwnProperty(keyPath) === true) { // Get wallet let wallet = self.wallets[keyPath]; // Append encrypting wallet to list encryptWallets.push(new Promise(function(resolve, reject) { // Get random salt var salt = crypto.getRandomValues(new Uint8Array(Wallet.SALT_LENGTH)); // Get number of iterations var numberOfIterations = Wallet.DEFAULT_NUMBER_OF_ITERATIONS; // Get random initialization vector var initializationVector = crypto.getRandomValues(new Uint8Array(Wallet.INITIALIZATION_VECTOR_LENGTH)); // Check if wallet isn't a hardware wallet if(wallet.getHardwareType() === Wallet.NO_HARDWARE_TYPE) { // Return wallet's encrypting seed and BIP39 salt return wallet.encryptSeedAndBip39Salt(passwordCandidate, salt, numberOfIterations, initializationVector).then(function(encryptedSeedAndBip39Salt) { // Append values to new encryption values newEncryptionValues[keyPath] = [ // Salt salt, // Number of iterations numberOfIterations, // Initialization vector initializationVector, // Encrypted seed encryptedSeedAndBip39Salt[Wallet.ENCRYPT_SEED_AND_BIP39_SALT_ENCRYPTED_SEED_INDEX], // Encrypted BIP39 salt encryptedSeedAndBip39Salt[Wallet.ENCRYPT_SEED_AND_BIP39_SALT_ENCRYPTED_BIP39_SALT_INDEX] ]; // Resolve resolve(); // Catch errors }).catch(function(error) { // Reject error reject(error); }); } // Otherwise else { // Return wallet's encrypting root public key return wallet.encryptRootPublicKey(passwordCandidate, salt, numberOfIterations, initializationVector).then(function(encryptedRootPublicKey) { // Append values to new encryption values newEncryptionValues[keyPath] = [ // Salt salt, // Number of iterations numberOfIterations, // Initialization vector initializationVector, // Encrypted root public key encryptedRootPublicKey ]; // Resolve resolve(); // Catch errors }).catch(function(error) { // Reject error reject(error); }); } })); } } // Return encrypting wallets return Promise.all(encryptWallets).then(function() { // Return creating a database transaction return Database.createTransaction(Wallets.OBJECT_STORE_NAME, Database.READ_AND_WRITE_MODE, Database.STRICT_DURABILITY).then(function(databaseTransaction) { // Initializ save wallets var saveWallets = []; // Go through all wallets for(var keyPath in self.wallets) { if(self.wallets.hasOwnProperty(keyPath) === true) { // Check if wallet has new encryption values if(keyPath in newEncryptionValues === true) { // Get wallet let wallet = self.wallets[keyPath]; // Get encryption values let encryptionValues = newEncryptionValues[keyPath]; // Append saving wallet to list saveWallets.push(new Promise(function(resolve, reject) { // Return saving wallet return self.saveWallet(wallet, function() { // Check if wallet isn't a hardware wallet if(wallet.getHardwareType() === Wallet.NO_HARDWARE_TYPE) { // Return return { // New salt value [Wallets.NEW_SALT_VALUE]: encryptionValues[Wallets.CHANGE_PASSWORD_SALT_INDEX], // New number of iterations [Wallets.NEW_NUMBER_OF_ITERATIONS_VALUE]: encryptionValues[Wallets.CHANGE_PASSWORD_NUMBER_OF_ITERATIONS_INDEX], // New initialization vector value [Wallets.NEW_INITIALIZATION_VECTOR_VALUE]: encryptionValues[Wallets.CHANGE_PASSWORD_INITIALIZATION_VECTOR_INDEX], // New encrypted seed [Wallets.NEW_ENCRYPTED_SEED_VALUE]: encryptionValues[Wallets.CHANGE_PASSWORD_ENCRYPTED_SEED_INDEX], // New encrypted BIP39 salt [Wallets.NEW_ENCRYPTED_BIP39_SALT_VALUE]: encryptionValues[Wallets.CHANGE_PASSWORD_ENCRYPTED_BIP39_SALT_INDEX] }; } // Otherwise else { // Return return { // New salt value [Wallets.NEW_SALT_VALUE]: encryptionValues[Wallets.CHANGE_PASSWORD_SALT_INDEX], // New number of iterations [Wallets.NEW_NUMBER_OF_ITERATIONS_VALUE]: encryptionValues[Wallets.CHANGE_PASSWORD_NUMBER_OF_ITERATIONS_INDEX], // New initialization vector value [Wallets.NEW_INITIALIZATION_VECTOR_VALUE]: encryptionValues[Wallets.CHANGE_PASSWORD_INITIALIZATION_VECTOR_INDEX], // New encrypted root public key [Wallets.NEW_ENCRYPTED_ROOT_PUBLIC_KEY_VALUE]: encryptionValues[Wallets.CHANGE_PASSWORD_ENCRYPTED_ROOT_PUBLIC_KEY_INDEX] }; } }, databaseTransaction).then(function() { // Resolve resolve(); // Catch errors }).catch(function(error) { // Reject error reject(Language.getDefaultTranslation('The database failed.')); }); })); } // Otherwise else { // Append saving wallet to list saveWallets.push(new Promise(function(resolve, reject) { // Reject error reject(Language.getDefaultTranslation('Changing your password failed.')); })); } } } // Return saving all wallets return Promise.all(saveWallets).then(function() { // Return committing database transaction return Database.commitTransaction(databaseTransaction).then(function() { // Go through all wallets for(var keyPath in self.wallets) { if(self.wallets.hasOwnProperty(keyPath) === true) { // Check if wallet has new encryption values if(keyPath in newEncryptionValues === true) { // Get wallet var wallet = self.wallets[keyPath]; // Get encryption values var encryptionValues = newEncryptionValues[keyPath]; // Update wallet's salt wallet.setSalt(encryptionValues[Wallets.CHANGE_PASSWORD_SALT_INDEX]); // Update wallet's number of iterations wallet.setNumberOfIterations(encryptionValues[Wallets.CHANGE_PASSWORD_NUMBER_OF_ITERATIONS_INDEX]); // Update wallet's initialization vector wallet.setInitializationVector(encryptionValues[Wallets.CHANGE_PASSWORD_INITIALIZATION_VECTOR_INDEX]); // Check if wallet isn't a hardware wallet if(wallet.getHardwareType() === Wallet.NO_HARDWARE_TYPE) { // Update wallet's encrypted seed wallet.setEncryptedSeed(encryptionValues[Wallets.CHANGE_PASSWORD_ENCRYPTED_SEED_INDEX]); // Update wallet's encrypted BIP39 salt wallet.setEncryptedBip39Salt(encryptionValues[Wallets.CHANGE_PASSWORD_ENCRYPTED_BIP39_SALT_INDEX]); } // Otherwise else { // Update wallet's encrypted root public key wallet.setEncryptedRootPublicKey(encryptionValues[Wallets.CHANGE_PASSWORD_ENCRYPTED_ROOT_PUBLIC_KEY_INDEX]); } } // Otherwise else { // Securely clear password candidate passwordCandidate.fill(0); // Trigger an unknown error new FatalError(FatalError.UNKNOWN_ERROR); // Return return; } } } // Securely clear password self.password.fill(0); // Update password self.password = passwordCandidate; // Resolve resolve(); // Catch errors }).catch(function(error) { // Securely clear password candidate passwordCandidate.fill(0); // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Reject error reject(Language.getDefaultTranslation('The database failed.')); // Catch errors }).catch(function(error) { // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); // Catch errors }).catch(function(error) { // Securely clear password candidate passwordCandidate.fill(0); // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Reject error reject(error); // Catch errors }).catch(function(error) { // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); // Catch errors }).catch(function(error) { // Securely clear password candidate passwordCandidate.fill(0); // Reject error reject(Language.getDefaultTranslation('The database failed.')); }); // Catch errors }).catch(function(error) { // Securely clear password candidate passwordCandidate.fill(0); // Reject error reject(Language.getDefaultTranslation('Changing your password failed.')); }); } }); } // Change order changeOrder(keyPathOne, keyPathTwo) { // Set self var self = this; // Return promise return new Promise(function(resolve, reject) { // Try try { // Get wallet one var walletOne = self.getWallet(keyPathOne); // Get wallet two var walletTwo = self.getWallet(keyPathTwo); } // Catch errors catch(error) { // Reject error reject(error); // Return return; } // Return creating a database transaction return Database.createTransaction(Wallets.OBJECT_STORE_NAME, Database.READ_AND_WRITE_MODE, Database.STRICT_DURABILITY).then(function(databaseTransaction) { // Get order one var orderOne = walletOne.getOrder(); // Get order two var orderTwo = walletTwo.getOrder(); // Initialize save unknown orders var saveUnknownOrders = []; // Append saving wallet with unknown order to list saveUnknownOrders.push(new Promise(function(resolve, reject) { // Return saving wallet one return self.saveWallet(walletOne, function() { // Return return { // New order value [Wallets.NEW_ORDER_VALUE]: Wallet.UNKNOWN_ORDER }; }, databaseTransaction).then(function() { // Resolve resolve(); // Catch errors }).catch(function(error) { // Reject error reject(error); }); })); // Append saving wallet with unknown order to list saveUnknownOrders.push(new Promise(function(resolve, reject) { // Return saving wallet two return self.saveWallet(walletTwo, function() { // Return return { // New order value [Wallets.NEW_ORDER_VALUE]: Wallet.UNKNOWN_ORDER }; }, databaseTransaction).then(function() { // Resolve resolve(); // Catch errors }).catch(function(error) { // Reject error reject(error); }); })); // Return saving all wallets with unknown orders return Promise.all(saveUnknownOrders).then(function() { // Initialize save wallets var saveWallets = []; // Append saving wallet to list saveWallets.push(new Promise(function(resolve, reject) { // Return saving wallet one return self.saveWallet(walletOne, function() { // Return return { // New order value [Wallets.NEW_ORDER_VALUE]: orderTwo }; }, databaseTransaction).then(function() { // Resolve resolve(); // Catch errors }).catch(function(error) { // Reject error reject(error); }); })); // Append saving wallet to list saveWallets.push(new Promise(function(resolve, reject) { // Return saving wallet two return self.saveWallet(walletTwo, function() { // Return return { // New order value [Wallets.NEW_ORDER_VALUE]: orderOne }; }, databaseTransaction).then(function() { // Resolve resolve(); // Catch errors }).catch(function(error) { // Reject error reject(error); }); })); // Return saving all wallets return Promise.all(saveWallets).then(function() { // Return committing database transaction return Database.commitTransaction(databaseTransaction).then(function() { // Update wallet one's order walletOne.setOrder(orderTwo); // Update wallet two's order walletTwo.setOrder(orderOne); // Resolve resolve(); // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Reject error reject(Language.getDefaultTranslation('The database failed.')); // Catch errors }).catch(function(error) { // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Reject error reject(Language.getDefaultTranslation('The database failed.')); // Catch errors }).catch(function(error) { // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Reject error reject(Language.getDefaultTranslation('The database failed.')); // Catch errors }).catch(function(error) { // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); // Catch errors }).catch(function(error) { // Reject error reject(Language.getDefaultTranslation('The database failed.')); }); }); } // Connect to node and listener connectToNodeAndListener(onlyConnectToNode = false) { // Restart node and don't disconnect first this.node.restart(false); // Check if not only connecting to node if(onlyConnectToNode === false) { // Start listener this.listener.start(); } } // Lock lock(closeWallets = true) { // Check if password exists if(this.password !== Wallets.NO_PASSWORD) { // Securely clear password this.password.fill(0); // Set password to no password this.password = Wallets.NO_PASSWORD; } // Check if closing wallets if(closeWallets === true) { // Go through all wallets for(var keyPath in this.wallets) { if(this.wallets.hasOwnProperty(keyPath) === true) { // Get wallet var wallet = this.wallets[keyPath]; // Close wallet wallet.close(); } } } } // Is locked isLocked() { // Return if password is no password return this.password === Wallets.NO_PASSWORD; } // Remove wallet removeWallet(keyPath) { // Set self var self = this; // Return promise return new Promise(function(resolve, reject) { // Check if locked if(self.isLocked() === true) { // Reject error reject(Language.getDefaultTranslation('The wallets are locked.')); } // Otherwise else { // Return obtaining wallet's exclusive transactions lock return self.transactions.obtainWalletsExclusiveTransactionsLock(keyPath).then(function() { // Check if wallet exists if(self.walletExists(keyPath) === true) { // Get wallet var wallet = self.wallets[keyPath]; // Return creating a database transaction return Database.createTransaction([ // Wallets object store Wallets.OBJECT_STORE_NAME, // Transactions object store Transactions.OBJECT_STORE_NAME, ], Database.READ_AND_WRITE_MODE, Database.STRICT_DURABILITY).then(function(databaseTransaction) { // Return deleting wallet from the database return Database.deleteResult(Wallets.OBJECT_STORE_NAME, keyPath, databaseTransaction, Database.STRICT_DURABILITY).then(function() { // Check if wallet exists if(self.walletExists(keyPath) === true) { // Return deleting all wallet's transactions return self.transactions.deleteWalletsTransactions(keyPath, databaseTransaction).then(function() { // Return committing database transaction return Database.commitTransaction(databaseTransaction).then(function() { // Check if wallet's name doesn't exist if(wallet.getName() === Wallet.NO_NAME) { // Log message Log.logMessage(Language.getDefaultTranslation('Deleted wallet Wallet %1$s.'), [keyPath.toFixed()]); } // Otherwise else { // Log message Log.logMessage(Language.getDefaultTranslation('Deleted wallet %1$y.'), [wallet.getName()]); } // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Check if wallet exists if(self.walletExists(keyPath) === true) { // Close wallet wallet.close(); // Check it wallet has an address suffix if(wallet.getAddressSuffix() !== Wallet.NO_ADDRESS_SUFFIX) { // Delete wallet's address suffix self.deleteAddressSuffix(wallet.getAddressSuffix()); // Set wallet's address suffix to no address suffix wallet.setAddressSuffix(Wallet.NO_ADDRESS_SUFFIX); } // Remove wallets from list of wallets delete self.wallets[keyPath]; } // Resolve resolve(); // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject error reject(Language.getDefaultTranslation('The database failed.')); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject error reject(Language.getDefaultTranslation('The database failed.')); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); } // Otherwise else { // Return committing database transaction return Database.commitTransaction(databaseTransaction).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Resolve resolve(); // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject error reject(Language.getDefaultTranslation('The database failed.')); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); } // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject error reject(Language.getDefaultTranslation('The database failed.')); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject error reject(Language.getDefaultTranslation('The database failed.')); }); } // Otherwise else { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Resolve resolve(); } }); } }); } // Remove all wallets removeAllWallets() { // Set self var self = this; // Return promise return new Promise(function(resolve, reject) { // Initialize obtain transactions locks var obtainTransactionsLocks = []; // Initialize obtained transactions locks var obtainedTransactionsLocks = []; // Go through all wallets for(var keyPath in self.wallets) { if(self.wallets.hasOwnProperty(keyPath) === true) { // Get wallet let wallet = self.wallets[keyPath]; // Append to obtain transactions locks obtainTransactionsLocks.push(new Promise(function(resolve, reject) { // Return obtaining wallet's exclusive transactions lock return self.transactions.obtainWalletsExclusiveTransactionsLock(wallet.getKeyPath()).then(function() { // Append to obtained transactions locks obtainedTransactionsLocks.push(wallet.getKeyPath()); // Resolve resolve(); }); })); } } // Return obtaining transactions locks return Promise.all(obtainTransactionsLocks).then(function() { // Return creating a database transaction return Database.createTransaction([ // Wallets object store Wallets.OBJECT_STORE_NAME, // Transactions object store Transactions.OBJECT_STORE_NAME, ], Database.READ_AND_WRITE_MODE, Database.STRICT_DURABILITY).then(function(databaseTransaction) { // Return deleting all wallets with the wallet type and network type in the database return Database.deleteResultsWithValue(Wallets.OBJECT_STORE_NAME, Wallets.DATABASE_WALLET_TYPE_AND_NETWORK_TYPE_NAME, IDBKeyRange.only([ // Wallet type Consensus.getWalletType(), // Network type Consensus.getNetworkType() ]), databaseTransaction, Database.STRICT_DURABILITY).then(function() { // Return deleting all transactions for wallet's with the wallet type and network type return self.transactions.deleteAllTransactions(Consensus.getWalletType(), Consensus.getNetworkType(), databaseTransaction).then(function() { // Return committing database transaction return Database.commitTransaction(databaseTransaction).then(function() { // Log message Log.logMessage(Language.getDefaultTranslation('Deleted all wallets.')); // Go through all obtained transactions locks for(var i = 0; i < obtainedTransactionsLocks["length"]; ++i) { // Get obtained transactions lock var obtainedTransactionsLock = obtainedTransactionsLocks[i]; // Release obtained transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(obtainedTransactionsLock); } // Go through all wallets for(var keyPath in self.wallets) { if(self.wallets.hasOwnProperty(keyPath) === true) { // Get wallet var wallet = self.wallets[keyPath]; // Close wallet wallet.close(); // Check it wallet has an address suffix if(wallet.getAddressSuffix() !== Wallet.NO_ADDRESS_SUFFIX) { // Delete wallet's address suffix self.deleteAddressSuffix(wallet.getAddressSuffix()); // Set wallet's address suffix to no address suffix wallet.setAddressSuffix(Wallet.NO_ADDRESS_SUFFIX); } } } // Clear wallets self.wallets = {}; // Lock self.lock(); // Resolve resolve(); // Catch errore }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Go through all obtained transactions locks for(var i = 0; i < obtainedTransactionsLocks["length"]; ++i) { // Get obtained transactions lock var obtainedTransactionsLock = obtainedTransactionsLocks[i]; // Release obtained transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(obtainedTransactionsLock); } // Reject error reject(Language.getDefaultTranslation('The database failed.')); // Catch errors }).catch(function(error) { // Go through all obtained transactions locks for(var i = 0; i < obtainedTransactionsLocks["length"]; ++i) { // Get obtained transactions lock var obtainedTransactionsLock = obtainedTransactionsLocks[i]; // Release obtained transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(obtainedTransactionsLock); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Go through all obtained transactions locks for(var i = 0; i < obtainedTransactionsLocks["length"]; ++i) { // Get obtained transactions lock var obtainedTransactionsLock = obtainedTransactionsLocks[i]; // Release obtained transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(obtainedTransactionsLock); } // Reject error reject(Language.getDefaultTranslation('The database failed.')); // Catch errors }).catch(function(error) { // Go through all obtained transactions locks for(var i = 0; i < obtainedTransactionsLocks["length"]; ++i) { // Get obtained transactions lock var obtainedTransactionsLock = obtainedTransactionsLocks[i]; // Release obtained transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(obtainedTransactionsLock); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Go through all obtained transactions locks for(var i = 0; i < obtainedTransactionsLocks["length"]; ++i) { // Get obtained transactions lock var obtainedTransactionsLock = obtainedTransactionsLocks[i]; // Release obtained transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(obtainedTransactionsLock); } // Reject error reject(Language.getDefaultTranslation('The database failed.')); // Catch errors }).catch(function(error) { // Go through all obtained transactions locks for(var i = 0; i < obtainedTransactionsLocks["length"]; ++i) { // Get obtained transactions lock var obtainedTransactionsLock = obtainedTransactionsLocks[i]; // Release obtained transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(obtainedTransactionsLock); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); // Catch errors }).catch(function(error) { // Go through all obtained transactions locks for(var i = 0; i < obtainedTransactionsLocks["length"]; ++i) { // Get obtained transactions lock var obtainedTransactionsLock = obtainedTransactionsLocks[i]; // Release obtained transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(obtainedTransactionsLock); } // Reject error reject(Language.getDefaultTranslation('The database failed.')); }); }); }); } // Wallet exists walletExists(keyPath) { // Return if wallet exists return keyPath in this.wallets === true; } // Get wallet getWallet(keyPath) { // Check if locked if(this.isLocked() === true) { // Throw error throw Language.getDefaultTranslation('The wallets are locked.'); } // Otherwise check if wallet doesn't exist else if(this.walletExists(keyPath) === false) { // Throw error throw Language.getDefaultTranslation('The wallet doesn\'t exist.'); } // Otherwise else { // Return wallet return this.wallets[keyPath]; } } // Get wallets getWallets() { // Check if locked if(this.isLocked() === true) { // Throw error throw Language.getDefaultTranslation('The wallets are locked.'); } // Otherwise else { // Return wallets return this.wallets; } } // Get wallets in order getWalletsInOrder() { // Check if locked if(this.isLocked() === true) { // Throw error throw Language.getDefaultTranslation('The wallets are locked.'); } // Otherwise else { // Initialize wallets in order var walletsInOrder = []; // Go through all wallets for(var keyPath in this.wallets) { if(this.wallets.hasOwnProperty(keyPath) === true) { // Get wallet var wallet = this.wallets[keyPath]; // Append wallet to list walletsInOrder.push(wallet); } } // Sort wallets by order walletsInOrder.sort(function(walletOne, walletTwo) { // Check if wallet one's order is greater than wallet two's if(walletOne.getOrder() > walletTwo.getOrder()) { // Return sort greater than return Common.SORT_GREATER_THAN; } // Check if wallet one's order is less than wallet two's else if(walletOne.getOrder() < walletTwo.getOrder()) { // Return sort less than return Common.SORT_LESS_THAN; } // Otherwise else { // Return sort equal return Common.SORT_EQUAL; } }); // Return wallets in order return walletsInOrder; } } // Resync wallet resyncWallet(keyPath) { // Set self var self = this; // Return promise return new Promise(function(resolve, reject) { // Try try { // Get wallet var wallet = self.getWallet(keyPath); } // Catch errors catch(error) { // Reject error reject(error); // Return return; } // Return create a database transaction return Database.createTransaction(Wallets.OBJECT_STORE_NAME, Database.READ_AND_WRITE_MODE, Database.STRICT_DURABILITY).then(function(databaseTransaction) { // Return saving wallet return self.saveWallet(wallet, function() { // Return return { // New synced height value [Wallets.NEW_SYNCED_HEIGHT_VALUE]: new BigNumber((wallet.getHardwareType() === Wallet.NO_HARDWARE_TYPE && wallet.getUseBip39() === false) ? Consensus.FIRST_BLOCK_HEIGHT : Consensus.HARDWARE_WALLET_STARTING_HEIGHT) }; }, databaseTransaction).then(function() { // Return committing database transaction return Database.commitTransaction(databaseTransaction).then(function() { // Set wallet's synced height to the first block height wallet.setSyncedHeight(new BigNumber((wallet.getHardwareType() === Wallet.NO_HARDWARE_TYPE && wallet.getUseBip39() === false) ? Consensus.FIRST_BLOCK_HEIGHT : Consensus.HARDWARE_WALLET_STARTING_HEIGHT)); // Clear wallet's last sync index wallet.setLastSyncIndex(Wallet.NO_SYNC_INDEX); // Update wallet's starting sync height wallet.setStartingSyncHeight(wallet.getSyncedHeight()); // Set wallet's percent synced wallet.setPercentSynced(new BigNumber(Wallets.MINIMUM_PERCENT)); // Set wallet's syncing status to resyncing wallet.setSyncingStatus(Wallet.STATUS_RESYNCING); // Check if wallet exists if(self.walletExists(keyPath) === true) { // Trigger sync start event $(self).trigger(Wallets.SYNC_START_EVENT, [ // Key path keyPath, // Percent complete new BigNumber(Wallets.MINIMUM_PERCENT), // Last percent in group false ]); // Set timeout setTimeout(function() { // Start syncing self.startSyncing(); }, Wallets.RESYNC_DELAY_MILLISECONDS); // Resolve resolve(); } // Otherwise else { // Reject error reject(Language.getDefaultTranslation('The wallet doesn\'t exist.')); } // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Reject error reject(Language.getDefaultTranslation('The database failed.')); // Catch errors }).catch(function(error) { // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Reject error reject(Language.getDefaultTranslation('The database failed.')); // Catch errors }).catch(function(error) { // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); // Catch errors }).catch(function(error) { // Reject error reject(Language.getDefaultTranslation('The database failed.')); }); }); } // Rename wallet renameWallet(keyPath, name) { // Set self var self = this; // Return promise return new Promise(function(resolve, reject) { // Try try { // Get wallet var wallet = self.getWallet(keyPath); } // Catch errors catch(error) { // Reject error reject(error); // Return return; } // Return create a database transaction return Database.createTransaction(Wallets.OBJECT_STORE_NAME, Database.READ_AND_WRITE_MODE, Database.STRICT_DURABILITY).then(function(databaseTransaction) { // Return saving wallet return self.saveWallet(wallet, function() { // Return return { // New name value [Wallets.NEW_NAME_VALUE]: name }; }, databaseTransaction).then(function(newValues) { // Return committing database transaction return Database.commitTransaction(databaseTransaction).then(function() { // Get old name var oldName = wallet.getName(); // Update wallet's name wallet.setName(newValues[Wallets.NEW_NAME_VALUE]); // Check if wallet exists if(self.walletExists(keyPath) === true) { // Check if old name doesn't exist if(oldName === Wallet.NO_NAME) { // Log message Log.logMessage(Language.getDefaultTranslation('Renamed wallet Wallet %1$s to %2$y.'), [ // Key path keyPath.toFixed(), // New name newValues[Wallets.NEW_NAME_VALUE] ]); } // Otherwise else { // Log message Log.logMessage(Language.getDefaultTranslation('Renamed wallet %1$y to %2$y.'), [ // Old name oldName, // New name newValues[Wallets.NEW_NAME_VALUE] ]); } // Trigger change event $(document).trigger(Wallets.CHANGE_EVENT, [ // Key path keyPath, // Change Wallets.NAME_CHANGED, // New value newValues[Wallets.NEW_NAME_VALUE] ]); // Resolve resolve(); } // Otherwise else { // Reject error reject(Language.getDefaultTranslation('The wallet doesn\'t exist.')); } // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Reject error reject(Language.getDefaultTranslation('The database failed.')); // Catch errors }).catch(function(error) { // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Reject error reject(Language.getDefaultTranslation('The database failed.')); // Catch errors }).catch(function(error) { // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); // Catch errors }).catch(function(error) { // Reject error reject(Language.getDefaultTranslation('The database failed.')); }); }); } // Get fee getFee(keyPath, amount = Api.ALL_AMOUNT, baseFee = Api.DEFAULT_BASE_FEE, cancelOccurred = Common.NO_CANCEL_OCCURRED, walletsExclusiveTransactionsLockObtained = false) { // Set self var self = this; // Return promise return new Promise(function(resolve, reject) { // Check if cancel didn't occur if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) { // Try try { // Get wallet var wallet = self.getWallet(keyPath); } // Catch errors catch(error) { // Reject error reject(error); // Return return; } // Check if wallet isn't synced if(wallet.isSynced() === false) { // Reject error reject(Language.getDefaultTranslation('The wallet isn\'t synced.')); } // Otherwise else { // Obtain wallet's exclusive transactions lock var obtainWalletsExclusiveTransactionsLock = function() { // Return promise return new Promise(function(resolve, reject) { // Check if cancel didn't occur if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) { // Check if wallet's exclusive transactions lock isn't already obtained if(walletsExclusiveTransactionsLockObtained === false) { // Return obtaining wallet's exclusive transactions lock return self.transactions.obtainWalletsExclusiveTransactionsLock(keyPath).then(function() { // Check if cancel didn't occur if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) { // Resolve resolve(); } // Otherwise else { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject canceled error reject(Common.CANCELED_ERROR); } }); } // Otherwise else { // Resolve resolve(); } } // Otherwise else { // Reject canceled error reject(Common.CANCELED_ERROR); } }); }; // Return obtaining wallet's exclusive transactions lock return obtainWalletsExclusiveTransactionsLock().then(function() { // Check if cancel didn't occur if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) { // Return getting fee return self.api.getFee(wallet, amount, baseFee, cancelOccurred).then(function(fee) { // Check if wallet's exclusive transactions lock isn't already obtained if(walletsExclusiveTransactionsLockObtained === false) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); } // Check if cancel didn't occur if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) { // Resolve fee resolve(fee); } // Otherwise else { // Reject canceled error reject(Common.CANCELED_ERROR); } // Catch errors }).catch(function(error) { // Check if wallet's exclusive transactions lock isn't already obtained if(walletsExclusiveTransactionsLockObtained === false) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); } // Check if cancel didn't occur if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) { // Reject error reject(error); } // Otherwise else { // Reject canceled error reject(Common.CANCELED_ERROR); } }); } // Otherwise else { // Check if wallet's exclusive transactions lock isn't already obtained if(walletsExclusiveTransactionsLockObtained === false) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); } // Reject canceled error reject(Common.CANCELED_ERROR); } // Catch errors }).catch(function(error) { // Check if cancel didn't occur if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) { // Reject error reject(error); } // Otherwise else { // Reject canceled error reject(Common.CANCELED_ERROR); } }); } } // Otherwise else { // Reject canceled error reject(Common.CANCELED_ERROR); } }); } // Send send(keyPath, destination, amount, fee, baseFee = Api.DEFAULT_BASE_FEE, message = SlateParticipant.NO_MESSAGE, sendAsFile = false, cancelOccurred = Common.NO_CANCEL_OCCURRED, walletsExclusiveTransactionsLockObtained = false) { // Set self var self = this; // Return promise return new Promise(function(resolve, reject) { // Check if cancel didn't occur if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) { // Try try { // Get wallet var wallet = self.getWallet(keyPath); } // Catch errors catch(error) { // Reject error reject(Message.createText(error)); // Return return; } // Check if wallet isn't synced if(wallet.isSynced() === false) { // Reject error reject(Message.createText(Language.getDefaultTranslation('The wallet isn\'t synced.'))); } // Otherwise else { // Obtain wallet's exclusive transactions lock var obtainWalletsExclusiveTransactionsLock = function() { // Return promise return new Promise(function(resolve, reject) { // Check if cancel didn't occur if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) { // Check if wallet's exclusive transactions lock isn't already obtained if(walletsExclusiveTransactionsLockObtained === false) { // Return obtaining wallet's exclusive transactions lock return self.transactions.obtainWalletsExclusiveTransactionsLock(keyPath).then(function() { // Check if cancel didn't occur if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) { // Resolve resolve(); } // Otherwise else { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject canceled error reject(Common.CANCELED_ERROR); } }); } // Otherwise else { // Resolve resolve(); } } // Otherwise else { // Reject canceled error reject(Common.CANCELED_ERROR); } }); }; // Return obtaining wallet's exclusive transactions lock return obtainWalletsExclusiveTransactionsLock().then(function() { // Check if cancel didn't occur if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) { // Get wallet's last identifier var lastIdentifier = wallet.getLastIdentifier(); // Return sending return self.api.send(wallet, destination, amount, fee, baseFee, self.numberOfConfirmations, message, Slate.NO_LOCK_HEIGHT, Slate.NO_RELATIVE_HEIGHT, Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT, sendAsFile, cancelOccurred).then(function(value) { // Return creating a database transaction return Database.createTransaction([ // Wallets object store Wallets.OBJECT_STORE_NAME, // Transactions object store Transactions.OBJECT_STORE_NAME, ], Database.READ_AND_WRITE_MODE, Database.STRICT_DURABILITY).then(function(databaseTransaction) { // Return saving updated transactions return self.transactions.saveTransactions(value[Api.SEND_UPDATED_TRANSACTIONS_INDEX], databaseTransaction).then(function() { // Check if wallet exists if(self.walletExists(keyPath) === true) { // Return saving wallet return self.saveWallet(wallet, function() { // Return return { // New locked amount value [Wallets.NEW_LOCKED_AMOUNT_VALUE]: wallet.getLockedAmount().plus(value[Api.SEND_LOCKED_AMOUNT_INDEX]), // New unconfirmed amount value [Wallets.NEW_UNCONFIRMED_AMOUNT_VALUE]: wallet.getUnconfirmedAmount().plus(value[Api.SEND_UNCONFIRMED_AMOUNT_INDEX]), // New unspent amount value [Wallets.NEW_UNSPENT_AMOUNT_VALUE]: wallet.getUnspentAmount().minus(value[Api.SEND_LOCKED_AMOUNT_INDEX]) }; }, databaseTransaction).then(function(newValues) { // Check if wallet exists if(self.walletExists(keyPath) === true) { // Return committing database transaction return Database.commitTransaction(databaseTransaction).then(function() { // Check if wallet's exclusive transactions lock isn't already obtained if(walletsExclusiveTransactionsLockObtained === false) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); } // Update wallet's locked amount wallet.setLockedAmount(newValues[Wallets.NEW_LOCKED_AMOUNT_VALUE]); // Update wallet's unconfirmed amount wallet.setUnconfirmedAmount(newValues[Wallets.NEW_UNCONFIRMED_AMOUNT_VALUE]); // Update wallet's unspent amount wallet.setUnspentAmount(newValues[Wallets.NEW_UNSPENT_AMOUNT_VALUE]); // Check if wallet exists if(self.walletExists(keyPath) === true) { // Check if wallet's unspent amount changed if(value[Api.SEND_LOCKED_AMOUNT_INDEX].isZero() === false) { // Trigger change event $(document).trigger(Wallets.CHANGE_EVENT, [ // Key path keyPath, // Change Wallets.UNSPENT_AMOUNT_CHANGED, // New value newValues[Wallets.NEW_UNSPENT_AMOUNT_VALUE] ]); } // Check if wallet's unconfirmed amount changed if(value[Api.SEND_UNCONFIRMED_AMOUNT_INDEX].isZero() === false) { // Trigger change event $(document).trigger(Wallets.CHANGE_EVENT, [ // Key path keyPath, // Change Wallets.UNCONFIRMED_AMOUNT_CHANGED, // New value newValues[Wallets.NEW_UNCONFIRMED_AMOUNT_VALUE] ]); } // Trigger transactions change event $(self.transactions).trigger(Transactions.CHANGE_EVENT, [ // Transactions value[Api.SEND_UPDATED_TRANSACTIONS_INDEX] ]); } // Resolve resolve(); // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).catch(function() { // Finally }).finally(function() { // Check if wallet exists and wallet's last identifier changed if(self.walletExists(keyPath) === true && wallet.getLastIdentifier() !== Wallet.NO_LAST_IDENTIFIER && (lastIdentifier === Wallet.NO_LAST_IDENTIFIER || wallet.getLastIdentifier().equalsValue(lastIdentifier) === false)) { // Return saving wallet and catch errors return self.saveWallet(wallet).catch(function(error) { // Finally }).finally(function() { // Check if wallet's exclusive transactions lock isn't already obtained if(walletsExclusiveTransactionsLockObtained === false) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); } // Otherwise else { // Check if wallet's exclusive transactions lock isn't already obtained if(walletsExclusiveTransactionsLockObtained === false) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); } }); }); } // Otherwise else { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Check if wallet's exclusive transactions lock isn't already obtained if(walletsExclusiveTransactionsLockObtained === false) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); } // Resolve resolve(); // Catch errors }).catch(function() { // Check if wallet's exclusive transactions lock isn't already obtained if(walletsExclusiveTransactionsLockObtained === false) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); } // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).catch(function() { // Finally }).finally(function() { // Check if wallet exists and wallet's last identifier changed if(self.walletExists(keyPath) === true && wallet.getLastIdentifier() !== Wallet.NO_LAST_IDENTIFIER && (lastIdentifier === Wallet.NO_LAST_IDENTIFIER || wallet.getLastIdentifier().equalsValue(lastIdentifier) === false)) { // Return saving wallet and catch errors return self.saveWallet(wallet).catch(function(error) { // Finally }).finally(function() { // Check if wallet's exclusive transactions lock isn't already obtained if(walletsExclusiveTransactionsLockObtained === false) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); } // Otherwise else { // Check if wallet's exclusive transactions lock isn't already obtained if(walletsExclusiveTransactionsLockObtained === false) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); } }); }); } // Otherwise else { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Check if wallet's exclusive transactions lock isn't already obtained if(walletsExclusiveTransactionsLockObtained === false) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); } // Resolve resolve(); // Catch errors }).catch(function() { // Check if wallet's exclusive transactions lock isn't already obtained if(walletsExclusiveTransactionsLockObtained === false) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); } // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).catch(function() { // Finally }).finally(function() { // Check if wallet exists and wallet's last identifier changed if(self.walletExists(keyPath) === true && wallet.getLastIdentifier() !== Wallet.NO_LAST_IDENTIFIER && (lastIdentifier === Wallet.NO_LAST_IDENTIFIER || wallet.getLastIdentifier().equalsValue(lastIdentifier) === false)) { // Return saving wallet and catch errors return self.saveWallet(wallet).catch(function(error) { // Finally }).finally(function() { // Check if wallet's exclusive transactions lock isn't already obtained if(walletsExclusiveTransactionsLockObtained === false) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); } // Otherwise else { // Check if wallet's exclusive transactions lock isn't already obtained if(walletsExclusiveTransactionsLockObtained === false) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); } }); }); // Catch errors }).catch(function(error) { // Check if wallet exists and wallet's last identifier changed if(self.walletExists(keyPath) === true && wallet.getLastIdentifier() !== Wallet.NO_LAST_IDENTIFIER && (lastIdentifier === Wallet.NO_LAST_IDENTIFIER || wallet.getLastIdentifier().equalsValue(lastIdentifier) === false)) { // Return saving wallet and catch errors return self.saveWallet(wallet).catch(function(error) { // Finally }).finally(function() { // Check if wallet's exclusive transactions lock isn't already obtained if(walletsExclusiveTransactionsLockObtained === false) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); } // Otherwise else { // Check if wallet's exclusive transactions lock isn't already obtained if(walletsExclusiveTransactionsLockObtained === false) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); } }); // Catch errors }).catch(function(error) { // Check if wallet exists and wallet's last identifier changed if(self.walletExists(keyPath) === true && wallet.getLastIdentifier() !== Wallet.NO_LAST_IDENTIFIER && (lastIdentifier === Wallet.NO_LAST_IDENTIFIER || wallet.getLastIdentifier().equalsValue(lastIdentifier) === false)) { // Return saving wallet return self.saveWallet(wallet).then(function() { // Check if wallet's exclusive transactions lock isn't already obtained if(walletsExclusiveTransactionsLockObtained === false) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); } // Check if cancel didn't occur if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) { // Reject error reject(error); } // Otherwise else { // Reject canceled error reject(Common.CANCELED_ERROR); } // Catch errors }).catch(function(error) { // Check if wallet's exclusive transactions lock isn't already obtained if(walletsExclusiveTransactionsLockObtained === false) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); } // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); } // Otherwise else { // Check if wallet's exclusive transactions lock isn't already obtained if(walletsExclusiveTransactionsLockObtained === false) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); } // Check if cancel didn't occur if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) { // Reject error reject(error); } // Otherwise else { // Reject canceled error reject(Common.CANCELED_ERROR); } } }); } // Otherwise else { // Check if wallet's exclusive transactions lock isn't already obtained if(walletsExclusiveTransactionsLockObtained === false) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); } // Reject canceled error reject(Common.CANCELED_ERROR); } // Catch errors }).catch(function(error) { // Check if cancel didn't occur if(cancelOccurred === Common.NO_CANCEL_OCCURRED || cancelOccurred() === false) { // Reject error reject(error); } // Otherwise else { // Reject canceled error reject(Common.CANCELED_ERROR); } }); } } // Otherwise else { // Reject canceled error reject(Common.CANCELED_ERROR); } }); } // Create address suffix createAddressSuffix(keyPath, ignorePerformingAddressSuffixOperation = false) { // Check if wallet exists if(this.walletExists(keyPath) === true) { // Get wallet var wallet = this.wallets[keyPath]; // Check if wallet isn't performing an address suffix operation or ignoring performing address suffix operation if(wallet.getPerformingAddressSuffixOperation() === false || ignorePerformingAddressSuffixOperation === true) { // Set that wallet is performing an address suffix operation wallet.setPerformingAddressSuffixOperation(true); // Set self var self = this; // Have listener create a URL this.listener.createUrl().then(function(url) { // Check if wallet exists if(self.walletExists(keyPath) === true) { // Get old address suffix var oldAddressSuffix = wallet.getAddressSuffix(); // Save wallet self.saveWallet(wallet, function() { // Return return { // New address suffix value [Wallets.NEW_ADDRESS_SUFFIX_VALUE]: url }; }).then(function() { // Check if wallet exists if(self.walletExists(keyPath) === true) { // Check if wallet has a old address suffix if(oldAddressSuffix !== Wallet.NO_ADDRESS_SUFFIX) { // Delete wallet's old address suffix self.deleteAddressSuffix(oldAddressSuffix); } // Set that wallet's address suffix is verified wallet.setAddressSuffixVerified(true); // Set that wallet isn't performing an address suffix operation wallet.setPerformingAddressSuffixOperation(false); // Check if wallet's address suffix changed if(oldAddressSuffix !== url) { // Trigger change event $(document).trigger(Wallets.CHANGE_EVENT, [ // Key path keyPath, // Change Wallets.ADDRESS_SUFFIX_CHANGED, // New value url ]); } } // Catch errors }).catch(function(error) { // Delete address suffix self.deleteAddressSuffix(url); // Create address suffix self.createAddressSuffix(keyPath, true); }); } // Otherwise else { // Delete address suffix self.deleteAddressSuffix(url); // Set that wallet isn't performing an address suffix operation wallet.setPerformingAddressSuffixOperation(false); } // Catch errors }).catch(function(error) { // Check if wallet exists if(self.walletExists(keyPath) === true) { // Create address suffix self.createAddressSuffix(keyPath, true); } // Otherwise else { // Set that wallet isn't performing an address suffix operation wallet.setPerformingAddressSuffixOperation(false); } }); } } } // Verify address suffix verifyAddressSuffix(keyPath, ignorePerformingAddressSuffixOperation = false) { // Check if wallet exists if(this.walletExists(keyPath) === true) { // Get wallet var wallet = this.wallets[keyPath]; // Check if wallet's address suffix isn't already verified if(wallet.getAddressSuffixVerified() === false) { // Check if wallet isn't performing an address suffix operation or ignoring performing address suffix operation if(wallet.getPerformingAddressSuffixOperation() === false || ignorePerformingAddressSuffixOperation === true) { // Set that wallet is performing an address suffix operation wallet.setPerformingAddressSuffixOperation(true); // Get wallet's address suffix var addressSuffix = wallet.getAddressSuffix(); // Set self var self = this; // Have listener check if owns URL this.listener.ownsUrl(addressSuffix).then(function(ownsUrl) { // Check if wallet exists if(self.walletExists(keyPath) === true) { // Check if owns URL if(ownsUrl === true) { // Set that wallet's address suffix is verified wallet.setAddressSuffixVerified(true); // Set that wallet isn't performing an address suffix operation wallet.setPerformingAddressSuffixOperation(false); } // Otherwise else { // Create address suffix self.createAddressSuffix(keyPath, true); } } // Otherwise else { // Check if owns URL if(ownsUrl === true) { // Delete address suffix self.deleteAddressSuffix(addressSuffix); } // Set that wallet isn't performing an address suffix operation wallet.setPerformingAddressSuffixOperation(false); } // Catch errors }).catch(function(error) { // Check if wallet exists if(self.walletExists(keyPath) === true) { // Verify address suffix self.verifyAddressSuffix(keyPath, true); } // Otherwise else { // Set that wallet isn't performing an address suffix operation wallet.setPerformingAddressSuffixOperation(false); } }); } } } } // Change address suffix changeAddressSuffix(keyPath) { // Set self var self = this; // Return promise return new Promise(function(resolve, reject) { // Try try { // Get wallet var wallet = self.getWallet(keyPath); } // Catch errors catch(error) { // Reject error reject(Message.createText(error)); // Return return; } // Check if listener is connected if(self.listener.isConnected() === true) { // Check if wallet isn't performing an address suffix operation if(wallet.getPerformingAddressSuffixOperation() === false) { // Set that wallet is performing an address suffix operation wallet.setPerformingAddressSuffixOperation(true); // Get wallet's address suffix var addressSuffix = wallet.getAddressSuffix(); // Return having listener change the URL return self.listener.changeUrl(addressSuffix).then(function(url) { // Return saving wallet return self.saveWallet(wallet, function() { // Return return { // New address suffix value [Wallets.NEW_ADDRESS_SUFFIX_VALUE]: url }; }).then(function() { // Check if wallet exists if(self.walletExists(keyPath) === true) { // Set that wallet's address suffix is verified wallet.setAddressSuffixVerified(true); // Set that wallet isn't performing an address suffix operation wallet.setPerformingAddressSuffixOperation(false); // Trigger change event $(document).trigger(Wallets.CHANGE_EVENT, [ // Key path keyPath, // Change Wallets.ADDRESS_SUFFIX_CHANGED, // New value url ]); } // Resolve resolve(); // Catch errors }).catch(function(error) { // Delete address suffix self.deleteAddressSuffix(url); // Set that wallet's address suffix isn't verified wallet.setAddressSuffixVerified(false); // Set timeout setTimeout(function() { // Create address suffix self.createAddressSuffix(keyPath, true); }, 0); // Reject error reject(Message.createText(Language.getDefaultTranslation('The database failed.'))); }); // Catch errors }).catch(function(error) { // Set that wallet isn't performing an address suffix operation wallet.setPerformingAddressSuffixOperation(false); // Reject error reject(error); }); } // Otherwise else { // Reject error reject(Message.createText(Language.getDefaultTranslation('An operation is currently being performed on the wallet\'s address suffix.'))); } } // Otherwise else { // Reject error reject(Message.createText(Language.getDefaultTranslation('You aren\'t connected to a listener.')) + Message.createText(Language.getDefaultTranslation('(?<=.) ')) + Message.createText(Language.getDefaultTranslation('You won\'t be able to change address suffixes without being connected to a listener.'))); } }); } // Delete address suffix deleteAddressSuffix(addressSuffix) { // Set self var self = this; // Have listener delete URL and catch errors this.listener.deleteUrl(addressSuffix).catch(function(error) { // Delete address suffix self.deleteAddressSuffix(addressSuffix); }); } // Wait for hardware wallet to connect waitForHardwareWalletToConnect(keyPath, text, textArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) { // Set self var self = this; // Return promise return new Promise(function(resolve, reject) { // Otherwise check if wallet doesn't exist if(self.walletExists(keyPath) === false) { // Reject error reject("Wallet doesn\'t exist."); } // Otherwise else { // Get wallet var wallet = self.wallets[keyPath]; // Check if wallet isn't open( if(wallet.isOpen() === false) { // Reject error reject("Wallet isn't open."); } // Otherwise check if wallet isn't a hardware wallet else if(wallet.getHardwareType() === Wallet.NO_HARDWARE_TYPE) { // Reject error reject("Wallet isn't a hardware wallet."); } // Otherwise check if the wallet's hardware wallet isn't connected else if(wallet.isHardwareConnected() === false) { // Return application showing hardware wallet connect message return self.application.showHardwareWalletConnectMessage(wallet, text, textArguments, allowUnlock, preventMessages, cancelOccurred).then(function() { // Resolve resolve(); // Catch errors }).catch(function(error) { // Reject error reject(error); }); } // Otherwise else { // Resolve resolve(); } } }); } // Wait for hardware wallet to approve waitForHardwareWalletToApprove(keyPath, text, allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) { // Set self var self = this; // Return promise return new Promise(function(resolve, reject) { // Otherwise check if wallet doesn't exist if(self.walletExists(keyPath) === false) { // Reject error reject("Wallet doesn\'t exist."); } // Otherwise else { // Get wallet var wallet = self.wallets[keyPath]; // Check if wallet isn't open( if(wallet.isOpen() === false) { // Reject error reject("Wallet isn't open."); } // Otherwise check if wallet isn't a hardware wallet else if(wallet.getHardwareType() === Wallet.NO_HARDWARE_TYPE) { // Reject error reject("Wallet isn't a hardware wallet."); } // Otherwise check if the wallet's hardware wallet isn't connected else if(wallet.isHardwareConnected() === false) { // Reject hardware wallet disconnected error reject(HardwareWallet.DISCONNECTED_ERROR); } // Otherwise else { // Return application showing hardware wallet pending message return self.application.showHardwareWalletPendingMessage(wallet.getHardwareWallet(), text, allowUnlock, preventMessages, cancelOccurred).then(function(canceled) { // Resolve canceled resolve(canceled); // Catch errors }).catch(function(error) { // Reject error reject(error); }); } } }); } // Hardware wallet done approving hardwareWalletDoneApproving(preventMessages = false) { // Set self var self = this; // Return promise return new Promise(function(resolve, reject) { // Return setting that application hardware wallet pending message is done return self.application.hardwareWalletPendingMessageDone(preventMessages).then(function() { // Resolve resolve(); }); }); } // Obtain exclusive hardware lock obtainExclusiveHardwareLock() { // Set self var self = this; // Return promise return new Promise(function(resolve, reject) { // Check if exclusive hardware lock is locked if(self.exclusiveHardwareLock === true) { // Get current exclusive hardware lock release event index var index = self.exclusiveHardwareLockReleaseEventIndex++; // Check if current exclusive hardware lock release event index is at the max safe integer if(index === Number.MAX_SAFE_INTEGER) // Reset exclusive hardware lock release event index self.exclusiveHardwareLockReleaseEventIndex = 0; // Exclusive hardware lock release index event $(self).on(Wallets.EXCLUSIVE_HARDWARE_LOCK_RELEASE_EVENT + "." + index.toFixed(), function(event) { // Check if exclusive hardware lock isn't locked if(self.exclusiveHardwareLock === false) { // Turn off exclusive hardware lock release index event $(self).off(Wallets.EXCLUSIVE_HARDWARE_LOCK_RELEASE_EVENT + "." + index.toFixed()); // Lock exclusive hardware lock self.exclusiveHardwareLock = true; // Resolve resolve(); } }); } // Otherwise else { // Lock the exclusive hardware lock self.exclusiveHardwareLock = true; // Resolve resolve(); } }); } // Release exclusive hardware lock releaseExclusiveHardwareLock() { // Check if exclusive hardware lock is locked if(this.exclusiveHardwareLock === true) { // Set self var self = this; // Set timeout setTimeout(function() { // Unlock exclusive hardware lock self.exclusiveHardwareLock = false; // Trigger exclusive hardware lock release event $(self).trigger(Wallets.EXCLUSIVE_HARDWARE_LOCK_RELEASE_EVENT); }, 0); } } // Cancel transaction cancelTransaction(walletKeyPath, transactionKeyPath) { // Set self var self = this; // Return promise return new Promise(function(resolve, reject) { // Try try { // Get wallet var wallet = self.getWallet(walletKeyPath); } // Catch errors catch(error) { // Reject error reject(error); // Return return; } // Return obtaining wallet's exclusive transactions lock return self.transactions.obtainWalletsExclusiveTransactionsLock(walletKeyPath).then(function() { // Return getting transaction return self.transactions.getTransactions([transactionKeyPath]).then(function(transaction) { // Check if transaction doesn't exist if(transaction["length"] === 0) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(walletKeyPath); // Reject error reject(Language.getDefaultTranslation('The transaction doesn\'t exist.')); } // Otherwise else { // Get transaction transaction = transaction[0]; // Check if transaction doesn't belong to the wallet if(transaction.getWalletKeyPath() !== walletKeyPath) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(walletKeyPath); // Reject error reject(Language.getDefaultTranslation('The transaction doesn\'t belong to the wallet.')); } // Otherwise check if transaction is canceled else if(transaction.getCanceled() === true) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(walletKeyPath); // Reject error reject(Language.getDefaultTranslation('The transaction is already canceled.')); } // Otherwise check if transaction is confirmed else if((transaction.getReceived() === true && transaction.getStatus() !== Transaction.STATUS_UNCONFIRMED) || (transaction.getReceived() === false && transaction.getAmountReleased() === true)) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(walletKeyPath); // Reject error reject(Language.getDefaultTranslation('Confirmed transactions can\'t be canceled.')); } // Otherwise check if transaction is expired else if(transaction.getExpired() === true) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(walletKeyPath); // Reject error reject(Language.getDefaultTranslation('Expired transactions can\'t be canceled.')); } // Otherwise check if transaction is broadcast and isn't a coinbase transaction else if(transaction.getBroadcast() === true && transaction.getIsCoinbase() === false) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(walletKeyPath); // Reject error reject(Language.getDefaultTranslation('Broadcast transactions can\'t be canceled.')); } // Otherwise else { // Initialize spent outputs var spentOutputs = []; // Initialize change outputs var changeOutputs = []; // Initialize updated transactions var updatedTransactions = []; // Initialize unspent amount change var unspentAmountChange = new BigNumber(0); // Initialize unconfirmed amount change var unconfirmedAmountChange = new BigNumber(0); // Initialize locked amount change var lockedAmountChange = new BigNumber(0); // Set that transaction is canceled transaction.setCanceled(true); // Check if transaction was received if(transaction.getReceived() === true) { // Subtract transaction's amount from unconfirmed amount change unconfirmedAmountChange = unconfirmedAmountChange.minus(transaction.getAmount()); } // Otherwise else { // Set spent outputs to the transaction's spent outputs spentOutputs = transaction.getSpentOutputs(); // Set change outputs to the transaction's change outputs changeOutputs = transaction.getChangeOutputs(); } // Append transaction to list of updated transactions updatedTransactions.push(transaction); // Return getting spent transactions return self.transactions.getTransactions(spentOutputs).then(function(spentTransactions) { // Go through all spent transactions for(var i = 0; i < spentTransactions["length"]; ++i) { // Get spent transaction var spentTransaction = spentTransactions[i]; // Set spent transaction's status to unspent spentTransaction.setStatus(Transaction.STATUS_UNSPENT); // Add spent transaction's amount to unspent amount change unspentAmountChange = unspentAmountChange.plus(spentTransaction.getAmount()); // Subtract spent transaction's amount from locked amount change lockedAmountChange = lockedAmountChange.minus(spentTransaction.getAmount()); // Append spent transaction to list of updated transactions updatedTransactions.push(spentTransaction); } // Return getting change transactions return self.transactions.getTransactions(changeOutputs).then(function(changeTransactions) { // Go through all change transactions for(var i = 0; i < changeTransactions["length"]; ++i) { // Get change transaction var changeTransaction = changeTransactions[i]; // Set that change transaction is canceled changeTransaction.setCanceled(true); // Subtract change transaction's amount from unconfirmed amount change unconfirmedAmountChange = unconfirmedAmountChange.minus(changeTransaction.getAmount()); // Append change transaction to list of updated transactions updatedTransactions.push(changeTransaction); } // Return creating a database transaction return Database.createTransaction([ // Wallets object store Wallets.OBJECT_STORE_NAME, // Transactions object store Transactions.OBJECT_STORE_NAME, ], Database.READ_AND_WRITE_MODE, Database.STRICT_DURABILITY).then(function(databaseTransaction) { // Return saving updated transactions return self.transactions.saveTransactions(updatedTransactions, databaseTransaction).then(function() { // Check if wallet exists if(self.walletExists(walletKeyPath) === true) { // Return saving wallet return self.saveWallet(wallet, function() { // Get values var values = { // New locked amount value [Wallets.NEW_LOCKED_AMOUNT_VALUE]: wallet.getLockedAmount().plus(lockedAmountChange), // New unconfirmed amount value [Wallets.NEW_UNCONFIRMED_AMOUNT_VALUE]: wallet.getUnconfirmedAmount().plus(unconfirmedAmountChange), // New unspent amount value [Wallets.NEW_UNSPENT_AMOUNT_VALUE]: wallet.getUnspentAmount().plus(unspentAmountChange) }; // Return values return values; }, databaseTransaction).then(function(newValues) { // Check if wallet exists if(self.walletExists(walletKeyPath) === true) { // Return committing database transaction return Database.commitTransaction(databaseTransaction).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(walletKeyPath); // Update wallet's locked amount wallet.setLockedAmount(newValues[Wallets.NEW_LOCKED_AMOUNT_VALUE]); // Update wallet's unconfirmed amount wallet.setUnconfirmedAmount(newValues[Wallets.NEW_UNCONFIRMED_AMOUNT_VALUE]); // Update wallet's unspent amount wallet.setUnspentAmount(newValues[Wallets.NEW_UNSPENT_AMOUNT_VALUE]); // Check if wallet exists if(self.walletExists(walletKeyPath) === true) { // Check if wallet's unspent amount changed if(unspentAmountChange.isZero() === false) { // Trigger change event $(document).trigger(Wallets.CHANGE_EVENT, [ // Key path walletKeyPath, // Change Wallets.UNSPENT_AMOUNT_CHANGED, // New value newValues[Wallets.NEW_UNSPENT_AMOUNT_VALUE] ]); } // Check if wallet's unconfirmed amount changed if(unconfirmedAmountChange.isZero() === false) { // Trigger change event $(document).trigger(Wallets.CHANGE_EVENT, [ // Key path walletKeyPath, // Change Wallets.UNCONFIRMED_AMOUNT_CHANGED, // New value newValues[Wallets.NEW_UNCONFIRMED_AMOUNT_VALUE] ]); } // Trigger transactions change event $(self.transactions).trigger(Transactions.CHANGE_EVENT, [ // Transactions updatedTransactions ]); } // Resolve resolve(); // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(walletKeyPath); // Reject error reject(Language.getDefaultTranslation('The database failed.')); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(walletKeyPath); // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); } // Otherwise else { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(walletKeyPath); // Resolve resolve(); // Catch errors }).catch(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(walletKeyPath); // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); } // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(walletKeyPath); // Reject error reject(error); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(walletKeyPath); // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); } // Otherwise else { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(walletKeyPath); // Resolve resolve(); // Catch errors }).catch(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(walletKeyPath); // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); } // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(walletKeyPath); // Reject error reject(error); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(walletKeyPath); // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(walletKeyPath); // Reject error reject(Language.getDefaultTranslation('The database failed.')); }); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(walletKeyPath); // Reject error reject(Language.getDefaultTranslation('The database failed.')); }); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(walletKeyPath); // Reject error reject(Language.getDefaultTranslation('The database failed.')); }); } } // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(walletKeyPath); // Reject error reject(error); }); }); }); } // Sync start event static get SYNC_START_EVENT() { // Return sync start event return "WalletsSyncStartEvent"; } // Sync done event static get SYNC_DONE_EVENT() { // Return sync done event return "WalletsSyncDoneEvent"; } // Sync fail event static get SYNC_FAIL_EVENT() { // Return sync fail event return "WalletsSyncFailEvent"; } // Currency receive event static get CURRENCY_RECEIVE_EVENT() { // Return currency receive event return "WalletsCurrencyReceiveEvent"; } // No message static get NO_MESSAGE() { // Return no message return null; } // Change event static get CHANGE_EVENT() { // Return change event return "WalletsChangeEvent"; } // Address suffix changed static get ADDRESS_SUFFIX_CHANGED() { // Return address suffix changed return 0; } // Unspent amount changed static get UNSPENT_AMOUNT_CHANGED() { // Return unspent amount changed return Wallets.ADDRESS_SUFFIX_CHANGED + 1; } // Unconfirmed amount changed static get UNCONFIRMED_AMOUNT_CHANGED() { // Return unconfirmed amount changed return Wallets.UNSPENT_AMOUNT_CHANGED + 1; } // Pending amount changed static get PENDING_AMOUNT_CHANGED() { // Return pending amount changed return Wallets.UNCONFIRMED_AMOUNT_CHANGED + 1; } // Expired amount changed static get EXPIRED_AMOUNT_CHANGED() { // Return expired amount changed return Wallets.PENDING_AMOUNT_CHANGED + 1; } // Name changed static get NAME_CHANGED() { // Return name changed return Wallets.EXPIRED_AMOUNT_CHANGED + 1; } // Hardware type changed static get HARDWARE_TYPE_CHANGED() { // Return hardware type changed return Wallets.NAME_CHANGED + 1; } // Private // Get wallet from address suffix getWalletFromAddressSuffix(addressSuffix) { // Set self var self = this; // Return promise return new Promise(function(resolve, reject) { // Return getting wallet with the wallet type, network type, and address suffix from the database return Database.getResults(Wallets.OBJECT_STORE_NAME, 0, 1, Wallets.DATABASE_WALLET_TYPE_NETWORK_TYPE_AND_ADDRESS_SUFFIX_NAME, IDBKeyRange.only([ // Wallet type Consensus.getWalletType(), // Network type Consensus.getNetworkType(), // Address suffix addressSuffix.toLowerCase() ])).then(function(results) { // Check if wallet doesn't exist in the database if(results["length"] === 0) { // Reject error reject(Language.getDefaultTranslation('The wallet doesn\'t exist.')); } // Otherwise else { // Get key path from result var keyPath = results[0][Database.KEY_PATH_NAME]; // Check if wallet doesn't exist if(self.walletExists(keyPath) === false) { // Reject error reject(Language.getDefaultTranslation('The wallet doesn\'t exist.')); } // Otherwise else { // Resolve wallet resolve(self.wallets[keyPath]); } } // Catch errors }).catch(function(error) { // Reject error reject(Language.getDefaultTranslation('The database failed.')); }); }); } // Sync sync(ignoreAlreadySyncing = false, ignoreSyncedStatus = false) { // Check if ignoring synced status if(ignoreSyncedStatus === true) { // Set ignore synced status this.ignoreSyncedStatus = true; } // Check if not stop syncing if(this.stopSyncing === false) { // Check if not already syncing or ignoring if already syncing if(this.isSyncing === false || ignoreAlreadySyncing === true) { // Set is syncing this.isSyncing = true; // Set tip height to the node's current height var tipHeight = this.node.getCurrentHeight(); // Check if tip height is known if(tipHeight.getHeight() !== Node.UNKNOWN_HEIGHT) { // Get recent heights's highest height var highestRecentHeight = this.recentHeights.getHighestHeight(); // Check if node is synced and there are no recent heights or the tip height is at least equal to the highest recent height if(tipHeight.getHeight().isEqualTo(Consensus.FIRST_BLOCK_HEIGHT) === false && (highestRecentHeight === RecentHeights.NO_HEIGHT || tipHeight.getHeight().isGreaterThanOrEqualTo(highestRecentHeight) === true)) { // Set self var self = this; // Get recent heights's highest verified height this.recentHeights.getHighestVerifiedHeight(tipHeight).then(function(verifyingHeightsResult) { // Get highest verified height var highestVerifiedHeight = verifyingHeightsResult[RecentHeights.HIGHEST_VERIFIED_HEIGHT_INDEX]; // Get if a reorg occurred var reorgOccurred = verifyingHeightsResult[RecentHeights.REORG_OCCURRED_INDEX]; // Check if a reorg occurred if(reorgOccurred === true) { // Clear node's cache self.node.clearCache(); } // Otherwise else { // Optimize node's cache self.node.optimizeCache(); } // Check if not stop syncing if(self.stopSyncing === false) { // Initialized syncing wallets var syncingWallets = []; // Initialize percent completes var percentCompletes = []; // Set lowest synced height to highest verified height or the first block height if no verified heights exist var lowestSyncedHeight = (highestVerifiedHeight !== RecentHeights.NO_VERIFIED_HEIGHT) ? highestVerifiedHeight : new BigNumber(Consensus.FIRST_BLOCK_HEIGHT); // Go through all wallets var applicableWalletFound = false; for(var keyPath in self.wallets) { if(self.wallets.hasOwnProperty(keyPath) === true) { // Get wallet var wallet = self.wallets[keyPath]; // Check if wallet's synced height isn't the current height if(wallet.getSyncedHeight() !== Wallet.CURRENT_HEIGHT) { // Set applicable wallet found applicableWalletFound = true; // Check if the wallet's synced height is less than the lowest synced height if(wallet.getSyncedHeight().isLessThan(lowestSyncedHeight) === true) { // Set lowest synced height to the wallet's synced height lowestSyncedHeight = wallet.getSyncedHeight(); } } // Append key path to list of syncing wallets syncingWallets.push(wallet.getKeyPath()); // Check if wallet isn't synced or ignoring synced status and its synced height or the highest verified height are less than the tip height if((wallet.isSynced() === false || self.ignoreSyncedStatus === true) && wallet.getSyncedHeight() !== Wallet.CURRENT_HEIGHT && (wallet.getSyncedHeight().isLessThan(tipHeight.getHeight()) === true || (highestVerifiedHeight !== RecentHeights.NO_VERIFIED_HEIGHT && highestVerifiedHeight.isLessThan(tipHeight.getHeight()) === true))) { // Get current percent value var currentPercentValue = (highestVerifiedHeight !== RecentHeights.NO_VERIFIED_HEIGHT && highestVerifiedHeight.isLessThan(wallet.getSyncedHeight())) ? highestVerifiedHeight : wallet.getSyncedHeight(); currentPercentValue = currentPercentValue.minus(wallet.getStartingSyncHeight()); // Set wallet's sync complete value wallet.setSyncCompleteValue(tipHeight.getHeight().minus(wallet.getStartingSyncHeight())); // Get percent complete var percentComplete = (wallet.getSyncCompleteValue().isEqualTo(0) === true) ? new BigNumber(Wallets.MAXIMUM_PERCENT) : currentPercentValue.dividedBy(wallet.getSyncCompleteValue()).multipliedBy(Wallets.MAXIMUM_PERCENT); // Keep percent complete in bounds if(percentComplete.isLessThan(Wallets.MINIMUM_PERCENT) === true) percentComplete = new BigNumber(Wallets.MINIMUM_PERCENT); else if(percentComplete.isGreaterThan(Wallets.MAXIMUM_PERCENT) === true) percentComplete = new BigNumber(Wallets.MAXIMUM_PERCENT); // Append percent complete to list percentCompletes.push(percentComplete); // Set wallet's percent synced wallet.setPercentSynced(percentComplete); // Set wallet's syncing status to syncing wallet.setSyncingStatus(Wallet.STATUS_SYNCING); // Trigger sync start event $(self).trigger(Wallets.SYNC_START_EVENT, [ // Key path wallet.getKeyPath(), // Percent complete percentComplete, // Last percent in group false ]); } // Otherwise else { // Update wallet's starting sync height wallet.setStartingSyncHeight(wallet.getSyncedHeight()); // Append unknown percent complete to list percentCompletes.push(Wallets.UNKNOWN_PERCENT_COMPLETE); // Set wallet's percent synced wallet.setPercentSynced(new BigNumber(Wallets.MAXIMUM_PERCENT)); // Set wallet's syncing status to synced wallet.setSyncingStatus(Wallet.STATUS_SYNCED); // Trigger sync done event $(self).trigger(Wallets.SYNC_DONE_EVENT, wallet.getKeyPath()); } } } // Initialize lowest last sync start index and last retrieved index var lowestLastSyncStartIndex = Wallets.NO_LAST_SYNC_START_INDEX; var lowestLastSyncLastRetrievedIndex = Wallets.NO_LAST_SYNC_LAST_RETRIEVED_INDEX; // Go through all syncing wallets for(var i = 0; i < syncingWallets["length"]; ++i) { // Get wallet var wallet = self.wallets[syncingWallets[i]]; // Check if wallet's synced height isn't the current height and it's the lowest synced height if(wallet.getSyncedHeight() !== Wallet.CURRENT_HEIGHT && wallet.getSyncedHeight().isEqualTo(lowestSyncedHeight) === true) { // Check if wallet's last sync start index is no sync index or it's less than the lowest last sync start index if(lowestLastSyncStartIndex === Wallets.NO_LAST_SYNC_START_INDEX || wallet.getLastSyncIndex() === Wallet.NO_SYNC_INDEX || (lowestLastSyncStartIndex !== Wallet.NO_SYNC_INDEX && wallet.getLastSyncIndex()[Wallet.LAST_SYNC_INDEX_START_INDEX_INDEX].isLessThan(lowestLastSyncStartIndex) === true)) // Set lowest last sync start index to the wallet's last sync start index lowestLastSyncStartIndex = (wallet.getLastSyncIndex() === Wallet.NO_SYNC_INDEX) ? Wallet.NO_SYNC_INDEX : wallet.getLastSyncIndex()[Wallet.LAST_SYNC_INDEX_START_INDEX_INDEX]; // Check if wallet's last sync last retrieved index is no sync index or it's less than the lowest last sync last retrieved index if(lowestLastSyncLastRetrievedIndex === Wallets.NO_LAST_SYNC_LAST_RETRIEVED_INDEX || wallet.getLastSyncIndex() === Wallet.NO_SYNC_INDEX || (lowestLastSyncLastRetrievedIndex !== Wallet.NO_SYNC_INDEX && wallet.getLastSyncIndex()[Wallet.LAST_SYNC_INDEX_LAST_RETRIEVED_INDEX_INDEX].isLessThan(lowestLastSyncLastRetrievedIndex) === true)) // Set lowest last sync last retrieved index to the wallet's last sync last retrieved index lowestLastSyncLastRetrievedIndex = (wallet.getLastSyncIndex() === Wallet.NO_SYNC_INDEX) ? Wallet.NO_SYNC_INDEX : wallet.getLastSyncIndex()[Wallet.LAST_SYNC_INDEX_LAST_RETRIEVED_INDEX_INDEX]; } } // Check if ignoring synced status if(self.ignoreSyncedStatus === true) { // Clear ignore synced status self.ignoreSyncedStatus = false; } // Check if an applicable walet was found if(applicableWalletFound === true) { // Set start height to lowest synced height var startHeight = lowestSyncedHeight; // Check if tip height isn't verified and tip height is greater than or equal to the start height or the tip height is greater than the start height if((highestVerifiedHeight !== RecentHeights.NO_VERIFIED_HEIGHT && highestVerifiedHeight.isLessThan(tipHeight.getHeight()) === true && tipHeight.getHeight().isGreaterThanOrEqualTo(startHeight) === true) || tipHeight.getHeight().isGreaterThan(startHeight) === true) { // Log start height //console.log("Sync start height: " + startHeight.toFixed()); // Get node's PMMR indices to go from start height to tip height self.node.getPmmrIndices(startHeight, tipHeight.getHeight()).then(function(pmmrIndices) { // Check if not stop syncing if(self.stopSyncing === false) { // Get start index var startIndex = pmmrIndices["last_retrieved_index"]; // Check if start index is equal to the last start index if(lowestLastSyncStartIndex !== Wallets.NO_LAST_SYNC_START_INDEX && lowestLastSyncStartIndex !== Wallet.NO_SYNC_INDEX && startIndex.isEqualTo(lowestLastSyncStartIndex) === true) { // Check if the last retrieved index is valid if(lowestLastSyncLastRetrievedIndex !== Wallets.NO_LAST_SYNC_LAST_RETRIEVED_INDEX && lowestLastSyncLastRetrievedIndex !== Wallet.NO_SYNC_INDEX && lowestLastSyncLastRetrievedIndex.isGreaterThanOrEqualTo(lowestLastSyncStartIndex) === true) { // Set start index to one after the last retrieved index startIndex = lowestLastSyncLastRetrievedIndex.plus(1); } } // Get end index var endIndex = pmmrIndices["highest_index"]; // Check if start and end index are valid if(startIndex.isLessThanOrEqualTo(endIndex) === true) { // Get node's unspent outputs from the start index in groups to the end index self.node.getUnspentOutputs(startIndex, endIndex, Wallets.OUTPUTS_GROUP_SIZE, pmmrIndices).then(function(unspentOutputs) { // Check if not stop syncing if(self.stopSyncing === false) { // Get highest synced height in the group var highestSyncedHeight = (unspentOutputs["outputs"]["length"] === 0) ? tipHeight.getHeight() : unspentOutputs["outputs"][unspentOutputs["outputs"]["length"] - 1]["block_height"]; // Log end height //console.log("Sync end height: " + highestSyncedHeight.toFixed()); // Initialize checking outputs var checkingOutputs = []; // Initialize outputs checked var outputsChecked = []; // Initialize starting percent value var startingPercentValue = []; // Initialize last percent complete var lastPercentComplete = []; // Go through all syncing wallets for(var i = 0; i < syncingWallets["length"]; ++i) { // Get key path var keyPath = syncingWallets[i]; // Check if wallet doesn't exist if(self.walletExists(keyPath) === false) // Remove syncing wallet from list syncingWallets.splice(i--, 1); // Otherwise else { // Get wallet var wallet = self.wallets[keyPath]; // Check if wallet's syncing status is resyncing if(wallet.getSyncingStatus() === Wallet.STATUS_RESYNCING) // Remove syncing wallet from list syncingWallets.splice(i--, 1); // Otherwise check if wallet's synced height is the current height else if(wallet.getSyncedHeight() === Wallet.CURRENT_HEIGHT) // Remove syncing wallet from list syncingWallets.splice(i--, 1); // Otherwise check if wallet's synced height and the highest verified height are greater than the highest synced height else if(wallet.getSyncedHeight().isGreaterThan(highestSyncedHeight) === true && (highestVerifiedHeight === RecentHeights.NO_VERIFIED_HEIGHT || highestVerifiedHeight.isGreaterThan(highestSyncedHeight) === true)) // Remove syncing wallet from list syncingWallets.splice(i--, 1); // Otherwise else { // Append empty array to checking outputs checkingOutputs.push([]); // Append zero to outputs checked outputsChecked.push(0); // Append starting percent value to list startingPercentValue.push((highestVerifiedHeight !== RecentHeights.NO_VERIFIED_HEIGHT && highestVerifiedHeight.isLessThan(wallet.getSyncedHeight())) ? highestVerifiedHeight : wallet.getSyncedHeight()); // Append wallet's percent complete to last percent complete lastPercentComplete.push((percentCompletes[i] !== Wallets.UNKNOWN_PERCENT_COMPLETE) ? percentCompletes[i].toFixed(Wallets.PERCENT_COMPLETE_PRECISION, BigNumber.ROUND_FLOOR) : Wallets.UNKNOWN_PERCENT_COMPLETE); } } } // Get highest index var highestIndex = unspentOutputs["highest_index"]; // Get last retrieved index var lastRetrievedIndex = unspentOutputs["last_retrieved_index"]; // Get if at the last output var atLastOutput = highestIndex.isLessThanOrEqualTo(lastRetrievedIndex) === true; // Get outputs ending percent value var outputsEndingPercentValue = highestSyncedHeight; // Initializing checking wallets var checkingWallets = []; // Go through all unspent outputs or run at least once for(var i = 0; i < unspentOutputs["outputs"]["length"] || i === 0; ++i) { // Create output let output = (unspentOutputs["outputs"]["length"] !== 0) ? new Output(unspentOutputs["outputs"][i]["commit"], unspentOutputs["outputs"][i]["proof"], unspentOutputs["outputs"][i]["output_type"], unspentOutputs["outputs"][i]["block_height"]) : Wallets.NO_OUTPUT; // Go through all syncing wallets for(let j = 0; j < syncingWallets["length"]; ++j) { // Get key path let keyPath = syncingWallets[j]; // Get wallet let wallet = self.wallets[keyPath]; // Check if unspent outputs exists if(unspentOutputs["outputs"]["length"] !== 0) { // Append checking output to list for wallet checkingOutputs[j].push(new Promise(function(resolve, reject) { // Return checking if wallet owns output return wallet.ownsOutput(output).then(function(outputInformation) { // Check if wallet isn't synced and its syncing status isn't resyncing if(wallet.isSynced() === false && wallet.getSyncingStatus() !== Wallet.STATUS_RESYNCING) { // Increment outputs checked for wallet ++outputsChecked[j]; // Get current percent value for wallet var currentPercentValue = Common.map(outputsChecked[j], 0, unspentOutputs["outputs"]["length"], startingPercentValue[j], outputsEndingPercentValue); currentPercentValue = currentPercentValue.minus(wallet.getStartingSyncHeight()); // Get percent complete var percentComplete = (wallet.getSyncCompleteValue().isEqualTo(0) === true) ? new BigNumber(Wallets.MAXIMUM_PERCENT) : currentPercentValue.dividedBy(wallet.getSyncCompleteValue()).multipliedBy(Wallets.MAXIMUM_PERCENT); // Keep percent complete in bounds if(percentComplete.isLessThan(Wallets.MINIMUM_PERCENT) === true) percentComplete = new BigNumber(Wallets.MINIMUM_PERCENT); else if(percentComplete.isGreaterThan(Wallets.MAXIMUM_PERCENT) === true) percentComplete = new BigNumber(Wallets.MAXIMUM_PERCENT); // Set wallet's percent synced wallet.setPercentSynced(percentComplete); // Get new percent complete var newPercentComplete = percentComplete.toFixed(Wallets.PERCENT_COMPLETE_PRECISION, BigNumber.ROUND_FLOOR); // Check if there is unknown last percent complete for wallet or the percent complete for wallet noticeably changed if(lastPercentComplete[j] === Wallets.UNKNOWN_PERCENT_COMPLETE || lastPercentComplete[j] !== newPercentComplete) { // Trigger sync start event $(self).trigger(Wallets.SYNC_START_EVENT, [ // Key path keyPath, // Percent complete percentComplete, // Last percent in group outputsChecked[j] === unspentOutputs["outputs"]["length"] ]); } // Update last percent complete for wallet lastPercentComplete[j] = newPercentComplete; } // Resolve output information resolve(outputInformation); // Catch errors }).catch(function(error) { // Reject error reject(error); }); })); } // Check if unspent outputs don't exist or at the last unspent output if(unspentOutputs["outputs"]["length"] === 0 || i === unspentOutputs["outputs"]["length"] - 1) { // Append checking wallet to list checkingWallets.push(new Promise(function(resolve, reject) { // Return waiting for wallet to finish checking all outputs return Promise.all(checkingOutputs[j]).then(function(outputsInformation) { // Return obtaining wallet's exclusive transactions lock return self.transactions.obtainWalletsExclusiveTransactionsLock(keyPath, false).then(function() { // Initialize process transactions var processTransactions = []; // Initialize updated transactions var updatedTransactions = []; // Initialize spent amount change var spentAmountChange = new BigNumber(0); // Initialize unspent amount change var unspentAmountChange = new BigNumber(0); // Initialize unconfirmed amount change var unconfirmedAmountChange = new BigNumber(0); // Initialize locked amount change var lockedAmountChange = new BigNumber(0); // Initialize pending amount change var pendingAmountChange = new BigNumber(0); // Initialize expired amount change var expiredAmountChange = new BigNumber(0); // Initialize highest identifier var highestIdentifier = Wallet.NO_LAST_IDENTIFIER; // Initialize transactions verified var transactionsVerified = []; // Go through all outputs' information for(let k = 0; k < outputsInformation["length"]; ++k) { // Append processing transaction to list processTransactions.push(new Promise(function(resolve, reject) { // Get output information var outputInformation = outputsInformation[k]; // Check if output information exists if(outputInformation !== Output.NO_INFORMATION) { // Check if wallet is a hardware wallet and the output has an incompatible switch type if(wallet.getHardwareType() !== Wallet.NO_HARDWARE_TYPE && outputInformation.getSwitchType() === Crypto.SWITCH_TYPE_NONE) { // Resolve resolve(); // Return return; } // Get output information's identifier height var identifierHeight = outputInformation.getIdentifier().getHeight(); // Check if identifier height exists if(identifierHeight !== Identifier.NO_HEIGHT) { // Get output information's output height var outputHeight = outputInformation.getOutput().getHeight(); // Set identifier's height to include the part of the output height that exceeds the maximum identifier height identifierHeight = identifierHeight.plus(outputHeight.dividedBy(Identifier.MAXIMUM_HEIGHT + 1).decimalPlaces(0, BigNumber.ROUND_HALF_CEIL).multipliedBy(Identifier.MAXIMUM_HEIGHT + 1)); // Check if identifier height is now too large and it can be reduced if(identifierHeight.minus(outputHeight).isGreaterThan(Wallets.IDENTIFIER_HEIGHT_OVERAGE_THRESHOLD) === true && identifierHeight.isGreaterThanOrEqualTo(Identifier.MAXIMUM_HEIGHT + 1) === true) { // Reduce identifier height identifierHeight = identifierHeight.minus(Identifier.MAXIMUM_HEIGHT + 1); } // Check if output height exceeds the identifier height by at least the replay detection threshold if(outputHeight.minus(identifierHeight).isGreaterThan(Wallets.REPLAY_DETECTION_THRESHOLD) === true) { // Resolve resolve(); // Return return; } } // Check if highest identifier doesn't exist if(highestIdentifier === Wallet.NO_LAST_IDENTIFIER) { // Get default identifier var defaultIdentifier = new Identifier(); // Set the highest identifier candidate to the default identifier's child identifier var highestIdentifierCandidate = defaultIdentifier.getChild(); // Check if output information's identifier included the highest identifier candidate if(outputInformation.getIdentifier().includesValue(highestIdentifierCandidate) === true) { // Set the highest identifier to the output information's identifier without the extras highestIdentifier = outputInformation.getIdentifier().removeExtras(); } } // Otherwise check if output information's identifier included the highest identifier else if(outputInformation.getIdentifier().includesValue(highestIdentifier) === true) { // Set the highest identifier to the output information's identifier without the extras highestIdentifier = outputInformation.getIdentifier().removeExtras(); } // Check if transactions wasn't already verified if(transactionsVerified.indexOf(Common.toHexString(outputInformation.getOutput().getCommit())) === Common.INDEX_NOT_FOUND) { // Append transaction to list of transactions verified transactionsVerified.push(Common.toHexString(outputInformation.getOutput().getCommit())); // Return getting header at output's height return self.node.getHeader(outputInformation.getOutput().getHeight()).then(function(header) { // Get timestamp var timestamp = header["timestamp"]; // Return getting transaction return self.transactions.getTransaction(wallet.getWalletType(), wallet.getNetworkType(), outputInformation.getOutput().getCommit()).then(function(transaction) { // Check if transaction exists if(transaction !== Transactions.NO_TRANSACTION_FOUND) { // Check if transaction is for the wallet if(transaction.getWalletKeyPath() === keyPath) { // Set transaction changed var transactionChanged = false; // Check if transaction's confirmed timestamp needs to be updated if(transaction.getConfirmedTimestamp() !== timestamp) { // Set transaction's confirmed timestamp transaction.setConfirmedTimestamp(timestamp); // Set transaction changed transactionChanged = true; } // Check if transaction's received needs to be updated if(transaction.getReceived() === false) { // Set transaction's received transaction.setReceived(true); // Set transaction changed transactionChanged = true; } // Check if transaction's height needs to be updated if(transaction.getHeight() === Transaction.UNKNOWN_HEIGHT || transaction.getHeight().isEqualTo(outputInformation.getOutput().getHeight()) === false) { // Set transaction's height transaction.setHeight(outputInformation.getOutput().getHeight()); // Set transaction changed transactionChanged = true; } // Check if transaction's is coinbase needs to be updated if(transaction.getIsCoinbase() !== outputInformation.getOutput().isCoinbase()) { // Set transaction's is coinbase transaction.setIsCoinbase(outputInformation.getOutput().isCoinbase()); // Set transaction changed transactionChanged = true; } // Check if transaction's identifier needs to be updated if(transaction.getIdentifier().equalsValue(outputInformation.getIdentifier()) === false) { // Set transaction's identifier transaction.setIdentifier(outputInformation.getIdentifier()); // Set transaction changed transactionChanged = true; } // Check if transaction's switch type needs to be updated if(transaction.getSwitchType() !== outputInformation.getSwitchType()) { // Set transaction's switch type transaction.setSwitchType(outputInformation.getSwitchType()); // Set transaction changed transactionChanged = true; } // Check if transaction's broadcast needs to be updated if(transaction.getBroadcast() === false) { // Set transaction's broadcast transaction.setBroadcast(true); // Set transaction changed transactionChanged = true; } // Get new spendable height as the output height added to the transaction's number of confirmations var newSpendableHeight = outputInformation.getOutput().getHeight().plus(transaction.getRequiredNumberOfConfirmations().minus(1)); // Check if output's maturity height is greater than the new spendable height if(outputInformation.getOutput().getMaturityHeight().isGreaterThan(newSpendableHeight) === true) { // Set the new spendable height to the output's maturity height newSpendableHeight = outputInformation.getOutput().getMaturityHeight(); } // Check if the transaction's lock height exists and if it added to the number of confirmation is greater than the new spendable height if(transaction.getLockHeight() !== Transaction.UNKNOWN_LOCK_HEIGHT && transaction.getLockHeight() !== Transaction.NO_LOCK_HEIGHT && transaction.getLockHeight().plus(transaction.getRequiredNumberOfConfirmations().minus(1)).isGreaterThan(newSpendableHeight) === true) { // Set the new spendable height to the transaction's lock height added to the number of confirmation newSpendableHeight = transaction.getLockHeight().plus(transaction.getRequiredNumberOfConfirmations().minus(1)); } // Check transaction's status switch(transaction.getStatus()) { // Spent case Transaction.STATUS_SPENT: // Revert transaction's status to locked since transaction wasn't completed yet transaction.setStatus(Transaction.STATUS_LOCKED); // Add transaction's new amount to locked amount change lockedAmountChange = lockedAmountChange.plus(outputInformation.getAmount()); // Subtract transaction's current amount from spent amount change spentAmountChange = spentAmountChange.minus(transaction.getAmount()); // Set transaction changed transactionChanged = true; // Break break; // Unconfirmed case Transaction.STATUS_UNCONFIRMED: // Update transaction's status to unspent since transaction is confirmed on the chain transaction.setStatus(Transaction.STATUS_UNSPENT); // Check if the transaction's new spendable height is the next block if(newSpendableHeight.isLessThanOrEqualTo(tipHeight.getHeight().plus(1)) === true) { // Check if the transaction's amount hasn't been released if(transaction.getAmountReleased() === false) { // Set transaction amount has been released transaction.setAmountReleased(true); // Add transaction's new amount to unspent amount change unspentAmountChange = unspentAmountChange.plus(outputInformation.getAmount()); } // Otherwise else { // Subtract transaction's current amount from unspent amount change unspentAmountChange = unspentAmountChange.minus(transaction.getAmount()); // Add transaction's new amount to unspent amount change unspentAmountChange = unspentAmountChange.plus(outputInformation.getAmount()); } } // Otherwise else { // Add transaction's new amount to pending amount change pendingAmountChange = pendingAmountChange.plus(outputInformation.getAmount()); // Check if the transaction's amount has been released if(transaction.getAmountReleased() === true) { // Set transaction amount hasn't been released transaction.setAmountReleased(false); // Subtract transaction's current amount from unspent amount change unspentAmountChange = unspentAmountChange.minus(transaction.getAmount()); } } // Check if transaction isn't canceled and expired if(transaction.getCanceled() === false && transaction.getExpired() === false) { // Subtract transaction's current amount from unconfirmed amount change unconfirmedAmountChange = unconfirmedAmountChange.minus(transaction.getAmount()); } // Set transaction changed transactionChanged = true; // Break break; // Unspent case Transaction.STATUS_UNSPENT: // Check if transaction's amount hasn't been released if(transaction.getAmountReleased() === false) { // Check if the transaction's new spendable height is the next block if(newSpendableHeight.isLessThanOrEqualTo(tipHeight.getHeight().plus(1)) === true) { // Set transaction amount has been released transaction.setAmountReleased(true); // Add transaction's new amount to unspent amount change unspentAmountChange = unspentAmountChange.plus(outputInformation.getAmount()); // Subtract transaction's current amount from pending amount change pendingAmountChange = pendingAmountChange.minus(transaction.getAmount()); // Set transaction changed transactionChanged = true; } // Otherwise check if the transaction's amount changed else if(transaction.getAmount().isEqualTo(outputInformation.getAmount()) === false) { // Subtract transaction's current amount from pending amount change pendingAmountChange = pendingAmountChange.minus(transaction.getAmount()); // Add transaction's new amount to pending amount change pendingAmountChange = pendingAmountChange.plus(outputInformation.getAmount()); // Set transaction changed transactionChanged = true; } } // Otherwise else { // Check if the transaction's new spendable height isn't the next block if(newSpendableHeight.isGreaterThan(tipHeight.getHeight().plus(1)) === true) { // Set transaction amount hasn't been released transaction.setAmountReleased(false); // Subtract transaction's current amount from unspent amount change unspentAmountChange = unspentAmountChange.minus(transaction.getAmount()); // Add transaction's new amount to pending amount change pendingAmountChange = pendingAmountChange.plus(outputInformation.getAmount()); // Set transaction changed transactionChanged = true; } // Otherwise check if the transaction's amount changed else if(transaction.getAmount().isEqualTo(outputInformation.getAmount()) === false) { // Subtract transaction's current amount from unspent amount change unspentAmountChange = unspentAmountChange.minus(transaction.getAmount()); // Add transaction's new amount to unspent amount change unspentAmountChange = unspentAmountChange.plus(outputInformation.getAmount()); // Set transaction changed transactionChanged = true; } } // Break break; // Locked case Transaction.STATUS_LOCKED: // Check if the transaction's amount changed if(transaction.getAmount().isEqualTo(outputInformation.getAmount()) === false) { // Subtract transaction's current amount from locked amount change lockedAmountChange = lockedAmountChange.minus(transaction.getAmount()); // Add transaction's new amount to locked amount change lockedAmountChange = lockedAmountChange.plus(outputInformation.getAmount()); // Set transaction changed transactionChanged = true; } // Break break; } // Check if transaction's spendable height needs to be updated if(transaction.getSpendableHeight() === Transaction.UNKNOWN_SPENDABLE_HEIGHT || transaction.getSpendableHeight().isEqualTo(newSpendableHeight) === false) { // Set transaction's spendable height transaction.setSpendableHeight(newSpendableHeight); // Set transaction changed transactionChanged = true; } // Check if transaction's amount needs to be updated if(transaction.getAmount().isEqualTo(outputInformation.getAmount()) === false) { // Set transactions amount transaction.setAmount(outputInformation.getAmount()); // Set transaction changed transactionChanged = true; } // Check if transaction's canceled needs to be updated if(transaction.getCanceled() === true) { // Set transaction's canceled transaction.setCanceled(false); // Set transaction changed transactionChanged = true; } // Check if transaction's expired needs to be updated if(transaction.getExpired() === true) { // Set transaction's expired transaction.setExpired(false); // Check if transaction isn't change output if(transaction.getDisplay() === true) { // Subtract transaction's current amount from expired amount change expiredAmountChange = expiredAmountChange.minus(transaction.getAmount()); } // Set transaction changed transactionChanged = true; } // Check if transaction's checked needs to be updated if(transaction.getChecked() === false) { // Set transaction's checked transaction.setChecked(true); // Set transaction changed transactionChanged = true; } // Check if transaction changed if(transactionChanged === true) { // Append transaction to list of updated transactions updatedTransactions.push(transaction); } } // Resolve resolve(); } // Otherwise else { // Get recorded timestamp var recordedTimestamp = Date.now(); // Get prices var prices = self.prices.getPrices(); // Get spendable height as the output height added to the number of confirmations var spendableHeight = outputInformation.getOutput().getHeight().plus(self.numberOfConfirmations.minus(1)); // Check if output's maturity height is greater than the spendable height if(outputInformation.getOutput().getMaturityHeight().isGreaterThan(spendableHeight) === true) { // Set the spendable height to the output's maturity height spendableHeight = outputInformation.getOutput().getMaturityHeight(); } // Create new transaction var newTransaction = new Transaction(wallet.getWalletType(), wallet.getNetworkType(), outputInformation.getOutput().getCommit(), keyPath, true, recordedTimestamp, Transaction.UNKNOWN_CREATED_TIMESTAMP, outputInformation.getOutput().getHeight(), Transaction.UNKNOWN_LOCK_HEIGHT, outputInformation.getOutput().isCoinbase(), Transaction.STATUS_UNSPENT, outputInformation.getAmount(), false, Transaction.UNKNOWN_KERNEL_EXCESS, outputInformation.getIdentifier(), outputInformation.getSwitchType(), true, Transaction.UNKNOWN_KERNEL_OFFSET, Transaction.UNKNOWN_ID, Transaction.UNKNOWN_MESSAGE, Transaction.UNKNOWN_TIME_TO_LIVE_CUT_OFF_HEIGHT, false, timestamp, Transaction.UNKNOWN_FEE, Transaction.UNKNOWN_SENDER_ADDRESS, Transaction.UNKNOWN_RECEIVER_ADDRESS, Transaction.UNKNOWN_RECEIVER_SIGNATURE, Transaction.UNUSED_DESTINATION, spendableHeight, self.numberOfConfirmations, Transaction.UNUSED_SPENT_OUTPUTS, Transaction.UNUSED_CHANGE_OUTPUTS, true, Transaction.UNKNOWN_REBROADCAST_MESSAGE, Transaction.UNUSED_FILE_RESPONSE, (prices !== Prices.NO_PRICES_FOUND) ? prices : Transaction.UNKNOWN_PRICES_WHEN_RECORDED, true); // Check if the new transaction's spendable height is the next block if(newTransaction.getSpendableHeight().isLessThanOrEqualTo(tipHeight.getHeight().plus(1)) === true) { // Set new transaction amount has been released newTransaction.setAmountReleased(true); // Add new transaction's amount to unspent amount change unspentAmountChange = unspentAmountChange.plus(newTransaction.getAmount()); } // Otherwise else { // Add new transaction's amount to pending amount change pendingAmountChange = pendingAmountChange.plus(newTransaction.getAmount()); } // Append new transaction to list of updated transactions updatedTransactions.push(newTransaction); // Resolve resolve(); } // Catch errors }).catch(function(error) { // Reject error reject(error); }); // Catch errors }).catch(function(error) { // Reject error reject(error); }); } // Otherwise else { // Resolve resolve(); } } // Otherwise else { // Resolve resolve(); } })); } // Return processing all transactions for the wallet return Promise.all(processTransactions).then(function() { // Return getting wallet's transactions in the height range between the start height and the highest synced height return self.transactions.getWalletsTransactionsInHeightRange(keyPath, startHeight, highestSyncedHeight).then(function(transactions) { // Return getting wallet's unreleased, received transactions that can be included in the next block return self.transactions.getWalletsUnreleasedReceivedTransactionsInSpendableHeightRange(keyPath, Consensus.FIRST_BLOCK_HEIGHT, tipHeight.getHeight().plus(1)).then(function(unreleasedTransactions) { // Return getting wallet's unbroadcast transactions that should have expired by the tip height return self.transactions.getWalletsUnbroadcastTransactionsInTimeToLiveCutOffHeightRange(keyPath, Consensus.FIRST_BLOCK_HEIGHT, tipHeight.getHeight()).then(function(expiredTransactions) { // Return getting wallet's unreleased, sent transactions that have been broadcast return self.transactions.getWalletsUnreleasedSentBroadcastTransactions(keyPath).then(function(sentUnreleasedTransactions) { // Return getting wallet's unexpired, sent, unbroadcast transactions that can be included in the next block return self.transactions.getWalletsUnexpiredSentUnbroadcastTransactionsInLockHeightRange(keyPath, Consensus.FIRST_BLOCK_HEIGHT, tipHeight.getHeight().plus(1)).then(function(sentUnbroadcastTransactions) { // Return getting the wallet's unchecked transactions return self.transactions.getWalletsUncheckedTransactions(keyPath).then(function(uncheckedTransactions) { // Initialize processed sent transactions var processedSentTransactions = []; // Initialize verifying send transactions var verifyingSentTransactions = []; // Initialize pending transactions var pendingTransactions = []; // Initialize spent outputs var spentOutputs = [ // Change to spent [], // Change to unspent [], // Change to locked [] ]; // Initialize spent outputs to change var spentOutputsToChange = []; // Initialize change outputs to change var changeOutputsToChange = []; // Go through all of the wallet's transactions in the height range, the wallet's unreleased transactions that should have been released by the tip height, the wallet's unexpired transactions that should have expired by the tip height, the wallet's unreleased sent transactions that have been broadcast, the wallet's unexpired sent unbroadcast transactions that can be included in the next block, and the wallet's unchecked transactions for(var k = 0; k < transactions["length"] + unreleasedTransactions["length"] + expiredTransactions["length"] + sentUnreleasedTransactions["length"] + sentUnbroadcastTransactions["length"] + uncheckedTransactions["length"]; ++k) { // Initialize transaction let transaction; // Check if transaction is in the height range if(k < transactions["length"]) { // Set transaction transaction = transactions[k]; } // Otherwise check if transaction is an unreleased transactions that should have been released by the tip height else if(k - transactions["length"] < unreleasedTransactions["length"]) { // Set transaction transaction = unreleasedTransactions[k - transactions["length"]]; } // Otherwise check if transaction is an unreleased transactions that should have expired by the tip height else if(k - transactions["length"] - unreleasedTransactions["length"] < expiredTransactions["length"]) { // Set transaction transaction = expiredTransactions[k - transactions["length"] - unreleasedTransactions["length"]]; } // Otherwise check if transaction is a sent unreleased transaction else if(k - transactions["length"] - unreleasedTransactions["length"] - expiredTransactions["length"] < sentUnreleasedTransactions["length"]) { // Set transaction transaction = sentUnreleasedTransactions[k - transactions["length"] - unreleasedTransactions["length"] - expiredTransactions["length"]]; } // Otherwise check if transaction is a sent unbroadcast transaction else if(k - transactions["length"] - unreleasedTransactions["length"] - expiredTransactions["length"] - sentUnreleasedTransactions["length"] < sentUnbroadcastTransactions["length"]) { // Set transaction transaction = sentUnbroadcastTransactions[k - transactions["length"] - unreleasedTransactions["length"] - expiredTransactions["length"] - sentUnreleasedTransactions["length"]]; } // Otherwise check if transaction is an unchecked transactions else if(k - transactions["length"] - unreleasedTransactions["length"] - expiredTransactions["length"] - sentUnreleasedTransactions["length"] - sentUnbroadcastTransactions["length"] < uncheckedTransactions["length"]) { // Set transaction transaction = uncheckedTransactions[k - transactions["length"] - unreleasedTransactions["length"] - expiredTransactions["length"] - sentUnreleasedTransactions["length"] - sentUnbroadcastTransactions["length"]]; } // Check if transaction was received if(transaction.getReceived() === true) { // Check if transactions wasn't already verified if(transactionsVerified.indexOf(Common.toHexString(transaction.getCommit())) === Common.INDEX_NOT_FOUND) { // Append transaction to list of transactions verified transactionsVerified.push(Common.toHexString(transaction.getCommit())); // Append transaction to list of pending transactions pendingTransactions.push(transaction); } } // Otherwise else { // Check if transaction wasn't already processed if(processedSentTransactions.indexOf(transaction.getKeyPath()) === Common.INDEX_NOT_FOUND) { // Append transaction to list of sent transactions that have been processed processedSentTransactions.push(transaction.getKeyPath()); // Append verifying sent transaction to list verifyingSentTransactions.push(new Promise(function(resolve, reject) { // Set get transaction's kernel var getTransactionsKernel = function() { // Return promise return new Promise(function(resolve, reject) { // Check if transaction has been broadcast if(transaction.getBroadcast() === true) { // Set kernel minimum height var kernelMinimumHeight = transaction.getHeight().minus(Wallets.VARIATION_FROM_PREVIOUS_BLOCK_HEIGHT); // Set kernel maximum height var kernelMaximumHeight = transaction.getHeight().plus(Wallets.VARIATION_TO_NEXT_BLOCK_HEIGHT); // Check if kernel minimum height is less than the first block height if(kernelMinimumHeight.isLessThan(Consensus.FIRST_BLOCK_HEIGHT) === true) { // Set kernel minimum height to the first block height kernelMinimumHeight = new BigNumber(Consensus.FIRST_BLOCK_HEIGHT); } // Check if kernel maximum height exceeds the tip height if(kernelMaximumHeight.isGreaterThan(tipHeight.getHeight()) === true) { // Set kernel maximum height to the tip height kernelMaximumHeight = tipHeight.getHeight(); } // Return getting transaction's kernel in the range around its height return self.node.getKernel(transaction.getKernelExcess(), kernelMinimumHeight, kernelMaximumHeight).then(function(kernel) { // Resolve kernel resolve(kernel); // Catch errors }).catch(function(error) { // Reject error reject(error); }); } // Otherwise else { // Resolve no kernel found resolve(Node.NO_KERNEL_FOUND); } }); }; // Return getting transaction's kernel return getTransactionsKernel().then(function(kernel) { // Set transaction changed var transactionChanged = false; // Check if kernel exists if(kernel !== Node.NO_KERNEL_FOUND) { // Go through all the transaction's spent outputs for(var l = 0; l < transaction.getSpentOutputs()["length"]; ++l) { // Get spent output var spentOutput = transaction.getSpentOutputs()[l]; // Check if spent output isn't in the change to spent list if(spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_SPENT_INDEX].indexOf(spentOutput) === Common.INDEX_NOT_FOUND) { // Add spent output to change to spent list spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_SPENT_INDEX].push(spentOutput); } // Check if spent output exists in change to unspent list if(spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_UNSPENT_INDEX].indexOf(spentOutput) !== Common.INDEX_NOT_FOUND) { // Remove spent output from change to unspent list spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_UNSPENT_INDEX].splice(spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_UNSPENT_INDEX].indexOf(spentOutput), 1); } // Check if spent output exists in change to locked list if(spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_LOCKED_INDEX].indexOf(spentOutput) !== Common.INDEX_NOT_FOUND) { // Remove spent output from change to locked list spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_LOCKED_INDEX].splice(spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_LOCKED_INDEX].indexOf(spentOutput), 1); } // Check if spent output doesn't exists in the list of outputs to change if(spentOutputsToChange.indexOf(spentOutput) === Common.INDEX_NOT_FOUND) { // Append spent output to list of spent outputs to change spentOutputsToChange.push(spentOutput); } } // Check if transaction's amount wasn't released if(transaction.getAmountReleased() === false) { // Set transaction amount has been released transaction.setAmountReleased(true); // Set transaction changed transactionChanged = true; } // Check if transaction's amount was expired if(transaction.getExpired() === true) { // Set transaction isn't expired transaction.setExpired(false); // Set transaction changed transactionChanged = true; } // Check if transaction's height needs to be updated if(transaction.getHeight().isEqualTo(kernel["height"]) === false) { // Update transaction's height to the kernel's height transaction.setHeight(kernel["height"]); // Set transaction changed transactionChanged = true; } // Check if transaction's checked needs to be updated if(transaction.getChecked() === false) { // Set transaction's checked transaction.setChecked(true); // Set transaction changed transactionChanged = true; } // Return getting header at kernel's height return self.node.getHeader(kernel["height"]).then(function(header) { // Get timestamp var timestamp = header["timestamp"]; // Check if transaction's confirmed timestamp needs to be updated if(transaction.getConfirmedTimestamp() !== timestamp) { // Set transaction's confirmed timestamp transaction.setConfirmedTimestamp(timestamp); // Set transaction changed transactionChanged = true; } // Check if transaction changed if(transactionChanged === true) { // Append transaction to list of updated transactions updatedTransactions.push(transaction); } // Resolve resolve(); // Catch errors }).catch(function(error) { // Reject error reject(error); }); } // Otherwise else { // Check if transaction hasn't been broadcast and it's expired if(transaction.getBroadcast() === false && transaction.getTimeToLiveCutOffHeight() !== Transaction.UNKNOWN_TIME_TO_LIVE_CUT_OFF_HEIGHT && transaction.getTimeToLiveCutOffHeight() !== Transaction.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT && transaction.getTimeToLiveCutOffHeight().isLessThanOrEqualTo(tipHeight.getHeight()) === true) { // Go through all the transaction's spent outputs for(var l = 0; l < transaction.getSpentOutputs()["length"]; ++l) { // Get spent output var spentOutput = transaction.getSpentOutputs()[l]; // Check if spent output doesn't exists in change to spent list or change to locked list if(spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_SPENT_INDEX].indexOf(spentOutput) === Common.INDEX_NOT_FOUND && spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_LOCKED_INDEX].indexOf(spentOutput) === Common.INDEX_NOT_FOUND) { // Check if spent output isn't in the change to unspent list if(spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_UNSPENT_INDEX].indexOf(spentOutput) === Common.INDEX_NOT_FOUND) { // Add spent output to change to unspent list spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_UNSPENT_INDEX].push(spentOutput); } } // Check if spent output doesn't exists in the list of outputs to change if(spentOutputsToChange.indexOf(spentOutput) === Common.INDEX_NOT_FOUND) { // Append spent output to list of spent outputs to change spentOutputsToChange.push(spentOutput); } } } // Otherwise else { // Go through all the transaction's spent outputs for(var l = 0; l < transaction.getSpentOutputs()["length"]; ++l) { // Get spent output var spentOutput = transaction.getSpentOutputs()[l]; // Check if spent output doesn't exists in change to spent list if(spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_SPENT_INDEX].indexOf(spentOutput) === Common.INDEX_NOT_FOUND) { // Check if spent output isn't in the change to locked list if(spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_LOCKED_INDEX].indexOf(spentOutput) === Common.INDEX_NOT_FOUND) { // Add spent output to change to locked list spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_LOCKED_INDEX].push(spentOutput); } // Check if spent output exists in change to unspent list if(spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_UNSPENT_INDEX].indexOf(spentOutput) !== Common.INDEX_NOT_FOUND) { // Remove spent output from change to unspent list spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_UNSPENT_INDEX].splice(spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_UNSPENT_INDEX].indexOf(spentOutput), 1); } } // Check if spent output doesn't exists in the list of outputs to change if(spentOutputsToChange.indexOf(spentOutput) === Common.INDEX_NOT_FOUND) { // Append spent output to list of spent outputs to change spentOutputsToChange.push(spentOutput); } } } // Check if transaction's amount was released if(transaction.getAmountReleased() === true) { // Set transaction amount hasn't been released transaction.setAmountReleased(false); // Set transaction changed transactionChanged = true; } // Check if transaction hasn't been broadcast, it isn't expired, and its time to live cut off height has past if(transaction.getBroadcast() === false && transaction.getExpired() === false && transaction.getTimeToLiveCutOffHeight() !== Transaction.UNKNOWN_TIME_TO_LIVE_CUT_OFF_HEIGHT && transaction.getTimeToLiveCutOffHeight() !== Transaction.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT && transaction.getTimeToLiveCutOffHeight().isLessThanOrEqualTo(tipHeight.getHeight()) === true) { // Set that transaction is expired transaction.setExpired(true); // Set transaction changed transactionChanged = true; } // Check if transaction's checked needs to be updated if(transaction.getChecked() === false) { // Set transaction's checked transaction.setChecked(true); // Set transaction changed transactionChanged = true; } // Check if transaction hasn't been broadcast, it isn't expired, and its lock height is the next block if(transaction.getBroadcast() === false && transaction.getExpired() === false && transaction.getLockHeight() !== Transaction.UNKNOWN_LOCK_HEIGHT && transaction.getLockHeight() !== Transaction.NO_LOCK_HEIGHT && transaction.getLockHeight().isLessThanOrEqualTo(tipHeight.getHeight().plus(1)) === true) { // Return broadcasting transaction to the node return self.node.broadcastTransaction(JSONBigNumber.parse(transaction.getRebroadcastMessage())).then(function() { // Set transaction has been broadcast transaction.setBroadcast(true); // Go through all of the transaction's change outputs for(var l = 0; l < transaction.getChangeOutputs()["length"]; ++l) { // Get change output var changeOutput = transaction.getChangeOutputs()[l]; // Append change output to list of outputs to change changeOutputsToChange.push(changeOutput); } // Append transaction to list of updated transactions updatedTransactions.push(transaction); // Resolve resolve(); // Catch errors }).catch(function(error) { // Check if transaction changed if(transactionChanged === true) { // Append transaction to list of updated transactions updatedTransactions.push(transaction); } // Resolve resolve(); }); } // Otherwise else { // Check if transaction changed if(transactionChanged === true) { // Append transaction to list of updated transactions updatedTransactions.push(transaction); } // Resolve resolve(); } } // Catch errors }).catch(function(error) { // Reject error reject(error); }); })); } } } // Return verifying sent transactions return Promise.all(verifyingSentTransactions).then(function() { // Go through all pending transactions for(var k = 0; k < pendingTransactions["length"]; ++k) { // Get pending transaction var pendingTransaction = pendingTransactions[k]; // Check if pending transaction is a change output that's changing if(changeOutputsToChange.indexOf(pendingTransaction.getKeyPath()) !== Common.INDEX_NOT_FOUND) { // Remove pending transaction from list pendingTransactions.splice(k--, 1); } // Otherwise check if pending transaction is a spent output that's changing else if(spentOutputsToChange.indexOf(pendingTransaction.getKeyPath()) !== Common.INDEX_NOT_FOUND) { // Remove pending transaction from list pendingTransactions.splice(k--, 1); } } // Return getting change transactions return self.transactions.getTransactions(changeOutputsToChange).then(function(changeTransactions) { // Go through all change transactions for(var k = 0; k < changeTransactions["length"]; ++k) { // Get change transaction var changeTransaction = changeTransactions[k]; // Set change transaction has been broadcast changeTransaction.setBroadcast(true); // Set changed transaction has been checked changeTransaction.setChecked(true); // Append change transaction to list of updated transactions updatedTransactions.push(changeTransaction); } // Go through all updated transactions for(var k = 0; k < updatedTransactions["length"]; ++k) { // Get updated transaction var updatedTransaction = updatedTransactions[k]; // Check if updated transaction is a spent output that's changing if(updatedTransaction.getKeyPath() !== Transaction.NO_KEY_PATH && spentOutputsToChange.indexOf(updatedTransaction.getKeyPath()) !== Common.INDEX_NOT_FOUND) { // Check updated transaction's status switch(updatedTransaction.getStatus()) { // Spent case Transaction.STATUS_SPENT: // Subtract updated transaction's amount from spent amount change spentAmountChange = spentAmountChange.minus(updatedTransaction.getAmount()); // Break break; // Unconfirmed case Transaction.STATUS_UNCONFIRMED: // Subtract updated transaction's amount from unconfirmed amount change unconfirmedAmountChange = unconfirmedAmountChange.minus(updatedTransaction.getAmount()); // Break break; // Unspent case Transaction.STATUS_UNSPENT: // Check if updated transaction's amount has been released if(updatedTransaction.getAmountReleased() === true) { // Subtract updated transaction's amount from unspent amount change unspentAmountChange = unspentAmountChange.minus(updatedTransaction.getAmount()); } // Otherwis else { // Subtract updated transaction's amount from pending amount change pendingAmountChange = pendingAmountChange.minus(updatedTransaction.getAmount()); } // Break break; // Locked case Transaction.STATUS_LOCKED: // Subtract updated transaction's amount from locked amount change lockedAmountChange = lockedAmountChange.minus(updatedTransaction.getAmount()); // Break break; } // Check if updated transaction is being changed to spent if(spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_SPENT_INDEX].indexOf(updatedTransaction.getKeyPath()) !== Common.INDEX_NOT_FOUND) { // Set updated transaction's status to spent updatedTransaction.setStatus(Transaction.STATUS_SPENT); // Set updated transaction's amount has been released updatedTransaction.setAmountReleased(true); // Add updated transaction's amount to spent amount change spentAmountChange = spentAmountChange.plus(updatedTransaction.getAmount()); } // Otherwise check if updated transaction is being changed to unspent else if(spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_UNSPENT_INDEX].indexOf(updatedTransaction.getKeyPath()) !== Common.INDEX_NOT_FOUND) { // Set updated transaction's status to unspent updatedTransaction.setStatus(Transaction.STATUS_UNSPENT); // Check if the updated transaction's spendable height is the next block if(updatedTransaction.getSpendableHeight() !== Transaction.UNKNOWN_SPENDABLE_HEIGHT && updatedTransaction.getSpendableHeight().isLessThanOrEqualTo(tipHeight.getHeight().plus(1)) === true) { // Set updated transaction's amount has been released updatedTransaction.setAmountReleased(true); // Add updated transaction's amount to unspent amount change unspentAmountChange = unspentAmountChange.plus(updatedTransaction.getAmount()); } // Otherwise else { // Set updated transaction's amount hasn't been released updatedTransaction.setAmountReleased(false); // Add updated transaction's amount to pending amount change pendingAmountChange = pendingAmountChange.plus(updatedTransaction.getAmount()); } } // Otherwise check if updated transaction is being changed to locked else if(spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_LOCKED_INDEX].indexOf(updatedTransaction.getKeyPath()) !== Common.INDEX_NOT_FOUND) { // Set updated transaction's status to locked updatedTransaction.setStatus(Transaction.STATUS_LOCKED); // Set updated transaction's amount has been released updatedTransaction.setAmountReleased(true); // Add updated transaction's amount to locked amount change lockedAmountChange = lockedAmountChange.plus(updatedTransaction.getAmount()); } // Remove spent output to change from list spentOutputsToChange.splice(spentOutputsToChange.indexOf(updatedTransaction.getKeyPath()), 1); } } // Return getting spent transactions return self.transactions.getTransactions(spentOutputsToChange).then(function(spentTransactions) { // Initializing verifying spent transactions var verifyingSpentTransactions = []; // Go through all spent transactions for(let k = 0; k < spentTransactions["length"]; ++k) { // Append verifying spent transaction to list verifyingSpentTransactions.push(new Promise(function(resolve, reject) { // Get spent transaction var spentTransaction = spentTransactions[k]; // Set transaction changed var transactionChanged = false; // Check if spent transaction is being changed to spent if(spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_SPENT_INDEX].indexOf(spentTransaction.getKeyPath()) !== Common.INDEX_NOT_FOUND) { // Check spent transaction's status switch(spentTransaction.getStatus()) { // Unconfirmed case Transaction.STATUS_UNCONFIRMED: // Check if spent transaction is expired if(spentTransaction.getExpired() === true) { // Check if spent transaction isn't change output if(spentTransaction.getDisplay() === true) { // Subtract spent transaction's amount from expired amount change expiredAmountChange = expiredAmountChange.minus(spentTransaction.getAmount()); } } // Otherwise else { // Subtract spent transaction's amount from unconfirmed amount change unconfirmedAmountChange = unconfirmedAmountChange.minus(spentTransaction.getAmount()); } // Add spent transaction's amount to spent amount change spentAmountChange = spentAmountChange.plus(spentTransaction.getAmount()); // Break break; // Unspent case Transaction.STATUS_UNSPENT: // Check if spent transaction's amount has been released if(spentTransaction.getAmountReleased() === true) { // Subtract spent transaction's amount from unspent amount change unspentAmountChange = unspentAmountChange.minus(spentTransaction.getAmount()); } // Otherwis else { // Subtract spent transaction's amount from pending amount change pendingAmountChange = pendingAmountChange.minus(spentTransaction.getAmount()); } // Add spent transaction's amount to spent amount change spentAmountChange = spentAmountChange.plus(spentTransaction.getAmount()); // Break break; // Locked case Transaction.STATUS_LOCKED: // Subtract spent transaction's amount from locked amount change lockedAmountChange = lockedAmountChange.minus(spentTransaction.getAmount()); // Add spent transaction's amount to spent amount change spentAmountChange = spentAmountChange.plus(spentTransaction.getAmount()); // Break break; } // Check if spent transaction's amount released needs to be updated if(spentTransaction.getAmountReleased() === false) { // Set spent transaction's amount has been released spentTransaction.setAmountReleased(true); // Set transaction changed transactionChanged = true; } // Check if spent transaction's expired needs to be updated if(spentTransaction.getExpired() === true) { // Set spent transaction's expired spentTransaction.setExpired(false); // Set transaction changed transactionChanged = true; } // Check if spent transaction's broadcast needs to be updated if(spentTransaction.getBroadcast() === false) { // Set spent transaction's broadcast spentTransaction.setBroadcast(true); // Set transaction changed transactionChanged = true; } // Check if spent transaction's status needs to be updated if(spentTransaction.getStatus() !== Transaction.STATUS_SPENT) { // Set spent transaction's status to spent spentTransaction.setStatus(Transaction.STATUS_SPENT); // Set transaction changed transactionChanged = true; } // Check if spent transaction's checked needs to be updated if(spentTransaction.getChecked() === false) { // Set spent transaction's checked spentTransaction.setChecked(true); // Set transaction changed transactionChanged = true; } // Check if transaction changed if(transactionChanged === true) { // Append spent transaction to list of updated transactions updatedTransactions.push(spentTransaction); } // Resolve resolve(); } // Otherwise check if spent transaction is being changed to unspent else if(spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_UNSPENT_INDEX].indexOf(spentTransaction.getKeyPath()) !== Common.INDEX_NOT_FOUND) { // Return getting node's output for the spent transaction return self.node.getOutputs([spentTransaction.getCommit()]).then(function(outputs) { // Get output var output = outputs[0]; // Get wallet owns output var getWalletOwnsOutput = function() { // Return promise return new Promise(function(resolve, reject) { // Check if output was found if(output !== Node.NO_OUTPUT_FOUND) { // Return getting if wallet owns output return wallet.ownsOutput(new Output(output["commit"], output["proof"], output["output_type"], output["block_height"])).then(function(outputInformation) { // Check if output information exists if(outputInformation !== Output.NO_INFORMATION) { // Resolve true resolve(true); } // Otherwise else { // Resolve false resolve(false); } // Catch errors }).catch(function(error) { // Reject error reject(error); }); } // Otherwise else { // Resolve false resolve(false); } }); }; // Return getting if the wallet owns the output return getWalletOwnsOutput().then(function(walletOwnsOutput) { // Check if wallet owns output if(walletOwnsOutput === true) { // Check if spent transaction's is coinbase needs to be updated if(spentTransaction.getIsCoinbase() !== (output["output_type"] === Output.COINBASE_TYPE)) { // Set sent transaction's is coinbase spentTransaction.setIsCoinbase(output["output_type"] === Output.COINBASE_TYPE); // Set transaction changed transactionChanged = true; } // Check if spent transaction's height needs to be updated if(spentTransaction.getHeight() === Transaction.UNKNOWN_HEIGHT || spentTransaction.getHeight().isEqualTo(output["block_height"]) === false) { // Set spent transaction's height spentTransaction.setHeight(output["block_height"]); // Set transaction changed transactionChanged = true; } // Get new spendable height as the output height added to the spent transaction's number of confirmations var newSpendableHeight = output["block_height"].plus(spentTransaction.getRequiredNumberOfConfirmations().minus(1)); // Check if maturity height is greater than the new spendable height if(output["block_height"].plus((spentTransaction.getIsCoinbase() === true) ? Consensus.COINBASE_MATURITY - 1 : 0).isGreaterThan(newSpendableHeight) === true) { // Set the new spendable height to the maturity height newSpendableHeight = output["block_height"].plus((spentTransaction.getIsCoinbase() === true) ? Consensus.COINBASE_MATURITY - 1 : 0); } // Check if spent transaction's lock height exists and if it added to the number of confirmation is greater than the new spendable height if(spentTransaction.getLockHeight() !== Transaction.UNKNOWN_LOCK_HEIGHT && spentTransaction.getLockHeight() !== Transaction.NO_LOCK_HEIGHT && spentTransaction.getLockHeight().plus(spentTransaction.getRequiredNumberOfConfirmations().minus(1)).isGreaterThan(newSpendableHeight) === true) { // Set the new spendable height to the spent transaction's lock height added to the number of confirmation newSpendableHeight = spentTransaction.getLockHeight().plus(spentTransaction.getRequiredNumberOfConfirmations().minus(1)); } // Check spent transaction's status switch(spentTransaction.getStatus()) { // Spent case Transaction.STATUS_SPENT: // Subtract spent transaction's amount from spent amount change spentAmountChange = spentAmountChange.minus(spentTransaction.getAmount()); // Check if the spent transaction's new spendable height is the next block if(newSpendableHeight.isLessThanOrEqualTo(tipHeight.getHeight().plus(1)) === true) { // Add spent transaction's amount to unspent amount change unspentAmountChange = unspentAmountChange.plus(spentTransaction.getAmount()); } // Otherwise else { // Add spent transaction's amount to pending amount change pendingAmountChange = pendingAmountChange.plus(spentTransaction.getAmount()); } // Break break; // Unconfirmed case Transaction.STATUS_UNCONFIRMED: // Check if spent transaction is expired if(spentTransaction.getExpired() === true) { // Check if spent transaction isn't change output if(spentTransaction.getDisplay() === true) { // Subtract spent transaction's amount from expired amount change expiredAmountChange = expiredAmountChange.minus(spentTransaction.getAmount()); } } // Otherwise else { // Subtract spent transaction's amount from unconfirmed amount change unconfirmedAmountChange = unconfirmedAmountChange.minus(spentTransaction.getAmount()); } // Check if the spent transaction's new spendable height is the next block if(newSpendableHeight.isLessThanOrEqualTo(tipHeight.getHeight().plus(1)) === true) { // Add spent transaction's amount to unspent amount change unspentAmountChange = unspentAmountChange.plus(spentTransaction.getAmount()); } // Otherwise else { // Add spent transaction's amount to pending amount change pendingAmountChange = pendingAmountChange.plus(spentTransaction.getAmount()); } // Break break; // Unspent case Transaction.STATUS_UNSPENT: // Check if the spent transaction's new spendable height is the next block if(newSpendableHeight.isLessThanOrEqualTo(tipHeight.getHeight().plus(1)) === true) { // Check if spent transaction's amount hasn't been released if(spentTransaction.getAmountReleased() === false) { // Subtract spent transaction's amount from pending amount change pendingAmountChange = pendingAmountChange.minus(spentTransaction.getAmount()); // Add spent transaction's amount to unspent amount change unspentAmountChange = unspentAmountChange.plus(spentTransaction.getAmount()); } } // Otherwise else { // Check if spent transaction's amount has been released if(spentTransaction.getAmountReleased() === true) { // Subtract spent transaction's amount from unspent amount change unspentAmountChange = unspentAmountChange.minus(spentTransaction.getAmount()); // Add spent transaction's amount to pending amount change pendingAmountChange = pendingAmountChange.plus(spentTransaction.getAmount()); } } // Break break; // Locked case Transaction.STATUS_LOCKED: // Subtract spent transaction's amount from locked amount change lockedAmountChange = lockedAmountChange.minus(spentTransaction.getAmount()); // Check if the spent transaction's new spendable height is the next block if(newSpendableHeight.isLessThanOrEqualTo(tipHeight.getHeight().plus(1)) === true) { // Add spent transaction's amount to unspent amount change unspentAmountChange = unspentAmountChange.plus(spentTransaction.getAmount()); } // Otherwise else { // Add spent transaction's amount to pending amount change pendingAmountChange = pendingAmountChange.plus(spentTransaction.getAmount()); } // Break break; } // Check if the spent transaction's new spendable height is the next block if(newSpendableHeight.isLessThanOrEqualTo(tipHeight.getHeight().plus(1)) === true) { // Check if spent transaction's amount released needs to be updated if(spentTransaction.getAmountReleased() === false) { // Set spent transaction's amount has been released spentTransaction.setAmountReleased(true); // Set transaction changed transactionChanged = true; } } // Otherwise else { // Check if spent transaction's amount released needs to be updated if(spentTransaction.getAmountReleased() === true) { // Set spent transaction's amount hasn't been released spentTransaction.setAmountReleased(false); // Set transaction changed transactionChanged = true; } } // Check if spent transaction's expired needs to be updated if(spentTransaction.getExpired() === true) { // Set spent transaction's expired spentTransaction.setExpired(false); // Set transaction changed transactionChanged = true; } // Check if spent transaction's broadcast needs to be updated if(spentTransaction.getBroadcast() === false) { // Set spent transaction's broadcast spentTransaction.setBroadcast(true); // Set transaction changed transactionChanged = true; } // Check if spent transaction's status needs to be updated if(spentTransaction.getStatus() !== Transaction.STATUS_UNSPENT) { // Set spent transaction's status to unspent spentTransaction.setStatus(Transaction.STATUS_UNSPENT); // Set transaction changed transactionChanged = true; } // Check if spent transaction's spendable height needs to be updated if(spentTransaction.getSpendableHeight() === Transaction.UNKNOWN_SPENDABLE_HEIGHT || spentTransaction.getSpendableHeight().isEqualTo(newSpendableHeight) === false) { // Set spent transaction's spendable height spentTransaction.setSpendableHeight(newSpendableHeight); // Set transaction changed transactionChanged = true; } // Check if spent transaction's checked needs to be updated if(spentTransaction.getChecked() === false) { // Set spent transaction's checked spentTransaction.setChecked(true); // Set transaction changed transactionChanged = true; } // Return getting header at output's height return self.node.getHeader(output["block_height"]).then(function(header) { // Get timestamp var timestamp = header["timestamp"]; // Check if spent transaction's confirmed timestamp needs to be updated if(spentTransaction.getConfirmedTimestamp() !== timestamp) { // Set spent transaction's confirmed timestamp spentTransaction.setConfirmedTimestamp(timestamp); // Set transaction changed transactionChanged = true; } // Check if transaction changed if(transactionChanged === true) { // Append spent transaction to list of updated transactions updatedTransactions.push(spentTransaction); } // Resolve resolve(); // Catch errors }).catch(function(error) { // Reject error reject(error); }); } // Otherwise else { // Check spent transaction's status switch(spentTransaction.getStatus()) { // Spent case Transaction.STATUS_SPENT: // Subtract spent transaction's amount from spent amount change spentAmountChange = spentAmountChange.minus(spentTransaction.getAmount()); // Check if the spent transaction's is expired if(spentTransaction.getBroadcast() === false && spentTransaction.getTimeToLiveCutOffHeight() !== Transaction.UNKNOWN_TIME_TO_LIVE_CUT_OFF_HEIGHT && spentTransaction.getTimeToLiveCutOffHeight() !== Transaction.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT && spentTransaction.getTimeToLiveCutOffHeight().isLessThanOrEqualTo(tipHeight.getHeight()) === true) { // Check if spent transaction isn't change output if(spentTransaction.getDisplay() === true) { // Add spent transaction's amount to expired amount change expiredAmountChange = expiredAmountChange.plus(spentTransaction.getAmount()); } } // Otherwise else { // Add spent transaction's amount to unconfirmed amount change unconfirmedAmountChange = unconfirmedAmountChange.plus(spentTransaction.getAmount()); } // Break break; // Unconfirmed case Transaction.STATUS_UNCONFIRMED: // Check if the spent transaction's is expired if(spentTransaction.getBroadcast() === false && spentTransaction.getTimeToLiveCutOffHeight() !== Transaction.UNKNOWN_TIME_TO_LIVE_CUT_OFF_HEIGHT && spentTransaction.getTimeToLiveCutOffHeight() !== Transaction.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT && spentTransaction.getTimeToLiveCutOffHeight().isLessThanOrEqualTo(tipHeight.getHeight()) === true) { // Check if spent transaction isn't expired if(spentTransaction.getExpired() === false) { // Subtract spent transaction's amount from unconfirmed amount change unconfirmedAmountChange = unconfirmedAmountChange.minus(spentTransaction.getAmount()); // Check if spent transaction isn't change output if(spentTransaction.getDisplay() === true) { // Add spent transaction's amount to expired amount change expiredAmountChange = expiredAmountChange.plus(spentTransaction.getAmount()); } } } // Otherwise else { // Check if spent transaction is expired if(spentTransaction.getExpired() === true) { // Check if spent transaction isn't change output if(spentTransaction.getDisplay() === true) { // Subtract spent transaction's amount from expired amount change expiredAmountChange = expiredAmountChange.minus(spentTransaction.getAmount()); } // Add spent transaction's amount to unconfirmed amount change unconfirmedAmountChange = unconfirmedAmountChange.plus(spentTransaction.getAmount()); } } // Break break; // Unspent case Transaction.STATUS_UNSPENT: // Check if spent transaction's amount has been released if(spentTransaction.getAmountReleased() === true) { // Subtract spent transaction's amount from unspent amount change unspentAmountChange = unspentAmountChange.minus(spentTransaction.getAmount()); } // Otherwis else { // Subtract spent transaction's amount from pending amount change pendingAmountChange = pendingAmountChange.minus(spentTransaction.getAmount()); } // Check if the spent transaction's is expired if(spentTransaction.getBroadcast() === false && spentTransaction.getTimeToLiveCutOffHeight() !== Transaction.UNKNOWN_TIME_TO_LIVE_CUT_OFF_HEIGHT && spentTransaction.getTimeToLiveCutOffHeight() !== Transaction.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT && spentTransaction.getTimeToLiveCutOffHeight().isLessThanOrEqualTo(tipHeight.getHeight()) === true) { // Check if spent transaction isn't change output if(spentTransaction.getDisplay() === true) { // Add spent transaction's amount to expired amount change expiredAmountChange = expiredAmountChange.plus(spentTransaction.getAmount()); } } // Otherwise else { // Add spent transaction's amount to unconfirmed amount change unconfirmedAmountChange = unconfirmedAmountChange.plus(spentTransaction.getAmount()); } // Break break; // Locked case Transaction.STATUS_LOCKED: // Subtract spent transaction's amount from locked amount change lockedAmountChange = lockedAmountChange.minus(spentTransaction.getAmount()); // Check if the spent transaction's is expired if(spentTransaction.getBroadcast() === false && spentTransaction.getTimeToLiveCutOffHeight() !== Transaction.UNKNOWN_TIME_TO_LIVE_CUT_OFF_HEIGHT && spentTransaction.getTimeToLiveCutOffHeight() !== Transaction.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT && spentTransaction.getTimeToLiveCutOffHeight().isLessThanOrEqualTo(tipHeight.getHeight()) === true) { // Check if spent transaction isn't change output if(spentTransaction.getDisplay() === true) { // Add spent transaction's amount to expired amount change expiredAmountChange = expiredAmountChange.plus(spentTransaction.getAmount()); } } // Otherwise else { // Add spent transaction's amount to unconfirmed amount change unconfirmedAmountChange = unconfirmedAmountChange.plus(spentTransaction.getAmount()); } // Break break; } // Check if spent transaction's amount released needs to be updated if(spentTransaction.getAmountReleased() === true) { // Set spent transaction's amount released spentTransaction.setAmountReleased(false); // Set transaction changed transactionChanged = true; } // Check if the spent transaction's is expired if(spentTransaction.getBroadcast() === false && spentTransaction.getTimeToLiveCutOffHeight() !== Transaction.UNKNOWN_TIME_TO_LIVE_CUT_OFF_HEIGHT && spentTransaction.getTimeToLiveCutOffHeight() !== Transaction.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT && spentTransaction.getTimeToLiveCutOffHeight().isLessThanOrEqualTo(tipHeight.getHeight()) === true) { // Check if spent transaction's expired needs to be updated if(spentTransaction.getExpired() === false) { // Set that spent transaction's is expired spentTransaction.setExpired(true); // Set transaction changed transactionChanged = true; } } // Otherwise else { // Check if spent transaction's expired needs to be updated if(spentTransaction.getExpired() === true) { // Set that spent transaction's isn't expired spentTransaction.setExpired(false); // Set transaction changed transactionChanged = true; } } // Check if spent transaction's status needs to be updated if(spentTransaction.getStatus() !== Transaction.STATUS_UNCONFIRMED) { // Set spent transaction's status to unconfirmed spentTransaction.setStatus(Transaction.STATUS_UNCONFIRMED); // Set transaction changed transactionChanged = true; } // Check if spent transaction's confirmed timestamp needs to be updated if(spentTransaction.getConfirmedTimestamp() !== Transaction.NO_CONFIRMED_TIMESTAMP) { // Set spent transaction's confirmed timestamp spentTransaction.setConfirmedTimestamp(Transaction.NO_CONFIRMED_TIMESTAMP); // Set transaction changed transactionChanged = true; } // Check if spent transaction's checked needs to be updated if(spentTransaction.getChecked() === false) { // Set spent transaction's checked spentTransaction.setChecked(true); // Set transaction changed transactionChanged = true; } // Check if transaction changed if(transactionChanged === true) { // Append spent transaction to list of updated transactions updatedTransactions.push(spentTransaction); } // Resolve resolve(); } // Catch errors }).catch(function(error) { // Reject error reject(error); }); // Catch errors }).catch(function(error) { // Reject error reject(error); }); } // Otherwise check if spent transaction is being changed to locked else if(spentOutputs[Wallets.SPENT_OUTPUTS_CHANGE_TO_LOCKED_INDEX].indexOf(spentTransaction.getKeyPath()) !== Common.INDEX_NOT_FOUND) { // Check spent transaction's status switch(spentTransaction.getStatus()) { // Spent case Transaction.STATUS_SPENT: // Subtract spent transaction's amount from spent amount change spentAmountChange = spentAmountChange.minus(spentTransaction.getAmount()); // Add spent transaction's amount to locked amount change lockedAmountChange = lockedAmountChange.plus(spentTransaction.getAmount()); // Break break; // Unconfirmed case Transaction.STATUS_UNCONFIRMED: // Check if spent transaction is expired if(spentTransaction.getExpired() === true) { // Check if spent transaction isn't change output if(spentTransaction.getDisplay() === true) { // Subtract spent transaction's amount from expired amount change expiredAmountChange = expiredAmountChange.minus(spentTransaction.getAmount()); } } // Otherwise else { // Subtract spent transaction's amount from unconfirmed amount change unconfirmedAmountChange = unconfirmedAmountChange.minus(spentTransaction.getAmount()); } // Add spent transaction's amount to locked amount change lockedAmountChange = lockedAmountChange.plus(spentTransaction.getAmount()); // Break break; // Unspent case Transaction.STATUS_UNSPENT: // Check if spent transaction's amount has been released if(spentTransaction.getAmountReleased() === true) { // Subtract spent transaction's amount from unspent amount change unspentAmountChange = unspentAmountChange.minus(spentTransaction.getAmount()); } // Otherwis else { // Subtract spent transaction's amount from pending amount change pendingAmountChange = pendingAmountChange.minus(spentTransaction.getAmount()); } // Add spent transaction's amount to locked amount change lockedAmountChange = lockedAmountChange.plus(spentTransaction.getAmount()); // Break break; } // Check if spent transaction's amount released needs to be updated if(spentTransaction.getAmountReleased() === false) { // Set spent transaction's amount has been released spentTransaction.setAmountReleased(true); // Set transaction changed transactionChanged = true; } // Check if spent transaction's expired needs to be updated if(spentTransaction.getExpired() === true) { // Set spent transaction's expired spentTransaction.setExpired(false); // Set transaction changed transactionChanged = true; } // Check if spent transaction's broadcast needs to be updated if(spentTransaction.getBroadcast() === false) { // Set spent transaction's broadcast spentTransaction.setBroadcast(true); // Set transaction changed transactionChanged = true; } // Check if spent transaction's status needs to be updated if(spentTransaction.getStatus() !== Transaction.STATUS_LOCKED) { // Set spent transaction's status to locked spentTransaction.setStatus(Transaction.STATUS_LOCKED); // Set transaction changed transactionChanged = true; } // Check if spent transaction's checked needs to be updated if(spentTransaction.getChecked() === false) { // Set spent transaction's checked spentTransaction.setChecked(true); // Set transaction changed transactionChanged = true; } // Check if transaction changed if(transactionChanged === true) { // Append spent transaction to list of updated transactions updatedTransactions.push(spentTransaction); } // Resolve resolve(); } // Otherwise else { // Check if spent transaction's checked needs to be updated if(spentTransaction.getChecked() === false) { // Set spent transaction's checked spentTransaction.setChecked(true); // Set transaction changed transactionChanged = true; } // Check if transaction changed if(transactionChanged === true) { // Append spent transaction to list of updated transactions updatedTransactions.push(spentTransaction); } // Resolve resolve(); } })); } // Return verifying spent transactions return Promise.all(verifyingSpentTransactions).then(function() { // Initialize verifying pending transactions var verifyingPendingTransactions = []; // Go through all groups of pending transactions for(let k = 0; k < pendingTransactions["length"]; k += Wallets.VERIFYING_OUTPUTS_GROUP_SIZE) { // Append verifying group of pending transactions to list verifyingPendingTransactions.push(new Promise(function(resolve, reject) { // Return getting node's outputs for the group of pending transaction return self.node.getOutputs(pendingTransactions.slice(k, k + Wallets.VERIFYING_OUTPUTS_GROUP_SIZE).map(function(pendingTransaction) { // Return pending transaction's commit return pendingTransaction.getCommit(); })).then(function(outputs) { // Initialize verifying pending transactions group var verifyingPendingTransactionsGroup = []; // Go through all outputs for(let l = 0; l < outputs["length"]; ++l) { // Append verifying pending transaction group to list verifyingPendingTransactionsGroup.push(new Promise(function(resolve, reject) { // Get output var output = outputs[l]; // Get pending transaction var pendingTransaction = pendingTransactions[k + l]; // Get wallet owns output var getWalletOwnsOutput = function() { // Return promise return new Promise(function(resolve, reject) { // Check if output was found if(output !== Node.NO_OUTPUT_FOUND) { // Return getting if wallet owns output return wallet.ownsOutput(new Output(output["commit"], output["proof"], output["output_type"], output["block_height"])).then(function(outputInformation) { // Check if output information exists if(outputInformation !== Output.NO_INFORMATION) { // Resolve true resolve(true); } // Otherwise else { // Resolve false resolve(false); } // Catch errors }).catch(function(error) { // Reject error reject(error); }); } // Otherwise else { // Resolve false resolve(false); } }); }; // Return getting if the wallet owns the output return getWalletOwnsOutput().then(function(walletOwnsOutput) { // Set transaction changed var transactionChanged = false; // Check if wallet owns output if(walletOwnsOutput === true) { // Check if pending transaction's is coinbase needs to be updated if(pendingTransaction.getIsCoinbase() !== (output["output_type"] === Output.COINBASE_TYPE)) { // Set pending transaction's is coinbase pendingTransaction.setIsCoinbase(output["output_type"] === Output.COINBASE_TYPE); // Set transaction changed transactionChanged = true; } // Check if pending transaction's height needs to be updated if(pendingTransaction.getHeight() === Transaction.UNKNOWN_HEIGHT || pendingTransaction.getHeight().isEqualTo(output["block_height"]) === false) { // Set pending transaction's height pendingTransaction.setHeight(output["block_height"]); // Set transaction changed transactionChanged = true; } // Check if pending transaction's broadcast needs to be updated if(pendingTransaction.getBroadcast() === false) { // Set pending transaction's broadcast pendingTransaction.setBroadcast(true); // Set transaction changed transactionChanged = true; } // Get new spendable height as the output height added to the pending transaction's number of confirmations var newSpendableHeight = output["block_height"].plus(pendingTransaction.getRequiredNumberOfConfirmations().minus(1)); // Check if maturity height is greater than the new spendable height if(output["block_height"].plus((pendingTransaction.getIsCoinbase() === true) ? Consensus.COINBASE_MATURITY - 1 : 0).isGreaterThan(newSpendableHeight) === true) { // Set the new spendable height to the maturity height newSpendableHeight = output["block_height"].plus((pendingTransaction.getIsCoinbase() === true) ? Consensus.COINBASE_MATURITY - 1 : 0); } // Check if pending transaction's lock height exists and if it added to the number of confirmation is greater than the new spendable height if(pendingTransaction.getLockHeight() !== Transaction.UNKNOWN_LOCK_HEIGHT && pendingTransaction.getLockHeight() !== Transaction.NO_LOCK_HEIGHT && pendingTransaction.getLockHeight().plus(pendingTransaction.getRequiredNumberOfConfirmations().minus(1)).isGreaterThan(newSpendableHeight) === true) { // Set the new spendable height to the pending transaction's lock height added to the number of confirmation newSpendableHeight = pendingTransaction.getLockHeight().plus(pendingTransaction.getRequiredNumberOfConfirmations().minus(1)); } // Check pending transaction's status switch(pendingTransaction.getStatus()) { // Spent case Transaction.STATUS_SPENT: // Revert pending transaction's status to locked since transaction wasn't completed yet pendingTransaction.setStatus(Transaction.STATUS_LOCKED); // Add pending transaction's amount to locked amount change lockedAmountChange = lockedAmountChange.plus(pendingTransaction.getAmount()); // Subtract pending transaction's amount from spent amount change spentAmountChange = spentAmountChange.minus(pendingTransaction.getAmount()); // Set transaction changed transactionChanged = true; // Break break; // Unconfirmed case Transaction.STATUS_UNCONFIRMED: // Update pending transaction's status to unspent since transaction is confirmed on the chain pendingTransaction.setStatus(Transaction.STATUS_UNSPENT); // Check if the pending transaction's new spendable height is the next block if(newSpendableHeight.isLessThanOrEqualTo(tipHeight.getHeight().plus(1)) === true) { // Check if the pending transaction's amount hasn't been released if(pendingTransaction.getAmountReleased() === false) { // Set pending transaction amount has been released pendingTransaction.setAmountReleased(true); // Add pending transaction's amount to unspent amount change unspentAmountChange = unspentAmountChange.plus(pendingTransaction.getAmount()); } } // Otherwise else { // Add pending transaction's amount to pending amount change pendingAmountChange = pendingAmountChange.plus(pendingTransaction.getAmount()); // Check if the pending transaction's amount has been released if(pendingTransaction.getAmountReleased() === true) { // Set pending transaction amount hasn't been released pendingTransaction.setAmountReleased(false); // Subtract pending transaction's amount from unspent amount change unspentAmountChange = unspentAmountChange.minus(pendingTransaction.getAmount()); } } // Check if pending transaction isn't canceled and expired if(pendingTransaction.getCanceled() === false && pendingTransaction.getExpired() === false) { // Subtract pending transaction's amount from unconfirmed amount change unconfirmedAmountChange = unconfirmedAmountChange.minus(pendingTransaction.getAmount()); } // Set transaction changed transactionChanged = true; // Break break; // Unspent case Transaction.STATUS_UNSPENT: // Check if pending transaction's amount hasn't been released and the pending transaction's new spendable height is the next block if(pendingTransaction.getAmountReleased() === false && newSpendableHeight.isLessThanOrEqualTo(tipHeight.getHeight().plus(1)) === true) { // Set pending transaction amount has been released pendingTransaction.setAmountReleased(true); // Add pending transaction's amount to unspent amount change unspentAmountChange = unspentAmountChange.plus(pendingTransaction.getAmount()); // Subtract pending transaction's amount from pending amount change pendingAmountChange = pendingAmountChange.minus(pendingTransaction.getAmount()); // Set transaction changed transactionChanged = true; } // Otherwise check if pending transaction's amount has been released and the pending transaction's new spendable height isn't the next block else if(pendingTransaction.getAmountReleased() === true && newSpendableHeight.isGreaterThan(tipHeight.getHeight().plus(1)) === true) { // Set pending transaction amount hasn't been released pendingTransaction.setAmountReleased(false); // Subtract pending transaction's amount from unspent amount change unspentAmountChange = unspentAmountChange.minus(pendingTransaction.getAmount()); // Add pending transaction's amount to pending amount change pendingAmountChange = pendingAmountChange.plus(pendingTransaction.getAmount()); // Set transaction changed transactionChanged = true; } // Break break; } // Check if pending transaction's spendable height needs to be updated if(pendingTransaction.getSpendableHeight() === Transaction.UNKNOWN_SPENDABLE_HEIGHT || pendingTransaction.getSpendableHeight().isEqualTo(newSpendableHeight) === false) { // Set pending transaction's spendable height pendingTransaction.setSpendableHeight(newSpendableHeight); // Set transaction changed transactionChanged = true; } // Check if pending transaction's canceled needs to be updated if(pendingTransaction.getCanceled() === true) { // Set pending transaction's canceled pendingTransaction.setCanceled(false); // Set transaction changed transactionChanged = true; } // Check if pending transaction's expired needs to be updated if(pendingTransaction.getExpired() === true) { // Set pending transaction's expired pendingTransaction.setExpired(false); // Check if pending transaction isn't change output if(pendingTransaction.getDisplay() === true) { // Subtract pending transaction's amount from expired amount change expiredAmountChange = expiredAmountChange.minus(pendingTransaction.getAmount()); } // Set transaction changed transactionChanged = true; } // Check if pending transaction's checked needs to be updated if(pendingTransaction.getChecked() === false) { // Set pending transaction's checked pendingTransaction.setChecked(true); // Set transaction changed transactionChanged = true; } // Return getting header at output's height return self.node.getHeader(output["block_height"]).then(function(header) { // Get timestamp var timestamp = header["timestamp"]; // Check if pending transaction's confirmed timestamp needs to be updated if(pendingTransaction.getConfirmedTimestamp() !== timestamp) { // Set pending transaction's confirmed timestamp pendingTransaction.setConfirmedTimestamp(timestamp); // Set transaction changed transactionChanged = true; } // Check if transaction changed if(transactionChanged === true) { // Append pending transaction to list of updated transactions updatedTransactions.push(pendingTransaction); } // Resolve resolve(); // Catch errors }).catch(function(error) { // Reject error reject(error); }); } // Otherwise else { // Check pending transaction's status switch(pendingTransaction.getStatus()) { // Spent case Transaction.STATUS_SPENT: // Check if pending transaction's checked needs to be updated if(pendingTransaction.getChecked() === false) { // Set pending transaction's checked pendingTransaction.setChecked(true); // Set transaction changed transactionChanged = true; } // Check if pending transaction's height is known if(pendingTransaction.getHeight() !== Transaction.UNKNOWN_HEIGHT) { // Return getting header at pending transaction's height return self.node.getHeader(pendingTransaction.getHeight()).then(function(header) { // Get timestamp var timestamp = header["timestamp"]; // Check if pending transaction's confirmed timestamp needs to be updated if(pendingTransaction.getConfirmedTimestamp() !== timestamp) { // Set pending transaction's confirmed timestamp pendingTransaction.setConfirmedTimestamp(timestamp); // Set transaction changed transactionChanged = true; } // Check if transaction changed if(transactionChanged === true) { // Append pending transaction to list of updated transactions updatedTransactions.push(pendingTransaction); } // Resolve resolve(); // Catch errors }).catch(function(error) { // Reject error reject(error); }); } // Otherwise else { // Check if transaction changed if(transactionChanged === true) { // Append pending transaction to list of updated transactions updatedTransactions.push(pendingTransaction); } // Resolve resolve(); } // Break break; // Unconfirmed case Transaction.STATUS_UNCONFIRMED: // Check if pending transaction isn't already expired and its time to live cut off height has past if(pendingTransaction.getExpired() === false && pendingTransaction.getBroadcast() === false && pendingTransaction.getTimeToLiveCutOffHeight() !== Transaction.UNKNOWN_TIME_TO_LIVE_CUT_OFF_HEIGHT && pendingTransaction.getTimeToLiveCutOffHeight() !== Transaction.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT && pendingTransaction.getTimeToLiveCutOffHeight().isLessThanOrEqualTo(tipHeight.getHeight()) === true) { // Set that pending transaction is expired pendingTransaction.setExpired(true); // Check if pending transaction isn't a change output if(pendingTransaction.getDisplay() === true) { // Add pending transaction's amount to expired amount change expiredAmountChange = expiredAmountChange.plus(pendingTransaction.getAmount()); } // Subtract pending transaction's amount from unconfirmed amount change unconfirmedAmountChange = unconfirmedAmountChange.minus(pendingTransaction.getAmount()); // Set transaction changed transactionChanged = true; } // Check if pending transaction's checked needs to be updated if(pendingTransaction.getChecked() === false) { // Set pending transaction's checked pendingTransaction.setChecked(true); // Set transaction changed transactionChanged = true; } // Check if transaction changed if(transactionChanged === true) { // Append pending transaction to list of updated transactions updatedTransactions.push(pendingTransaction); } // Resolve resolve(); // Break break; // Unspent case Transaction.STATUS_UNSPENT: // Set get transaction's kernel var getTransactionsKernel = new Promise(function(resolve, reject) { // Check if pending transaction has a known kernel excess if(pendingTransaction.getKernelExcess() !== Transaction.UNKNOWN_KERNEL_EXCESS) { // Check if pending transaction's height exists if(pendingTransaction.getHeight() !== Transaction.UNKNOWN_HEIGHT) { // Set kernel minimum height var kernelMinimumHeight = pendingTransaction.getHeight().minus(Wallets.VARIATION_FROM_PREVIOUS_BLOCK_HEIGHT); // Set kernel maximum height var kernelMaximumHeight = pendingTransaction.getHeight().plus(Wallets.VARIATION_TO_NEXT_BLOCK_HEIGHT); } // Otherwise else { // Set kernel minimum height var kernelMinimumHeight = startHeight.minus(Wallets.VARIATION_FROM_PREVIOUS_BLOCK_HEIGHT); // Set kernel maximum height var kernelMaximumHeight = highestSyncedHeight.plus(Wallets.VARIATION_TO_NEXT_BLOCK_HEIGHT); } // Check if kernel minimum height is less than the first block height if(kernelMinimumHeight.isLessThan(Consensus.FIRST_BLOCK_HEIGHT) === true) { // Set kernel minimum height to the first block height kernelMinimumHeight = new BigNumber(Consensus.FIRST_BLOCK_HEIGHT); } // Check if kernel maximum height exceeds the tip height if(kernelMaximumHeight.isGreaterThan(tipHeight.getHeight()) === true) { // Set kernel maximum height to the tip height kernelMaximumHeight = tipHeight.getHeight(); } // Return getting pending transaction's kernel in the range around its height return self.node.getKernel(pendingTransaction.getKernelExcess(), kernelMinimumHeight, kernelMaximumHeight).then(function(kernel) { // Resolve kernel resolve(kernel); // Catch errors }).catch(function(error) { // Reject error reject(error); }); } // Otherwise else { // Resolve no kernel found resolve(Node.NO_KERNEL_FOUND); } }); // Return getting transaction's kernel return getTransactionsKernel.then(function(kernel) { // Check if pending transaction's amount has been released if(pendingTransaction.getAmountReleased() === true) { // Subtract pending transaction's amount from unspent amount change unspentAmountChange = unspentAmountChange.minus(pendingTransaction.getAmount()); } // Otherwise else { // Subtract pending transaction's amount from pending amount change pendingAmountChange = pendingAmountChange.minus(pendingTransaction.getAmount()); } // Check if kernel exists if(kernel !== Node.NO_KERNEL_FOUND) { // Update pending transaction's status to spent since it was confirmed on the chain previously pendingTransaction.setStatus(Transaction.STATUS_SPENT); // Add pending transaction's amount to spent amount change spentAmountChange = spentAmountChange.plus(pendingTransaction.getAmount()); // Set pending transaction amount has been released pendingTransaction.setAmountReleased(true); // Set pending transaction isn't expired pendingTransaction.setExpired(false); // Set pending transaction isn't canceled pendingTransaction.setCanceled(false); // Set pending transaction is broadcast pendingTransaction.setBroadcast(true); // Update pending transaction's height to the kernel's height pendingTransaction.setHeight(kernel["height"]); // Update pending transaction's is coinbase to kernel's features pendingTransaction.setIsCoinbase(SlateKernel.textToFeatures(Object.keys(kernel["tx_kernel"]["features"])[0]) === SlateKernel.COINBASE_FEATURES); // Get new spendable height as the kernel's height added to the pending transaction's number of confirmations var newSpendableHeight = kernel["height"].plus(pendingTransaction.getRequiredNumberOfConfirmations().minus(1)); // Check if maturity height is greater than the new spendable height if(kernel["height"].plus((pendingTransaction.getIsCoinbase() === true) ? Consensus.COINBASE_MATURITY - 1 : 0).isGreaterThan(newSpendableHeight) === true) { // Set the new spendable height to the maturity height newSpendableHeight = kernel["height"].plus((pendingTransaction.getIsCoinbase() === true) ? Consensus.COINBASE_MATURITY - 1 : 0); } // Check if pending transaction's lock height exists and if it added to the number of confirmation is greater than the new spendable height if(pendingTransaction.getLockHeight() !== Transaction.UNKNOWN_LOCK_HEIGHT && pendingTransaction.getLockHeight() !== Transaction.NO_LOCK_HEIGHT && pendingTransaction.getLockHeight().plus(pendingTransaction.getRequiredNumberOfConfirmations().minus(1)).isGreaterThan(newSpendableHeight) === true) { // Set the new spendable height to the pending transaction's lock height added to the number of confirmation newSpendableHeight = pendingTransaction.getLockHeight().plus(pendingTransaction.getRequiredNumberOfConfirmations().minus(1)); } // Update pending transaction's spendable height pendingTransaction.setSpendableHeight(newSpendableHeight); // Set pending transaction's checked pendingTransaction.setChecked(true); // Return getting header at kernel's height return self.node.getHeader(kernel["height"]).then(function(header) { // Get timestamp var timestamp = header["timestamp"]; // Set pending transaction's confirmed timestamp pendingTransaction.setConfirmedTimestamp(timestamp); // Append pending transaction to list of updated transactions updatedTransactions.push(pendingTransaction); // Resolve resolve(); // Catch errors }).catch(function(error) { // Reject error reject(error); }); } // Otherwise else { // Revert pending transaction's status to unconfirmed since transaction is no longer confirmed on the chain pendingTransaction.setStatus(Transaction.STATUS_UNCONFIRMED); // Set pending transaction amount hasn't been released pendingTransaction.setAmountReleased(false); // Set pending transaction's confirmed timestamp to no confirmed timestamp pendingTransaction.setConfirmedTimestamp(Transaction.NO_CONFIRMED_TIMESTAMP); // Check if pending transaction's time to live cut off height has past if(pendingTransaction.getBroadcast() === false && pendingTransaction.getTimeToLiveCutOffHeight() !== Transaction.UNKNOWN_TIME_TO_LIVE_CUT_OFF_HEIGHT && pendingTransaction.getTimeToLiveCutOffHeight() !== Transaction.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT && pendingTransaction.getTimeToLiveCutOffHeight().isLessThanOrEqualTo(tipHeight.getHeight()) === true) { // Set that pending transaction is expired pendingTransaction.setExpired(true); // Check if pending transaction isn't change output if(pendingTransaction.getDisplay() === true) { // Add pending transaction's amount to expired amount change expiredAmountChange = expiredAmountChange.plus(pendingTransaction.getAmount()); } } // Otherwise else { // Add pending transaction's amount to unconfirmed amount change unconfirmedAmountChange = unconfirmedAmountChange.plus(pendingTransaction.getAmount()); } // Set pending transaction's checked pendingTransaction.setChecked(true); // Append pending transaction to list of updated transactions updatedTransactions.push(pendingTransaction); // Resolve resolve(); } // Catch errors }).catch(function(error) { // Reject error reject(error); }); // Locked case Transaction.STATUS_LOCKED: // Update pending transaction's status to spent since transaction was completed pendingTransaction.setStatus(Transaction.STATUS_SPENT); // Add pending transaction's amount to spent amount change spentAmountChange = spentAmountChange.plus(pendingTransaction.getAmount()); // Subtract pending transaction's amount from locked amount change lockedAmountChange = lockedAmountChange.minus(pendingTransaction.getAmount()); // Set pending transaction's checked pendingTransaction.setChecked(true); // Append pending transaction to list of updated transactions updatedTransactions.push(pendingTransaction); // Resolve resolve(); // Break break; // Default default: // Check if pending transaction's checked needs to be updated if(pendingTransaction.getChecked() === false) { // Set pending transaction's checked pendingTransaction.setChecked(true); // Set transaction changed transactionChanged = true; } // Check if transaction changed if(transactionChanged === true) { // Append pending transaction to list of updated transactions updatedTransactions.push(pendingTransaction); } // Resolve resolve(); // Break break; } } // Catch errors }).catch(function(error) { // Reject error reject(error); }); })); } // Return verifying pending transactions group return Promise.all(verifyingPendingTransactionsGroup).then(function() { // Resolve resolve(); // Catch errors }).catch(function(error) { // Reject error reject(error); }); // Catch errors }).catch(function(error) { // Reject error reject(error); }); })); } // Return verifying pending transactions return Promise.all(verifyingPendingTransactions).then(function() { // Return creating a database transaction return Database.createTransaction([ // Wallets object store Wallets.OBJECT_STORE_NAME, // Transactions object store Transactions.OBJECT_STORE_NAME, ], Database.READ_AND_WRITE_MODE, Database.STRICT_DURABILITY).then(function(databaseTransaction) { // Return saving updated transactions return self.transactions.saveTransactions(updatedTransactions, databaseTransaction).then(function() { // Check if wallet exists if(self.walletExists(keyPath) === true) { // Return saving wallet return self.saveWallet(wallet, function() { // Get values var values = { // New locked amount value [Wallets.NEW_LOCKED_AMOUNT_VALUE]: wallet.getLockedAmount().plus(lockedAmountChange), // New unconfirmed amount value [Wallets.NEW_UNCONFIRMED_AMOUNT_VALUE]: wallet.getUnconfirmedAmount().plus(unconfirmedAmountChange), // New unspent amount value [Wallets.NEW_UNSPENT_AMOUNT_VALUE]: wallet.getUnspentAmount().plus(unspentAmountChange), // New spent amount value [Wallets.NEW_SPENT_AMOUNT_VALUE]: wallet.getSpentAmount().plus(spentAmountChange), // New pending amount value [Wallets.NEW_PENDING_AMOUNT_VALUE]: wallet.getPendingAmount().plus(pendingAmountChange), // New expired amount value [Wallets.NEW_EXPIRED_AMOUNT_VALUE]: wallet.getExpiredAmount().plus(expiredAmountChange) }; // Check if wallet's syncing status isn't resyncing if(wallet.getSyncingStatus() !== Wallet.STATUS_RESYNCING) { // New synced height value values[Wallets.NEW_SYNCED_HEIGHT_VALUE] = highestSyncedHeight; } // Check if highest identifier exists and the wallet's last identifier doesn't exist or doesn't include the highest identifier if(highestIdentifier !== Wallet.NO_LAST_IDENTIFIER && (wallet.getLastIdentifier() === Wallet.NO_LAST_IDENTIFIER || wallet.getLastIdentifier().includesValue(highestIdentifier) === false)) { // New last identifier value values[Wallets.NEW_LAST_IDENTIFIER_VALUE] = highestIdentifier; } // Return values return values; }, databaseTransaction).then(function(newValues) { // Check if wallet exists if(self.walletExists(keyPath) === true) { // Return committing database transaction return Database.commitTransaction(databaseTransaction).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Update wallet's locked amount wallet.setLockedAmount(newValues[Wallets.NEW_LOCKED_AMOUNT_VALUE]); // Update wallet's unconfirmed amount wallet.setUnconfirmedAmount(newValues[Wallets.NEW_UNCONFIRMED_AMOUNT_VALUE]); // Update wallet's unspent amount wallet.setUnspentAmount(newValues[Wallets.NEW_UNSPENT_AMOUNT_VALUE]); // Update wallet's spent amount wallet.setSpentAmount(newValues[Wallets.NEW_SPENT_AMOUNT_VALUE]); // Update wallet's pending amount wallet.setPendingAmount(newValues[Wallets.NEW_PENDING_AMOUNT_VALUE]); // Update wallet's expired amount wallet.setExpiredAmount(newValues[Wallets.NEW_EXPIRED_AMOUNT_VALUE]); // Check if wallet's synced height changed and its syncing status isn't resyncing if(Wallets.NEW_SYNCED_HEIGHT_VALUE in newValues === true && wallet.getSyncingStatus() !== Wallet.STATUS_RESYNCING) { // Update wallet's synced height wallet.setSyncedHeight(newValues[Wallets.NEW_SYNCED_HEIGHT_VALUE]); } // Check if wallet's last identifier changed if(Wallets.NEW_LAST_IDENTIFIER_VALUE in newValues === true) { // Update wallet's last identifier wallet.setLastIdentifier(newValues[Wallets.NEW_LAST_IDENTIFIER_VALUE]); } // Check if wallet exists if(self.walletExists(keyPath) === true) { // Check if wallet's unspent amount changed if(unspentAmountChange.isZero() === false) { // Trigger change event $(document).trigger(Wallets.CHANGE_EVENT, [ // Key path keyPath, // Change Wallets.UNSPENT_AMOUNT_CHANGED, // New value newValues[Wallets.NEW_UNSPENT_AMOUNT_VALUE] ]); } // Check if wallet's unconfirmed amount changed if(unconfirmedAmountChange.isZero() === false) { // Trigger change event $(document).trigger(Wallets.CHANGE_EVENT, [ // Key path keyPath, // Change Wallets.UNCONFIRMED_AMOUNT_CHANGED, // New value newValues[Wallets.NEW_UNCONFIRMED_AMOUNT_VALUE] ]); } // Check if wallet's pending amount changed if(pendingAmountChange.isZero() === false) { // Trigger change event $(document).trigger(Wallets.CHANGE_EVENT, [ // Key path keyPath, // Change Wallets.PENDING_AMOUNT_CHANGED, // New value newValues[Wallets.NEW_PENDING_AMOUNT_VALUE] ]); } // Check if wallet's expired amount changed if(expiredAmountChange.isZero() === false) { // Trigger change event $(document).trigger(Wallets.CHANGE_EVENT, [ // Key path keyPath, // Change Wallets.EXPIRED_AMOUNT_CHANGED, // New value newValues[Wallets.NEW_EXPIRED_AMOUNT_VALUE] ]); } // Check if transactions were updated if(updatedTransactions["length"] !== 0) { // Trigger transactions change event $(self.transactions).trigger(Transactions.CHANGE_EVENT, [ // Transactions updatedTransactions ]); } // Check if wallet's syncing status isn't resyncing if(wallet.getSyncingStatus() !== Wallet.STATUS_RESYNCING) { // Check if at the last output if(atLastOutput === true) { // Clear wallet's last sync index wallet.setLastSyncIndex(Wallet.NO_SYNC_INDEX); // Update wallet's starting sync height wallet.setStartingSyncHeight(wallet.getSyncedHeight()); // Set wallet's percent synced wallet.setPercentSynced(new BigNumber(Wallets.MAXIMUM_PERCENT)); // Set wallet's syncing status to synced wallet.setSyncingStatus(Wallet.STATUS_SYNCED); // Trigger sync done event $(self).trigger(Wallets.SYNC_DONE_EVENT, keyPath); } // Otherwise else { // Set wallet's last sync index wallet.setLastSyncIndex([ // Start index pmmrIndices["last_retrieved_index"], // Last retrieved index lastRetrievedIndex ]); } } } // Resolve resolve(); // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject error reject(error); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); } // Otherwise else { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Resolve resolve(); // Catch errors }).catch(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); } // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject error reject(error); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); } // Otherwise else { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Resolve resolve(); // Catch errors }).catch(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); } // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject error reject(error); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject error reject(error); }); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject error reject(error); }); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject error reject(error); }); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject error reject(error); }); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject error reject(error); }); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject error reject(error); }); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject error reject(error); }); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject error reject(error); }); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject error reject(error); }); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject error reject(error); }); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject error reject(error); }); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject error reject(error); }); // Catch errors }).catch(function(error) { // Release wallet's exclusive transactions lock self.transactions.releaseWalletsExclusiveTransactionsLock(keyPath); // Reject error reject(error); }); }); // Catch errors }).catch(function(error) { // Reject error reject(error); }); })); } } } // Wait for all wallets to finish checking the outputs Promise.all(checkingWallets).then(function() { // Save recent heights's heights and catch errors self.recentHeights.saveHeights(tipHeight).catch(function(error) { // Finally }).finally(function() { // Check if stop syncing if(self.stopSyncing === true) { // Clear is syncing self.isSyncing = false; } // Otherwise check if not at the last output else if(atLastOutput === false) { // Sync self.sync(true); } // Otherwise else { // Set continue syncing var continueSyncing = false; // Check if node's tip height has changed var currentTipHeight = self.node.getCurrentHeight(); if(currentTipHeight === Node.UNKNOWN_HEIGHT || currentTipHeight.getHeight().isEqualTo(Consensus.FIRST_BLOCK_HEIGHT) === true || tipHeight.getHeight().isEqualTo(currentTipHeight.getHeight()) === false || Common.arraysAreEqual(tipHeight.getHash(), currentTipHeight.getHash()) === false) { // Set continue syncing continueSyncing = true; } // Otherwise else { // Go through all wallets for(var keyPath in self.wallets) { if(self.wallets.hasOwnProperty(keyPath) === true) { // Get wallet var wallet = self.wallets[keyPath]; // Check if wallet is syncing if(wallet.isSyncing() === true) { // Set continue syncing continueSyncing = true; // Break break; } } } } // Check if continue syncing or ignoring synced status if(continueSyncing === true || self.ignoreSyncedStatus === true) { // Sync self.sync(true); } // Otherwise else { // Clear is syncing self.isSyncing = false; } } }); // Catch errors }).catch(function(error) { // Check if node is connected if(self.node.isConnected() === true) { // Sync failed self.syncFailed(true); } // Clear is syncing self.isSyncing = false; }); } // Otherwise else { // Clear is syncing self.isSyncing = false; } // Catch errors }).catch(function(error) { // Clear is syncing self.isSyncing = false; }); } // Otherwise else { // Check if node is connected if(self.node.isConnected() === true) { // Sync failed self.syncFailed(true); } // Clear is syncing self.isSyncing = false; } } // Otherwise else { // Clear is syncing self.isSyncing = false; } // Catch errors }).catch(function(error) { // Clear is syncing self.isSyncing = false; }); } // Otherwise else { // Clear is syncing self.isSyncing = false; } } // Otherwise else { // Clear is syncing self.isSyncing = false; } } // Otherwise else { // Clear is syncing self.isSyncing = false; } // Catch errors }).catch(function(error) { // Check if node is connected if(self.node.isConnected() === true) { // Sync failed self.syncFailed(true); } // Clear is syncing self.isSyncing = false; }); } // Otherwise else { // Sync waiting this.syncWaiting(); // Clear is syncing this.isSyncing = false; } } // Otherwise else { // Check if node is connected if(this.node.isConnected() === true) { // Sync failed this.syncFailed(true); } // Clear is syncing this.isSyncing = false; } } } } // Sync failed syncFailed(restartNode = false) { // Go through all wallets for(var keyPath in this.wallets) { if(this.wallets.hasOwnProperty(keyPath) === true) { // Get wallet var wallet = this.wallets[keyPath]; // Check if wallet is synced if(wallet.isSynced() === true) { // Set wallet's percent synced wallet.setPercentSynced(new BigNumber(Wallets.MINIMUM_PERCENT)); } // Set wallet's syncing status to error wallet.setSyncingStatus(Wallet.STATUS_ERROR); // Trigger sync fail event $(this).trigger(Wallets.SYNC_FAIL_EVENT, wallet.getKeyPath()); } } // Set stop syncing this.stopSyncing = true; // Check if restarting node if(restartNode === true) { // Restart node this.node.restart(); } } // Sync waiting syncWaiting() { // Go through all wallets for(var keyPath in this.wallets) { if(this.wallets.hasOwnProperty(keyPath) === true) { // Get wallet var wallet = this.wallets[keyPath]; // Set wallet's syncing status to syncing wallet.setSyncingStatus(Wallet.STATUS_SYNCING); // Trigger sync start event $(this).trigger(Wallets.SYNC_START_EVENT, [ // Key path wallet.getKeyPath() ]); } } // Set stop syncing this.stopSyncing = true; } // Save wallet saveWallet(wallet, newValues = Wallets.NO_NEW_VALUES, transaction = Wallets.NO_TRANSACTION) { // Set self var self = this; // Return promise return new Promise(function(resolve, reject) { // Get database transaction var getDatabaseTransaction = new Promise(function(resolve, reject) { // Check if no transaction is provided if(transaction === Wallets.NO_TRANSACTION) { // Return creating a database transaction return Database.createTransaction(Wallets.OBJECT_STORE_NAME, Database.READ_AND_WRITE_MODE, Database.STRICT_DURABILITY).then(function(databaseTransaction) { // Resolve database transaction resolve(databaseTransaction); // Catch errors }).catch(function(error) { // Reject error reject(error); }); } // Otherwise else { // Resolve transaction resolve(transaction); } }); // Return creating database transaction return getDatabaseTransaction.then(function(databaseTransaction) { // Get new values newValues = (newValues === Wallets.NO_NEW_VALUES) ? {} : newValues(); // Get creating new key path var creatingNewKeyPath = wallet.getKeyPath() === Wallet.NO_KEY_PATH; // Check if no creating a new key path and the wallet doesn't exist if(creatingNewKeyPath === false && self.walletExists(wallet.getKeyPath()) === false) { // Check if a transaction was provided if(transaction !== Wallets.NO_TRANSACTION) { // Resolve new values resolve(newValues); } // Otherwise else { // Return committing database transaction return Database.commitTransaction(databaseTransaction).then(function() { // Check if a new NAME value was set if(Wallets.NEW_NAME_VALUE in newValues === true) { // Set wallet's name to its new value wallet.setName(newValues[Wallets.NEW_NAME_VALUE]); } // Check if a new synced height value was set if(Wallets.NEW_SYNCED_HEIGHT_VALUE in newValues === true) { // Set wallet's synced height to its new value wallet.setSyncedHeight(newValues[Wallets.NEW_SYNCED_HEIGHT_VALUE]); } // Check if a new unconfirmed amount value was set if(Wallets.NEW_UNCONFIRMED_AMOUNT_VALUE in newValues === true) { // Set wallet's unconfirmed amount to its new value wallet.setUnconfirmedAmount(newValues[Wallets.NEW_UNCONFIRMED_AMOUNT_VALUE]); } // Check if a new locked amount value was set if(Wallets.NEW_LOCKED_AMOUNT_VALUE in newValues === true) { // Set wallet's locked amount to its new value wallet.setLockedAmount(newValues[Wallets.NEW_LOCKED_AMOUNT_VALUE]); } // Check if a new unspent amount value was set if(Wallets.NEW_UNSPENT_AMOUNT_VALUE in newValues === true) { // Set wallet's unspent amount to its new value wallet.setUnspentAmount(newValues[Wallets.NEW_UNSPENT_AMOUNT_VALUE]); } // Check if a new spent amount value was set if(Wallets.NEW_SPENT_AMOUNT_VALUE in newValues === true) { // Set wallet's spent amount to its new value wallet.setSpentAmount(newValues[Wallets.NEW_SPENT_AMOUNT_VALUE]); } // Check if a new pending amount value was set if(Wallets.NEW_PENDING_AMOUNT_VALUE in newValues === true) { // Set wallet's pending amount to its new value wallet.setPendingAmount(newValues[Wallets.NEW_PENDING_AMOUNT_VALUE]); } // Check if a new expired amount value was set if(Wallets.NEW_EXPIRED_AMOUNT_VALUE in newValues === true) { // Set wallet's expired amount to its new value wallet.setExpiredAmount(newValues[Wallets.NEW_EXPIRED_AMOUNT_VALUE]); } // Check if a new address suffix value was set if(Wallets.NEW_ADDRESS_SUFFIX_VALUE in newValues === true) { // Set wallet's address suffix to its new value wallet.setAddressSuffix(newValues[Wallets.NEW_ADDRESS_SUFFIX_VALUE]); } // Check if a new last identifier value was set if(Wallets.NEW_LAST_IDENTIFIER_VALUE in newValues === true) { // Set wallet's last identifier to its new value wallet.setLastIdentifier(newValues[Wallets.NEW_LAST_IDENTIFIER_VALUE]); } // Check if a new salt value is set if(Wallets.NEW_SALT_VALUE in newValues === true) { // Set wallet's salt to its new value wallet.setSalt(newValues[Wallets.NEW_SALT_VALUE]); } // Check if a new number of iterations value is set if(Wallets.NEW_NUMBER_OF_ITERATIONS_VALUE in newValues === true) { // Set wallet's number of iterations to its new value wallet.setNumberOfIterations(newValues[Wallets.NEW_NUMBER_OF_ITERATIONS_VALUE]); } // Check if a new initialization vector value is set if(Wallets.NEW_INITIALIZATION_VECTOR_VALUE in newValues === true) { // Set wallet's intiialization vector to its new value wallet.setInitializationVector(newValues[Wallets.NEW_INITIALIZATION_VECTOR_VALUE]); } // Check if a new encrypted seed value is set if(Wallets.NEW_ENCRYPTED_SEED_VALUE in newValues === true) { // Set wallet's encrypted seed to its new value wallet.setEncryptedSeed(newValues[Wallets.NEW_ENCRYPTED_SEED_VALUE]); } // Check if a new order value is set if(Wallets.NEW_ORDER_VALUE in newValues === true) { // Set wallet's order to its new value wallet.setOrder(newValues[Wallets.NEW_ORDER_VALUE]); } // Check if a new encrypted root public key value is set if(Wallets.NEW_ENCRYPTED_ROOT_PUBLIC_KEY_VALUE in newValues === true) { // Set wallet's encrypted root public key to its new value wallet.setEncryptedRootPublicKey(newValues[Wallets.NEW_ENCRYPTED_ROOT_PUBLIC_KEY_VALUE]); } // Check if a new encrypted BIP39 salt value is set if(Wallets.NEW_ENCRYPTED_BIP39_SALT_VALUE in newValues === true) { // Set wallet's encrypted BIP39 salt to its new value wallet.setEncryptedBip39Salt(newValues[Wallets.NEW_ENCRYPTED_BIP39_SALT_VALUE]); } // Check if a new hardware type value is set if(Wallets.NEW_HARDWARE_TYPE_VALUE in newValues === true) { // Set wallet's hardware type to its new value wallet.setHardwareType(newValues[Wallets.NEW_HARDWARE_TYPE_VALUE]); } // Resolve new values resolve(newValues); // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Reject error reject(Language.getDefaultTranslation('The database failed.')); // Catch errors }).catch(function(error) { // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); } } // Otherwise else { // Check if wallet doesn't have an order if(wallet.getOrder() === Wallet.NO_ORDER) { // Return getting wallet with the wallet type, network type, and the highest order from the database return Database.getResults(Wallets.OBJECT_STORE_NAME, 0, 1, Wallets.DATABASE_WALLET_TYPE_NETWORK_TYPE_AND_ORDER_NAME, IDBKeyRange.bound([ // Wallet type lower bound Consensus.getWalletType(), // Network type lower bound Consensus.getNetworkType(), // Order lower bound Number.NEGATIVE_INFINITY ], [ // Wallet type upper bound Consensus.getWalletType(), // Network type upper bound Consensus.getNetworkType(), // Order upper bound Number.POSITIVE_INFINITY ]), Database.BACKWARD_DIRECTION, databaseTransaction).then(function(results) { // Get new order var newOrder = (results["length"] !== 0) ? results[0][Database.toKeyPath(Wallets.DATABASE_ORDER_NAME)] + 1 : 0; // Return saving wallet in the database return Database.saveResult(Wallets.OBJECT_STORE_NAME, { // Name [Database.toKeyPath(Wallets.DATABASE_NAME_NAME)]: (Wallets.NEW_NAME_VALUE in newValues === true) ? newValues[Wallets.NEW_NAME_VALUE] : wallet.getName(), // Color [Database.toKeyPath(Wallets.DATABASE_COLOR_NAME)]: parseInt(wallet.getColor().substring("#"["length"]), Common.HEX_NUMBER_BASE), // Salt [Database.toKeyPath(Wallets.DATABASE_SALT_NAME)]: (Wallets.NEW_SALT_VALUE in newValues === true) ? newValues[Wallets.NEW_SALT_VALUE] : wallet.getSalt(), // Initialization vector [Database.toKeyPath(Wallets.DATABASE_INITIALIZATION_VECTOR_NAME)]: (Wallets.NEW_INITIALIZATION_VECTOR_VALUE in newValues === true) ? newValues[Wallets.NEW_INITIALIZATION_VECTOR_VALUE] : wallet.getInitializationVector(), // Number of iterations [Database.toKeyPath(Wallets.DATABASE_NUMBER_OF_ITERATIONS_NAME)]: (Wallets.NEW_NUMBER_OF_ITERATIONS_VALUE in newValues === true) ? newValues[Wallets.NEW_NUMBER_OF_ITERATIONS_VALUE] : wallet.getNumberOfIterations(), // Encrypted seed [Database.toKeyPath(Wallets.DATABASE_ENCRYPTED_SEED_NAME)]: (Wallets.NEW_ENCRYPTED_SEED_VALUE in newValues === true) ? newValues[Wallets.NEW_ENCRYPTED_SEED_VALUE] : wallet.getEncryptedSeed(), // Address suffix [Database.toKeyPath(Wallets.DATABASE_ADDRESS_SUFFIX_NAME)]: (Wallets.NEW_ADDRESS_SUFFIX_VALUE in newValues === true) ? newValues[Wallets.NEW_ADDRESS_SUFFIX_VALUE] : wallet.getAddressSuffix(), // Order [Database.toKeyPath(Wallets.DATABASE_ORDER_NAME)]: (Wallets.NEW_ORDER_VALUE in newValues === true) ? newValues[Wallets.NEW_ORDER_VALUE] : newOrder, // Synced height [Database.toKeyPath(Wallets.DATABASE_SYNCED_HEIGHT_NAME)]: (Wallets.NEW_SYNCED_HEIGHT_VALUE in newValues === true) ? ((newValues[Wallets.NEW_SYNCED_HEIGHT_VALUE] !== Wallet.CURRENT_HEIGHT) ? newValues[Wallets.NEW_SYNCED_HEIGHT_VALUE].toFixed() : Wallet.CURRENT_HEIGHT) : ((wallet.getSyncedHeight() !== Wallet.CURRENT_HEIGHT) ? wallet.getSyncedHeight().toFixed() : Wallet.CURRENT_HEIGHT), // Spent amount [Database.toKeyPath(Wallets.DATABASE_SPENT_AMOUNT_NAME)]: (Wallets.NEW_SPENT_AMOUNT_VALUE in newValues === true) ? newValues[Wallets.NEW_SPENT_AMOUNT_VALUE].toFixed() : wallet.getSpentAmount().toFixed(), // Unspent amount [Database.toKeyPath(Wallets.DATABASE_UNSPENT_AMOUNT_NAME)]: (Wallets.NEW_UNSPENT_AMOUNT_VALUE in newValues === true) ? newValues[Wallets.NEW_UNSPENT_AMOUNT_VALUE].toFixed() : wallet.getUnspentAmount().toFixed(), // Unconfirmed amount [Database.toKeyPath(Wallets.DATABASE_UNCONFIRMED_AMOUNT_NAME)]: (Wallets.NEW_UNCONFIRMED_AMOUNT_VALUE in newValues === true) ? newValues[Wallets.NEW_UNCONFIRMED_AMOUNT_VALUE].toFixed() : wallet.getUnconfirmedAmount().toFixed(), // Locked amount [Database.toKeyPath(Wallets.DATABASE_LOCKED_AMOUNT_NAME)]: (Wallets.NEW_LOCKED_AMOUNT_VALUE in newValues === true) ? newValues[Wallets.NEW_LOCKED_AMOUNT_VALUE].toFixed() : wallet.getLockedAmount().toFixed(), // Pending amount [Database.toKeyPath(Wallets.DATABASE_PENDING_AMOUNT_NAME)]: (Wallets.NEW_PENDING_AMOUNT_VALUE in newValues === true) ? newValues[Wallets.NEW_PENDING_AMOUNT_VALUE].toFixed() : wallet.getPendingAmount().toFixed(), // Expired amount [Database.toKeyPath(Wallets.DATABASE_EXPIRED_AMOUNT_NAME)]: (Wallets.NEW_EXPIRED_AMOUNT_VALUE in newValues === true) ? newValues[Wallets.NEW_EXPIRED_AMOUNT_VALUE].toFixed() : wallet.getExpiredAmount().toFixed(), // Wallet type [Database.toKeyPath(Wallets.DATABASE_WALLET_TYPE_NAME)]: wallet.getWalletType(), // Network type [Database.toKeyPath(Wallets.DATABASE_NETWORK_TYPE_NAME)]: wallet.getNetworkType(), // Last identifier [Database.toKeyPath(Wallets.DATABASE_LAST_IDENTIFIER_NAME)]: (Wallets.NEW_LAST_IDENTIFIER_VALUE in newValues === true) ? ((newValues[Wallets.NEW_LAST_IDENTIFIER_VALUE] !== Wallet.NO_LAST_IDENTIFIER) ? newValues[Wallets.NEW_LAST_IDENTIFIER_VALUE].getValue() : Wallet.NO_LAST_IDENTIFIER) : ((wallet.getLastIdentifier() !== Wallet.NO_LAST_IDENTIFIER) ? wallet.getLastIdentifier().getValue() : Wallet.NO_LAST_IDENTIFIER), // Hardware type [Database.toKeyPath(Wallets.DATABASE_HARDWARE_TYPE_NAME)]: (Wallets.NEW_HARDWARE_TYPE_VALUE in newValues === true) ? newValues[Wallets.NEW_HARDWARE_TYPE_VALUE] : wallet.getHardwareType(), // Encrypted root public key [Database.toKeyPath(Wallets.DATABASE_ENCRYPTED_ROOT_PUBLIC_KEY_NAME)]: (Wallets.NEW_ENCRYPTED_ROOT_PUBLIC_KEY_VALUE in newValues === true) ? newValues[Wallets.NEW_ENCRYPTED_ROOT_PUBLIC_KEY_VALUE] : wallet.getEncryptedRootPublicKey(), // Use BIP39 [Database.toKeyPath(Wallets.DATABASE_USE_BIP39_NAME)]: wallet.getUseBip39(), // Encrypted BIP39 salt [Database.toKeyPath(Wallets.DATABASE_ENCRYPTED_BIP39_SALT_NAME)]: (Wallets.NEW_ENCRYPTED_BIP39_SALT_VALUE in newValues === true) ? newValues[Wallets.NEW_ENCRYPTED_BIP39_SALT_VALUE] : wallet.getEncryptedBip39Salt(), // Account number [Database.toKeyPath(Wallets.DATABASE_ACCOUNT_NUMBER_NAME)]: wallet.getAccountNumber(), // Payment proof index [Database.toKeyPath(Wallets.DATABASE_PAYMENT_PROOF_INDEX_NAME)]: wallet.getPaymentProofIndex() }, (creatingNewKeyPath === true) ? Database.CREATE_NEW_KEY_PATH : wallet.getKeyPath(), databaseTransaction, Database.STRICT_DURABILITY).then(function(keyPath) { // Check if a transaction was provided if(transaction !== Wallets.NO_TRANSACTION) { // Set wallet's order wallet.setOrder(newOrder); // Set wallet's key path wallet.setKeyPath(keyPath); // Resolve new values resolve(newValues); } // Otherwise else { // Return committing database transaction return Database.commitTransaction(databaseTransaction).then(function() { // Set wallet's order wallet.setOrder(newOrder); // Set wallet's key path wallet.setKeyPath(keyPath); // Check if a new name value was set if(Wallets.NEW_NAME_VALUE in newValues === true) { // Set wallet's name to its new value wallet.setName(newValues[Wallets.NEW_NAME_VALUE]); } // Check if a new synced height value was set if(Wallets.NEW_SYNCED_HEIGHT_VALUE in newValues === true) { // Set wallet's synced height to its new value wallet.setSyncedHeight(newValues[Wallets.NEW_SYNCED_HEIGHT_VALUE]); } // Check if a new unconfirmed amount value was set if(Wallets.NEW_UNCONFIRMED_AMOUNT_VALUE in newValues === true) { // Set wallet's unconfirmed amount to its new value wallet.setUnconfirmedAmount(newValues[Wallets.NEW_UNCONFIRMED_AMOUNT_VALUE]); } // Check if a new locked amount value was set if(Wallets.NEW_LOCKED_AMOUNT_VALUE in newValues === true) { // Set wallet's locked amount to its new value wallet.setLockedAmount(newValues[Wallets.NEW_LOCKED_AMOUNT_VALUE]); } // Check if a new unspent amount value was set if(Wallets.NEW_UNSPENT_AMOUNT_VALUE in newValues === true) { // Set wallet's unspent amount to its new value wallet.setUnspentAmount(newValues[Wallets.NEW_UNSPENT_AMOUNT_VALUE]); } // Check if a new spent amount value was set if(Wallets.NEW_SPENT_AMOUNT_VALUE in newValues === true) { // Set wallet's spent amount to its new value wallet.setSpentAmount(newValues[Wallets.NEW_SPENT_AMOUNT_VALUE]); } // Check if a new pending amount value was set if(Wallets.NEW_PENDING_AMOUNT_VALUE in newValues === true) { // Set wallet's pending amount to its new value wallet.setPendingAmount(newValues[Wallets.NEW_PENDING_AMOUNT_VALUE]); } // Check if a new expired amount value was set if(Wallets.NEW_EXPIRED_AMOUNT_VALUE in newValues === true) { // Set wallet's expired amount to its new value wallet.setExpiredAmount(newValues[Wallets.NEW_EXPIRED_AMOUNT_VALUE]); } // Check if a new address suffix value was set if(Wallets.NEW_ADDRESS_SUFFIX_VALUE in newValues === true) { // Set wallet's address suffix to its new value wallet.setAddressSuffix(newValues[Wallets.NEW_ADDRESS_SUFFIX_VALUE]); } // Check if a new last identifier value was set if(Wallets.NEW_LAST_IDENTIFIER_VALUE in newValues === true) { // Set wallet's last identifier to its new value wallet.setLastIdentifier(newValues[Wallets.NEW_LAST_IDENTIFIER_VALUE]); } // Check if a new salt value is set if(Wallets.NEW_SALT_VALUE in newValues === true) { // Set wallet's salt to its new value wallet.setSalt(newValues[Wallets.NEW_SALT_VALUE]); } // Check if a new number of iterations value is set if(Wallets.NEW_NUMBER_OF_ITERATIONS_VALUE in newValues === true) { // Set wallet's number of iterations to its new value wallet.setNumberOfIterations(newValues[Wallets.NEW_NUMBER_OF_ITERATIONS_VALUE]); } // Check if a new initialization vector value is set if(Wallets.NEW_INITIALIZATION_VECTOR_VALUE in newValues === true) { // Set wallet's intiialization vector to its new value wallet.setInitializationVector(newValues[Wallets.NEW_INITIALIZATION_VECTOR_VALUE]); } // Check if a new encrypted seed value is set if(Wallets.NEW_ENCRYPTED_SEED_VALUE in newValues === true) { // Set wallet's encrypted seed to its new value wallet.setEncryptedSeed(newValues[Wallets.NEW_ENCRYPTED_SEED_VALUE]); } // Check if a new order value is set if(Wallets.NEW_ORDER_VALUE in newValues === true) { // Set wallet's order to its new value wallet.setOrder(newValues[Wallets.NEW_ORDER_VALUE]); } // Check if a new encrypted root public key value is set if(Wallets.NEW_ENCRYPTED_ROOT_PUBLIC_KEY_VALUE in newValues === true) { // Set wallet's encrypted root public key to its new value wallet.setEncryptedRootPublicKey(newValues[Wallets.NEW_ENCRYPTED_ROOT_PUBLIC_KEY_VALUE]); } // Check if a new encrypted BIP39 salt value is set if(Wallets.NEW_ENCRYPTED_BIP39_SALT_VALUE in newValues === true) { // Set wallet's encrypted BIP39 salt to its new value wallet.setEncryptedBip39Salt(newValues[Wallets.NEW_ENCRYPTED_BIP39_SALT_VALUE]); } // Check if a new hardware type value is set if(Wallets.NEW_HARDWARE_TYPE_VALUE in newValues === true) { // Set wallet's hardware type to its new value wallet.setHardwareType(newValues[Wallets.NEW_HARDWARE_TYPE_VALUE]); } // Resolve new values resolve(newValues); // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Reject error reject(Language.getDefaultTranslation('The database failed.')); // Catch errors }).catch(function(error) { // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); } // Catch errors }).catch(function(error) { // Check if a transaction was provided if(transaction !== Wallets.NO_TRANSACTION) { // Reject error reject(Language.getDefaultTranslation('The database failed.')); } // Otherwise else { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Reject error reject(Language.getDefaultTranslation('The database failed.')); // Catch errors }).catch(function(error) { // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); } }); // Catch errors }).catch(function(error) { // Check if a transaction was provided if(transaction !== Wallets.NO_TRANSACTION) { // Reject error reject(Language.getDefaultTranslation('The database failed.')); } // Otherwise else { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Reject error reject(Language.getDefaultTranslation('The database failed.')); // Catch errors }).catch(function(error) { // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); } }); } // Otherwise else { // Return saving wallet in the database return Database.saveResult(Wallets.OBJECT_STORE_NAME, { // Name [Database.toKeyPath(Wallets.DATABASE_NAME_NAME)]: (Wallets.NEW_NAME_VALUE in newValues === true) ? newValues[Wallets.NEW_NAME_VALUE] : wallet.getName(), // Color [Database.toKeyPath(Wallets.DATABASE_COLOR_NAME)]: parseInt(wallet.getColor().substring("#"["length"]), Common.HEX_NUMBER_BASE), // Salt [Database.toKeyPath(Wallets.DATABASE_SALT_NAME)]: (Wallets.NEW_SALT_VALUE in newValues === true) ? newValues[Wallets.NEW_SALT_VALUE] : wallet.getSalt(), // Initialization vector [Database.toKeyPath(Wallets.DATABASE_INITIALIZATION_VECTOR_NAME)]: (Wallets.NEW_INITIALIZATION_VECTOR_VALUE in newValues === true) ? newValues[Wallets.NEW_INITIALIZATION_VECTOR_VALUE] : wallet.getInitializationVector(), // Number of iterations [Database.toKeyPath(Wallets.DATABASE_NUMBER_OF_ITERATIONS_NAME)]: (Wallets.NEW_NUMBER_OF_ITERATIONS_VALUE in newValues === true) ? newValues[Wallets.NEW_NUMBER_OF_ITERATIONS_VALUE] : wallet.getNumberOfIterations(), // Encrypted seed [Database.toKeyPath(Wallets.DATABASE_ENCRYPTED_SEED_NAME)]: (Wallets.NEW_ENCRYPTED_SEED_VALUE in newValues === true) ? newValues[Wallets.NEW_ENCRYPTED_SEED_VALUE] : wallet.getEncryptedSeed(), // Address suffix [Database.toKeyPath(Wallets.DATABASE_ADDRESS_SUFFIX_NAME)]: (Wallets.NEW_ADDRESS_SUFFIX_VALUE in newValues === true) ? newValues[Wallets.NEW_ADDRESS_SUFFIX_VALUE] : wallet.getAddressSuffix(), // Order [Database.toKeyPath(Wallets.DATABASE_ORDER_NAME)]: (Wallets.NEW_ORDER_VALUE in newValues === true) ? newValues[Wallets.NEW_ORDER_VALUE] : wallet.getOrder(), // Synced height [Database.toKeyPath(Wallets.DATABASE_SYNCED_HEIGHT_NAME)]: (Wallets.NEW_SYNCED_HEIGHT_VALUE in newValues === true) ? ((newValues[Wallets.NEW_SYNCED_HEIGHT_VALUE] !== Wallet.CURRENT_HEIGHT) ? newValues[Wallets.NEW_SYNCED_HEIGHT_VALUE].toFixed() : Wallet.CURRENT_HEIGHT) : ((wallet.getSyncedHeight() !== Wallet.CURRENT_HEIGHT) ? wallet.getSyncedHeight().toFixed() : Wallet.CURRENT_HEIGHT), // Spent amount [Database.toKeyPath(Wallets.DATABASE_SPENT_AMOUNT_NAME)]: (Wallets.NEW_SPENT_AMOUNT_VALUE in newValues === true) ? newValues[Wallets.NEW_SPENT_AMOUNT_VALUE].toFixed() : wallet.getSpentAmount().toFixed(), // Unspent amount [Database.toKeyPath(Wallets.DATABASE_UNSPENT_AMOUNT_NAME)]: (Wallets.NEW_UNSPENT_AMOUNT_VALUE in newValues === true) ? newValues[Wallets.NEW_UNSPENT_AMOUNT_VALUE].toFixed() : wallet.getUnspentAmount().toFixed(), // Unconfirmed amount [Database.toKeyPath(Wallets.DATABASE_UNCONFIRMED_AMOUNT_NAME)]: (Wallets.NEW_UNCONFIRMED_AMOUNT_VALUE in newValues === true) ? newValues[Wallets.NEW_UNCONFIRMED_AMOUNT_VALUE].toFixed() : wallet.getUnconfirmedAmount().toFixed(), // Locked amount [Database.toKeyPath(Wallets.DATABASE_LOCKED_AMOUNT_NAME)]: (Wallets.NEW_LOCKED_AMOUNT_VALUE in newValues === true) ? newValues[Wallets.NEW_LOCKED_AMOUNT_VALUE].toFixed() : wallet.getLockedAmount().toFixed(), // Pending amount [Database.toKeyPath(Wallets.DATABASE_PENDING_AMOUNT_NAME)]: (Wallets.NEW_PENDING_AMOUNT_VALUE in newValues === true) ? newValues[Wallets.NEW_PENDING_AMOUNT_VALUE].toFixed() : wallet.getPendingAmount().toFixed(), // Expired amount [Database.toKeyPath(Wallets.DATABASE_EXPIRED_AMOUNT_NAME)]: (Wallets.NEW_EXPIRED_AMOUNT_VALUE in newValues === true) ? newValues[Wallets.NEW_EXPIRED_AMOUNT_VALUE].toFixed() : wallet.getExpiredAmount().toFixed(), // Wallet type [Database.toKeyPath(Wallets.DATABASE_WALLET_TYPE_NAME)]: wallet.getWalletType(), // Network type [Database.toKeyPath(Wallets.DATABASE_NETWORK_TYPE_NAME)]: wallet.getNetworkType(), // Last identifier [Database.toKeyPath(Wallets.DATABASE_LAST_IDENTIFIER_NAME)]: (Wallets.NEW_LAST_IDENTIFIER_VALUE in newValues === true) ? ((newValues[Wallets.NEW_LAST_IDENTIFIER_VALUE] !== Wallet.NO_LAST_IDENTIFIER) ? newValues[Wallets.NEW_LAST_IDENTIFIER_VALUE].getValue() : Wallet.NO_LAST_IDENTIFIER) : ((wallet.getLastIdentifier() !== Wallet.NO_LAST_IDENTIFIER) ? wallet.getLastIdentifier().getValue() : Wallet.NO_LAST_IDENTIFIER), // Hardware type [Database.toKeyPath(Wallets.DATABASE_HARDWARE_TYPE_NAME)]: (Wallets.NEW_HARDWARE_TYPE_VALUE in newValues === true) ? newValues[Wallets.NEW_HARDWARE_TYPE_VALUE] : wallet.getHardwareType(), // Encrypted root public key [Database.toKeyPath(Wallets.DATABASE_ENCRYPTED_ROOT_PUBLIC_KEY_NAME)]: (Wallets.NEW_ENCRYPTED_ROOT_PUBLIC_KEY_VALUE in newValues === true) ? newValues[Wallets.NEW_ENCRYPTED_ROOT_PUBLIC_KEY_VALUE] : wallet.getEncryptedRootPublicKey(), // Use BIP39 [Database.toKeyPath(Wallets.DATABASE_USE_BIP39_NAME)]: wallet.getUseBip39(), // Encrypted BIP39 salt [Database.toKeyPath(Wallets.DATABASE_ENCRYPTED_BIP39_SALT_NAME)]: (Wallets.NEW_ENCRYPTED_BIP39_SALT_VALUE in newValues === true) ? newValues[Wallets.NEW_ENCRYPTED_BIP39_SALT_VALUE] : wallet.getEncryptedBip39Salt(), // Account number [Database.toKeyPath(Wallets.DATABASE_ACCOUNT_NUMBER_NAME)]: wallet.getAccountNumber(), // Payment proof index [Database.toKeyPath(Wallets.DATABASE_PAYMENT_PROOF_INDEX_NAME)]: wallet.getPaymentProofIndex() }, (creatingNewKeyPath === true) ? Database.CREATE_NEW_KEY_PATH : wallet.getKeyPath(), databaseTransaction, Database.STRICT_DURABILITY).then(function(keyPath) { // Check if a transaction was provided if(transaction !== Wallets.NO_TRANSACTION) { // Set wallet's key path wallet.setKeyPath(keyPath); // Resolve new values resolve(newValues); } // Otherwise else { // Return committing database transaction return Database.commitTransaction(databaseTransaction).then(function() { // Set wallet's key path wallet.setKeyPath(keyPath); // Check if a new name value was set if(Wallets.NEW_NAME_VALUE in newValues === true) { // Set wallet's name to its new value wallet.setName(newValues[Wallets.NEW_NAME_VALUE]); } // Check if a new synced height value was set if(Wallets.NEW_SYNCED_HEIGHT_VALUE in newValues === true) { // Set wallet's synced height to its new value wallet.setSyncedHeight(newValues[Wallets.NEW_SYNCED_HEIGHT_VALUE]); } // Check if a new unconfirmed amount value was set if(Wallets.NEW_UNCONFIRMED_AMOUNT_VALUE in newValues === true) { // Set wallet's unconfirmed amount to its new value wallet.setUnconfirmedAmount(newValues[Wallets.NEW_UNCONFIRMED_AMOUNT_VALUE]); } // Check if a new locked amount value was set if(Wallets.NEW_LOCKED_AMOUNT_VALUE in newValues === true) { // Set wallet's locked amount to its new value wallet.setLockedAmount(newValues[Wallets.NEW_LOCKED_AMOUNT_VALUE]); } // Check if a new unspent amount value was set if(Wallets.NEW_UNSPENT_AMOUNT_VALUE in newValues === true) { // Set wallet's unspent amount to its new value wallet.setUnspentAmount(newValues[Wallets.NEW_UNSPENT_AMOUNT_VALUE]); } // Check if a new spent amount value was set if(Wallets.NEW_SPENT_AMOUNT_VALUE in newValues === true) { // Set wallet's spent amount to its new value wallet.setSpentAmount(newValues[Wallets.NEW_SPENT_AMOUNT_VALUE]); } // Check if a new pending amount value was set if(Wallets.NEW_PENDING_AMOUNT_VALUE in newValues === true) { // Set wallet's pending amount to its new value wallet.setPendingAmount(newValues[Wallets.NEW_PENDING_AMOUNT_VALUE]); } // Check if a new expired amount value was set if(Wallets.NEW_EXPIRED_AMOUNT_VALUE in newValues === true) { // Set wallet's expired amount to its new value wallet.setExpiredAmount(newValues[Wallets.NEW_EXPIRED_AMOUNT_VALUE]); } // Check if a new address suffix value was set if(Wallets.NEW_ADDRESS_SUFFIX_VALUE in newValues === true) { // Set wallet's address suffix to its new value wallet.setAddressSuffix(newValues[Wallets.NEW_ADDRESS_SUFFIX_VALUE]); } // Check if a new last identifier value was set if(Wallets.NEW_LAST_IDENTIFIER_VALUE in newValues === true) { // Set wallet's last identifier to its new value wallet.setLastIdentifier(newValues[Wallets.NEW_LAST_IDENTIFIER_VALUE]); } // Check if a new salt value is set if(Wallets.NEW_SALT_VALUE in newValues === true) { // Set wallet's salt to its new value wallet.setSalt(newValues[Wallets.NEW_SALT_VALUE]); } // Check if a new number of iterations value is set if(Wallets.NEW_NUMBER_OF_ITERATIONS_VALUE in newValues === true) { // Set wallet's number of iterations to its new value wallet.setNumberOfIterations(newValues[Wallets.NEW_NUMBER_OF_ITERATIONS_VALUE]); } // Check if a new initialization vector value is set if(Wallets.NEW_INITIALIZATION_VECTOR_VALUE in newValues === true) { // Set wallet's intiialization vector to its new value wallet.setInitializationVector(newValues[Wallets.NEW_INITIALIZATION_VECTOR_VALUE]); } // Check if a new encrypted seed value is set if(Wallets.NEW_ENCRYPTED_SEED_VALUE in newValues === true) { // Set wallet's encrypted seed to its new value wallet.setEncryptedSeed(newValues[Wallets.NEW_ENCRYPTED_SEED_VALUE]); } // Check if a new order value is set if(Wallets.NEW_ORDER_VALUE in newValues === true) { // Set wallet's order to its new value wallet.setOrder(newValues[Wallets.NEW_ORDER_VALUE]); } // Check if a new encrypted root public key value is set if(Wallets.NEW_ENCRYPTED_ROOT_PUBLIC_KEY_VALUE in newValues === true) { // Set wallet's encrypted root public key to its new value wallet.setEncryptedRootPublicKey(newValues[Wallets.NEW_ENCRYPTED_ROOT_PUBLIC_KEY_VALUE]); } // Check if a new encrypted BIP39 salt value is set if(Wallets.NEW_ENCRYPTED_BIP39_SALT_VALUE in newValues === true) { // Set wallet's encrypted BIP39 salt to its new value wallet.setEncryptedBip39Salt(newValues[Wallets.NEW_ENCRYPTED_BIP39_SALT_VALUE]); } // Check if a new hardware type value is set if(Wallets.NEW_HARDWARE_TYPE_VALUE in newValues === true) { // Set wallet's hardware type to its new value wallet.setHardwareType(newValues[Wallets.NEW_HARDWARE_TYPE_VALUE]); } // Resolve new values resolve(newValues); // Catch errors }).catch(function(error) { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Reject error reject(Language.getDefaultTranslation('The database failed.')); // Catch errors }).catch(function(error) { // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); }); } // Catch errors }).catch(function(error) { // Check if a transaction was provided if(transaction !== Wallets.NO_TRANSACTION) { // Reject error reject(Language.getDefaultTranslation('The database failed.')); } // Otherwise else { // Return aborting database transaction return Database.abortTransaction(databaseTransaction).then(function() { // Reject error reject(Language.getDefaultTranslation('The database failed.')); // Catch errors }).catch(function(error) { // Trigger a fatal error new FatalError(FatalError.DATABASE_ERROR); }); } }); } } // Catch errors }).catch(function(error) { // Reject error reject(Language.getDefaultTranslation('The database failed.')); }); }); } // Connect to hardware wallets connectToHardwareWallets() { // Set self var self = this; // Return promise return new Promise(function(resolve, reject) { // Check if locked if(self.isLocked() === true) { // Reject error reject("Wallets locked."); } // Otherwise else { // Initialize hardware wallet found var hardwareWalletFound = false; // Go through all wallets for(var keyPath in self.wallets) { if(self.wallets.hasOwnProperty(keyPath) === true) { // Get wallet var wallet = self.wallets[keyPath]; // Check if wallet is open, it's a hardware wallet, and its hardware isn't connected if(wallet.isOpen() === true && wallet.getHardwareType() !== Wallet.NO_HARDWARE_TYPE && wallet.isHardwareConnected() === false) { // Set hardware wallet found hardwareWalletFound = true; // Break break; } } } // Check if a hardware wallet was found if(hardwareWalletFound === true) { // Check if hardware wallets are supported if(HardwareWallet.isSupported() === true) { // Check if USB is supported if("usb" in navigator === true) { // Return obtain exclusive hardware lock return self.obtainExclusiveHardwareLock().then(function() { // Return getting available hardware wallet descriptors return HardwareWallet.getAvailableHardwareWalletDescriptors().then(function(availableHardwareWalletDescriptors) { // Initialize connect to hardware wallets var connectToHardwareWallets = []; // Go through all available hardware wallet descriptors for(let i = 0; i < availableHardwareWalletDescriptors["length"]; ++i) { // Create hardware wallet let hardwareWallet = new HardwareWallet(self.application); // Append connecting to hardware wallet to list connectToHardwareWallets.push(new Promise(function(resolve, reject) { // Return connect to available hardware wallet descriptor return hardwareWallet.connect(availableHardwareWalletDescriptors[i], true).then(function() { // Initialize connect wallets to hardware var connectWalletsToHardware = []; // Go through all wallets for(var keyPath in self.wallets) { if(self.wallets.hasOwnProperty(keyPath) === true) { // Get wallet let wallet = self.wallets[keyPath]; // Check if wallet is a hardware wallet if(wallet.getHardwareType() !== Wallet.NO_HARDWARE_TYPE) { // Append connecting wallet to hardware to list connectWalletsToHardware.push(new Promise(function(resolve, reject) { // Return connecting wallet to the applicable hardware wallet return wallet.connectToApplicableHardware([hardwareWallet]).then(function() { // Resolve resolve(); // Catch errors }).catch(function(error) { // Reject error reject(error); }); })); } } } // Return connecting wallets to hardware return Promise.allSettled(connectWalletsToHardware).then(function() { // Check if hardware wallet isn't in use if(hardwareWallet.getInUse() === false) { // Close hardware wallet hardwareWallet.close(); } // Resolve resolve(); }); // Catch errors }).catch(function(error) { // Reject error reject(error); }); })); } // Return connecting to hardware wallets return Promise.allSettled(connectToHardwareWallets).then(function() { // Release exclusive hardware lock self.releaseExclusiveHardwareLock(); // Resolve resolve(); }); // Catch errors }).catch(function(error) { // Release exclusive hardware lock self.releaseExclusiveHardwareLock(); // Reject error reject(error); }); }); } // Otherwise else { // Resolve resolve(); } } // Otherwise else { // Resolve resolve(); } } // Otherwise else { // Resolve resolve(); } } }); } // Get wallet from result static getWalletFromResult(result) { // Return wallet from result return new Wallet( // Name result[Database.toKeyPath(Wallets.DATABASE_NAME_NAME)], // Color "#" + result[Database.toKeyPath(Wallets.DATABASE_COLOR_NAME)].toString(Common.HEX_NUMBER_BASE).padStart(Common.HEX_COLOR_LENGTH, Common.HEX_NUMBER_PADDING), // Salt result[Database.toKeyPath(Wallets.DATABASE_SALT_NAME)], // Initialization vector result[Database.toKeyPath(Wallets.DATABASE_INITIALIZATION_VECTOR_NAME)], // Number of iterations result[Database.toKeyPath(Wallets.DATABASE_NUMBER_OF_ITERATIONS_NAME)], // Encrypted seed result[Database.toKeyPath(Wallets.DATABASE_ENCRYPTED_SEED_NAME)], // Address suffix result[Database.toKeyPath(Wallets.DATABASE_ADDRESS_SUFFIX_NAME)], // Order result[Database.toKeyPath(Wallets.DATABASE_ORDER_NAME)], // Synced height (result[Database.toKeyPath(Wallets.DATABASE_SYNCED_HEIGHT_NAME)] !== Wallet.CURRENT_HEIGHT) ? new BigNumber(result[Database.toKeyPath(Wallets.DATABASE_SYNCED_HEIGHT_NAME)]) : Wallet.CURRENT_HEIGHT, // Spent amount new BigNumber(result[Database.toKeyPath(Wallets.DATABASE_SPENT_AMOUNT_NAME)]), // Unspent amount new BigNumber(result[Database.toKeyPath(Wallets.DATABASE_UNSPENT_AMOUNT_NAME)]), // Unconfirmed amount new BigNumber(result[Database.toKeyPath(Wallets.DATABASE_UNCONFIRMED_AMOUNT_NAME)]), // Locked amount new BigNumber(result[Database.toKeyPath(Wallets.DATABASE_LOCKED_AMOUNT_NAME)]), // Pending amount new BigNumber(result[Database.toKeyPath(Wallets.DATABASE_PENDING_AMOUNT_NAME)]), // Expired amount new BigNumber(result[Database.toKeyPath(Wallets.DATABASE_EXPIRED_AMOUNT_NAME)]), // Wallet type result[Database.toKeyPath(Wallets.DATABASE_WALLET_TYPE_NAME)], // Network type result[Database.toKeyPath(Wallets.DATABASE_NETWORK_TYPE_NAME)], // Last identifier (result[Database.toKeyPath(Wallets.DATABASE_LAST_IDENTIFIER_NAME)] !== Wallet.NO_LAST_IDENTIFIER) ? new Identifier(Common.toHexString(result[Database.toKeyPath(Wallets.DATABASE_LAST_IDENTIFIER_NAME)])) : Wallet.NO_LAST_IDENTIFIER, // Hardware type result[Database.toKeyPath(Wallets.DATABASE_HARDWARE_TYPE_NAME)], // Encrypted root public key result[Database.toKeyPath(Wallets.DATABASE_ENCRYPTED_ROOT_PUBLIC_KEY_NAME)], // Use BIP39 result[Database.toKeyPath(Wallets.DATABASE_USE_BIP39_NAME)], // Encrypted BIP39 salt result[Database.toKeyPath(Wallets.DATABASE_ENCRYPTED_BIP39_SALT_NAME)], // Account number (Database.toKeyPath(Wallets.DATABASE_ACCOUNT_NUMBER_NAME) in result === true) ? result[Database.toKeyPath(Wallets.DATABASE_ACCOUNT_NUMBER_NAME)] : 0, // Payment proof index (Database.toKeyPath(Wallets.DATABASE_PAYMENT_PROOF_INDEX_NAME) in result === true) ? result[Database.toKeyPath(Wallets.DATABASE_PAYMENT_PROOF_INDEX_NAME)] : 0, // Key path result[Database.KEY_PATH_NAME] ); } // No password static get NO_PASSWORD() { // Return no password return null; } // Object store name static get OBJECT_STORE_NAME() { // Return object store name return "Wallets"; } // Database name name static get DATABASE_NAME_NAME() { // Return database name name return "Name"; } // Database color name static get DATABASE_COLOR_NAME() { // Return database color name return "Color"; } // Database salt name static get DATABASE_SALT_NAME() { // Return database salt name return "Salt"; } // Database initialization vector name static get DATABASE_INITIALIZATION_VECTOR_NAME() { // Return database initialization vector name return "Initialization Vector"; } // Database number of iterations name static get DATABASE_NUMBER_OF_ITERATIONS_NAME() { // Return database number of iterations name return "Number Of Iterations"; } // Database encrypted seed name static get DATABASE_ENCRYPTED_SEED_NAME() { // Return database encrypted seed name return "Encrypted Seed"; } // Database address suffix name static get DATABASE_ADDRESS_SUFFIX_NAME() { // Return database address suffix name return "Address Suffix"; } // Database order name static get DATABASE_ORDER_NAME() { // Return database order name return "Order"; } // Database synced height name static get DATABASE_SYNCED_HEIGHT_NAME() { // Return database synced height name return "Synced Height"; } // Database spent amount name static get DATABASE_SPENT_AMOUNT_NAME() { // Return database spent amount name return "Spent Amount"; } // Database unspent amount name static get DATABASE_UNSPENT_AMOUNT_NAME() { // Return database unspent amount name return "Unspent Amount"; } // Database unconfirmed amount name static get DATABASE_UNCONFIRMED_AMOUNT_NAME() { // Return database unconfirmed amount name return "Unconfirmed Amount"; } // Database locked amount name static get DATABASE_LOCKED_AMOUNT_NAME() { // Return database locked amount name return "Locked Amount"; } // Database pending amount name static get DATABASE_PENDING_AMOUNT_NAME() { // Return database pending amount name return "Pending Amount"; } // Database expired amount name static get DATABASE_EXPIRED_AMOUNT_NAME() { // Return database expired amount name return "Expired Amount"; } // Database wallet type name static get DATABASE_WALLET_TYPE_NAME() { // Return database wallet type name return "Wallet Type"; } // Database network type name static get DATABASE_NETWORK_TYPE_NAME() { // Return database network type name return "Network Type"; } // Database last identifier name static get DATABASE_LAST_IDENTIFIER_NAME() { // Return database last identifier name return "Last Identifier"; } // Database hardware type name static get DATABASE_HARDWARE_TYPE_NAME() { // Return database hardware type name return "Hardware Type"; } // Database encrypted root public key name static get DATABASE_ENCRYPTED_ROOT_PUBLIC_KEY_NAME() { // Return database encrypted root public key name return "Encrypted Root Public Key"; } // Database use BIP39 name static get DATABASE_USE_BIP39_NAME() { // Return database use BIP39 name return "Use BIP39"; } // Database encrypted BIP39 salt name static get DATABASE_ENCRYPTED_BIP39_SALT_NAME() { // Return database encrypted BIP39 salt name return "Encrypted BIP39 Salt"; } // Database account number name static get DATABASE_ACCOUNT_NUMBER_NAME() { // Return database account number name return "Account Number"; } // Database payment proof index name static get DATABASE_PAYMENT_PROOF_INDEX_NAME() { // Return database payment proof index name return "Payment Proof Index"; } // Database wallet type, network type, and address suffix name static get DATABASE_WALLET_TYPE_NETWORK_TYPE_AND_ADDRESS_SUFFIX_NAME() { // Return database wallet type, network type, and address suffix name return "Wallet Type, Network Type, And Address Suffix"; } // Database wallet type, network type, and order name static get DATABASE_WALLET_TYPE_NETWORK_TYPE_AND_ORDER_NAME() { // Return database wallet type, network type, and order name return "Wallet Type, Network Type, And Order"; } // Database wallet type and network type name static get DATABASE_WALLET_TYPE_AND_NETWORK_TYPE_NAME() { // Return database wallet type and network type name return "Wallet Type And Network Type"; } // Outputs group size static get OUTPUTS_GROUP_SIZE() { // Check if device has low memory if(Common.isLowMemoryDevice() === true) // Return outputs group size return new BigNumber(250); // Otherwise check if device has high memory else if(Common.isHighMemoryDevice() === true) // Return outputs group size return new BigNumber(1000); // Otherwise else // Return outputs group size return new BigNumber(400); } // Verifying outputs group size static get VERIFYING_OUTPUTS_GROUP_SIZE() { // Return verifying outputs group size return 100; } // Resync delay milliseconds static get RESYNC_DELAY_MILLISECONDS() { // Return resync delay milliseconds return 600; } // Unknown percent complete static get UNKNOWN_PERCENT_COMPLETE() { // Return unknown percent complete return null; } // Minimum percent static get MINIMUM_PERCENT() { // Return minimum percent return 0; } // Maximum percent static get MAXIMUM_PERCENT() { // Return maximum percent return 100; } // Variation from previous block height static get VARIATION_FROM_PREVIOUS_BLOCK_HEIGHT() { // Return variation from previous block height return Consensus.BLOCK_HEIGHT_WEEK; } // Variation to next block height static get VARIATION_TO_NEXT_BLOCK_HEIGHT() { // Return variation to next block height return Consensus.BLOCK_HEIGHT_WEEK; } // No last sync start index static get NO_LAST_SYNC_START_INDEX() { // Return no last sync start index return -1; } // No last sync last retrieved index static get NO_LAST_SYNC_LAST_RETRIEVED_INDEX() { // Return no last sync last retrieved index return -1; } // Percent complete precision static get PERCENT_COMPLETE_PRECISION() { // Return percent complete precision return 1; } // Replay detection threshold static get REPLAY_DETECTION_THRESHOLD() { // Return replay detection threshold return Consensus.BLOCK_HEIGHT_WEEK; } // Identifier height overage threshold static get IDENTIFIER_HEIGHT_OVERAGE_THRESHOLD() { // Return identifier height overage threshold return Consensus.BLOCK_HEIGHT_WEEK; } // No output static get NO_OUTPUT() { // Return no output return null; } // No new values static get NO_NEW_VALUES() { // Return no new values return null; } // No transaction static get NO_TRANSACTION() { // Return no transaction return null; } // New name value static get NEW_NAME_VALUE() { // Return new name value return "New Name Value"; } // New synced height value static get NEW_SYNCED_HEIGHT_VALUE() { // Return new synced height value return "New Synced Height Value"; } // New unconfirmed amount value static get NEW_UNCONFIRMED_AMOUNT_VALUE() { // Return new unconfirmed amount value return "New Unconfirmed Amount Value"; } // New locked amount value static get NEW_LOCKED_AMOUNT_VALUE() { // Return new locked amount value return "New Locked Amount Value"; } // New unspent amount value static get NEW_UNSPENT_AMOUNT_VALUE() { // Return new unspent amount value return "New Unspent Amount Value"; } // New spent amount value static get NEW_SPENT_AMOUNT_VALUE() { // Return new spent amount value return "New Spent Amount Value"; } // New pending amount value static get NEW_PENDING_AMOUNT_VALUE() { // Return new pending amount value return "New Pending Amount Value"; } // New expired amount value static get NEW_EXPIRED_AMOUNT_VALUE() { // Return new expired amount value return "New Expired Amount Value"; } // New address suffix value static get NEW_ADDRESS_SUFFIX_VALUE() { // Return new address suffix value return "New Address Suffix Value"; } // New last identifier value static get NEW_LAST_IDENTIFIER_VALUE() { // Return new last identifier value return "New Last Identifier Value"; } // New salt value static get NEW_SALT_VALUE() { // Return new salt value return "New Salt Value"; } // New number of iterations value static get NEW_NUMBER_OF_ITERATIONS_VALUE() { // Return new number of iterations value return "New Number Of Iterations Value"; } // New initialization vector value static get NEW_INITIALIZATION_VECTOR_VALUE() { // Return new initialization vector value return "New Initialization Vector Value"; } // New encrypted seed value static get NEW_ENCRYPTED_SEED_VALUE() { // Return new encrypted seed value return "New Encrypted Seed Value"; } // New order value static get NEW_ORDER_VALUE() { // Return new order value return "New Order Value"; } // New encrypted root public key value static get NEW_ENCRYPTED_ROOT_PUBLIC_KEY_VALUE() { // Return new encrypted root public key value return "New Encrypted Root Public Key Value"; } // New encrypted BIP39 salt value static get NEW_ENCRYPTED_BIP39_SALT_VALUE() { // Return new encrypted BIP39 salt value return "New Encrypted BIP39 Salt Value"; } // New hardware type value static get NEW_HARDWARE_TYPE_VALUE() { // Return new hardware type value return "New Hardware Type Value"; } // Change password salt index static get CHANGE_PASSWORD_SALT_INDEX() { // Return change password salt index return 0; } // Change password number of iterations index static get CHANGE_PASSWORD_NUMBER_OF_ITERATIONS_INDEX() { // Return change password number of iterations index return Wallets.CHANGE_PASSWORD_SALT_INDEX + 1; } // Change password initialization vector index static get CHANGE_PASSWORD_INITIALIZATION_VECTOR_INDEX() { // Return change password initialization vector index return Wallets.CHANGE_PASSWORD_NUMBER_OF_ITERATIONS_INDEX + 1; } // Change password encrypted seed index static get CHANGE_PASSWORD_ENCRYPTED_SEED_INDEX() { // Return change password encrypted seed index return Wallets.CHANGE_PASSWORD_INITIALIZATION_VECTOR_INDEX + 1; } // Change password encrypted BIP39 salt index static get CHANGE_PASSWORD_ENCRYPTED_BIP39_SALT_INDEX() { // Return change password encrypted BIP39 salt index return Wallets.CHANGE_PASSWORD_ENCRYPTED_SEED_INDEX + 1; } // Change password encrypted root public key index static get CHANGE_PASSWORD_ENCRYPTED_ROOT_PUBLIC_KEY_INDEX() { // Return change password encrypted root public key index return Wallets.CHANGE_PASSWORD_INITIALIZATION_VECTOR_INDEX + 1; } // Settings number of confirmations name static get SETTINGS_NUMBER_OF_CONFIRMATIONS_NAME() { // Return settings number of confirmations name return "Number Of Confirmations"; } // Settings primary address type default value static get SETTINGS_NUMBER_OF_CONFIRMATIONS_DEFAULT_VALUE() { // Return settings number of confirmations default value return (new BigNumber(10)).toFixed(); } // Exclusive hardware lock release event static get EXCLUSIVE_HARDWARE_LOCK_RELEASE_EVENT() { // Return exclusive hardware lock release event return "WalletsExclusiveHardwareLockReleaseEvent"; } // Spent outputs change to spent index static get SPENT_OUTPUTS_CHANGE_TO_SPENT_INDEX() { // Return spent outputs change to spent index return 0; } // Spent outputs change to unspent index static get SPENT_OUTPUTS_CHANGE_TO_UNSPENT_INDEX() { // Return spent outputs change to unspent index return Wallets.SPENT_OUTPUTS_CHANGE_TO_SPENT_INDEX + 1; } // Spent outputs change to locked index static get SPENT_OUTPUTS_CHANGE_TO_LOCKED_INDEX() { // Return spent outputs change to locked index return Wallets.SPENT_OUTPUTS_CHANGE_TO_UNSPENT_INDEX + 1; } } // Main function // Set global object's wallets globalThis["Wallets"] = Wallets;