@bsv/wallet-toolbox-client
Version:
Client only Wallet Storage
142 lines • 6.95 kB
JavaScript
;
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