UNPKG

wallet-storage-client

Version:
227 lines 10.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createAction = createAction; exports.completeSignedTransaction = completeSignedTransaction; exports.processAction = processAction; const sdk_1 = require("@bsv/sdk"); const index_client_1 = require("../../index.client"); async function createAction(wallet, auth, vargs) { var _a; const r = {}; let prior = undefined; if (vargs.isNewTx) { prior = await createNewTx(wallet, vargs); if (vargs.isSignAction) { return makeSignableTransactionResult(prior, wallet, vargs); } prior.tx = await completeSignedTransaction(prior, {}, wallet); r.txid = prior.tx.id('hex'); r.noSendChange = (_a = prior.dcr.noSendChangeOutputVouts) === null || _a === void 0 ? void 0 : _a.map(vout => `${r.txid}.${vout}`); if (!vargs.options.returnTXIDOnly) r.tx = (0, index_client_1.makeAtomicBeef)(prior.tx, prior.dcr.inputBeef); } r.sendWithResults = await processAction(prior, wallet, auth, vargs); return r; } async function createNewTx(wallet, args) { const storageArgs = removeUnlockScripts(args); const dcr = await wallet.storage.createAction(storageArgs); const reference = dcr.reference; const { tx, amount, pdi } = buildSignableTransaction(dcr, args, wallet); const prior = { reference, dcr, args, amount, tx, pdi }; return prior; } function makeSignableTransactionResult(prior, wallet, args) { var _a; if (!prior.dcr.inputBeef) throw new index_client_1.sdk.WERR_INTERNAL('prior.dcr.inputBeef must be valid'); const txid = prior.tx.id('hex'); const r = { noSendChange: args.isNoSend ? (_a = prior.dcr.noSendChangeOutputVouts) === null || _a === void 0 ? void 0 : _a.map(vout => `${txid}.${vout}`) : undefined, signableTransaction: { reference: prior.dcr.reference, tx: (0, index_client_1.makeAtomicBeef)(prior.tx, prior.dcr.inputBeef) } }; wallet.pendingSignActions[r.signableTransaction.reference] = prior; return r; } /** * Derive a change output locking script */ function makeChangeLock(out, dctr, args, changeKeys, wallet) { const derivationPrefix = dctr.derivationPrefix; const derivationSuffix = (0, index_client_1.verifyTruthy)(out.derivationSuffix); const sabppp = new index_client_1.ScriptTemplateSABPPP({ derivationPrefix, derivationSuffix, keyDeriver: wallet.keyDeriver }); const lockingScript = sabppp.lock(changeKeys.privateKey, changeKeys.publicKey); return lockingScript; } async function completeSignedTransaction(prior, spends, wallet) { ///////////////////// // Insert the user provided unlocking scripts from "spends" arg ///////////////////// for (const [key, spend] of Object.entries(spends)) { const vin = Number(key); const createInput = prior.args.inputs[vin]; const input = prior.tx.inputs[vin]; if (!createInput || !input || createInput.unlockingScript || !Number.isInteger(createInput.unlockingScriptLength)) throw new index_client_1.sdk.WERR_INVALID_PARAMETER('args', `spend does not correspond to prior input with valid unlockingScriptLength.`); if (spend.unlockingScript.length / 2 > createInput.unlockingScriptLength) throw new index_client_1.sdk.WERR_INVALID_PARAMETER('args', `spend unlockingScript length ${spend.unlockingScript.length} exceeds expected length ${createInput.unlockingScriptLength}`); input.unlockingScript = (0, index_client_1.asBsvSdkScript)(spend.unlockingScript); if (spend.sequenceNumber !== undefined) input.sequence = spend.sequenceNumber; } const results = { sdk: {} }; ///////////////////// // Insert SABPPP unlock templates for storage signed inputs ///////////////////// for (const pdi of prior.pdi) { const sabppp = new index_client_1.ScriptTemplateSABPPP({ derivationPrefix: pdi.derivationPrefix, derivationSuffix: pdi.derivationSuffix, keyDeriver: wallet.keyDeriver }); const keys = wallet.getClientChangeKeyPair(); const lockerPrivKey = keys.privateKey; const unlockerPubKey = pdi.unlockerPubKey || keys.publicKey; const sourceSatoshis = pdi.sourceSatoshis; const lockingScript = (0, index_client_1.asBsvSdkScript)(pdi.lockingScript); const unlockTemplate = sabppp.unlock(lockerPrivKey, unlockerPubKey, sourceSatoshis, lockingScript); const input = prior.tx.inputs[pdi.vin]; input.unlockingScriptTemplate = unlockTemplate; } ///////////////////// // Sign storage signed inputs making transaction fully valid. ///////////////////// await prior.tx.sign(); return prior.tx; } function removeUnlockScripts(args) { let storageArgs = args; if (!storageArgs.inputs.every(i => i.unlockingScript === undefined)) { // Never send unlocking scripts to storage, all it needs is the script length. storageArgs = { ...args, inputs: [] }; for (const i of args.inputs) { const di = { ...i, unlockingScriptLength: i.unlockingScript !== undefined ? i.unlockingScript.length : i.unlockingScriptLength }; delete di.unlockingScript; storageArgs.inputs.push(di); } } return storageArgs; } async function processAction(prior, wallet, auth, vargs) { const args = { isNewTx: vargs.isNewTx, isSendWith: vargs.isSendWith, isNoSend: vargs.isNoSend, isDelayed: vargs.isDelayed, reference: prior ? prior.reference : undefined, txid: prior ? prior.tx.id('hex') : undefined, rawTx: prior ? prior.tx.toBinary() : undefined, sendWith: vargs.isSendWith ? vargs.options.sendWith : [], }; const r = await wallet.storage.processAction(args); return r.sendWithResults; } function buildSignableTransaction(dctr, args, wallet) { const changeKeys = wallet.getClientChangeKeyPair(); const { inputs: storageInputs, outputs: storageOutputs, } = dctr; const tx = new sdk_1.Transaction(args.version, [], [], args.lockTime); // The order of outputs in storageOutputs is always: // CreateActionArgs.outputs in the original order // Commission output // Change outputs // The Vout values will be randomized if args.options.randomizeOutputs is true. Default is true. const voutToIndex = Array(storageOutputs.length); for (let vout = 0; vout < storageOutputs.length; vout++) { const i = storageOutputs.findIndex(o => o.vout === vout); if (i < 0) throw new index_client_1.sdk.WERR_INVALID_PARAMETER('output.vout', `sequential. ${vout} is missing`); voutToIndex[vout] = i; } ////////////// // Add OUTPUTS ///////////// for (let vout = 0; vout < storageOutputs.length; vout++) { const i = voutToIndex[vout]; const out = storageOutputs[i]; if (vout !== out.vout) throw new index_client_1.sdk.WERR_INVALID_PARAMETER('output.vout', `equal to array index. ${out.vout} !== ${vout}`); const change = out.providedBy === 'storage' && out.purpose === 'change'; const lockingScript = change ? makeChangeLock(out, dctr, args, changeKeys, wallet) : (0, index_client_1.asBsvSdkScript)(out.lockingScript); const output = { satoshis: out.satoshis, lockingScript, change }; tx.addOutput(output); } ////////////// // Merge and sort INPUTS info by vin order. ///////////// const inputs = []; for (const storageInput of storageInputs) { const argsInput = (storageInput.vin !== undefined && storageInput.vin < args.inputs.length) ? args.inputs[storageInput.vin] : undefined; inputs.push({ argsInput, storageInput }); } inputs.sort((a, b) => a.storageInput.vin < b.storageInput.vin ? -1 : a.storageInput.vin === b.storageInput.vin ? 0 : 1); const pendingStorageInputs = []; ////////////// // Add INPUTS ///////////// let totalChangeInputs = 0; for (const { storageInput, argsInput } of inputs) { // Two types of inputs are handled: user specified wth/without unlockingScript and storage specified using SABPPP template. if (argsInput) { // Type 1: User supplied input, with or without an explicit unlockingScript. // If without, signAction must be used to provide the actual unlockScript. const hasUnlock = typeof argsInput.unlockingScript === 'string'; const unlock = hasUnlock ? (0, index_client_1.asBsvSdkScript)(argsInput.unlockingScript) : new sdk_1.Script(); const inputToAdd = { sourceTXID: argsInput.outpoint.txid, sourceOutputIndex: argsInput.outpoint.vout, unlockingScript: unlock, sequence: argsInput.sequenceNumber }; tx.addInput(inputToAdd); } else { // Type2: SABPPP protocol inputs which are signed using ScriptTemplateSABPPP. if (storageInput.type !== 'P2PKH') throw new index_client_1.sdk.WERR_INVALID_PARAMETER('type', `vin ${storageInput.vin}, "${storageInput.type}" is not a supported unlocking script type.`); pendingStorageInputs.push({ vin: tx.inputs.length, derivationPrefix: (0, index_client_1.verifyTruthy)(storageInput.derivationPrefix), derivationSuffix: (0, index_client_1.verifyTruthy)(storageInput.derivationSuffix), unlockerPubKey: storageInput.senderIdentityKey, sourceSatoshis: storageInput.sourceSatoshis, lockingScript: storageInput.sourceLockingScript }); const inputToAdd = { sourceTXID: storageInput.sourceTxid, sourceOutputIndex: storageInput.sourceVout, unlockingScript: new sdk_1.Script(), sequence: 0xffffffff }; tx.addInput(inputToAdd); totalChangeInputs += (0, index_client_1.verifyTruthy)(storageInput.sourceSatoshis); } } // The amount is the total of non-foreign inputs minus change outputs // Note that the amount can be negative when we are redeeming more inputs than we are spending const totalChangeOutputs = storageOutputs.filter(x => x.purpose === 'change').reduce((acc, el) => acc + el.satoshis, 0); const amount = totalChangeInputs - totalChangeOutputs; return { tx, amount, pdi: pendingStorageInputs, log: '' }; } //# sourceMappingURL=createAction.js.map