UNPKG

@bsv/wallet-toolbox

Version:

BRC100 conforming wallet, wallet storage and wallet signer components

292 lines (287 loc) 11.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.listOutputs = listOutputs; const sdk_1 = require("@bsv/sdk"); const ListOutputsSpecOp_1 = require("./ListOutputsSpecOp"); const utilityHelpers_1 = require("../../utility/utilityHelpers"); const utilityHelpers_noBuffer_1 = require("../../utility/utilityHelpers.noBuffer"); async function listOutputs(dsk, auth, vargs, originator) { const trx = undefined; const userId = (0, utilityHelpers_1.verifyId)(auth.userId); const limit = vargs.limit; let offset = vargs.offset; let orderBy = 'asc'; if (offset < 0) { offset = -offset - 1; orderBy = 'desc'; } const k = dsk.toDb(trx); const r = { totalOutputs: 0, outputs: [] }; /* ValidListOutputsArgs { basket: BasketStringUnder300Bytes tags: OutputTagStringUnder300Bytes[] tagQueryMode: 'all' | 'any' // default any limit: PositiveIntegerDefault10Max10000 // default 10 offset: number // default 0 } */ let { specOp, basket, tags } = (0, ListOutputsSpecOp_1.getListOutputsSpecOp)(vargs.basket, vargs.tags); let basketId = undefined; const basketsById = {}; if (basket) { const baskets = await dsk.findOutputBaskets({ partial: { userId, name: basket }, trx }); if (baskets.length !== 1) { // If basket does not exist, result is no outputs. return r; } basketId = baskets[0].basketId; basketsById[basketId] = baskets[0]; } let tagIds = []; const specOpTags = []; if (specOp && specOp.tagsParamsCount) { specOpTags.push(...tags.splice(0, Math.min(tags.length, specOp.tagsParamsCount))); } if (specOp && specOp.tagsToIntercept) { // Pull out tags used by current specOp const ts = tags; tags = []; for (const t of ts) { if (specOp.tagsToIntercept.length === 0 || specOp.tagsToIntercept.indexOf(t) >= 0) { specOpTags.push(t); if (t === 'all') { basketId = undefined; } } else { tags.push(t); } } } if (specOp && specOp.resultFromTags) { const r = await specOp.resultFromTags(dsk, auth, vargs, specOpTags); return r; } if (tags && tags.length > 0) { const q = k('output_tags') .where({ userId: userId, isDeleted: false }) .whereNotNull('outputTagId') .whereIn('tag', tags) .select('outputTagId'); const r = await q; tagIds = r.map(r => r.outputTagId); } const isQueryModeAll = vargs.tagQueryMode === 'all'; if (isQueryModeAll && tagIds.length < tags.length) // all the required tags don't exist, impossible to satisfy. return r; if (!isQueryModeAll && tagIds.length === 0 && tags.length > 0) // any and only non-existing tags, impossible to satisfy. return r; let columns = [ 'outputId', 'transactionId', 'basketId', 'spendable', 'txid', 'vout', 'satoshis', 'customInstructions', 'outputDescription', 'spendingDescription' ]; if (vargs.includeLockingScripts || (specOp === null || specOp === void 0 ? void 0 : specOp.includeOutputScripts)) columns = [...columns, 'lockingScript', 'scriptLength', 'scriptOffset']; const noTags = tagIds.length === 0; const includeSpent = specOp && specOp.includeSpent ? specOp.includeSpent : false; const txStatusAllowed = ['completed', 'unproven', 'nosend', 'sending']; const outputColumns = columns.map(c => `o.${c} as ${c}`); const applyBaseFilters = (q) => { q.join('transactions as t', 't.transactionId', 'o.transactionId'); q.where('o.userId', userId); q.whereIn('t.status', txStatusAllowed); if (basketId) q.where('o.basketId', basketId); if (!includeSpent) q.where('o.spendable', true); }; const makeWithTagsQuery = () => { const q = k('outputs as o'); applyBaseFilters(q); if (isQueryModeAll) { for (const tagId of tagIds) { q.whereExists(function () { this.select(k.raw('1')) .from('output_tags_map as m') .whereRaw('m.outputId = o.outputId') .where('m.outputTagId', tagId) .whereNot('m.isDeleted', true); }); } } else { q.whereExists(function () { this.select(k.raw('1')) .from('output_tags_map as m') .whereRaw('m.outputId = o.outputId') .whereIn('m.outputTagId', tagIds) .whereNot('m.isDeleted', true); }); } return q; }; const makeWithTagsQueries = () => { const q = makeWithTagsQuery(); const qcount = q.clone(); q.select(outputColumns); qcount.clearSelect().clearOrder().count('o.outputId as total'); return { q, qcount }; }; const makeWithoutTagsQueries = () => { const q = k('outputs as o'); applyBaseFilters(q); const qcount = q.clone().clearSelect().clearOrder().count('o.outputId as total'); q.select(outputColumns); return { q, qcount }; }; if (specOp === null || specOp === void 0 ? void 0 : specOp.totalOutputsIsSumOfSatoshis) { if (noTags) { const q = k('outputs as o'); applyBaseFilters(q); q.sum('o.satoshis as totalSatoshis'); const rsum = await q.first(); r.totalOutputs = Number(rsum ? rsum['totalSatoshis'] || 0 : 0); return r; } else { columns = ['outputId', 'basketId', 'spendable', 'satoshis']; const q = makeWithTagsQuery(); q.sum('o.satoshis as totalSatoshis'); const rsum = await q.first(); r.totalOutputs = Number(rsum ? rsum['totalSatoshis'] || 0 : 0); return r; } } const { q, qcount } = noTags ? makeWithoutTagsQueries() : makeWithTagsQueries(); // Sort order when limit and offset are possible must be ascending for determinism. if (!specOp || !specOp.ignoreLimit) q.limit(limit).offset(offset); q.orderBy('o.outputId', orderBy); let outputs = await q; if (specOp) { if (specOp.filterOutputs) outputs = await specOp.filterOutputs(dsk, auth, vargs, specOpTags, outputs); if (specOp.resultFromOutputs) { const r = await specOp.resultFromOutputs(dsk, auth, vargs, specOpTags, outputs); return r; } } if (!limit || outputs.length < limit) r.totalOutputs = outputs.length; else { const total = (0, utilityHelpers_1.verifyOne)(await qcount)['total']; r.totalOutputs = Number(total); } /* ListOutputsArgs { include?: 'locking scripts' | 'entire transactions' includeCustomInstructions?: BooleanDefaultFalse includeTags?: BooleanDefaultFalse includeLabels?: BooleanDefaultFalse } ListOutputsResult { totalOutputs: PositiveIntegerOrZero BEEF?: BEEF outputs: Array<WalletOutput> } WalletOutput { satoshis: SatoshiValue spendable: boolean outpoint: OutpointString customInstructions?: string lockingScript?: HexString tags?: OutputTagStringUnder300Bytes[] labels?: LabelStringUnder300Bytes[] } */ const labelsByTransactionId = {}; const tagsByOutputId = {}; if (vargs.includeLabels) { const txIds = [...new Set(outputs.map(o => o.transactionId).filter((id) => id !== undefined))]; if (txIds.length > 0) { const labels = await k('tx_labels as l') .join('tx_labels_map as lm', 'lm.txLabelId', 'l.txLabelId') .whereIn('lm.transactionId', txIds) .whereNot('lm.isDeleted', true) .whereNot('l.isDeleted', true) .select('lm.transactionId', 'l.label'); for (const row of labels) { const txid = Number(row.transactionId); if (!labelsByTransactionId[txid]) labelsByTransactionId[txid] = []; labelsByTransactionId[txid].push(String(row.label)); } } } if (vargs.includeTags) { const outputIds = [...new Set(outputs.map(o => o.outputId).filter((id) => id !== undefined))]; if (outputIds.length > 0) { const tags = await k('output_tags as ot') .join('output_tags_map as om', 'om.outputTagId', 'ot.outputTagId') .whereIn('om.outputId', outputIds) .whereNot('om.isDeleted', true) .whereNot('ot.isDeleted', true) .select('om.outputId', 'ot.tag'); for (const row of tags) { const outputId = Number(row.outputId); if (!tagsByOutputId[outputId]) tagsByOutputId[outputId] = []; tagsByOutputId[outputId].push(String(row.tag)); } } } const beef = new sdk_1.Beef(); for (const o of outputs) { const wo = { satoshis: Number(o.satoshis), spendable: !!o.spendable, outpoint: `${o.txid}.${o.vout}` }; r.outputs.push(wo); //if (vargs.includeBasket && o.basketId) { // if (!basketsById[o.basketId]) { // basketsById[o.basketId] = verifyTruthy(await dsk.findOutputBasketId(o.basketId!, trx)) // } // wo.basket = basketsById[o.basketId].name //} if (vargs.includeCustomInstructions && o.customInstructions) wo.customInstructions = o.customInstructions; if (vargs.includeLabels && o.transactionId !== undefined) wo.labels = labelsByTransactionId[o.transactionId] || []; if (vargs.includeTags && o.outputId !== undefined) wo.tags = tagsByOutputId[o.outputId] || []; if (vargs.includeLockingScripts) { await dsk.validateOutputScript(o, trx); if (o.lockingScript) wo.lockingScript = (0, utilityHelpers_noBuffer_1.asString)(o.lockingScript); } if (vargs.includeTransactions && !beef.findTxid(o.txid)) { await dsk.getValidBeefForKnownTxid(o.txid, beef, undefined, vargs.knownTxids, trx); } } if (vargs.includeTransactions) { r.BEEF = beef.toBinary(); } return r; } //# sourceMappingURL=listOutputsKnex.js.map