@node-dlc/core
Advanced tools
Comparing version 0.23.0 to 0.23.1
@@ -23,2 +23,3 @@ import { Value } from '@node-dlc/bitcoin'; | ||
LinearPayout, | ||
roundDownToNearestMultiplier, | ||
roundUpToNearestMultiplier, | ||
@@ -635,6 +636,15 @@ } from '../../../lib'; | ||
// Fees are very high, so use dust threshold for max gain | ||
const expectedMaxGainForContractSize = Value.fromSats( | ||
const offerFees = Value.fromSats( | ||
getFinalizerByCount(feePerByte, numOfferInputs, 3, numContracts) | ||
.offerFees, | ||
).addn(Value.fromSats(dustThreshold(feePerByte))); | ||
); | ||
const expectedMaxGainForContractSize_ = offerFees.addn( | ||
Value.fromSats(dustThreshold(BigInt(feePerByte))), | ||
); | ||
const expectedMaxGainForContractSize = Value.fromSats( | ||
roundUpToNearestMultiplier( | ||
expectedMaxGainForContractSize_.sats, | ||
BigInt(100), | ||
), | ||
); | ||
@@ -649,3 +659,3 @@ const expectedMaxLossForContractSize = Value.fromSats( | ||
const expectedNormalizedMaxGain = Value.fromSats( | ||
roundUpToNearestMultiplier( | ||
roundDownToNearestMultiplier( | ||
(expectedMaxGainForContractSize.sats * BigInt(1e8)) / | ||
@@ -657,6 +667,2 @@ contractSize.sats, | ||
expect(actualNormalizedMaxGain.sats).to.equal( | ||
expectedNormalizedMaxGain.sats, | ||
); | ||
expect(actualNormalizedMaxLoss.sats).to.equal(normalizedMaxLoss.sats); | ||
expect(actualMaxGainForContractSize.sats).to.equal( | ||
@@ -668,3 +674,7 @@ expectedMaxGainForContractSize.sats, | ||
); | ||
expect(minPayout).to.equal(BigInt(119900)); | ||
expect(actualNormalizedMaxLoss.sats).to.equal(normalizedMaxLoss.sats); | ||
expect(actualNormalizedMaxGain.sats).to.equal( | ||
expectedNormalizedMaxGain.sats, | ||
); // TODO: Fix issue with this line | ||
expect(minPayout).to.equal(BigInt(121200)); | ||
expect(maxPayout).to.equal(collateral.sats); | ||
@@ -714,6 +724,15 @@ expect(actualContractSize.sats).to.equal(contractSize.sats); | ||
// Fees are very high, so use dust threshold for max gain | ||
const expectedMaxGainForContractSize = Value.fromSats( | ||
const offerFees = Value.fromSats( | ||
getFinalizerByCount(feePerByte, numOfferInputs, 3, numContracts) | ||
.offerFees, | ||
).addn(Value.fromSats(dustThreshold(feePerByte))); | ||
); | ||
const expectedMaxGainForContractSize_ = offerFees.addn( | ||
Value.fromSats(dustThreshold(BigInt(feePerByte))), | ||
); | ||
const expectedMaxGainForContractSize = Value.fromSats( | ||
roundUpToNearestMultiplier( | ||
expectedMaxGainForContractSize_.sats, | ||
BigInt(100), | ||
), | ||
); | ||
@@ -728,3 +747,3 @@ const expectedMaxLossForContractSize = Value.fromSats( | ||
const expectedNormalizedMaxGain = Value.fromSats( | ||
roundUpToNearestMultiplier( | ||
roundDownToNearestMultiplier( | ||
(expectedMaxGainForContractSize.sats * BigInt(1e8)) / | ||
@@ -746,3 +765,3 @@ contractSize.sats, | ||
); | ||
expect(minPayout).to.equal(BigInt(119900)); | ||
expect(minPayout).to.equal(BigInt(121200)); | ||
expect(maxPayout).to.equal(collateral.sats); | ||
@@ -792,6 +811,15 @@ expect(actualContractSize.sats).to.equal(contractSize.sats); | ||
// Fees are very high, so use dust threshold for max gain | ||
const expectedMaxGainForContractSize = Value.fromSats( | ||
const offerFees = Value.fromSats( | ||
getFinalizerByCount(feePerByte, numOfferInputs, 3, numContracts) | ||
.offerFees, | ||
).addn(Value.fromSats(dustThreshold(feePerByte))); | ||
); | ||
const expectedMaxGainForContractSize_ = offerFees.addn( | ||
Value.fromSats(dustThreshold(BigInt(feePerByte))), | ||
); | ||
const expectedMaxGainForContractSize = Value.fromSats( | ||
roundUpToNearestMultiplier( | ||
expectedMaxGainForContractSize_.sats, | ||
BigInt(100), | ||
), | ||
); | ||
@@ -806,3 +834,3 @@ const expectedMaxLossForContractSize = Value.fromSats( | ||
const expectedNormalizedMaxGain = Value.fromSats( | ||
roundUpToNearestMultiplier( | ||
roundDownToNearestMultiplier( | ||
(expectedMaxGainForContractSize.sats * BigInt(1e8)) / | ||
@@ -824,3 +852,3 @@ contractSize.sats, | ||
); | ||
expect(minPayout).to.equal(BigInt(119900)); | ||
expect(minPayout).to.equal(BigInt(121200)); | ||
expect(maxPayout).to.equal(collateral.sats); | ||
@@ -827,0 +855,0 @@ expect(actualContractSize.sats).to.equal(contractSize.sats); |
@@ -1,1 +0,1 @@ | ||
{"processes":{"3c0d0f1a-a3da-4252-9234-f74a9f0bafe2":{"parent":null,"children":[]}},"files":{"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/CETCalculator.ts":["3c0d0f1a-a3da-4252-9234-f74a9f0bafe2"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/utils/BigIntUtils.ts":["3c0d0f1a-a3da-4252-9234-f74a9f0bafe2"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/finance/CoveredCall.ts":["3c0d0f1a-a3da-4252-9234-f74a9f0bafe2"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/HyperbolaPayoutCurve.ts":["3c0d0f1a-a3da-4252-9234-f74a9f0bafe2"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/utils/Precision.ts":["3c0d0f1a-a3da-4252-9234-f74a9f0bafe2"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/finance/ShortPut.ts":["3c0d0f1a-a3da-4252-9234-f74a9f0bafe2"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/index.ts":["3c0d0f1a-a3da-4252-9234-f74a9f0bafe2"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/finance/Builder.ts":["3c0d0f1a-a3da-4252-9234-f74a9f0bafe2"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/CoinSelect.ts":["3c0d0f1a-a3da-4252-9234-f74a9f0bafe2"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/TxFinalizer.ts":["3c0d0f1a-a3da-4252-9234-f74a9f0bafe2"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/finance/LinearPayout.ts":["3c0d0f1a-a3da-4252-9234-f74a9f0bafe2"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/PolynomialPayoutCurve.ts":["3c0d0f1a-a3da-4252-9234-f74a9f0bafe2"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/finance/LongCall.ts":["3c0d0f1a-a3da-4252-9234-f74a9f0bafe2"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/finance/LongPut.ts":["3c0d0f1a-a3da-4252-9234-f74a9f0bafe2"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/finance/CsoInfo.ts":["3c0d0f1a-a3da-4252-9234-f74a9f0bafe2"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/finance/OptionInfo.ts":["3c0d0f1a-a3da-4252-9234-f74a9f0bafe2"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/PayoutCurve.ts":["3c0d0f1a-a3da-4252-9234-f74a9f0bafe2"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/TxBuilder.ts":["3c0d0f1a-a3da-4252-9234-f74a9f0bafe2"]},"externalIds":{}} | ||
{"processes":{"a8bae97d-9104-4e1c-a1dd-9015c00a5dc3":{"parent":null,"children":[]}},"files":{"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/CETCalculator.ts":["a8bae97d-9104-4e1c-a1dd-9015c00a5dc3"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/utils/BigIntUtils.ts":["a8bae97d-9104-4e1c-a1dd-9015c00a5dc3"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/finance/CoveredCall.ts":["a8bae97d-9104-4e1c-a1dd-9015c00a5dc3"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/HyperbolaPayoutCurve.ts":["a8bae97d-9104-4e1c-a1dd-9015c00a5dc3"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/utils/Precision.ts":["a8bae97d-9104-4e1c-a1dd-9015c00a5dc3"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/finance/ShortPut.ts":["a8bae97d-9104-4e1c-a1dd-9015c00a5dc3"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/index.ts":["a8bae97d-9104-4e1c-a1dd-9015c00a5dc3"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/finance/Builder.ts":["a8bae97d-9104-4e1c-a1dd-9015c00a5dc3"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/CoinSelect.ts":["a8bae97d-9104-4e1c-a1dd-9015c00a5dc3"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/TxFinalizer.ts":["a8bae97d-9104-4e1c-a1dd-9015c00a5dc3"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/finance/LinearPayout.ts":["a8bae97d-9104-4e1c-a1dd-9015c00a5dc3"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/PolynomialPayoutCurve.ts":["a8bae97d-9104-4e1c-a1dd-9015c00a5dc3"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/finance/LongCall.ts":["a8bae97d-9104-4e1c-a1dd-9015c00a5dc3"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/finance/LongPut.ts":["a8bae97d-9104-4e1c-a1dd-9015c00a5dc3"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/finance/CsoInfo.ts":["a8bae97d-9104-4e1c-a1dd-9015c00a5dc3"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/finance/OptionInfo.ts":["a8bae97d-9104-4e1c-a1dd-9015c00a5dc3"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/PayoutCurve.ts":["a8bae97d-9104-4e1c-a1dd-9015c00a5dc3"],"/Users/matthewblack/code/github.com/AtomicFinance/node-dlc/packages/core/lib/dlc/TxBuilder.ts":["a8bae97d-9104-4e1c-a1dd-9015c00a5dc3"]},"externalIds":{}} |
@@ -42,2 +42,3 @@ import { Value } from '@node-dlc/bitcoin'; | ||
export declare const roundUpToNearestMultiplier: (num: bigint, multiplier: bigint) => bigint; | ||
export declare const roundDownToNearestMultiplier: (num: bigint, multiplier: bigint) => bigint; | ||
export declare type DlcParty = 'offeror' | 'acceptor' | 'neither'; | ||
@@ -44,0 +45,0 @@ /** |
@@ -6,3 +6,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.buildRoundingIntervalsFromIntervals = exports.buildCustomStrategyOrderOffer = exports.buildLinearOrderOffer = exports.buildLongPutOrderOffer = exports.buildLongCallOrderOffer = exports.buildShortPutOrderOffer = exports.buildCoveredCallOrderOffer = exports.buildOptionOrderOffer = exports.buildOrderOffer = exports.getDigitDecompositionEventDescriptor = exports.computeRoundingModulus = exports.roundUpToNearestMultiplier = exports.roundToNearestMultiplier = exports.UNIT_MULTIPLIER = void 0; | ||
exports.buildRoundingIntervalsFromIntervals = exports.buildCustomStrategyOrderOffer = exports.buildLinearOrderOffer = exports.buildLongPutOrderOffer = exports.buildLongCallOrderOffer = exports.buildShortPutOrderOffer = exports.buildCoveredCallOrderOffer = exports.buildOptionOrderOffer = exports.buildOrderOffer = exports.getDigitDecompositionEventDescriptor = exports.computeRoundingModulus = exports.roundDownToNearestMultiplier = exports.roundUpToNearestMultiplier = exports.roundToNearestMultiplier = exports.UNIT_MULTIPLIER = void 0; | ||
const bitcoin_1 = require("@node-dlc/bitcoin"); | ||
@@ -59,2 +59,4 @@ const messaging_1 = require("@node-dlc/messaging"); | ||
exports.roundUpToNearestMultiplier = roundUpToNearestMultiplier; | ||
const roundDownToNearestMultiplier = (num, multiplier) => num - (num % multiplier); | ||
exports.roundDownToNearestMultiplier = roundDownToNearestMultiplier; | ||
/** | ||
@@ -61,0 +63,0 @@ * Compute rounding intervals for a linear or hyperbola payout curve |
@@ -109,6 +109,3 @@ "use strict"; | ||
normalizedMaxLoss.sub(startOutcomeValue); | ||
const maxGainForContractSize = bitcoin_1.Value.fromBitcoin(new decimal_js_1.default(normalizedMaxGain.bitcoin) | ||
.times(contractSize.bitcoin) | ||
.toDecimalPlaces(8 - Math.log10(Number(Builder_1.UNIT_MULTIPLIER[unit.toLowerCase()]))) | ||
.toNumber()); | ||
const maxGainForContractSize = bitcoin_1.Value.fromSats(Builder_1.roundUpToNearestMultiplier((normalizedMaxGain.sats * contractSize.sats) / BigInt(1e8), BigInt(Builder_1.UNIT_MULTIPLIER[unit.toLowerCase()]))); | ||
const maxLossForContractSize = bitcoin_1.Value.fromSats(Builder_1.roundUpToNearestMultiplier((normalizedMaxLoss.sats * contractSize.sats) / BigInt(1e8), BigInt(Builder_1.UNIT_MULTIPLIER[unit.toLowerCase()]))); | ||
@@ -115,0 +112,0 @@ const offerCollateral = collateral.clone(); |
@@ -9,1 +9,9 @@ import { DlcAcceptWithoutSigs, DlcOfferV0 } from '@node-dlc/messaging'; | ||
} | ||
export declare class BatchDlcTxBuilder { | ||
readonly dlcOffers: DlcOfferV0[]; | ||
readonly dlcAccepts: DlcAcceptWithoutSigs[]; | ||
constructor(dlcOffers: DlcOfferV0[], dlcAccepts: DlcAcceptWithoutSigs[]); | ||
buildFundingTransaction(): Tx; | ||
private ensureSameFundingInputs; | ||
private arraysEqual; | ||
} |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.DlcTxBuilder = void 0; | ||
exports.BatchDlcTxBuilder = exports.DlcTxBuilder = void 0; | ||
const messaging_1 = require("@node-dlc/messaging"); | ||
const bitcoin_1 = require("@node-lightning/bitcoin"); | ||
const decimal_js_1 = __importDefault(require("decimal.js")); | ||
const TxFinalizer_1 = require("./TxFinalizer"); | ||
@@ -13,22 +17,43 @@ class DlcTxBuilder { | ||
buildFundingTransaction() { | ||
const txBuilder = new BatchDlcTxBuilder([this.dlcOffer], [this.dlcAccept]); | ||
return txBuilder.buildFundingTransaction(); | ||
} | ||
} | ||
exports.DlcTxBuilder = DlcTxBuilder; | ||
class BatchDlcTxBuilder { | ||
constructor(dlcOffers, dlcAccepts) { | ||
this.dlcOffers = dlcOffers; | ||
this.dlcAccepts = dlcAccepts; | ||
} | ||
buildFundingTransaction() { | ||
const tx = new bitcoin_1.TxBuilder(); | ||
tx.version = 2; | ||
tx.locktime = bitcoin_1.LockTime.zero(); | ||
const multisigScript = Buffer.compare(this.dlcOffer.fundingPubKey, this.dlcAccept.fundingPubKey) === -1 | ||
? bitcoin_1.Script.p2msLock(2, this.dlcOffer.fundingPubKey, this.dlcAccept.fundingPubKey) | ||
: bitcoin_1.Script.p2msLock(2, this.dlcAccept.fundingPubKey, this.dlcOffer.fundingPubKey); | ||
const witScript = bitcoin_1.Script.p2wshLock(multisigScript); | ||
const offerInput = this.dlcOffer.offerCollateralSatoshis; | ||
const acceptInput = this.dlcAccept.acceptCollateralSatoshis; | ||
const totalInput = offerInput + acceptInput; | ||
const finalizer = new TxFinalizer_1.DualFundingTxFinalizer(this.dlcOffer.fundingInputs, this.dlcOffer.payoutSPK, this.dlcOffer.changeSPK, this.dlcAccept.fundingInputs, this.dlcAccept.payoutSPK, this.dlcAccept.changeSPK, this.dlcOffer.feeRatePerVb); | ||
this.dlcOffer.fundingInputs.forEach((input) => { | ||
if (this.dlcOffers.length !== this.dlcAccepts.length) | ||
throw Error('DlcOffers and DlcAccepts must be the same length'); | ||
if (this.dlcOffers.length === 0) | ||
throw Error('DlcOffers must not be empty'); | ||
if (this.dlcAccepts.length === 0) | ||
throw Error('DlcAccepts must not be empty'); | ||
// Ensure all DLC offers and accepts have the same funding inputs | ||
this.ensureSameFundingInputs(); | ||
const multisigScripts = []; | ||
for (let i = 0; i < this.dlcOffers.length; i++) { | ||
const offer = this.dlcOffers[i]; | ||
const accept = this.dlcAccepts[i]; | ||
multisigScripts.push(Buffer.compare(offer.fundingPubKey, accept.fundingPubKey) === -1 | ||
? bitcoin_1.Script.p2msLock(2, offer.fundingPubKey, accept.fundingPubKey) | ||
: bitcoin_1.Script.p2msLock(2, accept.fundingPubKey, offer.fundingPubKey)); | ||
} | ||
const witScripts = multisigScripts.map((multisigScript) => bitcoin_1.Script.p2wshLock(multisigScript)); | ||
const finalizer = new TxFinalizer_1.DualFundingTxFinalizer(this.dlcOffers[0].fundingInputs, this.dlcOffers[0].payoutSPK, this.dlcOffers[0].changeSPK, this.dlcAccepts[0].fundingInputs, this.dlcAccepts[0].payoutSPK, this.dlcAccepts[0].changeSPK, this.dlcOffers[0].feeRatePerVb, this.dlcOffers.length); | ||
this.dlcOffers[0].fundingInputs.forEach((input) => { | ||
if (input.type !== messaging_1.MessageType.FundingInputV0) | ||
throw Error('FundingInput must be V0'); | ||
}); | ||
const offerFundingInputs = this.dlcOffer.fundingInputs.map((input) => input); | ||
const offerFundingInputs = this.dlcOffers[0].fundingInputs.map((input) => input); | ||
const offerTotalFunding = offerFundingInputs.reduce((total, input) => { | ||
return total + input.prevTx.outputs[input.prevTxVout].value.sats; | ||
}, BigInt(0)); | ||
const acceptTotalFunding = this.dlcAccept.fundingInputs.reduce((total, input) => { | ||
const acceptTotalFunding = this.dlcAccepts[0].fundingInputs.reduce((total, input) => { | ||
return total + input.prevTx.outputs[input.prevTxVout].value.sats; | ||
@@ -38,3 +63,3 @@ }, BigInt(0)); | ||
...offerFundingInputs, | ||
...this.dlcAccept.fundingInputs, | ||
...this.dlcAccepts[0].fundingInputs, | ||
]; | ||
@@ -45,20 +70,41 @@ fundingInputs.sort((a, b) => Number(a.inputSerialId) - Number(b.inputSerialId)); | ||
}); | ||
const fundingValue = totalInput + finalizer.offerFutureFee + finalizer.acceptFutureFee; | ||
const offerInput = this.dlcOffers.reduce((total, offer) => total + offer.offerCollateralSatoshis, BigInt(0)); | ||
const acceptInput = this.dlcAccepts.reduce((total, accept) => total + accept.acceptCollateralSatoshis, BigInt(0)); | ||
const totalInputs = this.dlcOffers.map((offer, i) => { | ||
const offerInput = offer.offerCollateralSatoshis; | ||
const acceptInput = this.dlcAccepts[i].acceptCollateralSatoshis; | ||
return offerInput + acceptInput; | ||
}); | ||
const fundingValues = totalInputs.map((totalInput) => { | ||
const offerFutureFeePerOffer = new decimal_js_1.default(finalizer.offerFutureFee.toString()) | ||
.div(this.dlcOffers.length) | ||
.ceil() | ||
.toNumber(); | ||
const acceptFutureFeePerAccept = new decimal_js_1.default(finalizer.acceptFutureFee.toString()) | ||
.div(this.dlcAccepts.length) | ||
.ceil() | ||
.toNumber(); | ||
return (totalInput + | ||
bitcoin_1.Value.fromSats(offerFutureFeePerOffer).sats + | ||
bitcoin_1.Value.fromSats(acceptFutureFeePerAccept).sats); | ||
}); | ||
const offerChangeValue = offerTotalFunding - offerInput - finalizer.offerFees; | ||
const acceptChangeValue = acceptTotalFunding - acceptInput - finalizer.acceptFees; | ||
const outputs = []; | ||
outputs.push({ | ||
value: bitcoin_1.Value.fromSats(Number(fundingValue)), | ||
script: witScript, | ||
serialId: this.dlcOffer.fundOutputSerialId, | ||
witScripts.forEach((witScript, i) => { | ||
outputs.push({ | ||
value: bitcoin_1.Value.fromSats(Number(fundingValues[i])), | ||
script: witScript, | ||
serialId: this.dlcOffers[i].fundOutputSerialId, | ||
}); | ||
}); | ||
outputs.push({ | ||
value: bitcoin_1.Value.fromSats(Number(offerChangeValue)), | ||
script: bitcoin_1.Script.p2wpkhLock(this.dlcOffer.changeSPK.slice(2)), | ||
serialId: this.dlcOffer.changeSerialId, | ||
script: bitcoin_1.Script.p2wpkhLock(this.dlcOffers[0].changeSPK.slice(2)), | ||
serialId: this.dlcOffers[0].changeSerialId, | ||
}); | ||
outputs.push({ | ||
value: bitcoin_1.Value.fromSats(Number(acceptChangeValue)), | ||
script: bitcoin_1.Script.p2wpkhLock(this.dlcAccept.changeSPK.slice(2)), | ||
serialId: this.dlcAccept.changeSerialId, | ||
script: bitcoin_1.Script.p2wpkhLock(this.dlcAccepts[0].changeSPK.slice(2)), | ||
serialId: this.dlcAccepts[0].changeSerialId, | ||
}); | ||
@@ -71,4 +117,31 @@ outputs.sort((a, b) => Number(a.serialId) - Number(b.serialId)); | ||
} | ||
ensureSameFundingInputs() { | ||
// Check for offers | ||
const referenceOfferInputs = this.dlcOffers[0].fundingInputs.map((input) => input.serialize().toString('hex')); | ||
for (let i = 1; i < this.dlcOffers.length; i++) { | ||
const currentInputs = this.dlcOffers[i].fundingInputs.map((input) => input.serialize().toString('hex')); | ||
if (!this.arraysEqual(referenceOfferInputs, currentInputs)) { | ||
throw new Error(`Funding inputs for offer ${i} do not match the first offer's funding inputs.`); | ||
} | ||
} | ||
// Check for accepts | ||
const referenceAcceptInputs = this.dlcAccepts[0].fundingInputs.map((input) => input.serialize().toString('hex')); | ||
for (let i = 1; i < this.dlcAccepts.length; i++) { | ||
const currentInputs = this.dlcAccepts[i].fundingInputs.map((input) => input.serialize().toString('hex')); | ||
if (!this.arraysEqual(referenceAcceptInputs, currentInputs)) { | ||
throw new Error(`Funding inputs for accept ${i} do not match the first accept's funding inputs.`); | ||
} | ||
} | ||
} | ||
arraysEqual(arr1, arr2) { | ||
if (arr1.length !== arr2.length) | ||
return false; | ||
for (let i = 0; i < arr1.length; i++) { | ||
if (arr1[i] !== arr2[i]) | ||
return false; | ||
} | ||
return true; | ||
} | ||
} | ||
exports.DlcTxBuilder = DlcTxBuilder; | ||
exports.BatchDlcTxBuilder = BatchDlcTxBuilder; | ||
//# sourceMappingURL=TxBuilder.js.map |
@@ -5,2 +5,3 @@ "use strict"; | ||
const messaging_1 = require("@node-dlc/messaging"); | ||
const decimal_js_1 = require("decimal.js"); | ||
const BATCH_FUND_TX_BASE_WEIGHT = 42; | ||
@@ -27,4 +28,8 @@ const FUNDING_OUTPUT_SIZE = 43; | ||
const futureFeeWeight = 249 + 4 * payoutSPK.length; | ||
const futureFeeVBytes = Math.ceil(futureFeeWeight / 4); | ||
const futureFee = this.feeRate * BigInt(futureFeeVBytes) * BigInt(numContracts); | ||
const futureFeeVBytes = new decimal_js_1.Decimal(futureFeeWeight) | ||
.times(numContracts) | ||
.div(4) | ||
.ceil() | ||
.toNumber(); | ||
const futureFee = this.feeRate * BigInt(futureFeeVBytes); | ||
// https://github.com/discreetlogcontracts/dlcspecs/blob/8ee4bbe816c9881c832b1ce320b9f14c72e3506f/Transactions.md#expected-weight-of-the-funding-transaction | ||
@@ -37,3 +42,3 @@ const inputWeight = inputs.reduce((total, input) => { | ||
const weight = outputWeight + inputWeight; | ||
const vbytes = Math.ceil(weight / 4); | ||
const vbytes = new decimal_js_1.Decimal(weight).div(4).ceil().toNumber(); | ||
const fundingFee = this.feeRate * BigInt(vbytes); | ||
@@ -89,3 +94,3 @@ return { futureFee, fundingFee }; | ||
const weight = 213 + outputWeight + inputWeight; | ||
const vbytes = Math.ceil(weight / 4); | ||
const vbytes = new decimal_js_1.Decimal(weight).div(4).ceil().toNumber(); | ||
const fee = this.feeRate * BigInt(vbytes); | ||
@@ -92,0 +97,0 @@ return fee; |
@@ -76,2 +76,7 @@ import { Value } from '@node-dlc/bitcoin'; | ||
export const roundDownToNearestMultiplier = ( | ||
num: bigint, | ||
multiplier: bigint, | ||
): bigint => num - (num % multiplier); | ||
export type DlcParty = 'offeror' | 'acceptor' | 'neither'; | ||
@@ -78,0 +83,0 @@ |
@@ -199,9 +199,7 @@ import { Value } from '@node-dlc/bitcoin'; | ||
const maxGainForContractSize = Value.fromBitcoin( | ||
new Decimal(normalizedMaxGain.bitcoin) | ||
.times(contractSize.bitcoin) | ||
.toDecimalPlaces( | ||
8 - Math.log10(Number(UNIT_MULTIPLIER[unit.toLowerCase()])), | ||
) | ||
.toNumber(), | ||
const maxGainForContractSize = Value.fromSats( | ||
roundUpToNearestMultiplier( | ||
(normalizedMaxGain.sats * contractSize.sats) / BigInt(1e8), | ||
BigInt(UNIT_MULTIPLIER[unit.toLowerCase()]), | ||
), | ||
); | ||
@@ -208,0 +206,0 @@ |
@@ -15,2 +15,3 @@ import { | ||
} from '@node-lightning/bitcoin'; | ||
import Decimal from 'decimal.js'; | ||
@@ -26,2 +27,14 @@ import { DualFundingTxFinalizer } from './TxFinalizer'; | ||
public buildFundingTransaction(): Tx { | ||
const txBuilder = new BatchDlcTxBuilder([this.dlcOffer], [this.dlcAccept]); | ||
return txBuilder.buildFundingTransaction(); | ||
} | ||
} | ||
export class BatchDlcTxBuilder { | ||
constructor( | ||
readonly dlcOffers: DlcOfferV0[], | ||
readonly dlcAccepts: DlcAcceptWithoutSigs[], | ||
) {} | ||
public buildFundingTransaction(): Tx { | ||
const tx = new TxBuilder(); | ||
@@ -31,39 +44,43 @@ tx.version = 2; | ||
const multisigScript = | ||
Buffer.compare( | ||
this.dlcOffer.fundingPubKey, | ||
this.dlcAccept.fundingPubKey, | ||
) === -1 | ||
? Script.p2msLock( | ||
2, | ||
this.dlcOffer.fundingPubKey, | ||
this.dlcAccept.fundingPubKey, | ||
) | ||
: Script.p2msLock( | ||
2, | ||
this.dlcAccept.fundingPubKey, | ||
this.dlcOffer.fundingPubKey, | ||
); | ||
const witScript = Script.p2wshLock(multisigScript); | ||
if (this.dlcOffers.length !== this.dlcAccepts.length) | ||
throw Error('DlcOffers and DlcAccepts must be the same length'); | ||
if (this.dlcOffers.length === 0) throw Error('DlcOffers must not be empty'); | ||
if (this.dlcAccepts.length === 0) | ||
throw Error('DlcAccepts must not be empty'); | ||
const offerInput = this.dlcOffer.offerCollateralSatoshis; | ||
const acceptInput = this.dlcAccept.acceptCollateralSatoshis; | ||
// Ensure all DLC offers and accepts have the same funding inputs | ||
this.ensureSameFundingInputs(); | ||
const totalInput = offerInput + acceptInput; | ||
const multisigScripts: Script[] = []; | ||
for (let i = 0; i < this.dlcOffers.length; i++) { | ||
const offer = this.dlcOffers[i]; | ||
const accept = this.dlcAccepts[i]; | ||
multisigScripts.push( | ||
Buffer.compare(offer.fundingPubKey, accept.fundingPubKey) === -1 | ||
? Script.p2msLock(2, offer.fundingPubKey, accept.fundingPubKey) | ||
: Script.p2msLock(2, accept.fundingPubKey, offer.fundingPubKey), | ||
); | ||
} | ||
const witScripts = multisigScripts.map((multisigScript) => | ||
Script.p2wshLock(multisigScript), | ||
); | ||
const finalizer = new DualFundingTxFinalizer( | ||
this.dlcOffer.fundingInputs, | ||
this.dlcOffer.payoutSPK, | ||
this.dlcOffer.changeSPK, | ||
this.dlcAccept.fundingInputs, | ||
this.dlcAccept.payoutSPK, | ||
this.dlcAccept.changeSPK, | ||
this.dlcOffer.feeRatePerVb, | ||
this.dlcOffers[0].fundingInputs, | ||
this.dlcOffers[0].payoutSPK, | ||
this.dlcOffers[0].changeSPK, | ||
this.dlcAccepts[0].fundingInputs, | ||
this.dlcAccepts[0].payoutSPK, | ||
this.dlcAccepts[0].changeSPK, | ||
this.dlcOffers[0].feeRatePerVb, | ||
this.dlcOffers.length, | ||
); | ||
this.dlcOffer.fundingInputs.forEach((input) => { | ||
this.dlcOffers[0].fundingInputs.forEach((input) => { | ||
if (input.type !== MessageType.FundingInputV0) | ||
throw Error('FundingInput must be V0'); | ||
}); | ||
const offerFundingInputs: FundingInputV0[] = this.dlcOffer.fundingInputs.map( | ||
const offerFundingInputs: FundingInputV0[] = this.dlcOffers[0].fundingInputs.map( | ||
(input) => input as FundingInputV0, | ||
@@ -76,3 +93,3 @@ ); | ||
const acceptTotalFunding = this.dlcAccept.fundingInputs.reduce( | ||
const acceptTotalFunding = this.dlcAccepts[0].fundingInputs.reduce( | ||
(total, input) => { | ||
@@ -86,3 +103,3 @@ return total + input.prevTx.outputs[input.prevTxVout].value.sats; | ||
...offerFundingInputs, | ||
...this.dlcAccept.fundingInputs, | ||
...this.dlcAccepts[0].fundingInputs, | ||
]; | ||
@@ -102,4 +119,38 @@ | ||
const fundingValue = | ||
totalInput + finalizer.offerFutureFee + finalizer.acceptFutureFee; | ||
const offerInput = this.dlcOffers.reduce( | ||
(total, offer) => total + offer.offerCollateralSatoshis, | ||
BigInt(0), | ||
); | ||
const acceptInput = this.dlcAccepts.reduce( | ||
(total, accept) => total + accept.acceptCollateralSatoshis, | ||
BigInt(0), | ||
); | ||
const totalInputs = this.dlcOffers.map((offer, i) => { | ||
const offerInput = offer.offerCollateralSatoshis; | ||
const acceptInput = this.dlcAccepts[i].acceptCollateralSatoshis; | ||
return offerInput + acceptInput; | ||
}); | ||
const fundingValues = totalInputs.map((totalInput) => { | ||
const offerFutureFeePerOffer = new Decimal( | ||
finalizer.offerFutureFee.toString(), | ||
) | ||
.div(this.dlcOffers.length) | ||
.ceil() | ||
.toNumber(); | ||
const acceptFutureFeePerAccept = new Decimal( | ||
finalizer.acceptFutureFee.toString(), | ||
) | ||
.div(this.dlcAccepts.length) | ||
.ceil() | ||
.toNumber(); | ||
return ( | ||
totalInput + | ||
Value.fromSats(offerFutureFeePerOffer).sats + | ||
Value.fromSats(acceptFutureFeePerAccept).sats | ||
); | ||
}); | ||
const offerChangeValue = | ||
@@ -111,16 +162,18 @@ offerTotalFunding - offerInput - finalizer.offerFees; | ||
const outputs: Output[] = []; | ||
outputs.push({ | ||
value: Value.fromSats(Number(fundingValue)), | ||
script: witScript, | ||
serialId: this.dlcOffer.fundOutputSerialId, | ||
witScripts.forEach((witScript, i) => { | ||
outputs.push({ | ||
value: Value.fromSats(Number(fundingValues[i])), | ||
script: witScript, | ||
serialId: this.dlcOffers[i].fundOutputSerialId, | ||
}); | ||
}); | ||
outputs.push({ | ||
value: Value.fromSats(Number(offerChangeValue)), | ||
script: Script.p2wpkhLock(this.dlcOffer.changeSPK.slice(2)), | ||
serialId: this.dlcOffer.changeSerialId, | ||
script: Script.p2wpkhLock(this.dlcOffers[0].changeSPK.slice(2)), | ||
serialId: this.dlcOffers[0].changeSerialId, | ||
}); | ||
outputs.push({ | ||
value: Value.fromSats(Number(acceptChangeValue)), | ||
script: Script.p2wpkhLock(this.dlcAccept.changeSPK.slice(2)), | ||
serialId: this.dlcAccept.changeSerialId, | ||
script: Script.p2wpkhLock(this.dlcAccepts[0].changeSPK.slice(2)), | ||
serialId: this.dlcAccepts[0].changeSerialId, | ||
}); | ||
@@ -136,2 +189,42 @@ | ||
} | ||
private ensureSameFundingInputs(): void { | ||
// Check for offers | ||
const referenceOfferInputs = this.dlcOffers[0].fundingInputs.map((input) => | ||
input.serialize().toString('hex'), | ||
); | ||
for (let i = 1; i < this.dlcOffers.length; i++) { | ||
const currentInputs = this.dlcOffers[i].fundingInputs.map((input) => | ||
input.serialize().toString('hex'), | ||
); | ||
if (!this.arraysEqual(referenceOfferInputs, currentInputs)) { | ||
throw new Error( | ||
`Funding inputs for offer ${i} do not match the first offer's funding inputs.`, | ||
); | ||
} | ||
} | ||
// Check for accepts | ||
const referenceAcceptInputs = this.dlcAccepts[0].fundingInputs.map( | ||
(input) => input.serialize().toString('hex'), | ||
); | ||
for (let i = 1; i < this.dlcAccepts.length; i++) { | ||
const currentInputs = this.dlcAccepts[i].fundingInputs.map((input) => | ||
input.serialize().toString('hex'), | ||
); | ||
if (!this.arraysEqual(referenceAcceptInputs, currentInputs)) { | ||
throw new Error( | ||
`Funding inputs for accept ${i} do not match the first accept's funding inputs.`, | ||
); | ||
} | ||
} | ||
} | ||
private arraysEqual(arr1: string[], arr2: string[]): boolean { | ||
if (arr1.length !== arr2.length) return false; | ||
for (let i = 0; i < arr1.length; i++) { | ||
if (arr1[i] !== arr2[i]) return false; | ||
} | ||
return true; | ||
} | ||
} | ||
@@ -138,0 +231,0 @@ |
import { FundingInput, FundingInputV0, MessageType } from '@node-dlc/messaging'; | ||
import { Decimal } from 'decimal.js'; | ||
@@ -33,5 +34,8 @@ const BATCH_FUND_TX_BASE_WEIGHT = 42; | ||
const futureFeeWeight = 249 + 4 * payoutSPK.length; | ||
const futureFeeVBytes = Math.ceil(futureFeeWeight / 4); | ||
const futureFee = | ||
this.feeRate * BigInt(futureFeeVBytes) * BigInt(numContracts); | ||
const futureFeeVBytes = new Decimal(futureFeeWeight) | ||
.times(numContracts) | ||
.div(4) | ||
.ceil() | ||
.toNumber(); | ||
const futureFee = this.feeRate * BigInt(futureFeeVBytes); | ||
@@ -46,3 +50,3 @@ // https://github.com/discreetlogcontracts/dlcspecs/blob/8ee4bbe816c9881c832b1ce320b9f14c72e3506f/Transactions.md#expected-weight-of-the-funding-transaction | ||
const weight = outputWeight + inputWeight; | ||
const vbytes = Math.ceil(weight / 4); | ||
const vbytes = new Decimal(weight).div(4).ceil().toNumber(); | ||
const fundingFee = this.feeRate * BigInt(vbytes); | ||
@@ -120,3 +124,3 @@ | ||
const weight = 213 + outputWeight + inputWeight; | ||
const vbytes = Math.ceil(weight / 4); | ||
const vbytes = new Decimal(weight).div(4).ceil().toNumber(); | ||
const fee = this.feeRate * BigInt(vbytes); | ||
@@ -123,0 +127,0 @@ |
{ | ||
"name": "@node-dlc/core", | ||
"version": "0.23.0", | ||
"version": "0.23.1", | ||
"description": "DLC Core", | ||
@@ -26,4 +26,4 @@ "scripts": { | ||
"dependencies": { | ||
"@node-dlc/bitcoin": "^0.23.0", | ||
"@node-dlc/messaging": "^0.23.0", | ||
"@node-dlc/bitcoin": "^0.23.1", | ||
"@node-dlc/messaging": "^0.23.1", | ||
"@node-lightning/core": "0.26.1", | ||
@@ -40,3 +40,3 @@ "bignumber.js": "^9.0.1", | ||
}, | ||
"gitHead": "896e77ea5caca60c20f2d9b40c10b1cfdda4f2be" | ||
"gitHead": "d456ac896161a1e25e847c62f8996a23278cd36c" | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1492939
9019
Updated@node-dlc/bitcoin@^0.23.1
Updated@node-dlc/messaging@^0.23.1