UNPKG

@bsv/wallet-toolbox-client

Version:
1,184 lines 86.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (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 () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.normalizeDate = exports.triggerForeignKeyConstraintError = exports.triggerUniqueConstraintError = exports.logUniqueConstraintError = exports.setLogging = exports.validateUpdateTime = exports.verifyValues = exports.updateTable = exports.logger = exports._tu = exports.TestUtilsWalletStorage = void 0; exports.expectToThrowWERR = expectToThrowWERR; exports.cleanUnsentTransactionsUsingAbort = cleanUnsentTransactionsUsingAbort; exports.cleanUnsignedTransactionsUsingAbort = cleanUnsignedTransactionsUsingAbort; exports.cleanUnprocessedTransactionsUsingAbort = cleanUnprocessedTransactionsUsingAbort; exports.logTransaction = logTransaction; exports.logOutput = logOutput; exports.logInput = logInput; exports.logBasket = logBasket; const dotenv = __importStar(require("dotenv")); const path_1 = __importDefault(require("path")); const fs_1 = require("fs"); const knex_1 = require("knex"); const sdk_1 = require("@bsv/sdk"); const StorageIdb_1 = require("../../src/storage/StorageIdb"); const Setup_1 = require("../../src/Setup"); const StorageKnex_1 = require("../../src/storage/StorageKnex"); const Services_1 = require("../../src/services/Services"); const WERR_errors_1 = require("../../src/sdk/WERR_errors"); const WalletStorageManager_1 = require("../../src/storage/WalletStorageManager"); const Monitor_1 = require("../../src/monitor/Monitor"); const PrivilegedKeyManager_1 = require("../../src/sdk/PrivilegedKeyManager"); const Wallet_1 = require("../../src/Wallet"); const StorageClient_1 = require("../../src/storage/remoting/StorageClient"); const utilityHelpers_1 = require("../../src/utility/utilityHelpers"); const WalletError_1 = require("../../src/sdk/WalletError"); const StorageSyncReader_1 = require("../../src/storage/StorageSyncReader"); const utilityHelpers_noBuffer_1 = require("../../src/utility/utilityHelpers.noBuffer"); const ScriptTemplateBRC29_1 = require("../../src/utility/ScriptTemplateBRC29"); dotenv.config(); const localMySqlConnection = process.env.MYSQL_CONNECTION || ''; class TestUtilsWalletStorage { /** * @param chain * @returns true if .env has truthy idenityKey, idenityKey2 values for chain */ static noEnv(chain) { try { Setup_1.Setup.getEnv(chain); return false; } catch (_a) { return true; } } /** * @param chain * @returns true if .env is not valid for chain or testIdentityKey or testFilePath are undefined or empty. */ static noTestEnv(chain) { try { const env = _tu.getEnv(chain); return !env.testIdentityKey || !env.testFilePath; } catch (_a) { return true; } } static getEnvFlags(chain) { const logTests = !!process.env.LOGTESTS; const runMySQL = !!process.env.RUNMYSQL; const runSlowTests = !!process.env.RUNSLOWTESTS; return { chain, runMySQL, runSlowTests, logTests }; } static getEnv(chain) { const flagsEnv = _tu.getEnvFlags(chain); // Identity keys of the lead maintainer of this repo... const identityKey = (chain === 'main' ? process.env.MY_MAIN_IDENTITY : process.env.MY_TEST_IDENTITY) || ''; const filePath = chain === 'main' ? process.env.MY_MAIN_FILEPATH : process.env.MY_TEST_FILEPATH; const identityKey2 = (chain === 'main' ? process.env.MY_MAIN_IDENTITY2 : process.env.MY_TEST_IDENTITY2) || ''; const testIdentityKey = chain === 'main' ? process.env.TEST_MAIN_IDENTITY : process.env.TEST_TEST_IDENTITY; const testFilePath = chain === 'main' ? process.env.TEST_MAIN_FILEPATH : process.env.TEST_TEST_FILEPATH; const cloudMySQLConnection = chain === 'main' ? process.env.MAIN_CLOUD_MYSQL_CONNECTION : process.env.TEST_CLOUD_MYSQL_CONNECTION; const DEV_KEYS = process.env.DEV_KEYS || '{}'; const taalApiKey = (chain === 'main' ? process.env.MAIN_TAAL_API_KEY : process.env.TEST_TAAL_API_KEY) || ''; const bitailsApiKey = (chain === 'main' ? process.env.MAIN_BITAILS_API_KEY : process.env.TEST_BITAILS_API_KEY) || ''; const whatsonchainApiKey = (chain === 'main' ? process.env.MAIN_WHATSONCHAIN_API_KEY : process.env.TEST_WHATSONCHAIN_API_KEY) || ''; return { ...flagsEnv, identityKey, identityKey2, taalApiKey, bitailsApiKey, whatsonchainApiKey, devKeys: JSON.parse(DEV_KEYS), filePath, testIdentityKey, testFilePath, cloudMySQLConnection }; } static async createMainReviewSetup() { const env = _tu.getEnv('main'); if (!env.cloudMySQLConnection) throw new WERR_errors_1.WERR_INVALID_PARAMETER('env.cloudMySQLConnection', 'valid'); const knex = Setup_1.Setup.createMySQLKnex(env.cloudMySQLConnection); const storage = new StorageKnex_1.StorageKnex({ chain: env.chain, knex: knex, commissionSatoshis: 0, commissionPubKeyHex: undefined, feeModel: { model: 'sat/kb', value: 1 } }); const servicesOptions = Services_1.Services.createDefaultOptions(env.chain); if (env.whatsonchainApiKey) servicesOptions.whatsOnChainApiKey = env.whatsonchainApiKey; const services = new Services_1.Services(servicesOptions); storage.setServices(services); await storage.makeAvailable(); return { env, storage, services }; } static async createNoSendP2PKHTestOutpoint(address, satoshis, noSendChange, wallet) { return await _tu.createNoSendP2PKHTestOutpoints(1, address, satoshis, noSendChange, wallet); } static async createNoSendP2PKHTestOutpoints(count, address, satoshis, noSendChange, wallet) { const outputs = []; for (let i = 0; i < count; i++) { outputs.push({ basket: `test-p2pkh-output-${i}`, satoshis, lockingScript: _tu.getLockP2PKH(address).toHex(), outputDescription: `p2pkh ${i}` }); } const createArgs = { description: `to ${address}`, outputs, options: { noSendChange, randomizeOutputs: false, signAndProcess: false, noSend: true } }; const cr = await wallet.createAction(createArgs); noSendChange = cr.noSendChange; expect(cr.noSendChange).toBeTruthy(); expect(cr.sendWithResults).toBeUndefined(); expect(cr.tx).toBeUndefined(); expect(cr.txid).toBeUndefined(); expect(cr.signableTransaction).toBeTruthy(); const st = cr.signableTransaction; expect(st.reference).toBeTruthy(); // const tx = Transaction.fromAtomicBEEF(st.tx) // Transaction doesn't support V2 Beef yet. const atomicBeef = sdk_1.Beef.fromBinary(st.tx); const tx = atomicBeef.txs[atomicBeef.txs.length - 1].tx; for (const input of tx.inputs) { expect(atomicBeef.findTxid(input.sourceTXID)).toBeTruthy(); } // Spending authorization check happens here... //expect(st.amount > 242 && st.amount < 300).toBe(true) // sign and complete const signArgs = { reference: st.reference, spends: {}, options: { returnTXIDOnly: true, noSend: true } }; const sr = await wallet.signAction(signArgs); let txid = sr.txid; // Update the noSendChange txid to final signed value. noSendChange = noSendChange.map(op => `${txid}.${op.split('.')[1]}`); return { noSendChange, txid, cr, sr }; } static getKeyPair(priv) { if (priv === undefined) priv = sdk_1.PrivateKey.fromRandom(); else if (typeof priv === 'string') priv = new sdk_1.PrivateKey(priv, 'hex'); const pub = sdk_1.PublicKey.fromPrivateKey(priv); const address = pub.toAddress(); return { privateKey: priv, publicKey: pub, address }; } static getLockP2PKH(address) { const p2pkh = new sdk_1.P2PKH(); const lock = p2pkh.lock(address); return lock; } static getUnlockP2PKH(priv, satoshis) { const p2pkh = new sdk_1.P2PKH(); const lock = _tu.getLockP2PKH(_tu.getKeyPair(priv).address); // Prepare to pay with SIGHASH_ALL and without ANYONE_CAN_PAY. // In otherwords: // - all outputs must remain in the current order, amount and locking scripts. // - all inputs must remain from the current outpoints and sequence numbers. // (unlock scripts are never signed) const unlock = p2pkh.unlock(priv, 'all', false, satoshis, lock); return unlock; } static async createWalletOnly(args) { args.chain || (args.chain = 'test'); args.rootKeyHex || (args.rootKeyHex = '1'.repeat(64)); const rootKey = sdk_1.PrivateKey.fromHex(args.rootKeyHex); const identityKey = rootKey.toPublicKey().toString(); const keyDeriver = new sdk_1.CachedKeyDeriver(rootKey); const chain = args.chain; const storage = new WalletStorageManager_1.WalletStorageManager(identityKey, args.active, args.backups); if (storage.canMakeAvailable()) await storage.makeAvailable(); const env = _tu.getEnv(args.chain); const serviceOptions = Services_1.Services.createDefaultOptions(env.chain); serviceOptions.taalApiKey = env.taalApiKey; serviceOptions.arcConfig.apiKey = env.taalApiKey; serviceOptions.bitailsApiKey = env.bitailsApiKey; serviceOptions.whatsOnChainApiKey = env.whatsonchainApiKey; const services = new Services_1.Services(serviceOptions); const monopts = Monitor_1.Monitor.createDefaultWalletMonitorOptions(chain, storage, services); const monitor = new Monitor_1.Monitor(monopts); monitor.addDefaultTasks(); let privilegedKeyManager = undefined; if (args.privKeyHex) { const privKey = sdk_1.PrivateKey.fromString(args.privKeyHex); privilegedKeyManager = new PrivilegedKeyManager_1.PrivilegedKeyManager(async () => privKey); } const wallet = new Wallet_1.Wallet({ chain, keyDeriver, storage, services, monitor, privilegedKeyManager }); const r = { rootKey, identityKey, keyDeriver, chain, storage, services, monitor, wallet }; return r; } /** * Creates a wallet with both local sqlite and cloud stores, the local store is left active. * * Requires a valid .env file with chain matching testIdentityKey and testFilePath properties valid. * Or `args` with those properties. * * Verifies wallet has at least 1000 satoshis in at least 10 change utxos. * * @param chain * * @returns {TestWalletNoSetup} */ static async createTestWallet(args) { let chain; let rootKeyHex; let filePath; let addLocalBackup = false; let setActiveClient = false; let useMySQLConnectionForClient = false; if (typeof args === 'string') { chain = args; const env = _tu.getEnv(chain); if (!env.testIdentityKey || !env.testFilePath) { throw new WERR_errors_1.WERR_INVALID_PARAMETER('env.testIdentityKey and env.testFilePath', 'valid'); } rootKeyHex = env.devKeys[env.testIdentityKey]; filePath = env.testFilePath; } else { chain = args.chain; rootKeyHex = args.rootKeyHex; filePath = args.filePath; addLocalBackup = args.addLocalBackup === true; setActiveClient = args.setActiveClient === true; useMySQLConnectionForClient = args.useMySQLConnectionForClient === true; } const databaseName = path_1.default.parse(filePath).name; const setup = await _tu.createSQLiteTestWallet({ filePath, rootKeyHex, databaseName, chain }); setup.localStorageIdentityKey = setup.storage.getActiveStore(); let client; if (useMySQLConnectionForClient) { const env = _tu.getEnv(chain); if (!env.cloudMySQLConnection) throw new WERR_errors_1.WERR_INVALID_PARAMETER('env.cloundMySQLConnection', 'valid'); const connection = JSON.parse(env.cloudMySQLConnection); client = new StorageKnex_1.StorageKnex({ ...StorageKnex_1.StorageKnex.defaultOptions(), knex: _tu.createMySQLFromConnection(connection), chain: env.chain }); } else { const endpointUrl = chain === 'main' ? 'https://storage.babbage.systems' : 'https://staging-storage.babbage.systems'; client = new StorageClient_1.StorageClient(setup.wallet, endpointUrl); } setup.clientStorageIdentityKey = (await client.makeAvailable()).storageIdentityKey; await setup.wallet.storage.addWalletStorageProvider(client); if (addLocalBackup) { const backupName = `${databaseName}_backup`; const backupPath = filePath.replace(databaseName, backupName); const localBackup = new StorageKnex_1.StorageKnex({ ...StorageKnex_1.StorageKnex.defaultOptions(), knex: _tu.createLocalSQLite(backupPath), chain }); await localBackup.migrate(backupName, (0, utilityHelpers_1.randomBytesHex)(33)); setup.localBackupStorageIdentityKey = (await localBackup.makeAvailable()).storageIdentityKey; await setup.wallet.storage.addWalletStorageProvider(localBackup); } // SETTING ACTIVE // SETTING ACTIVE // SETTING ACTIVE const log = await setup.storage.setActive(setActiveClient ? setup.clientStorageIdentityKey : setup.localStorageIdentityKey); (0, exports.logger)(log); let needsBackup = false; if (setup.storage.getActiveStore() === setup.localStorageIdentityKey) { const basket = (0, utilityHelpers_1.verifyOne)(await setup.activeStorage.findOutputBaskets({ partial: { userId: setup.storage.getActiveUser().userId, name: 'default' } })); if (basket.minimumDesiredUTXOValue !== 5 || basket.numberOfDesiredUTXOs < 32) { needsBackup = true; await setup.activeStorage.updateOutputBasket(basket.basketId, { minimumDesiredUTXOValue: 5, numberOfDesiredUTXOs: 32 }); } } const balance = await setup.wallet.balanceAndUtxos(); if (balance.total < 1000) { throw new WERR_errors_1.WERR_INSUFFICIENT_FUNDS(1000, 1000 - balance.total); } if (balance.utxos.length <= 10) { const args = { description: 'spread change' }; await setup.wallet.createAction(args); needsBackup = true; } if (needsBackup) { const log2 = await setup.storage.updateBackups(); console.log(log2); } return setup; } static async createTestWalletWithStorageClient(args) { args.chain || (args.chain = 'test'); const wo = await _tu.createWalletOnly({ chain: args.chain, rootKeyHex: args.rootKeyHex }); args.endpointUrl || (args.endpointUrl = args.chain === 'main' ? 'https://storage.babbage.systems' : 'https://staging-storage.babbage.systems'); const client = new StorageClient_1.StorageClient(wo.wallet, args.endpointUrl); await wo.storage.addWalletStorageProvider(client); return wo; } static async createKnexTestWalletWithSetup(args) { const wo = await _tu.createWalletOnly({ chain: args.chain, rootKeyHex: args.rootKeyHex, privKeyHex: args.privKeyHex }); const activeStorage = new StorageKnex_1.StorageKnex({ chain: wo.chain, knex: args.knex, commissionSatoshis: 0, commissionPubKeyHex: undefined, feeModel: { model: 'sat/kb', value: 1 } }); if (args.dropAll) await activeStorage.dropAllData(); await activeStorage.migrate(args.databaseName, (0, utilityHelpers_1.randomBytesHex)(33)); await activeStorage.makeAvailable(); const setup = await args.insertSetup(activeStorage, wo.identityKey); await wo.storage.addWalletStorageProvider(activeStorage); const { user, isNew } = await activeStorage.findOrInsertUser(wo.identityKey); const userId = user.userId; const r = { ...wo, activeStorage, setup, userId }; return r; } /** * Returns path to temporary file in project's './test/data/tmp/' folder. * * Creates any missing folders. * * Optionally tries to delete any existing file. This may fail if the file file is locked * by another process. * * Optionally copies filename (or if filename has no dir component, a file of the same filename from the project's './test/data' folder) to initialize file's contents. * * CAUTION: returned file path will include four random hex digits unless tryToDelete is true. Files must be purged periodically. * * @param filename target filename without path, optionally just extension in which case random name is used * @param tryToDelete true to attempt to delete an existing file at the returned file path. * @param copyToTmp true to copy file of same filename from './test/data' (or elsewhere if filename has path) to tmp folder * @param reuseExisting true to use existing file if found, otherwise a random string is added to filename. * @returns path in './test/data/tmp' folder. */ static async newTmpFile(filename = '', tryToDelete = false, copyToTmp = false, reuseExisting = false) { const tmpFolder = './test/data/tmp/'; const p = path_1.default.parse(filename); const dstDir = tmpFolder; const dstName = `${p.name}${tryToDelete || reuseExisting ? '' : (0, utilityHelpers_1.randomBytesHex)(6)}`; const dstExt = p.ext || 'tmp'; const dstPath = path_1.default.resolve(`${dstDir}${dstName}${dstExt}`); await fs_1.promises.mkdir(tmpFolder, { recursive: true }); if (!reuseExisting && (tryToDelete || copyToTmp)) try { await fs_1.promises.unlink(dstPath); } catch (eu) { const e = WalletError_1.WalletError.fromUnknown(eu); if (e.name !== 'ENOENT') { throw e; } } if (copyToTmp) { const srcPath = p.dir ? path_1.default.resolve(filename) : path_1.default.resolve(`./test/data/${filename}`); await fs_1.promises.copyFile(srcPath, dstPath); } return dstPath; } static async copyFile(srcPath, dstPath) { await fs_1.promises.copyFile(srcPath, dstPath); } static async existingDataFile(filename) { const folder = './test/data/'; return folder + filename; } static createLocalSQLite(filename) { const config = { client: 'sqlite3', connection: { filename }, useNullAsDefault: true }; const knex = (0, knex_1.knex)(config); return knex; } static createMySQLFromConnection(connection) { const config = { client: 'mysql2', connection, useNullAsDefault: true, pool: { min: 0, max: 7, idleTimeoutMillis: 15000 } }; const knex = (0, knex_1.knex)(config); return knex; } static createLocalMySQL(database) { const connection = JSON.parse(localMySqlConnection || '{}'); connection['database'] = database; const config = { client: 'mysql2', connection, useNullAsDefault: true, pool: { min: 0, max: 7, idleTimeoutMillis: 15000 } }; const knex = (0, knex_1.knex)(config); return knex; } static async createMySQLTestWallet(args) { return await this.createKnexTestWallet({ ...args, knex: _tu.createLocalMySQL(args.databaseName) }); } static async createMySQLTestSetup1Wallet(args) { return await this.createKnexTestSetup1Wallet({ ...args, dropAll: true, knex: _tu.createLocalMySQL(args.databaseName) }); } static async createMySQLTestSetup2Wallet(args) { return await this.createKnexTestSetup2Wallet({ ...args, dropAll: true, knex: _tu.createLocalMySQL(args.databaseName) }); } static async createSQLiteTestWallet(args) { const localSQLiteFile = args.filePath || (await _tu.newTmpFile(`${args.databaseName}.sqlite`, false, false, true)); return await this.createKnexTestWallet({ ...args, knex: _tu.createLocalSQLite(localSQLiteFile) }); } static async createSQLiteTestSetup1Wallet(args) { const localSQLiteFile = await _tu.newTmpFile(`${args.databaseName}.sqlite`, false, false, true); return await this.createKnexTestSetup1Wallet({ ...args, dropAll: true, knex: _tu.createLocalSQLite(localSQLiteFile) }); } static async createSQLiteTestSetup2Wallet(args) { const localSQLiteFile = await _tu.newTmpFile(`${args.databaseName}.sqlite`, false, false, true); return await this.createKnexTestSetup2Wallet({ ...args, dropAll: true, knex: _tu.createLocalSQLite(localSQLiteFile) }); } static async createKnexTestWallet(args) { return await _tu.createKnexTestWalletWithSetup({ ...args, insertSetup: insertEmptySetup }); } static async createKnexTestSetup1Wallet(args) { return await _tu.createKnexTestWalletWithSetup({ ...args, insertSetup: _tu.createTestSetup1 }); } static async createKnexTestSetup2Wallet(args) { return await _tu.createKnexTestWalletWithSetup({ ...args, insertSetup: async (storage, identityKey) => { return _tu.createTestSetup2(storage, identityKey, args.mockData); } }); } static async fileExists(file) { try { const f = await fs_1.promises.open(file, 'r'); await f.close(); return true; } catch (eu) { return false; } } //if (await _tu.fileExists(walletFile)) static async createLegacyWalletSQLiteCopy(databaseName) { const walletFile = await _tu.newTmpFile(`${databaseName}.sqlite`, false, false, true); const walletKnex = _tu.createLocalSQLite(walletFile); return await _tu.createLegacyWalletCopy(databaseName, walletKnex, walletFile); } static async createLegacyWalletMySQLCopy(databaseName) { const walletKnex = _tu.createLocalMySQL(databaseName); return await _tu.createLegacyWalletCopy(databaseName, walletKnex); } static async createLiveWalletSQLiteWARNING(databaseFullPath = './test/data/walletLiveTestData.sqlite') { return await this.createKnexTestWallet({ chain: 'test', rootKeyHex: _tu.legacyRootKeyHex, databaseName: 'walletLiveTestData', knex: _tu.createLocalSQLite(databaseFullPath) }); } static async createWalletSQLite(databaseFullPath = './test/data/tmp/walletNewTestData.sqlite', databaseName = 'walletNewTestData') { return await this.createSQLiteTestWallet({ filePath: databaseFullPath, databaseName, chain: 'test', rootKeyHex: '1'.repeat(64), dropAll: true }); } static async createLegacyWalletCopy(databaseName, walletKnex, tryCopyToPath) { const readerFile = await _tu.existingDataFile(`walletLegacyTestData.sqlite`); let useReader = true; if (tryCopyToPath) { await _tu.copyFile(readerFile, tryCopyToPath); //console.log('USING FILE COPY INSTEAD OF SOURCE DB SYNC') useReader = false; } const chain = 'test'; const rootKeyHex = _tu.legacyRootKeyHex; const identityKey = '03ac2d10bdb0023f4145cc2eba2fcd2ad3070cb2107b0b48170c46a9440e4cc3fe'; const rootKey = sdk_1.PrivateKey.fromHex(rootKeyHex); const keyDeriver = new sdk_1.CachedKeyDeriver(rootKey); const activeStorage = new StorageKnex_1.StorageKnex({ chain, knex: walletKnex, commissionSatoshis: 0, commissionPubKeyHex: undefined, feeModel: { model: 'sat/kb', value: 1 } }); if (useReader) await activeStorage.dropAllData(); await activeStorage.migrate(databaseName, (0, utilityHelpers_1.randomBytesHex)(33)); await activeStorage.makeAvailable(); const storage = new WalletStorageManager_1.WalletStorageManager(identityKey, activeStorage); await storage.makeAvailable(); if (useReader) { const readerKnex = _tu.createLocalSQLite(readerFile); const reader = new StorageKnex_1.StorageKnex({ chain, knex: readerKnex, commissionSatoshis: 0, commissionPubKeyHex: undefined, feeModel: { model: 'sat/kb', value: 1 } }); await reader.makeAvailable(); await storage.syncFromReader(identityKey, new StorageSyncReader_1.StorageSyncReader({ identityKey }, reader)); await reader.destroy(); } const services = new Services_1.Services(chain); const monopts = Monitor_1.Monitor.createDefaultWalletMonitorOptions(chain, storage, services); const monitor = new Monitor_1.Monitor(monopts); const wallet = new Wallet_1.Wallet({ chain, keyDeriver, storage, services, monitor }); const userId = (0, utilityHelpers_1.verifyTruthy)(await activeStorage.findUserByIdentityKey(identityKey)).userId; const r = { rootKey, identityKey, keyDeriver, chain, activeStorage, storage, setup: {}, services, monitor, wallet, userId }; return r; } static wrapProfiling(o, name) { const getFunctionsNames = (obj) => { let fNames = []; do { fNames = fNames.concat(Object.getOwnPropertyNames(obj).filter(p => p !== 'constructor' && typeof obj[p] === 'function')); } while ((obj = Object.getPrototypeOf(obj)) && obj !== Object.prototype); return fNames; }; const notifyPerformance = (fn, performanceDetails) => { setTimeout(() => { let { functionName, args, startTime, endTime } = performanceDetails; let _args = args; if (Array.isArray(args)) { _args = args.map(arg => { if (typeof arg === 'function') { let fName = arg.name; if (!fName) { fName = 'function'; } else if (fName === 'callbackWrapper') { fName = 'callback'; } arg = `[${fName} Function]`; } return arg; }); } fn({ functionName, args: _args, startTime, endTime }); }, 0); }; const stats = {}; function logger(args) { let s = stats[args.functionName]; if (!s) { s = { count: 0, totalMsecs: 0 }; stats[args.functionName] = s; } s.count++; s.totalMsecs += args.endTime - args.startTime; } const performanceWrapper = (obj, objectName, performanceNotificationCallback) => { let _notifyPerformance = notifyPerformance.bind(null, performanceNotificationCallback); let fNames = getFunctionsNames(obj); for (let fName of fNames) { let originalFunction = obj[fName]; let wrapperFunction = (...args) => { let callbackFnIndex = -1; let startTime = Date.now(); let _callBack = args.filter((arg, i) => { let _isFunction = typeof arg === 'function'; if (_isFunction) { callbackFnIndex = i; } return _isFunction; })[0]; if (_callBack) { let callbackWrapper = (...callbackArgs) => { let endTime = Date.now(); _notifyPerformance({ functionName: `${objectName}.${fName}`, args, startTime, endTime }); _callBack.apply(null, callbackArgs); }; args[callbackFnIndex] = callbackWrapper; } let originalReturnObject = originalFunction.apply(obj, args); let isPromiseType = originalReturnObject && typeof originalReturnObject.then === 'function' && typeof originalReturnObject.catch === 'function'; if (isPromiseType) { return originalReturnObject .then(resolveArgs => { let endTime = Date.now(); _notifyPerformance({ functionName: `${objectName}.${fName}`, args, startTime, endTime }); return Promise.resolve(resolveArgs); }) .catch((...rejectArgs) => { let endTime = Date.now(); _notifyPerformance({ functionName: `${objectName}.${fName}`, args, startTime, endTime }); return Promise.reject(...rejectArgs); }); } if (!_callBack && !isPromiseType) { let endTime = Date.now(); _notifyPerformance({ functionName: `${objectName}.${fName}`, args, startTime, endTime }); } return originalReturnObject; }; obj[fName] = wrapperFunction; } return obj; }; const functionNames = getFunctionsNames(o); performanceWrapper(o, name, logger); return stats; } static async createIdbLegacyWalletCopy(databaseName) { const chain = 'test'; const readerFile = await _tu.existingDataFile(`walletLegacyTestData.sqlite`); const readerKnex = _tu.createLocalSQLite(readerFile); const reader = new StorageKnex_1.StorageKnex({ chain, knex: readerKnex, commissionSatoshis: 0, commissionPubKeyHex: undefined, feeModel: { model: 'sat/kb', value: 1 } }); await reader.makeAvailable(); const rootKeyHex = _tu.legacyRootKeyHex; const identityKey = '03ac2d10bdb0023f4145cc2eba2fcd2ad3070cb2107b0b48170c46a9440e4cc3fe'; const rootKey = sdk_1.PrivateKey.fromHex(rootKeyHex); const keyDeriver = new sdk_1.CachedKeyDeriver(rootKey); const activeStorage = new StorageIdb_1.StorageIdb({ chain, commissionSatoshis: 0, commissionPubKeyHex: undefined, feeModel: { model: 'sat/kb', value: 1 } }); await activeStorage.dropAllData(); await activeStorage.migrate(databaseName, (0, utilityHelpers_1.randomBytesHex)(33)); await activeStorage.makeAvailable(); const storage = new WalletStorageManager_1.WalletStorageManager(identityKey, activeStorage); await storage.makeAvailable(); await storage.syncFromReader(identityKey, new StorageSyncReader_1.StorageSyncReader({ identityKey }, reader)); await reader.destroy(); const services = new Services_1.Services(chain); const monopts = Monitor_1.Monitor.createDefaultWalletMonitorOptions(chain, storage, services); const monitor = new Monitor_1.Monitor(monopts); const wallet = new Wallet_1.Wallet({ chain, keyDeriver, storage, services, monitor }); const userId = (0, utilityHelpers_1.verifyTruthy)(await activeStorage.findUserByIdentityKey(identityKey)).userId; const r = { rootKey, identityKey, keyDeriver, chain, activeStorage, storage, setup: {}, services, monitor, wallet, userId }; return r; } static makeSampleCert(subject) { subject || (subject = sdk_1.PrivateKey.fromRandom().toPublicKey().toString()); const certifier = sdk_1.PrivateKey.fromRandom(); const verifier = sdk_1.PrivateKey.fromRandom(); const cert = { type: sdk_1.Utils.toBase64(new Array(32).fill(1)), serialNumber: sdk_1.Utils.toBase64(new Array(32).fill(2)), revocationOutpoint: 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef.1', subject, certifier: certifier.toPublicKey().toString(), fields: { name: 'Alice', email: 'alice@example.com', organization: 'Example Corp' }, signature: '' }; return { cert, subject, certifier }; } static async insertTestProvenTx(storage, txid, trx) { const now = new Date(); const ptx = { created_at: now, updated_at: now, provenTxId: 0, txid: txid || (0, utilityHelpers_1.randomBytesHex)(32), height: 1, index: 0, merklePath: [1, 2, 3, 4, 5, 6, 7, 8], rawTx: [4, 5, 6], blockHash: (0, utilityHelpers_1.randomBytesHex)(32), merkleRoot: (0, utilityHelpers_1.randomBytesHex)(32) }; await storage.insertProvenTx(ptx, trx); return ptx; } static async insertTestProvenTxReq(storage, txid, provenTxId, onlyRequired) { const now = new Date(); const ptxreq = { // Required: created_at: now, updated_at: now, provenTxReqId: 0, txid: txid || (0, utilityHelpers_1.randomBytesHex)(32), status: 'nosend', attempts: 0, notified: false, history: '{}', notify: '{}', rawTx: [4, 5, 6], // Optional: provenTxId: provenTxId || undefined, batch: onlyRequired ? undefined : (0, utilityHelpers_1.randomBytesBase64)(10), inputBEEF: onlyRequired ? undefined : [1, 2, 3] }; await storage.insertProvenTxReq(ptxreq); return ptxreq; } static async insertTestUser(storage, identityKey) { const now = new Date(); const e = { created_at: now, updated_at: now, userId: 0, identityKey: identityKey || (0, utilityHelpers_1.randomBytesHex)(33), activeStorage: storage.getSettings().storageIdentityKey }; await storage.insertUser(e); return e; } static async insertTestCertificate(storage, u) { const now = new Date(); u || (u = await _tu.insertTestUser(storage)); const e = { created_at: now, updated_at: now, certificateId: 0, userId: u.userId, type: (0, utilityHelpers_1.randomBytesBase64)(33), serialNumber: (0, utilityHelpers_1.randomBytesBase64)(33), certifier: (0, utilityHelpers_1.randomBytesHex)(33), subject: (0, utilityHelpers_1.randomBytesHex)(33), verifier: undefined, revocationOutpoint: `${(0, utilityHelpers_1.randomBytesHex)(32)}.999`, signature: (0, utilityHelpers_1.randomBytesHex)(50), isDeleted: false }; await storage.insertCertificate(e); return e; } static async insertTestCertificateField(storage, c, name, value) { const now = new Date(); const e = { created_at: now, updated_at: now, certificateId: c.certificateId, userId: c.userId, fieldName: name, fieldValue: value, masterKey: (0, utilityHelpers_1.randomBytesBase64)(40) }; await storage.insertCertificateField(e); return e; } static async insertTestOutputBasket(storage, u, partial) { const now = new Date(); let user; if (u === undefined) { user = await _tu.insertTestUser(storage); } else if (typeof u === 'number') { user = (0, utilityHelpers_1.verifyOne)(await storage.findUsers({ partial: { userId: u } })); } else { user = u; } const e = { created_at: now, updated_at: now, basketId: 0, userId: user.userId, name: (0, utilityHelpers_1.randomBytesHex)(6), numberOfDesiredUTXOs: 42, minimumDesiredUTXOValue: 1642, isDeleted: false, ...(partial || {}) }; await storage.insertOutputBasket(e); return e; } static async insertTestTransaction(storage, u, onlyRequired, partial) { const now = new Date(); u || (u = await _tu.insertTestUser(storage)); const e = { // Required: created_at: now, updated_at: now, transactionId: 0, userId: u.userId, status: 'nosend', reference: (0, utilityHelpers_1.randomBytesBase64)(10), isOutgoing: true, satoshis: 9999, description: 'buy me a river', // Optional: version: onlyRequired ? undefined : 0, lockTime: onlyRequired ? undefined : 500000000, txid: onlyRequired ? undefined : (0, utilityHelpers_1.randomBytesHex)(32), inputBEEF: onlyRequired ? undefined : new sdk_1.Beef().toBinary(), rawTx: onlyRequired ? undefined : [1, 2, 3], ...(partial || {}) }; await storage.insertTransaction(e); return { tx: e, user: u }; } static async insertTestOutput(storage, t, vout, satoshis, basket, requiredOnly, partial) { const now = new Date(); const e = { created_at: now, updated_at: now, outputId: 0, userId: t.userId, transactionId: t.transactionId, basketId: basket ? basket.basketId : undefined, spendable: true, change: true, outputDescription: 'not mutch to say', vout, satoshis, providedBy: 'you', purpose: 'secret', type: 'custom', txid: requiredOnly ? undefined : (0, utilityHelpers_1.randomBytesHex)(32), senderIdentityKey: requiredOnly ? undefined : (0, utilityHelpers_1.randomBytesHex)(32), derivationPrefix: requiredOnly ? undefined : (0, utilityHelpers_1.randomBytesHex)(16), derivationSuffix: requiredOnly ? undefined : (0, utilityHelpers_1.randomBytesHex)(16), spentBy: undefined, // must be a valid transactionId sequenceNumber: requiredOnly ? undefined : 42, spendingDescription: requiredOnly ? undefined : (0, utilityHelpers_1.randomBytesHex)(16), scriptLength: requiredOnly ? undefined : 36, scriptOffset: requiredOnly ? undefined : 12, lockingScript: requiredOnly ? undefined : (0, utilityHelpers_noBuffer_1.asArray)((0, utilityHelpers_1.randomBytesHex)(36)), ...(partial || {}) }; await storage.insertOutput(e); return e; } static async insertTestOutputTag(storage, u, partial) { const now = new Date(); const e = { created_at: now, updated_at: now, outputTagId: 0, userId: u.userId, tag: (0, utilityHelpers_1.randomBytesHex)(6), isDeleted: false, ...(partial || {}) }; await storage.insertOutputTag(e); return e; } static async insertTestOutputTagMap(storage, o, tag) { const now = new Date(); const e = { created_at: now, updated_at: now, outputTagId: tag.outputTagId, outputId: o.outputId, isDeleted: false }; await storage.insertOutputTagMap(e); return e; } static async insertTestTxLabel(storage, u, partial) { const now = new Date(); const e = { created_at: now, updated_at: now, txLabelId: 0, userId: u.userId, label: (0, utilityHelpers_1.randomBytesHex)(6), isDeleted: false, ...(partial || {}) }; await storage.insertTxLabel(e); return e; } static async insertTestTxLabelMap(storage, tx, label, partial) { const now = new Date(); const e = { created_at: now, updated_at: now, txLabelId: label.txLabelId, transactionId: tx.transactionId, isDeleted: false, ...(partial || {}) }; await storage.insertTxLabelMap(e); return e; } static async insertTestSyncState(storage, u) { const now = new Date(); const settings = await storage.getSettings(); const e = { created_at: now, updated_at: now, syncStateId: 0, userId: u.userId, storageIdentityKey: settings.storageIdentityKey, storageName: settings.storageName, status: 'unknown', init: false, refNum: (0, utilityHelpers_1.randomBytesBase64)(10), syncMap: '{}' }; await storage.insertSyncState(e); return e; } static async insertTestMonitorEvent(storage) { const now = new Date(); const e = { created_at: now, updated_at: now, id: 0, event: 'nothing much happened' }; await storage.insertMonitorEvent(e); return e; } static async insertTestCommission(storage, t) { const now = new Date(); const e = { created_at: now, updated_at: now, commissionId: 0, userId: t.userId, transactionId: t.transactionId, satoshis: 200, keyOffset: (0, utilityHelpers_1.randomBytesBase64)(32), isRedeemed: false, lockingScript: [1, 2, 3] }; await storage.insertCommission(e); return e; } static async createTestSetup1(storage, u1IdentityKey) { const u1 = await _tu.insertTestUser(storage, u1IdentityKey); const u1basket1 = await _tu.insertTestOutputBasket(storage, u1); const u1basket2 = await _tu.insertTestOutputBasket(storage, u1); const u1label1 = await _tu.insertTestTxLabel(storage, u1); const u1label2 = await _tu.insertTestTxLabel(storage, u1); const u1tag1 = await _tu.insertTestOutputTag(storage, u1); const u1tag2 = await _tu.insertTestOutputTag(storage, u1); const { tx: u1tx1 } = await _tu.insertTestTransaction(storage, u1); const u1comm1 = await _tu.insertTestCommission(storage, u1tx1); const u1tx1label1 = await _tu.insertTestTxLabelMap(storage, u1tx1, u1label1); const u1tx1label2 = await _tu.insertTestTxLabelMap(storage, u1tx1, u1label2); const u1tx1o0 = await _tu.insertTestOutput(storage, u1tx1, 0, 101, u1basket1); const u1o0tag1 = await _tu.insertTestOutputTagMap(storage, u1tx1o0, u1tag1); const u1o0tag2 = await _tu.insertTestOutputTagMap(storage, u1tx1o0, u1tag2); const u1tx1o1 = await _tu.insertTestOutput(storage, u1tx1, 1, 111, u1basket2); const u1o1tag1 = await _tu.insertTestOutputTagMap(storage, u1tx1o1, u1tag1); const u1cert1 = await _tu.insertTestCertificate(storage, u1); const u1cert1field1 = await _tu.insertTestCertificateField(storage, u1cert1, 'bob', 'your uncle'); const u1cert1field2 = await _tu.insertTestCertificateField(storage, u1cert1, 'name', 'alice'); const u1cert2 = await _tu.insertTestCertificate(storage, u1); const u1cert2field1 = await _tu.insertTestCertificateField(storage, u1cert2, 'name', 'alice'); const u1cert3 = await _tu.insertTestCertificate(storage, u1); const u1sync1 = await _tu.insertTestSyncState(storage, u1); const u2 = await _tu.insertTestUser(storage); const u2basket1 = await _tu.insertTestOutputBasket(storage, u2); const u2label1 = await _tu.insertTestTxLabel(storage, u2); const { tx: u2tx1 } = await _tu.insertTestTransaction(storage, u2, true); const u2comm1 = await _tu.insertTestCommission(storage, u2tx1); const u2tx1label1 = await _tu.insertTestTxLabelMap(storage, u2tx1, u2label1); const u2tx1o0 = await _tu.insertTestOutput(storage, u2tx1, 0, 101, u2basket1); const { tx: u2tx2 } = await _tu.insertTestTransaction(storage, u2, true); const u2comm2 = await _tu.insertTestCommission(storage, u2tx2); const proven1 = await _tu.insertTestProvenTx(storage); const req1 = await _tu.insertTestProvenTxReq(storage, undefined, undefined, true); const req2 = await _tu.insertTestProvenTxReq(storage, proven1.txid, proven1.provenTxId); const we1 = await _tu.insertTestMonitorEvent(storage); return { u1, u1basket1, u1basket2, u1label1, u1label2, u1tag1, u1tag2, u1tx1, u1comm1, u1tx1label1, u1tx1label2, u1tx1o0, u1o0tag1, u1o0tag2, u1tx1o1, u1o1tag1, u1cert1, u1cert1field1, u1cert1field2, u1cert2, u1cert2field1, u1cert3, u1sync1, u2, u2basket1, u2label1, u2tx1, u2comm1, u2tx1label1, u2tx1o0, u2tx2, u2comm2, proven1, req1, req2, we1 }; } static async createTestSetup2(storage, identityKey, mockData = { actions: [] }) { if (!mockData || !mockData.actions) { throw new Error('mockData.actions is required'); } const now = new Date(); const inputTxMap = {}; const outputMap = {}; // only one user const user = await _tu.insertTestUser(storage, identityKey); // Fir