UNPKG

@bsv/wallet-toolbox-client

Version:
142 lines 6.95 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildSignableTransaction = buildSignableTransaction; exports.makeChangeLock = makeChangeLock; const sdk_1 = require("@bsv/sdk"); const WERR_errors_1 = require("../../sdk/WERR_errors"); const utilityHelpers_1 = require("../../utility/utilityHelpers"); const ScriptTemplateBRC29_1 = require("../../utility/ScriptTemplateBRC29"); function buildSignableTransaction(dctr, args, wallet) { var _a; const changeKeys = wallet.getClientChangeKeyPair(); const inputBeef = args.inputBEEF ? sdk_1.Beef.fromBinary(args.inputBEEF) : undefined; 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 WERR_errors_1.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 WERR_errors_1.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, utilityHelpers_1.asBsvSdkScript)(out.lockingScript); const output = { satoshis: out.satoshis, lockingScript, change }; tx.addOutput(output); } if (storageOutputs.length === 0) { // Add a dummy output to avoid transaction rejection by processors for having no outputs. const output = { satoshis: 0, lockingScript: sdk_1.Script.fromASM('OP_FALSE OP_RETURN 42'), change: false }; 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, utilityHelpers_1.asBsvSdkScript)(argsInput.unlockingScript) : new sdk_1.Script(); const sourceTransaction = args.isSignAction ? (_a = inputBeef === null || inputBeef === void 0 ? void 0 : inputBeef.findTxid(argsInput.outpoint.txid)) === null || _a === void 0 ? void 0 : _a.tx : undefined; const inputToAdd = { sourceTXID: argsInput.outpoint.txid, sourceOutputIndex: argsInput.outpoint.vout, // Include the source transaction for access to the outputs locking script and output satoshis for user side fee calculation. // TODO: Make this conditional to improve performance when user can supply locking scripts themselves. sourceTransaction, unlockingScript: unlock, sequence: argsInput.sequenceNumber }; tx.addInput(inputToAdd); } else { // Type2: SABPPP protocol inputs which are signed using ScriptTemplateBRC29. if (storageInput.type !== 'P2PKH') throw new WERR_errors_1.WERR_INVALID_PARAMETER('type', `vin ${storageInput.vin}, "${storageInput.type}" is not a supported unlocking script type.`); pendingStorageInputs.push({ vin: tx.inputs.length, derivationPrefix: (0, utilityHelpers_1.verifyTruthy)(storageInput.derivationPrefix), derivationSuffix: (0, utilityHelpers_1.verifyTruthy)(storageInput.derivationSuffix), unlockerPubKey: storageInput.senderIdentityKey, sourceSatoshis: storageInput.sourceSatoshis, lockingScript: storageInput.sourceLockingScript }); const inputToAdd = { sourceTXID: storageInput.sourceTxid, sourceOutputIndex: storageInput.sourceVout, sourceTransaction: storageInput.sourceTransaction ? sdk_1.Transaction.fromBinary(storageInput.sourceTransaction) : undefined, unlockingScript: new sdk_1.Script(), sequence: 0xffffffff }; tx.addInput(inputToAdd); totalChangeInputs += (0, utilityHelpers_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: '' }; } /** * Derive a change output locking script */ function makeChangeLock(out, dctr, args, changeKeys, wallet) { const derivationPrefix = dctr.derivationPrefix; const derivationSuffix = (0, utilityHelpers_1.verifyTruthy)(out.derivationSuffix); const sabppp = new ScriptTemplateBRC29_1.ScriptTemplateBRC29({ derivationPrefix, derivationSuffix, keyDeriver: wallet.keyDeriver }); const lockingScript = sabppp.lock(changeKeys.privateKey, changeKeys.publicKey); return lockingScript; } //# sourceMappingURL=buildSignableTransaction.js.map