UNPKG

bivcore-wallet-service

Version:
1,396 lines 50.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __spreadArrays = (this && this.__spreadArrays) || function () { for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; for (var r = Array(s), k = 0, i = 0; i < il; i++) for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Storage = void 0; var async = __importStar(require("async")); var lodash_1 = __importDefault(require("lodash")); var mongodb = __importStar(require("mongodb")); var logger_1 = __importDefault(require("./logger")); var model_1 = require("./model"); var BCHAddressTranslator = require('./bchaddresstranslator'); var $ = require('preconditions').singleton(); var collections = { WALLETS: 'wallets', TXS: 'txs', ADDRESSES: 'addresses', ADVERTISEMENTS: 'advertisements', NOTIFICATIONS: 'notifications', COPAYERS_LOOKUP: 'copayers_lookup', PREFERENCES: 'preferences', EMAIL_QUEUE: 'email_queue', CACHE: 'cache', FIAT_RATES2: 'fiat_rates2', TX_NOTES: 'tx_notes', SESSIONS: 'sessions', PUSH_NOTIFICATION_SUBS: 'push_notification_subs', TX_CONFIRMATION_SUBS: 'tx_confirmation_subs', LOCKS: 'locks' }; var Common = require('./common'); var Constants = Common.Constants; var Storage = (function () { function Storage(opts) { var _this = this; if (opts === void 0) { opts = {}; } this.walletCheck = function (params) { return __awaiter(_this, void 0, void 0, function () { var walletId; var _this = this; return __generator(this, function (_a) { walletId = params.walletId; return [2, new Promise(function (resolve) { var addressStream = _this.db.collection(collections.ADDRESSES).find({ walletId: walletId }); var sum = 0; var lastAddress; addressStream.on('data', function (walletAddress) { if (walletAddress.address) { lastAddress = walletAddress.address.replace(/:.*$/, ''); var addressSum = Buffer.from(lastAddress).reduce(function (tot, cur) { return (tot + cur) % Number.MAX_SAFE_INTEGER; }); sum = (sum + addressSum) % Number.MAX_SAFE_INTEGER; } }); addressStream.on('end', function () { resolve({ lastAddress: lastAddress, sum: sum }); }); })]; }); }); }; opts = opts || {}; this.db = opts.db; } Storage.createIndexes = function (db) { logger_1.default.info('Creating DB indexes'); if (!db.collection) { console.log('[storage.ts.55] no db.collection'); logger_1.default.error('DB not ready'); return; } db.collection(collections.WALLETS).createIndex({ id: 1 }); db.collection(collections.COPAYERS_LOOKUP).createIndex({ copayerId: 1 }); db.collection(collections.COPAYERS_LOOKUP).createIndex({ walletId: 1 }); db.collection(collections.TXS).createIndex({ walletId: 1, id: 1 }); db.collection(collections.TXS).createIndex({ walletId: 1, isPending: 1, txid: 1 }); db.collection(collections.TXS).createIndex({ walletId: 1, createdOn: -1 }); db.collection(collections.TXS).createIndex({ txid: 1 }); db.collection(collections.NOTIFICATIONS).createIndex({ walletId: 1, id: 1 }); db.collection(collections.ADVERTISEMENTS).createIndex({ advertisementId: 1, title: 1 }, { unique: true }); db.collection(collections.ADDRESSES).createIndex({ walletId: 1, createdOn: 1 }); db.collection(collections.ADDRESSES).createIndex({ address: 1 }, { unique: true }); db.collection(collections.ADDRESSES).createIndex({ address: 1, beRegistered: 1 }); db.collection(collections.ADDRESSES).createIndex({ walletId: 1, address: 1 }); db.collection(collections.EMAIL_QUEUE).createIndex({ id: 1 }); db.collection(collections.EMAIL_QUEUE).createIndex({ notificationId: 1 }); db.collection(collections.CACHE).createIndex({ walletId: 1, type: 1, key: 1 }); db.collection(collections.TX_NOTES).createIndex({ walletId: 1, txid: 1 }); db.collection(collections.PREFERENCES).createIndex({ walletId: 1 }); db.collection(collections.FIAT_RATES2).createIndex({ coin: 1, code: 1, ts: 1 }); db.collection(collections.PUSH_NOTIFICATION_SUBS).createIndex({ copayerId: 1 }); db.collection(collections.TX_CONFIRMATION_SUBS).createIndex({ copayerId: 1, txid: 1 }); db.collection(collections.TX_CONFIRMATION_SUBS).createIndex({ isActive: 1, copayerId: 1 }); db.collection(collections.SESSIONS).createIndex({ copayerId: 1 }); }; Storage.prototype.connect = function (opts, cb) { var _this = this; opts = opts || {}; if (this.db) return cb(); var config = opts.mongoDb || {}; if (opts.secondaryPreferred) { if (config.uri.indexOf('?') > 0) { config.uri = config.uri + '&'; } else { config.uri = config.uri + '?'; } config.uri = config.uri + 'readPreference=secondaryPreferred'; logger_1.default.info('Read operations set to secondaryPreferred'); } if (!config.dbname) { logger_1.default.error('No dbname at config.'); return cb(new Error('No dbname at config.')); } mongodb.MongoClient.connect(config.uri, { useUnifiedTopology: true }, function (err, client) { if (err) { logger_1.default.error('Unable to connect to the mongoDB. Check the credentials.'); return cb(err); } _this.db = client.db(config.dbname); _this.client = client; logger_1.default.info("Connection established to db: " + config.uri); Storage.createIndexes(_this.db); return cb(); }); }; Storage.prototype.disconnect = function (cb) { var _this = this; if (this.client) { this.client.close(function (err) { if (err) return cb(err); _this.db = null; _this.client = null; return cb(); }); } else { return cb(); } }; Storage.prototype.fetchWallet = function (id, cb) { if (!this.db) return cb('not ready'); this.db.collection(collections.WALLETS).findOne({ id: id }, function (err, result) { if (err) return cb(err); if (!result) return cb(); return cb(null, model_1.Wallet.fromObj(result)); }); }; Storage.prototype.storeWallet = function (wallet, cb) { this.db.collection(collections.WALLETS).replaceOne({ id: wallet.id }, wallet.toObject(), { w: 1, upsert: true }, cb); }; Storage.prototype.storeWalletAndUpdateCopayersLookup = function (wallet, cb) { var _this = this; var copayerLookups = lodash_1.default.map(wallet.copayers, function (copayer) { try { $.checkState(copayer.requestPubKeys); } catch (e) { return cb(e); } return { copayerId: copayer.id, walletId: wallet.id, requestPubKeys: copayer.requestPubKeys }; }); this.db.collection(collections.COPAYERS_LOOKUP).deleteMany({ walletId: wallet.id }, { w: 1 }, function (err) { if (err) return cb(err); _this.db.collection(collections.COPAYERS_LOOKUP).insertMany(copayerLookups, { w: 1 }, function (err) { if (err) return cb(err); return _this.storeWallet(wallet, cb); }); }); }; Storage.prototype.fetchCopayerLookup = function (copayerId, cb) { this.db.collection(collections.COPAYERS_LOOKUP).findOne({ copayerId: copayerId }, function (err, result) { if (err) return cb(err); if (!result) return cb(); if (!result.requestPubKeys) { result.requestPubKeys = [ { key: result.requestPubKey, signature: result.signature } ]; } return cb(null, result); }); }; Storage.prototype._completeTxData = function (walletId, txs, cb) { this.fetchWallet(walletId, function (err, wallet) { if (err) return cb(err); lodash_1.default.each([].concat(txs), function (tx) { tx.derivationStrategy = wallet.derivationStrategy || 'BIP45'; tx.creatorName = wallet.getCopayer(tx.creatorId).name; lodash_1.default.each(tx.actions, function (action) { action.copayerName = wallet.getCopayer(action.copayerId).name; }); if (tx.status == 'accepted') tx.raw = tx.getRawTx(); }); return cb(null, txs); }); }; Storage.prototype.fetchTx = function (walletId, txProposalId, cb) { var _this = this; if (!this.db) return cb(); this.db.collection(collections.TXS).findOne({ id: txProposalId, walletId: walletId }, function (err, result) { if (err) return cb(err); if (!result) return cb(); return _this._completeTxData(walletId, model_1.TxProposal.fromObj(result), cb); }); }; Storage.prototype.fetchTxByHash = function (hash, cb) { var _this = this; if (!this.db) return cb(); this.db.collection(collections.TXS).findOne({ txid: hash }, function (err, result) { if (err) return cb(err); if (!result) return cb(); return _this._completeTxData(result.walletId, model_1.TxProposal.fromObj(result), cb); }); }; Storage.prototype.fetchLastTxs = function (walletId, creatorId, limit, cb) { this.db .collection(collections.TXS) .find({ walletId: walletId, creatorId: creatorId }, { limit: limit || 5 }) .sort({ createdOn: -1 }) .toArray(function (err, result) { if (err) return cb(err); if (!result) return cb(); var txs = lodash_1.default.map(result, function (tx) { return model_1.TxProposal.fromObj(tx); }); return cb(null, txs); }); }; Storage.prototype.fetchEthPendingTxs = function (multisigTxpsInfo) { var _this = this; return new Promise(function (resolve, reject) { _this.db .collection(collections.TXS) .find({ txid: { $in: multisigTxpsInfo.map(function (txpInfo) { return txpInfo.transactionHash; }) } }) .sort({ createdOn: -1 }) .toArray(function (err, result) { return __awaiter(_this, void 0, void 0, function () { var multisigTxpsInfoByTransactionHash, actionsById, txs; return __generator(this, function (_a) { if (err) return [2, reject(err)]; if (!result) return [2, reject()]; multisigTxpsInfoByTransactionHash = lodash_1.default.groupBy(multisigTxpsInfo, 'transactionHash'); actionsById = {}; txs = lodash_1.default.compact(lodash_1.default.map(result, function (tx) { if (!tx.multisigContractAddress) { return undefined; } tx.status = 'pending'; tx.multisigTxId = multisigTxpsInfoByTransactionHash[tx.txid][0].transactionId; tx.actions.forEach(function (action) { if (lodash_1.default.some(multisigTxpsInfoByTransactionHash[tx.txid], { event: 'ExecutionFailure' })) { action.type = 'failed'; } }); if (tx.amount === 0) { actionsById[tx.multisigTxId] = __spreadArrays(tx.actions, (actionsById[tx.multisigTxId] || [])); return undefined; } return model_1.TxProposal.fromObj(tx); })); txs.forEach(function (tx) { if (actionsById[tx.multisigTxId]) { tx.actions = __spreadArrays(tx.actions, (actionsById[tx.multisigTxId] || [])); } }); return [2, resolve(txs)]; }); }); }); }); }; Storage.prototype.fetchPendingTxs = function (walletId, cb) { var _this = this; this.db .collection(collections.TXS) .find({ walletId: walletId, isPending: true }) .sort({ createdOn: -1 }) .toArray(function (err, result) { if (err) return cb(err); if (!result) return cb(); var txs = lodash_1.default.map(result, function (tx) { return model_1.TxProposal.fromObj(tx); }); return _this._completeTxData(walletId, txs, cb); }); }; Storage.prototype.fetchTxs = function (walletId, opts, cb) { var _this = this; opts = opts || {}; var tsFilter = {}; if (lodash_1.default.isNumber(opts.minTs)) tsFilter.$gte = opts.minTs; if (lodash_1.default.isNumber(opts.maxTs)) tsFilter.$lte = opts.maxTs; var filter = { walletId: walletId }; if (!lodash_1.default.isEmpty(tsFilter)) filter.createdOn = tsFilter; var mods = {}; if (lodash_1.default.isNumber(opts.limit)) mods.limit = opts.limit; this.db .collection(collections.TXS) .find(filter, mods) .sort({ createdOn: -1 }) .toArray(function (err, result) { if (err) return cb(err); if (!result) return cb(); var txs = lodash_1.default.map(result, function (tx) { return model_1.TxProposal.fromObj(tx); }); return _this._completeTxData(walletId, txs, cb); }); }; Storage.prototype.fetchBroadcastedTxs = function (walletId, opts, cb) { var _this = this; opts = opts || {}; var tsFilter = {}; if (lodash_1.default.isNumber(opts.minTs)) tsFilter.$gte = opts.minTs; if (lodash_1.default.isNumber(opts.maxTs)) tsFilter.$lte = opts.maxTs; var filter = { walletId: walletId, status: 'broadcasted' }; if (!lodash_1.default.isEmpty(tsFilter)) filter.broadcastedOn = tsFilter; var mods = {}; if (lodash_1.default.isNumber(opts.limit)) mods.limit = opts.limit; this.db .collection(collections.TXS) .find(filter, mods) .sort({ createdOn: -1 }) .toArray(function (err, result) { if (err) return cb(err); if (!result) return cb(); var txs = lodash_1.default.map(result, function (tx) { return model_1.TxProposal.fromObj(tx); }); return _this._completeTxData(walletId, txs, cb); }); }; Storage.prototype.fetchNotifications = function (walletId, notificationId, minTs, cb) { function makeId(timestamp) { return lodash_1.default.padStart(timestamp, 14, '0') + lodash_1.default.repeat('0', 4); } var minId = makeId(minTs); if (notificationId) { minId = notificationId > minId ? notificationId : minId; } this.db .collection(collections.NOTIFICATIONS) .find({ walletId: walletId, id: { $gt: minId } }) .sort({ id: 1 }) .toArray(function (err, result) { if (err) return cb(err); if (!result) return cb(); var notifications = lodash_1.default.map(result, function (notification) { return model_1.Notification.fromObj(notification); }); return cb(null, notifications); }); }; Storage.prototype.storeNotification = function (walletId, notification, cb) { if (!this.db) { logger_1.default.warn('Trying to store a notification with close DB', notification); return; } this.db.collection(collections.NOTIFICATIONS).insertOne(notification, { w: 1 }, cb); }; Storage.prototype.storeTx = function (walletId, txp, cb) { this.db.collection(collections.TXS).replaceOne({ id: txp.id, walletId: walletId }, txp.toObject(), { w: 1, upsert: true }, cb); }; Storage.prototype.removeTx = function (walletId, txProposalId, cb) { this.db.collection(collections.TXS).deleteOne({ id: txProposalId, walletId: walletId }, { w: 1 }, cb); }; Storage.prototype.removeWallet = function (walletId, cb) { var _this = this; async.parallel([ function (next) { _this.db.collection(collections.WALLETS).deleteOne({ id: walletId }, next); }, function (next) { var otherCollections = lodash_1.default.without(lodash_1.default.values(collections), collections.WALLETS); async.each(otherCollections, function (col, next) { _this.db.collection(col).deleteMany({ walletId: walletId }, next); }, next); } ], cb); }; Storage.prototype.fetchAddresses = function (walletId, cb) { this.db .collection(collections.ADDRESSES) .find({ walletId: walletId }) .sort({ createdOn: 1 }) .toArray(function (err, result) { if (err) return cb(err); if (!result) return cb(); return cb(null, result.map(model_1.Address.fromObj)); }); }; Storage.prototype.migrateToCashAddr = function (walletId, cb) { var _this = this; var cursor = this.db.collection(collections.ADDRESSES).find({ walletId: walletId }); cursor.on('end', function () { console.log("Migration to cash address of " + walletId + " Finished"); return _this.clearWalletCache(walletId, cb); }); cursor.on('err', function (err) { return cb(err); }); cursor.on('data', function (doc) { cursor.pause(); var x; try { x = BCHAddressTranslator.translate(doc.address, 'cashaddr'); } catch (e) { return cb(e); } _this.db.collection(collections.ADDRESSES).updateMany({ _id: doc._id }, { $set: { address: x } }); cursor.resume(); }); }; Storage.prototype.fetchUnsyncAddresses = function (walletId, cb) { this.db .collection(collections.ADDRESSES) .find({ walletId: walletId, beRegistered: null }) .toArray(function (err, result) { if (err) return cb(err); if (!result) return cb(); return cb(null, result); }); }; Storage.prototype.fetchNewAddresses = function (walletId, fromTs, cb) { this.db .collection(collections.ADDRESSES) .find({ walletId: walletId, createdOn: { $gte: fromTs } }) .sort({ createdOn: 1 }) .toArray(function (err, result) { if (err) return cb(err); if (!result) return cb(); return cb(null, result.map(model_1.Address.fromObj)); }); }; Storage.prototype.storeAddress = function (address, cb) { this.db.collection(collections.ADDRESSES).replaceOne({ walletId: address.walletId, address: address.address }, address, { w: 1, upsert: false }, cb); }; Storage.prototype.markSyncedAddresses = function (addresses, cb) { this.db.collection(collections.ADDRESSES).updateMany({ address: { $in: addresses } }, { $set: { beRegistered: true } }, { w: 1, upsert: false }, cb); }; Storage.prototype.deregisterWallet = function (walletId, cb) { var _this = this; this.db.collection(collections.WALLETS).updateOne({ id: walletId }, { $set: { beRegistered: null } }, { w: 1, upsert: false }, function () { _this.db.collection(collections.ADDRESSES).updateMany({ walletId: walletId }, { $set: { beRegistered: null } }, { w: 1, upsert: false }, function () { _this.clearWalletCache(walletId, cb); }); }); }; Storage.prototype.storeAddressAndWallet = function (wallet, addresses, cb) { var _this = this; var clonedAddresses = [].concat(addresses); if (lodash_1.default.isEmpty(addresses)) return cb(); var duplicate; this.db.collection(collections.ADDRESSES).insertMany(clonedAddresses, { w: 1 }, function (err) { if (err) { if (!err.toString().match(/E11000/)) { return cb(err); } else { duplicate = true; logger_1.default.warn('Found duplicate address: ' + lodash_1.default.join(lodash_1.default.map(clonedAddresses, 'address'), ',')); } } _this.storeWallet(wallet, function (err) { return cb(err, duplicate); }); }); }; Storage.prototype.fetchAddressByWalletId = function (walletId, address, cb) { this.db.collection(collections.ADDRESSES).findOne({ walletId: walletId, address: address }, function (err, result) { if (err) return cb(err); if (!result) return cb(); return cb(null, model_1.Address.fromObj(result)); }); }; Storage.prototype.fetchAddressesByWalletId = function (walletId, addresses, cb) { this.db .collection(collections.ADDRESSES) .find({ walletId: walletId, address: { $in: addresses } }, {}) .toArray(function (err, result) { if (err) return cb(err); if (!result) return cb(); return cb(null, result); }); }; Storage.prototype.fetchAddressByCoin = function (coin, address, cb) { if (!this.db) return cb(); this.db .collection(collections.ADDRESSES) .find({ address: address }) .toArray(function (err, result) { if (err) return cb(err); if (!result || lodash_1.default.isEmpty(result)) return cb(); if (result.length > 1) { result = lodash_1.default.find(result, function (address) { return coin == (address.coin || 'btc'); }); } else { result = lodash_1.default.head(result); } if (!result) return cb(); return cb(null, model_1.Address.fromObj(result)); }); }; Storage.prototype.fetchPreferences = function (walletId, copayerId, cb) { this.db .collection(collections.PREFERENCES) .find({ walletId: walletId }) .toArray(function (err, result) { if (err) return cb(err); if (copayerId) { result = lodash_1.default.find(result, { copayerId: copayerId }); } if (!result) return cb(); var preferences = lodash_1.default.map([].concat(result), function (r) { return model_1.Preferences.fromObj(r); }); if (copayerId) { return cb(null, preferences[0]); } else { return cb(null, preferences); } }); }; Storage.prototype.storePreferences = function (preferences, cb) { this.db.collection(collections.PREFERENCES).replaceOne({ walletId: preferences.walletId, copayerId: preferences.copayerId }, preferences, { w: 1, upsert: true }, cb); }; Storage.prototype.storeEmail = function (email, cb) { this.db.collection(collections.EMAIL_QUEUE).replaceOne({ id: email.id }, email, { w: 1, upsert: true }, cb); }; Storage.prototype.fetchUnsentEmails = function (cb) { this.db .collection(collections.EMAIL_QUEUE) .find({ status: 'fail' }) .toArray(function (err, result) { if (err) return cb(err); if (!result || lodash_1.default.isEmpty(result)) return cb(null, []); var emails = lodash_1.default.map(result, function (x) { return model_1.Email.fromObj(x); }); return cb(null, emails); }); }; Storage.prototype.fetchEmailByNotification = function (notificationId, cb) { this.db.collection(collections.EMAIL_QUEUE).findOne({ notificationId: notificationId }, function (err, result) { if (err) return cb(err); if (!result) return cb(); return cb(null, model_1.Email.fromObj(result)); }); }; Storage.prototype.getTxHistoryCacheStatusV8 = function (walletId, cb) { this.db.collection(collections.CACHE).findOne({ walletId: walletId, type: 'historyCacheStatusV8', key: null }, function (err, result) { if (err) return cb(err); if (!result) return cb(null, { tipId: null, tipIndex: null }); return cb(null, { updatedOn: result.updatedOn, updatedHeight: result.updatedHeight, tipIndex: result.tipIndex, tipTxId: result.tipTxId, tipHeight: result.tipHeight }); }); }; Storage.prototype.getWalletAddressChecked = function (walletId, cb) { this.db.collection(collections.CACHE).findOne({ walletId: walletId, type: 'addressChecked', key: null }, function (err, result) { if (err || !result) return cb(err); return cb(null, result.totalAddresses); }); }; Storage.prototype.setWalletAddressChecked = function (walletId, totalAddresses, cb) { this.db.collection(collections.CACHE).replaceOne({ walletId: walletId, type: 'addressChecked', key: null }, { walletId: walletId, type: 'addressChecked', key: null, totalAddresses: totalAddresses }, { w: 1, upsert: true }, cb); }; Storage.prototype.getTxHistoryCacheV8 = function (walletId, skip, limit, cb) { var _this = this; $.checkArgument(skip >= 0); $.checkArgument(limit >= 0); this.getTxHistoryCacheStatusV8(walletId, function (err, cacheStatus) { if (err) return cb(err); if (lodash_1.default.isNull(cacheStatus.tipId)) return cb(null, []); var firstPosition = cacheStatus.tipIndex - skip - limit + 1; var lastPosition = cacheStatus.tipIndex - skip + 1; if (firstPosition < 0) firstPosition = 0; if (lastPosition <= 0) return cb(null, []); _this.db .collection(collections.CACHE) .find({ walletId: walletId, type: 'historyCacheV8', key: { $gte: firstPosition, $lt: lastPosition } }) .sort({ key: -1 }) .toArray(function (err, result) { if (err) return cb(err); if (!result) return cb(); var txs = lodash_1.default.map(result, 'tx'); return cb(null, txs); }); }); }; Storage.prototype.clearWalletCache = function (walletId, cb) { this.db.collection(collections.CACHE).deleteMany({ walletId: walletId }, {}, cb); }; Storage.prototype.storeTxHistoryStreamV8 = function (walletId, streamKey, items, cb) { this.db.collection(collections.CACHE).replaceOne({ walletId: walletId, type: 'historyStream', key: null }, { walletId: walletId, type: 'historyStream', key: null, streamKey: streamKey, items: items }, { w: 1, upsert: true }, cb); }; Storage.prototype.clearTxHistoryStreamV8 = function (walletId, cb) { this.db.collection(collections.CACHE).deleteMany({ walletId: walletId, type: 'historyStream', key: null }, {}, cb); }; Storage.prototype.getTxHistoryStreamV8 = function (walletId, cb) { this.db.collection(collections.CACHE).findOne({ walletId: walletId, type: 'historyStream', key: null }, function (err, result) { if (err || !result) return cb(err); return cb(null, result); }); }; Storage.prototype.storeTxHistoryCacheV8 = function (walletId, tipIndex, items, updateHeight, cb) { var _this = this; var index = lodash_1.default.isNull(tipIndex) ? 0 : tipIndex + 1; var pos; lodash_1.default.each(items.reverse(), function (item) { item.position = index++; }); async.each(items, function (item, next) { pos = item.position; delete item.position; _this.db.collection(collections.CACHE).insertOne({ walletId: walletId, type: 'historyCacheV8', key: pos, tx: item }, next); }, function (err) { if (err) return cb(err); var first = lodash_1.default.first(items); var last = lodash_1.default.last(items); try { $.checkState(last.txid, 'missing txid in tx to be cached'); $.checkState(last.blockheight, 'missing blockheight in tx to be cached'); $.checkState(first.blockheight, 'missing blockheight in tx to be cached'); $.checkState(last.blockheight >= 0, 'blockheight <=0 om tx to be cached'); $.checkState(first.blockheight <= last.blockheight, 'tx to be cached are in wrong order (lastest should be first)'); } catch (e) { return cb(e); } logger_1.default.debug("Cache Last Item: " + last.txid + " blockh: " + last.blockheight + " updatedh: " + updateHeight); _this.db.collection(collections.CACHE).replaceOne({ walletId: walletId, type: 'historyCacheStatusV8', key: null }, { walletId: walletId, type: 'historyCacheStatusV8', key: null, updatedOn: Date.now(), updatedHeight: updateHeight, tipIndex: pos, tipTxId: last.txid, tipHeight: last.blockheight }, { w: 1, upsert: true }, cb); }); }; Storage.prototype.storeFiatRate = function (coin, rates, cb) { var _this = this; var now = Date.now(); async.each(rates, function (rate, next) { var i = { ts: now, coin: coin, code: rate.code, value: rate.value }; _this.db.collection(collections.FIAT_RATES2).insertOne(i, { w: 1 }, next); }, cb); }; Storage.prototype.fetchFiatRate = function (coin, code, ts, cb) { this.db .collection(collections.FIAT_RATES2) .find({ coin: coin, code: code, ts: { $lte: ts } }) .sort({ ts: -1 }) .limit(1) .toArray(function (err, result) { if (err || lodash_1.default.isEmpty(result)) return cb(err); return cb(null, result[0]); }); }; Storage.prototype.fetchHistoricalRates = function (coin, code, ts, cb) { this.db .collection(collections.FIAT_RATES2) .find({ coin: coin, code: code, ts: { $gte: ts } }) .sort({ ts: -1 }) .toArray(function (err, result) { if (err || lodash_1.default.isEmpty(result)) return cb(err); return cb(null, result); }); }; Storage.prototype.fetchTxNote = function (walletId, txid, cb) { var _this = this; this.db.collection(collections.TX_NOTES).findOne({ walletId: walletId, txid: txid }, function (err, result) { if (err) return cb(err); if (!result) return cb(); return _this._completeTxNotesData(walletId, model_1.TxNote.fromObj(result), cb); }); }; Storage.prototype._completeTxNotesData = function (walletId, notes, cb) { this.fetchWallet(walletId, function (err, wallet) { if (err) return cb(err); lodash_1.default.each([].concat(notes), function (note) { note.editedByName = wallet.getCopayer(note.editedBy).name; }); return cb(null, notes); }); }; Storage.prototype.fetchTxNotes = function (walletId, opts, cb) { var _this = this; var filter = { walletId: walletId }; if (lodash_1.default.isNumber(opts.minTs)) filter.editedOn = { $gte: opts.minTs }; this.db .collection(collections.TX_NOTES) .find(filter) .toArray(function (err, result) { if (err) return cb(err); var notes = lodash_1.default.compact(lodash_1.default.map(result, function (note) { return model_1.TxNote.fromObj(note); })); return _this._completeTxNotesData(walletId, notes, cb); }); }; Storage.prototype.storeTxNote = function (txNote, cb) { this.db.collection(collections.TX_NOTES).replaceOne({ txid: txNote.txid, walletId: txNote.walletId }, txNote.toObject(), { w: 1, upsert: true }, cb); }; Storage.prototype.getSession = function (copayerId, cb) { this.db.collection(collections.SESSIONS).findOne({ copayerId: copayerId }, function (err, result) { if (err || !result) return cb(err); return cb(null, model_1.Session.fromObj(result)); }); }; Storage.prototype.storeSession = function (session, cb) { this.db.collection(collections.SESSIONS).replaceOne({ copayerId: session.copayerId }, session.toObject(), { w: 1, upsert: true }, cb); }; Storage.prototype.fetchPushNotificationSubs = function (copayerId, cb) { this.db .collection(collections.PUSH_NOTIFICATION_SUBS) .find({ copayerId: copayerId }) .toArray(function (err, result) { if (err) return cb(err); if (!result) return cb(); var tokens = lodash_1.default.map([].concat(result), function (r) { return model_1.PushNotificationSub.fromObj(r); }); return cb(null, tokens); }); }; Storage.prototype.storePushNotificationSub = function (pushNotificationSub, cb) { this.db.collection(collections.PUSH_NOTIFICATION_SUBS).replaceOne({ copayerId: pushNotificationSub.copayerId, token: pushNotificationSub.token }, pushNotificationSub, { w: 1, upsert: true }, cb); }; Storage.prototype.removePushNotificationSub = function (copayerId, token, cb) { this.db.collection(collections.PUSH_NOTIFICATION_SUBS).deleteMany({ copayerId: copayerId, token: token }, { w: 1 }, cb); }; Storage.prototype.fetchActiveTxConfirmationSubs = function (copayerId, cb) { if (!this.db) { logger_1.default.warn('Trying to fetch notifications with closed DB'); return; } var filter = { isActive: true }; if (copayerId) filter.copayerId = copayerId; this.db .collection(collections.TX_CONFIRMATION_SUBS) .find(filter) .toArray(function (err, result) { if (err) return cb(err); if (!result) return cb(); var subs = lodash_1.default.map([].concat(result), function (r) { return model_1.TxConfirmationSub.fromObj(r); }); return cb(null, subs); }); }; Storage.prototype.storeTxConfirmationSub = function (txConfirmationSub, cb) { this.db.collection(collections.TX_CONFIRMATION_SUBS).replaceOne({ copayerId: txConfirmationSub.copayerId, txid: txConfirmationSub.txid }, txConfirmationSub, { w: 1, upsert: true }, cb); }; Storage.prototype.removeTxConfirmationSub = function (copayerId, txid, cb) { this.db.collection(collections.TX_CONFIRMATION_SUBS).deleteMany({ copayerId: copayerId, txid: txid }, { w: 1 }, cb); }; Storage.prototype._dump = function (cb, fn) { fn = fn || console.log; cb = cb || function () { }; this.db.collections(function (err, collections) { if (err) return cb(err); async.eachSeries(collections, function (col, next) { col.find().toArray(function (err, items) { fn('--------', col.s.name); fn(items); fn('------------------------------------------------------------------\n\n'); next(err); }); }, cb); }); }; Storage.prototype.checkAndUseGlobalCache = function (key, duration, cb) { var now = Date.now(); this.db.collection(collections.CACHE).findOne({ key: key, walletId: null, type: null }, function (err, ret) { if (err) return cb(err); if (!ret) return cb(); var validFor = ret.ts + duration - now; return cb(null, validFor > 0 ? ret.result : null, ret.result); }); }; Storage.prototype.storeGlobalCache = function (key, values, cb) { var now = Date.now(); this.db.collection(collections.CACHE).replaceOne({ key: key, walletId: null, type: null }, { $set: { ts: now, result: values } }, { w: 1, upsert: true }, cb); }; Storage.prototype.clearGlobalCache = function (key, cb) { this.db.collection(collections.CACHE).deleteMany({ key: key, walletId: null, type: null }, { w: 1 }, cb); }; Storage.prototype.acquireLock = function (key, expireTs, cb) { this.db.collection(collections.LOCKS).insertOne({ _id: key, expireOn: expireTs }, {}, cb); }; Storage.prototype.releaseLock = function (key, cb) { this.db.collection(collections.LOCKS).deleteMany({ _id: key }, {}, cb); }; Storage.prototype.clearExpiredLock = function (key, cb) { var _this = this; this.db.collection(collections.LOCKS).findOne({ _id: key }, function (err, ret) { if (err || !ret) return; if (ret.expireOn < Date.now()) { logger_1.default.info('Releasing expired lock : ' + key); return _this.releaseLock(key, cb); } return cb(); }); }; Storage.prototype.fetchTestingAdverts = function (cb) { this.db .collection(collections.ADVERTISEMENTS) .find({ isTesting: true }) .toArray(function (err, result) { if (err) return cb(err); if (!result) return cb(); return cb(null, result.map(model_1.Advertisement.fromObj)); }); }; Storage.prototype.fetchActiveAdverts = function (cb) { this.db .collection(collections.ADVERTISEMENTS) .find({ isAdActive: true }) .toArray(function (err, result) { if (err) return cb(err); if (!result) return cb(); return cb(null, result.map(model_1.Advertisement.fromObj)); }); }; Storage.prototype.fetchAdvertsByCountry = function (country, cb) { this.db .collection(collections.ADVERTISEMENTS) .find({ country: country }) .toArray(function (err, result) { if (err) return cb(err); if (!result) return cb(); return cb(null, result.map(model_1.Advertisement.fromObj)); }); }; Storage.prototype.fetchAllAdverts = function (cb) { this.db.collection(collections.ADVERTISEMENTS).find({}); }; Storage.prototype.removeAdvert = function (adId, cb) { this.db.collection(collections.ADVERTISEMENTS).deleteOne({ advertisementId: adId }, { w: 1 }, cb); }; Storage.prototype.storeAdvert = function (advert, cb) { this.db.collection(collections.ADVERTISEMENTS).updateOne({ advertisementId: advert.advertisementId }, { $set: advert }, { upsert: true }, cb); }; Storage.prototype.fetchAdvert = function (adId, cb) { this.db.collection(collections.ADVERTISEMENTS).findOne({ advertisementId: adId }, function (err, result) { if (err) return cb(err); if (!result) return cb(); return cb(null, model_1.Advertisement.fromObj(r