bitcore-wallet-service
Version:
A service for Mutisig HD Bitcoin Wallets
304 lines • 11.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TxProposal = void 0;
const lodash_1 = __importDefault(require("lodash"));
const index_1 = require("../chain/index");
const common_1 = require("../common");
const logger_1 = __importDefault(require("../logger"));
const txproposal_legacy_1 = require("./txproposal_legacy");
const txproposalaction_1 = require("./txproposalaction");
const $ = require('preconditions').singleton();
const Uuid = require('uuid');
const Constants = common_1.Common.Constants, Defaults = common_1.Common.Defaults, Utils = common_1.Common.Utils;
class TxProposal {
constructor() {
this.actions = [];
}
static create(opts) {
opts = opts || {};
const chain = opts.chain?.toLowerCase() || index_1.ChainService.getChain(opts.coin);
$.checkArgument(Utils.checkValueInCollection(opts.network, Constants.NETWORKS[chain]), `Invalid network: ${opts.network} at TxProposal.create()`);
const x = new TxProposal();
if (opts.version) {
$.checkArgument(opts.version >= 3);
}
x.version = opts.version || 3;
$.checkState(x.version <= 3, 'Failed state: txp version 4 not allowed yet at TxProposal.create()');
const now = Date.now();
x.createdOn = Math.floor(now / 1000);
x.id = opts.id || Uuid.v4();
x.walletId = opts.walletId;
x.creatorId = opts.creatorId;
x.coin = opts.coin;
x.chain = chain;
x.network = opts.network;
x.signingMethod = opts.signingMethod;
x.message = opts.message;
x.payProUrl = opts.payProUrl;
x.changeAddress = opts.changeAddress;
x.escrowAddress = opts.escrowAddress;
x.instantAcceptanceEscrow = opts.instantAcceptanceEscrow;
x.outputs = (opts.outputs || []).map(output => {
const out = {};
if (output.amount !== undefined)
out.amount = output.amount;
if (output.toAddress !== undefined)
out.toAddress = output.toAddress;
if (output.message !== undefined)
out.message = output.message;
if (output.data !== undefined)
out.data = output.data;
if (output.gasLimit !== undefined)
out.gasLimit = output.gasLimit;
if (output.script !== undefined)
out.script = output.script;
if (output.tag !== undefined)
out.tag = output.tag;
return out;
});
let numOutputs = x.outputs.length;
if (!opts.multiTx) {
numOutputs++;
}
if (x.instantAcceptanceEscrow) {
numOutputs++;
}
x.outputOrder = lodash_1.default.range(numOutputs);
if (!opts.noShuffleOutputs) {
x.outputOrder = lodash_1.default.shuffle(x.outputOrder);
}
x.walletM = opts.walletM;
x.walletN = opts.walletN;
x.requiredSignatures = x.walletM;
(x.requiredRejections = Math.min(x.walletM, x.walletN - x.walletM + 1)), (x.status = 'temporary');
x.actions = [];
x.feeLevel = opts.feeLevel;
x.feePerKb = opts.feePerKb;
x.excludeUnconfirmedUtxos = opts.excludeUnconfirmedUtxos;
x.addressType = opts.addressType || (x.walletN > 1 ? Constants.SCRIPT_TYPES.P2SH : Constants.SCRIPT_TYPES.P2PKH);
$.checkState(Utils.checkValueInCollection(x.addressType, Constants.SCRIPT_TYPES), 'Failed state: addressType not in ScriptTypes at <create()>');
x.customData = opts.customData;
x.amount = opts.amount ? opts.amount : x.getTotalAmount();
x.setInputs(opts.inputs);
x.fee = opts.fee;
if (x.version === 4) {
x.lockUntilBlockHeight = opts.lockUntilBlockHeight;
}
x.enableRBF = opts.enableRBF;
x.replaceTxByFee = opts.replaceTxByFee;
x.gasPrice = opts.gasPrice;
x.maxGasFee = opts.maxGasFee;
x.priorityGasFee = opts.priorityGasFee;
x.txType = opts.txType;
x.from = opts.from;
x.nonce = opts.nonce;
x.gasLimit = opts.gasLimit;
x.data = opts.data;
x.tokenAddress = opts.tokenAddress;
x.multiSendContractAddress = opts.multiSendContractAddress;
x.isTokenSwap = opts.isTokenSwap;
x.multisigContractAddress = opts.multisigContractAddress;
x.destinationTag = opts.destinationTag;
x.invoiceID = opts.invoiceID;
x.multiTx = opts.multiTx;
x.space = opts.space;
x.blockHash = opts.blockHash;
x.blockHeight = opts.blockHeight;
x.nonceAddress = opts.nonceAddress;
x.category = opts.category;
x.computeUnits = opts.computeUnits;
x.priorityFee = opts.priorityFee;
x.refreshOnPublish = opts.refreshOnPublish;
return x;
}
static fromObj(obj) {
if (!(obj.version >= 3)) {
return txproposal_legacy_1.TxProposalLegacy.fromObj(obj);
}
const x = new TxProposal();
x.version = obj.version;
x.createdOn = obj.createdOn;
x.id = obj.id;
x.walletId = obj.walletId;
x.creatorId = obj.creatorId;
x.coin = obj.coin || Defaults.COIN;
x.chain = obj.chain?.toLowerCase() || index_1.ChainService.getChain(x.coin);
x.network = obj.network;
x.outputs = obj.outputs;
x.amount = obj.amount;
x.message = obj.message;
x.payProUrl = obj.payProUrl;
x.changeAddress = obj.changeAddress;
x.escrowAddress = obj.escrowAddress;
x.instantAcceptanceEscrow = obj.instantAcceptanceEscrow;
x.inputs = obj.inputs;
x.walletM = obj.walletM;
x.walletN = obj.walletN;
x.requiredSignatures = obj.requiredSignatures;
x.requiredRejections = obj.requiredRejections;
x.status = obj.status;
x.txid = obj.txid;
x.txids = obj.txids;
x.broadcastedOn = obj.broadcastedOn;
x.inputPaths = obj.inputPaths;
x.actions = lodash_1.default.map(obj.actions, action => {
return txproposalaction_1.TxProposalAction.fromObj(action);
});
x.outputOrder = obj.outputOrder;
x.fee = obj.fee;
x.feeLevel = obj.feeLevel;
x.feePerKb = obj.feePerKb;
x.excludeUnconfirmedUtxos = obj.excludeUnconfirmedUtxos;
x.addressType = obj.addressType;
x.customData = obj.customData;
x.proposalSignature = obj.proposalSignature;
x.signingMethod = obj.signingMethod;
x.proposalSignaturePubKey = obj.proposalSignaturePubKey;
x.proposalSignaturePubKeySig = obj.proposalSignaturePubKeySig;
x.lockUntilBlockHeight = obj.lockUntilBlockHeight;
x.enableRBF = obj.enableRBF;
x.replaceTxByFee = obj.replaceTxByFee;
x.gasPrice = obj.gasPrice;
x.maxGasFee = obj.maxGasFee;
x.priorityGasFee = obj.priorityGasFee;
x.txType = obj.txType;
x.from = obj.from;
x.nonce = obj.nonce;
x.gasLimit = obj.gasLimit;
x.data = obj.data;
x.tokenAddress = obj.tokenAddress;
x.isTokenSwap = obj.isTokenSwap;
x.multiSendContractAddress = obj.multiSendContractAddress;
x.multisigContractAddress = obj.multisigContractAddress;
x.multisigTxId = obj.multisigTxId;
x.destinationTag = obj.destinationTag;
x.invoiceID = obj.invoiceID;
x.multiTx = obj.multiTx;
x.space = obj.space;
x.blockHash = obj.blockHash;
x.blockHeight = obj.blockHeight;
x.nonceAddress = obj.nonceAddress;
x.category = obj.category;
x.computeUnits = obj.computeUnits;
x.priorityFee = obj.priorityFee;
x.refreshOnPublish = obj.refreshOnPublish;
x.prePublishRaw = obj.prePublishRaw;
if (x.status == 'broadcasted') {
x.raw = obj.raw;
}
return x;
}
toObject() {
const x = lodash_1.default.cloneDeep(this);
x.isPending = this.isPending();
return x;
}
setInputs(inputs) {
this.inputs = inputs || [];
this.inputPaths = lodash_1.default.map(inputs, 'path') || [];
}
_updateStatus() {
if (this.status != 'pending')
return;
if (this.isRejected()) {
this.status = 'rejected';
}
else if (this.isAccepted()) {
this.status = 'accepted';
}
}
getCurrentSignatures() {
const acceptedActions = lodash_1.default.filter(this.actions, a => {
return a.type == 'accept';
});
return lodash_1.default.map(acceptedActions, x => {
return {
signatures: x.signatures,
xpub: x.xpub
};
});
}
getRawTx() {
const t = index_1.ChainService.getBitcoreTx(this);
return t.uncheckedSerialize();
}
getTotalAmount() {
return Number((this.outputs || []).reduce((total, o) => total += BigInt(o.amount), 0n));
}
getActors() {
return lodash_1.default.map(this.actions, 'copayerId');
}
getApprovers() {
return lodash_1.default.map(lodash_1.default.filter(this.actions, a => {
return a.type == 'accept';
}), 'copayerId');
}
getActionBy(copayerId) {
return lodash_1.default.find(this.actions, {
copayerId
});
}
addAction(copayerId, type, comment, signatures, xpub) {
const action = txproposalaction_1.TxProposalAction.create({
copayerId,
type,
signatures,
xpub,
comment
});
this.actions.push(action);
this._updateStatus();
}
sign(copayerId, signatures, xpub) {
try {
const tx = index_1.ChainService.getBitcoreTx(this);
index_1.ChainService.addSignaturesToBitcoreTx(this.chain, tx, this.inputs, this.inputPaths, signatures, xpub, this.signingMethod);
this.addAction(copayerId, 'accept', null, signatures, xpub);
if (this.status == 'accepted') {
this.raw = tx.uncheckedSerialize();
this.txid = tx.id;
if (this.multiTx) {
this.txids = tx?.txids && tx.txids() || [tx.id];
}
}
return true;
}
catch (e) {
logger_1.default.debug('%o', e);
return false;
}
}
reject(copayerId, reason) {
this.addAction(copayerId, 'reject', reason);
}
isRepublishEnabled() {
return !!this.refreshOnPublish;
}
isTemporary() {
return this.status == 'temporary';
}
isPending() {
return !lodash_1.default.includes(['temporary', 'broadcasted', 'rejected'], this.status);
}
isAccepted() {
const votes = lodash_1.default.countBy(this.actions, 'type');
return votes['accept'] >= this.requiredSignatures;
}
isRejected() {
const votes = lodash_1.default.countBy(this.actions, 'type');
return votes['reject'] >= this.requiredRejections;
}
isBroadcasted() {
return this.status == 'broadcasted';
}
setBroadcasted() {
$.checkState(this.txid, 'Failed state: this.txid at <setBroadcasted()>');
this.status = 'broadcasted';
this.broadcastedOn = Math.floor(Date.now() / 1000);
}
}
exports.TxProposal = TxProposal;
//# sourceMappingURL=txproposal.js.map