@atomicfinance/bitcoin-ddk-provider 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2557 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const provider_1 = __importDefault(require("@atomicfinance/provider"));
40
+ const types_1 = require("@atomicfinance/types");
41
+ const utils_1 = require("@atomicfinance/utils");
42
+ const bitcoin_1 = require("@node-dlc/bitcoin");
43
+ const bufio_1 = require("@node-dlc/bufio");
44
+ const core_1 = require("@node-dlc/core");
45
+ const core_2 = require("@node-dlc/core");
46
+ const crypto_1 = require("@node-dlc/crypto");
47
+ const messaging_1 = require("@node-dlc/messaging");
48
+ const assert_1 = __importDefault(require("assert"));
49
+ const bignumber_js_1 = __importDefault(require("bignumber.js"));
50
+ const bitcoin_networks_1 = require("bitcoin-networks");
51
+ const bitcoinjs_lib_1 = require("bitcoinjs-lib");
52
+ const crypto_2 = __importDefault(require("crypto"));
53
+ const ecpair_1 = require("ecpair");
54
+ const ecc = __importStar(require("tiny-secp256k1"));
55
+ const Utils_1 = require("./utils/Utils");
56
+ const ECPair = (0, ecpair_1.ECPairFactory)(ecc);
57
+ class BitcoinDdkProvider extends provider_1.default {
58
+ constructor(network, ddkLib) {
59
+ super();
60
+ this._network = network;
61
+ this._ddk = ddkLib;
62
+ }
63
+ async DdkLoaded() {
64
+ while (!this._ddk) {
65
+ await (0, utils_1.sleep)(10);
66
+ }
67
+ }
68
+ /**
69
+ * Find private key for DLC funding pubkey by deriving wallet addresses
70
+ */
71
+ async findDlcFundingPrivateKey(localFundPubkey, remoteFundPubkey) {
72
+ const targetPubkeys = [localFundPubkey, remoteFundPubkey];
73
+ // First check existing wallet addresses
74
+ const addresses = await this.getMethod('getAddresses')();
75
+ for (const addressInfo of addresses) {
76
+ if (addressInfo.derivationPath) {
77
+ try {
78
+ const keyPair = await this.getMethod('keyPair')(addressInfo.derivationPath);
79
+ const pubkey = Buffer.from(keyPair.publicKey);
80
+ const pubkeyHex = pubkey.toString('hex');
81
+ if (targetPubkeys.includes(pubkeyHex)) {
82
+ return Buffer.from(keyPair.privateKey).toString('hex');
83
+ }
84
+ }
85
+ catch {
86
+ continue;
87
+ }
88
+ }
89
+ }
90
+ // If not found in existing addresses, do comprehensive search
91
+ // For DLC splicing, funding pubkeys can be at much higher derivation paths
92
+ console.log('Searching extensively for DLC funding private key...');
93
+ for (const isChange of [false, true]) {
94
+ for (let i = 0; i < 1000; i++) {
95
+ // Search up to 1000 addresses for DLC keys
96
+ try {
97
+ const address = await this.client.wallet.getAddresses(i, 1, isChange);
98
+ if (address && address.length > 0) {
99
+ const addressInfo = address[0];
100
+ if (addressInfo.derivationPath) {
101
+ const keyPair = await this.getMethod('keyPair')(addressInfo.derivationPath);
102
+ const pubkey = Buffer.from(keyPair.publicKey);
103
+ const pubkeyHex = pubkey.toString('hex');
104
+ if (targetPubkeys.includes(pubkeyHex)) {
105
+ console.log(`Found DLC funding key at derivation path: ${addressInfo.derivationPath}`);
106
+ return Buffer.from(keyPair.privateKey).toString('hex');
107
+ }
108
+ }
109
+ }
110
+ }
111
+ catch {
112
+ continue;
113
+ }
114
+ }
115
+ }
116
+ throw new Error(`Could not find private key for DLC funding pubkeys: local=${localFundPubkey}, remote=${remoteFundPubkey}`);
117
+ }
118
+ async GetPrivKeysForInputs(inputs) {
119
+ const privKeys = [];
120
+ for (let i = 0; i < inputs.length; i++) {
121
+ const input = inputs[i];
122
+ if (input.isDlcInput()) {
123
+ // Handle DLC input - use the dedicated method to find the funding private key
124
+ const dlcInput = input.dlcInput;
125
+ const foundPrivKey = await this.findDlcFundingPrivateKey(dlcInput.localFundPubkey, dlcInput.remoteFundPubkey);
126
+ privKeys.push(foundPrivKey);
127
+ }
128
+ else {
129
+ // Handle regular input
130
+ let derivationPath = input.derivationPath;
131
+ if (!derivationPath) {
132
+ try {
133
+ derivationPath = (await this.getMethod('getWalletAddress')(input.address)).derivationPath;
134
+ }
135
+ catch (error) {
136
+ throw new Error(`Unable to find address ${input.address} in wallet. ` +
137
+ `This may happen when using derivation paths outside the normal range. ` +
138
+ `Error: ${error.message}`);
139
+ }
140
+ }
141
+ const keyPair = await this.getMethod('keyPair')(derivationPath);
142
+ const privKey = Buffer.from(keyPair.__D).toString('hex');
143
+ privKeys.push(privKey);
144
+ }
145
+ }
146
+ return privKeys;
147
+ }
148
+ async GetCfdNetwork() {
149
+ const network = await this.getConnectedNetwork();
150
+ switch (network.name) {
151
+ case 'bitcoin_testnet':
152
+ return 'testnet';
153
+ case 'bitcoin_regtest':
154
+ return 'regtest';
155
+ default:
156
+ return 'bitcoin';
157
+ }
158
+ }
159
+ /**
160
+ * Get inputs for amount with explicit supplementation control
161
+ */
162
+ async GetInputsForAmountWithMode(amounts, feeRatePerVb, fixedInputs = [], supplementation = types_1.InputSupplementationMode.Required) {
163
+ if (amounts.length === 0)
164
+ return [];
165
+ // For "none" mode, use exactly the provided inputs
166
+ if (supplementation === types_1.InputSupplementationMode.None) {
167
+ return fixedInputs;
168
+ }
169
+ // For "required" and "optional" modes, attempt supplementation
170
+ const fixedUtxos = fixedInputs.map((input) => input.toUtxo());
171
+ try {
172
+ const inputsForAmount = await this.getMethod('getInputsForDualFunding')(amounts, feeRatePerVb, fixedUtxos);
173
+ // Convert UTXO objects to Input class instances
174
+ return inputsForAmount.inputs.map((utxo) => types_1.Input.fromUTXO(utxo));
175
+ }
176
+ catch (e) {
177
+ const errorMessage = e instanceof Error ? e.message : 'Unknown error';
178
+ if (supplementation === types_1.InputSupplementationMode.Required) {
179
+ throw Error(`Not enough balance GetInputsForAmountWithMode. Error: ${errorMessage}`);
180
+ }
181
+ else {
182
+ // Optional mode: fallback to provided inputs
183
+ return fixedInputs;
184
+ }
185
+ }
186
+ }
187
+ async GetInputsForAmount(amounts, feeRatePerVb, fixedInputs = []) {
188
+ if (amounts.length === 0)
189
+ return [];
190
+ const fixedUtxos = fixedInputs.map((input) => input.toUtxo());
191
+ let inputs;
192
+ try {
193
+ const inputsForAmount = await this.getMethod('getInputsForDualFunding')(amounts, feeRatePerVb, fixedUtxos);
194
+ // Convert UTXO objects to Input class instances
195
+ inputs = inputsForAmount.inputs.map((utxo) => types_1.Input.fromUTXO(utxo));
196
+ }
197
+ catch (e) {
198
+ const errorMessage = e instanceof Error ? e.message : 'Unknown error';
199
+ if (fixedInputs.length === 0) {
200
+ throw Error(`Not enough balance getInputsForAmount. Error: ${errorMessage}`);
201
+ }
202
+ else {
203
+ inputs = fixedInputs;
204
+ }
205
+ }
206
+ return inputs;
207
+ }
208
+ async Initialize(collateral, feeRatePerVb, fixedInputs, inputSupplementationMode = types_1.InputSupplementationMode.Required) {
209
+ const network = await this.getConnectedNetwork();
210
+ const payoutAddress = await this.client.wallet.getUnusedAddress(false);
211
+ const payoutSPK = bitcoinjs_lib_1.address.toOutputScript(payoutAddress.address, network);
212
+ const changeAddress = await this.client.wallet.getUnusedAddress(true);
213
+ const changeSPK = bitcoinjs_lib_1.address.toOutputScript(changeAddress.address, network);
214
+ const fundingAddress = await this.client.wallet.getUnusedAddress(false);
215
+ const fundingPubKey = Buffer.from(fundingAddress.publicKey, 'hex');
216
+ if (fundingAddress.address === payoutAddress.address)
217
+ throw Error('Address reuse');
218
+ const inputs = await this.GetInputsForAmountWithMode([collateral], feeRatePerVb, fixedInputs, inputSupplementationMode);
219
+ const fundingInputs = await Promise.all(inputs.map(async (input) => {
220
+ return this.inputToFundingInput(input);
221
+ }));
222
+ const payoutSerialId = (0, Utils_1.generateSerialId)();
223
+ const changeSerialId = (0, Utils_1.generateSerialId)();
224
+ return {
225
+ fundingPubKey,
226
+ payoutSPK,
227
+ payoutSerialId,
228
+ fundingInputs,
229
+ changeSPK,
230
+ changeSerialId,
231
+ };
232
+ }
233
+ /**
234
+ * TODO: Add GetPayoutFromOutcomes
235
+ *
236
+ * private GetPayoutsFromOutcomes(
237
+ * contractDescriptor: ContractDescriptorV0,
238
+ * totalCollateral: bigint,
239
+ * ): PayoutRequest[] {}
240
+ */
241
+ GetPayoutsFromPayoutFunction(dlcOffer, contractDescriptor, oracleInfo, totalCollateral) {
242
+ const payoutFunction = contractDescriptor.payoutFunction;
243
+ if (payoutFunction.payoutFunctionPieces.length === 0)
244
+ throw Error('PayoutFunction must have at least once PayoutCurvePiece');
245
+ if (payoutFunction.payoutFunctionPieces.length > 1)
246
+ throw Error('More than one PayoutCurvePiece not supported');
247
+ const payoutCurvePiece = payoutFunction.payoutFunctionPieces[0]
248
+ .payoutCurvePiece;
249
+ if (payoutCurvePiece.payoutCurvePieceType !== messaging_1.PayoutCurvePieceType.Hyperbola)
250
+ throw Error('Must be HyperbolaPayoutCurvePiece');
251
+ if (!payoutCurvePiece.b.eq(messaging_1.F64.ZERO) || !payoutCurvePiece.c.eq(messaging_1.F64.ZERO))
252
+ throw Error('b and c HyperbolaPayoutCurvePiece values must be 0');
253
+ // Cast to SingleOracleInfo to access announcement property
254
+ const singleOracleInfo = oracleInfo;
255
+ const eventDescriptor = singleOracleInfo.announcement.oracleEvent
256
+ .eventDescriptor;
257
+ if (eventDescriptor.type !== messaging_1.MessageType.DigitDecompositionEventDescriptor)
258
+ throw Error('Only DigitDecomposition Oracle Events supported');
259
+ const roundingIntervals = contractDescriptor.roundingIntervals;
260
+ const cetPayouts = core_2.HyperbolaPayoutCurve.computePayouts(payoutFunction, totalCollateral, roundingIntervals);
261
+ const payoutGroups = [];
262
+ cetPayouts.forEach((p) => {
263
+ payoutGroups.push({
264
+ payout: p.payout,
265
+ groups: (0, core_2.groupByIgnoringDigits)(p.indexFrom, p.indexTo, eventDescriptor.base, contractDescriptor.numDigits),
266
+ });
267
+ });
268
+ const rValuesMessagesList = this.GenerateMessages(singleOracleInfo);
269
+ const { payouts, messagesList } = (0, Utils_1.outputsToPayouts)(payoutGroups, rValuesMessagesList, dlcOffer.offerCollateral, dlcOffer.contractInfo.totalCollateral - dlcOffer.offerCollateral, true);
270
+ return { payouts, payoutGroups, messagesList };
271
+ }
272
+ GetPayoutsFromPolynomialPayoutFunction(dlcOffer, contractDescriptor, oracleInfo, totalCollateral) {
273
+ const payoutFunction = contractDescriptor.payoutFunction;
274
+ if (payoutFunction.payoutFunctionPieces.length === 0)
275
+ throw Error('PayoutFunction must have at least once PayoutCurvePiece');
276
+ for (const piece of payoutFunction.payoutFunctionPieces) {
277
+ if (piece.payoutCurvePiece.type !== messaging_1.MessageType.PolynomialPayoutCurvePiece)
278
+ throw Error('Must be PolynomialPayoutCurvePiece');
279
+ }
280
+ const eventDescriptor = oracleInfo.announcement.oracleEvent
281
+ .eventDescriptor;
282
+ if (eventDescriptor.type !== messaging_1.MessageType.DigitDecompositionEventDescriptor)
283
+ throw Error('Only DigitDecomposition Oracle Events supported');
284
+ const roundingIntervals = contractDescriptor.roundingIntervals;
285
+ const cetPayouts = core_2.PolynomialPayoutCurve.computePayouts(payoutFunction, totalCollateral, roundingIntervals);
286
+ const payoutGroups = [];
287
+ cetPayouts.forEach((p) => {
288
+ payoutGroups.push({
289
+ payout: p.payout,
290
+ groups: (0, core_2.groupByIgnoringDigits)(p.indexFrom, p.indexTo, eventDescriptor.base, contractDescriptor.numDigits),
291
+ });
292
+ });
293
+ const rValuesMessagesList = this.GenerateMessages(oracleInfo);
294
+ const { payouts, messagesList } = (0, Utils_1.outputsToPayouts)(payoutGroups, rValuesMessagesList, dlcOffer.offerCollateral, dlcOffer.contractInfo.totalCollateral - dlcOffer.offerCollateral, true);
295
+ return { payouts, payoutGroups, messagesList };
296
+ }
297
+ GetPayouts(dlcOffer) {
298
+ const contractInfo = dlcOffer.contractInfo;
299
+ const totalCollateral = contractInfo.totalCollateral;
300
+ const contractOraclePairs = this.GetContractOraclePairs(contractInfo);
301
+ const payoutResponses = contractOraclePairs.map(({ contractDescriptor, oracleInfo }) => this.GetPayoutsFromContractDescriptor(dlcOffer, contractDescriptor, oracleInfo, totalCollateral));
302
+ return payoutResponses;
303
+ }
304
+ FlattenPayouts(payoutResponses) {
305
+ return payoutResponses.reduce((acc, { payouts, payoutGroups, messagesList }) => {
306
+ return {
307
+ payouts: acc.payouts.concat(payouts),
308
+ payoutGroups: acc.payoutGroups.concat(payoutGroups),
309
+ messagesList: acc.messagesList.concat(messagesList),
310
+ };
311
+ });
312
+ }
313
+ GetIndicesFromPayouts(payoutResponses) {
314
+ return payoutResponses.reduce((prev, acc) => {
315
+ return prev.concat({
316
+ startingMessagesIndex: prev[prev.length - 1].startingMessagesIndex +
317
+ acc.messagesList.length,
318
+ startingPayoutGroupsIndex: prev[prev.length - 1].startingPayoutGroupsIndex +
319
+ acc.payoutGroups.length,
320
+ });
321
+ }, [{ startingMessagesIndex: 0, startingPayoutGroupsIndex: 0 }]);
322
+ }
323
+ GetPayoutsFromEnumeratedDescriptor(dlcOffer, contractDescriptor, oracleInfo, totalCollateral) {
324
+ const payoutGroups = [];
325
+ const rValuesMessagesList = this.GenerateMessages(oracleInfo);
326
+ // For enumerated descriptors, each outcome creates one payout
327
+ // Each outcome maps to one index in the oracle's possible outcomes
328
+ contractDescriptor.outcomes.forEach((outcome, index) => {
329
+ payoutGroups.push({
330
+ payout: outcome.localPayout,
331
+ groups: [[index]], // Simple index-based grouping for enum outcomes
332
+ });
333
+ });
334
+ const { payouts, messagesList } = (0, Utils_1.outputsToPayouts)(payoutGroups, rValuesMessagesList, dlcOffer.offerCollateral, totalCollateral - dlcOffer.offerCollateral, true);
335
+ return { payouts, payoutGroups, messagesList };
336
+ }
337
+ GetPayoutsFromContractDescriptor(dlcOffer, contractDescriptor, oracleInfo, totalCollateral) {
338
+ switch (contractDescriptor.contractDescriptorType) {
339
+ case messaging_1.ContractDescriptorType.Enumerated: {
340
+ return this.GetPayoutsFromEnumeratedDescriptor(dlcOffer, contractDescriptor, oracleInfo, totalCollateral);
341
+ }
342
+ case messaging_1.ContractDescriptorType.NumericOutcome: {
343
+ const numericalDescriptor = contractDescriptor;
344
+ const payoutFunction = numericalDescriptor.payoutFunction;
345
+ // TODO: add a better check for this
346
+ const payoutCurvePiece = payoutFunction.payoutFunctionPieces[0].payoutCurvePiece;
347
+ switch (payoutCurvePiece.payoutCurvePieceType) {
348
+ case messaging_1.PayoutCurvePieceType.Hyperbola:
349
+ return this.GetPayoutsFromPayoutFunction(dlcOffer, numericalDescriptor, oracleInfo, totalCollateral);
350
+ case messaging_1.PayoutCurvePieceType.Polynomial:
351
+ return this.GetPayoutsFromPolynomialPayoutFunction(dlcOffer, numericalDescriptor, oracleInfo, totalCollateral);
352
+ }
353
+ }
354
+ }
355
+ }
356
+ /**
357
+ * Converts a @node-dlc/bitcoin Tx to a DDK Transaction
358
+ * @param tx The @node-dlc/bitcoin transaction
359
+ * @returns DDK Transaction object
360
+ */
361
+ convertTxToDdkTransaction(tx) {
362
+ return {
363
+ version: tx.version,
364
+ lockTime: tx.locktime.value,
365
+ inputs: tx.inputs.map((input) => ({
366
+ txid: input.outpoint.txid.toString(),
367
+ vout: input.outpoint.outputIndex,
368
+ scriptSig: Buffer.from(''),
369
+ sequence: input.sequence.value,
370
+ witness: input.witness.map((witness) => witness.serialize()),
371
+ })),
372
+ outputs: tx.outputs.map((output) => ({
373
+ value: BigInt(output.value.sats),
374
+ scriptPubkey: output.scriptPubKey.serialize(),
375
+ })),
376
+ rawBytes: tx.serialize(),
377
+ };
378
+ }
379
+ async createDlcTxs(dlcOffer, dlcAccept) {
380
+ const localFundPubkey = dlcOffer.fundingPubkey.toString('hex');
381
+ const remoteFundPubkey = dlcAccept.fundingPubkey.toString('hex');
382
+ const localFinalScriptPubkey = dlcOffer.payoutSpk.toString('hex');
383
+ const remoteFinalScriptPubkey = dlcAccept.payoutSpk.toString('hex');
384
+ const localChangeScriptPubkey = dlcOffer.changeSpk.toString('hex');
385
+ const remoteChangeScriptPubkey = dlcAccept.changeSpk.toString('hex');
386
+ // Separate regular inputs from DLC inputs (only from offeror side)
387
+ const localRegularInputs = [];
388
+ const localDlcInputs = [];
389
+ for (const fundingInput of dlcOffer.fundingInputs) {
390
+ if (fundingInput.dlcInput) {
391
+ // This is a DLC input for splicing
392
+ // The pubkeys should be from the original DLC to correctly spend its funding output
393
+ localDlcInputs.push({
394
+ fundTx: this.convertTxToDdkTransaction(fundingInput.prevTx),
395
+ fundVout: fundingInput.prevTxVout,
396
+ localFundPubkey: fundingInput.dlcInput.localFundPubkey,
397
+ remoteFundPubkey: fundingInput.dlcInput.remoteFundPubkey,
398
+ fundAmount: fundingInput.prevTx.outputs[fundingInput.prevTxVout].value.sats,
399
+ maxWitnessLen: fundingInput.maxWitnessLen,
400
+ inputSerialId: fundingInput.inputSerialId,
401
+ contractId: fundingInput.dlcInput.contractId,
402
+ });
403
+ }
404
+ else {
405
+ // Regular input
406
+ const input = await this.fundingInputToInput(fundingInput, false);
407
+ localRegularInputs.push(input.toUtxo());
408
+ }
409
+ }
410
+ // Process remote inputs (no DLC inputs from acceptor side)
411
+ const remoteInputs = await Promise.all(dlcAccept.fundingInputs.map(async (fundingInput) => {
412
+ const input = await this.fundingInputToInput(fundingInput, false);
413
+ return input.toUtxo();
414
+ }));
415
+ // Calculate input amounts
416
+ const localInputAmount = localRegularInputs.reduce((prev, cur) => prev + cur.amount.GetSatoshiAmount(), 0);
417
+ const remoteInputAmount = remoteInputs.reduce((prev, cur) => prev + cur.amount.GetSatoshiAmount(), 0);
418
+ let payouts = [];
419
+ let messagesList = [];
420
+ if (dlcOffer.contractInfo.type === messaging_1.MessageType.SingleContractInfo &&
421
+ dlcOffer.contractInfo.contractDescriptor.type ===
422
+ messaging_1.ContractDescriptorType.Enumerated) {
423
+ for (const outcome of dlcOffer.contractInfo
424
+ .contractDescriptor.outcomes) {
425
+ payouts.push({
426
+ local: outcome.localPayout,
427
+ remote: dlcOffer.offerCollateral +
428
+ dlcAccept.acceptCollateral -
429
+ outcome.localPayout,
430
+ });
431
+ messagesList.push({ messages: [outcome.outcome] });
432
+ }
433
+ }
434
+ else {
435
+ const payoutResponses = this.GetPayouts(dlcOffer);
436
+ const { payouts: tempPayouts, messagesList: tempMessagesList } = this.FlattenPayouts(payoutResponses);
437
+ payouts = tempPayouts;
438
+ messagesList = tempMessagesList;
439
+ }
440
+ const outcomes = payouts.map((payout) => ({
441
+ offer: BigInt(payout.local),
442
+ accept: BigInt(payout.remote),
443
+ }));
444
+ const localParams = {
445
+ fundPubkey: Buffer.from(localFundPubkey, 'hex'),
446
+ changeScriptPubkey: Buffer.from(localChangeScriptPubkey, 'hex'),
447
+ changeSerialId: BigInt(dlcOffer.changeSerialId),
448
+ payoutScriptPubkey: Buffer.from(localFinalScriptPubkey, 'hex'),
449
+ payoutSerialId: BigInt(dlcOffer.payoutSerialId),
450
+ inputs: localRegularInputs.map((input) => input.toTxInputInfo()),
451
+ inputAmount: BigInt(localInputAmount),
452
+ collateral: BigInt(dlcOffer.offerCollateral),
453
+ dlcInputs: localDlcInputs,
454
+ };
455
+ const remoteParams = {
456
+ fundPubkey: Buffer.from(remoteFundPubkey, 'hex'),
457
+ changeScriptPubkey: Buffer.from(remoteChangeScriptPubkey, 'hex'),
458
+ changeSerialId: BigInt(dlcAccept.changeSerialId),
459
+ payoutScriptPubkey: Buffer.from(remoteFinalScriptPubkey, 'hex'),
460
+ payoutSerialId: BigInt(dlcAccept.payoutSerialId),
461
+ inputs: remoteInputs.map((input) => input.toTxInputInfo()),
462
+ inputAmount: BigInt(remoteInputAmount),
463
+ collateral: BigInt(dlcAccept.acceptCollateral),
464
+ dlcInputs: [],
465
+ };
466
+ // Determine whether to use regular or spliced DLC transactions
467
+ const hasDlcInputs = localDlcInputs.length > 0;
468
+ let dlcTxs;
469
+ if (hasDlcInputs) {
470
+ // Use spliced DLC transactions when DLC inputs are present
471
+ dlcTxs = await this._ddk.createSplicedDlcTransactions(outcomes, localParams, remoteParams, dlcOffer.refundLocktime, BigInt(dlcOffer.feeRatePerVb), 0, dlcOffer.cetLocktime, dlcOffer.fundOutputSerialId);
472
+ }
473
+ else {
474
+ // Use regular DLC transactions when no DLC inputs
475
+ dlcTxs = this._ddk.createDlcTransactions(outcomes, localParams, remoteParams, dlcOffer.refundLocktime, BigInt(dlcOffer.feeRatePerVb), 0, dlcOffer.cetLocktime, BigInt(dlcOffer.fundOutputSerialId));
476
+ }
477
+ const dlcTransactions = new messaging_1.DlcTransactions();
478
+ dlcTransactions.fundTx = bitcoin_1.Tx.decode(bufio_1.StreamReader.fromBuffer(dlcTxs.fund.rawBytes));
479
+ // Build serial IDs based on actual outputs in the transaction
480
+ const actualOutputs = dlcTransactions.fundTx.outputs;
481
+ const serialIds = [];
482
+ // Always include the funding output serial ID
483
+ serialIds.push(BigInt(dlcOffer.fundOutputSerialId));
484
+ // Only include change serial IDs if there are actually change outputs
485
+ // For exact amount DLCs with no change, there will be only 1 output (the funding output)
486
+ if (actualOutputs.length > 1) {
487
+ // Multiple outputs means there are change outputs
488
+ if (dlcOffer.offerCollateral > 0n) {
489
+ serialIds.push(BigInt(dlcOffer.changeSerialId));
490
+ }
491
+ if (dlcAccept.acceptCollateral > 0n) {
492
+ serialIds.push(BigInt(dlcAccept.changeSerialId));
493
+ }
494
+ }
495
+ dlcTransactions.fundTxVout = serialIds
496
+ .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
497
+ .findIndex((i) => BigInt(i) === BigInt(dlcOffer.fundOutputSerialId));
498
+ // Validate that the calculated fundTxVout is valid
499
+ if (dlcTransactions.fundTxVout < 0 ||
500
+ dlcTransactions.fundTxVout >= dlcTransactions.fundTx.outputs.length) {
501
+ throw new Error(`Invalid fundTxVout calculation: calculated=${dlcTransactions.fundTxVout}, ` +
502
+ `fundTx.outputs.length=${dlcTransactions.fundTx.outputs.length}, ` +
503
+ `fundOutputSerialId=${dlcOffer.fundOutputSerialId}, ` +
504
+ `serialIds=[${serialIds.join(', ')}], ` +
505
+ `offerCollateral=${dlcOffer.offerCollateral}, ` +
506
+ `acceptCollateral=${dlcAccept.acceptCollateral}`);
507
+ }
508
+ dlcTransactions.cets = dlcTxs.cets.map((cetTx) => bitcoin_1.Tx.decode(bufio_1.StreamReader.fromBuffer(cetTx.rawBytes)));
509
+ dlcTransactions.refundTx = bitcoin_1.Tx.decode(bufio_1.StreamReader.fromBuffer(dlcTxs.refund.rawBytes));
510
+ return { dlcTransactions, messagesList };
511
+ }
512
+ /**
513
+ * Computes DLC-spec compliant tagged attestation message digest
514
+ * This matches what the oracle should sign according to the DLC specification
515
+ */
516
+ computeTaggedAttestationMessage(outcome) {
517
+ // DLC spec: H(H("DLC/oracle/attestation/v0") || H("DLC/oracle/attestation/v0") || H(outcome))
518
+ const tag = Buffer.from('DLC/oracle/attestation/v0', 'utf8');
519
+ const tagHash = (0, crypto_1.sha256)(tag);
520
+ const outcomeBuffer = Buffer.from(outcome, 'utf8');
521
+ // Compute H(tagHash || tagHash || outcomeHash)
522
+ const message = (0, crypto_1.sha256)(Buffer.concat([tagHash, tagHash, outcomeBuffer]));
523
+ return message.toString('hex');
524
+ }
525
+ /**
526
+ * Convert message lists to the format expected by DDK FFI
527
+ * DDK expects 32-byte message digests (tagged attestation messages)
528
+ */
529
+ convertMessagesForDdk(tempMessagesList) {
530
+ return tempMessagesList.map((message) => [
531
+ message.messages.map((m) =>
532
+ // Convert outcome string to tagged attestation message (32-byte hash)
533
+ Buffer.from(this.computeTaggedAttestationMessage(m), 'hex')),
534
+ ]);
535
+ }
536
+ GenerateEnumMessages(oracleEvent) {
537
+ const eventDescriptor = oracleEvent.eventDescriptor;
538
+ // For enum events, each oracle has one nonce and can attest to one of the possible outcomes
539
+ const messagesList = [];
540
+ // Pass raw outcome strings to dlcdevkit - it will handle the tagged hashing internally
541
+ // dlcdevkit expects raw strings and calls tagged_attestation_msg() internally
542
+ const messages = eventDescriptor.outcomes;
543
+ messagesList.push({ messages });
544
+ return messagesList;
545
+ }
546
+ convertToJsonSerializable(obj) {
547
+ if (obj === null || obj === undefined) {
548
+ return obj;
549
+ }
550
+ if (typeof obj === 'bigint') {
551
+ return Number(obj);
552
+ }
553
+ if (Buffer.isBuffer(obj)) {
554
+ return obj.toString('hex');
555
+ }
556
+ if (Array.isArray(obj)) {
557
+ return obj.map((item) => this.convertToJsonSerializable(item));
558
+ }
559
+ if (typeof obj === 'object') {
560
+ const result = {};
561
+ for (const [key, value] of Object.entries(obj)) {
562
+ result[key] = this.convertToJsonSerializable(value);
563
+ }
564
+ return result;
565
+ }
566
+ return obj;
567
+ }
568
+ GenerateDigitDecompositionMessages(oracleEvent) {
569
+ const oracleNonces = oracleEvent.oracleNonces;
570
+ const eventDescriptor = oracleEvent.eventDescriptor;
571
+ const messagesList = [];
572
+ oracleNonces.forEach(() => {
573
+ const messages = [];
574
+ for (let i = 0; i < eventDescriptor.base; i++) {
575
+ const m = i.toString();
576
+ messages.push(m);
577
+ }
578
+ messagesList.push({ messages });
579
+ });
580
+ return messagesList;
581
+ }
582
+ GenerateMessages(oracleInfo) {
583
+ // Handle both SingleOracleInfo and MultiOracleInfo using type property instead of instanceof
584
+ let oracleEvent;
585
+ if (oracleInfo.type === messaging_1.MessageType.SingleOracleInfo) {
586
+ const singleOracleInfo = oracleInfo;
587
+ oracleEvent = singleOracleInfo.announcement.oracleEvent;
588
+ }
589
+ else if (oracleInfo.type === messaging_1.MessageType.MultiOracleInfo) {
590
+ const multiOracleInfo = oracleInfo;
591
+ // For multi-oracle, use the first announcement for now
592
+ // TODO: This might need more sophisticated handling for multi-oracle scenarios
593
+ if (multiOracleInfo.announcements.length === 0) {
594
+ throw Error('MultiOracleInfo must have at least one announcement');
595
+ }
596
+ oracleEvent = multiOracleInfo.announcements[0].oracleEvent;
597
+ }
598
+ else {
599
+ throw Error(`OracleInfo must be SingleOracleInfo or MultiOracleInfo, got type: ${oracleInfo.type}`);
600
+ }
601
+ switch (oracleEvent.eventDescriptor.type) {
602
+ case messaging_1.MessageType.EnumEventDescriptor:
603
+ return this.GenerateEnumMessages(oracleEvent);
604
+ case messaging_1.MessageType.DigitDecompositionEventDescriptor:
605
+ return this.GenerateDigitDecompositionMessages(oracleEvent);
606
+ default:
607
+ throw Error('EventDescriptor must be Enum or DigitDecomposition');
608
+ }
609
+ }
610
+ GetContractOraclePairs(_contractInfo) {
611
+ // Use contractInfoType property instead of instanceof for more reliable type checking
612
+ if (_contractInfo.contractInfoType === messaging_1.ContractInfoType.Single) {
613
+ const singleInfo = _contractInfo;
614
+ return [
615
+ {
616
+ contractDescriptor: singleInfo.contractDescriptor,
617
+ oracleInfo: singleInfo.oracleInfo,
618
+ },
619
+ ];
620
+ }
621
+ else if (_contractInfo.contractInfoType === messaging_1.ContractInfoType.Disjoint) {
622
+ const disjointInfo = _contractInfo;
623
+ return disjointInfo.contractOraclePairs;
624
+ }
625
+ else {
626
+ throw Error('ContractInfo must be Single or Disjoint');
627
+ }
628
+ }
629
+ getFundOutputValueSats(dlcTxs) {
630
+ const fundOutput = dlcTxs.fundTx.outputs[dlcTxs.fundTxVout];
631
+ if (!fundOutput || !fundOutput.value) {
632
+ throw new Error(`Invalid fund output at vout ${dlcTxs.fundTxVout}: ` +
633
+ `outputs.length=${dlcTxs.fundTx.outputs.length}, ` +
634
+ `output exists=${!!fundOutput}, ` +
635
+ `output.value exists=${!!(fundOutput && fundOutput.value)}`);
636
+ }
637
+ return fundOutput.value.sats;
638
+ }
639
+ async CreateCetAdaptorAndRefundSigs(dlcOffer, dlcAccept, dlcTxs, messagesList, isOfferer) {
640
+ const network = await this.getConnectedNetwork();
641
+ const cetsHex = dlcTxs.cets.map((cet) => cet.serialize().toString('hex'));
642
+ // Create the correct P2WSH multisig funding script
643
+ const fundingPubKeys = Buffer.compare(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey) === -1
644
+ ? [dlcOffer.fundingPubkey, dlcAccept.fundingPubkey]
645
+ : [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
646
+ const p2ms = bitcoinjs_lib_1.payments.p2ms({
647
+ m: 2,
648
+ pubkeys: fundingPubKeys,
649
+ network,
650
+ });
651
+ // We need the redeem script (multisig), not the P2WSH output
652
+ const fundingSPK = p2ms.output;
653
+ // For finding the private key, we still need the individual P2WPKH address
654
+ const individualFundingSPK = bitcoin_1.Script.p2wpkhLock((0, crypto_1.hash160)(isOfferer ? dlcOffer.fundingPubkey : dlcAccept.fundingPubkey))
655
+ .serialize()
656
+ .slice(1);
657
+ const fundingAddress = bitcoinjs_lib_1.address.fromOutputScript(individualFundingSPK, network);
658
+ const { derivationPath } = await this.client.wallet.findAddress([
659
+ fundingAddress,
660
+ ]);
661
+ const fundPrivateKeyPair = await this.getMethod('keyPair')(derivationPath);
662
+ const fundPrivateKey = Buffer.from(fundPrivateKeyPair.__D).toString('hex');
663
+ const contractOraclePairs = this.GetContractOraclePairs(dlcOffer.contractInfo);
664
+ const sigs = [];
665
+ if (dlcOffer.contractInfo.contractInfoType === messaging_1.ContractInfoType.Single &&
666
+ dlcOffer.contractInfo.contractDescriptor.type ===
667
+ messaging_1.MessageType.ContractDescriptorV0) {
668
+ for (const { oracleInfo } of contractOraclePairs) {
669
+ if (oracleInfo.type !== messaging_1.MessageType.SingleOracleInfo) {
670
+ throw new Error('Only SingleOracleInfo supported in this context');
671
+ }
672
+ const oracleAnnouncement = oracleInfo
673
+ .announcement;
674
+ const adaptorSigRequestPromises = [];
675
+ const tempMessagesList = messagesList;
676
+ const tempCetsHex = cetsHex;
677
+ const ddkOracleInfo = {
678
+ publicKey: oracleAnnouncement.oraclePubkey,
679
+ nonces: oracleAnnouncement.oracleEvent.oracleNonces,
680
+ };
681
+ adaptorSigRequestPromises.push((async () => {
682
+ const cetsForDdk = tempCetsHex.map((cetHex) => this.convertTxToDdkTransaction(bitcoin_1.Tx.decode(bufio_1.StreamReader.fromHex(cetHex))));
683
+ const messagesForDdk = this.convertMessagesForDdk(tempMessagesList);
684
+ const response = this._ddk.createCetAdaptorSigsFromOracleInfo(cetsForDdk, [ddkOracleInfo], Buffer.from(fundPrivateKey, 'hex'), fundingSPK, this.getFundOutputValueSats(dlcTxs), messagesForDdk);
685
+ return response;
686
+ })());
687
+ const adaptorPairs = (await Promise.all(adaptorSigRequestPromises)).flat();
688
+ sigs.push(adaptorPairs.map((adaptorPair) => {
689
+ return {
690
+ encryptedSig: adaptorPair.signature,
691
+ dleqProof: adaptorPair.proof,
692
+ };
693
+ }));
694
+ }
695
+ }
696
+ else {
697
+ const indices = this.GetIndicesFromPayouts(this.GetPayouts(dlcOffer));
698
+ for (const [index, { oracleInfo }] of contractOraclePairs.entries()) {
699
+ if (oracleInfo.type !== messaging_1.MessageType.SingleOracleInfo) {
700
+ throw new Error('Only SingleOracleInfo supported in this context');
701
+ }
702
+ const oracleAnnouncement = oracleInfo
703
+ .announcement;
704
+ const startingIndex = indices[index].startingMessagesIndex, endingIndex = indices[index + 1].startingMessagesIndex;
705
+ const oracleEventMessagesList = messagesList.slice(startingIndex, endingIndex);
706
+ const oracleEventCetsHex = cetsHex.slice(startingIndex, endingIndex);
707
+ const chunk = 100;
708
+ const adaptorSigRequestPromises = [];
709
+ for (let i = 0, j = oracleEventMessagesList.length; i < j; i += chunk) {
710
+ const tempMessagesList = oracleEventMessagesList.slice(i, i + chunk);
711
+ const tempCetsHex = oracleEventCetsHex.slice(i, i + chunk);
712
+ const ddkOracleInfo = {
713
+ publicKey: oracleAnnouncement.oraclePubkey,
714
+ nonces: oracleAnnouncement.oracleEvent.oracleNonces,
715
+ };
716
+ const messagesForDdk = this.convertMessagesForDdk(tempMessagesList);
717
+ adaptorSigRequestPromises.push((async () => {
718
+ const response = this._ddk.createCetAdaptorSigsFromOracleInfo(tempCetsHex.map((cetHex) => this.convertTxToDdkTransaction(bitcoin_1.Tx.decode(bufio_1.StreamReader.fromHex(cetHex)))), [ddkOracleInfo], Buffer.from(fundPrivateKey, 'hex'), fundingSPK, this.getFundOutputValueSats(dlcTxs), messagesForDdk);
719
+ return response;
720
+ })());
721
+ }
722
+ const adaptorPairs = (await Promise.all(adaptorSigRequestPromises)).flat();
723
+ sigs.push(adaptorPairs.map((adaptorPair) => {
724
+ return {
725
+ encryptedSig: adaptorPair.signature,
726
+ dleqProof: adaptorPair.proof,
727
+ };
728
+ }));
729
+ }
730
+ }
731
+ const refundSignature = this._ddk.getRawFundingTransactionInputSignature(this.convertTxToDdkTransaction(dlcTxs.refundTx), Buffer.from(fundPrivateKey, 'hex'), dlcTxs.fundTx.txId.toString(), dlcTxs.fundTxVout, dlcOffer.contractInfo.totalCollateral);
732
+ const cetSignatures = new messaging_1.CetAdaptorSignatures();
733
+ cetSignatures.sigs = sigs.flat();
734
+ return { cetSignatures, refundSignature };
735
+ }
736
+ async VerifyCetAdaptorAndRefundSigs(dlcOffer, dlcAccept, dlcSign, dlcTxs, messagesList, isOfferer) {
737
+ const cetsHex = dlcTxs.cets.map((cet) => cet.serialize().toString('hex'));
738
+ const contractOraclePairs = this.GetContractOraclePairs(dlcOffer.contractInfo);
739
+ if (dlcOffer.contractInfo.type === messaging_1.MessageType.SingleContractInfo &&
740
+ dlcOffer.contractInfo.contractDescriptor.type ===
741
+ messaging_1.MessageType.ContractDescriptorV0) {
742
+ for (const { oracleInfo } of contractOraclePairs) {
743
+ if (oracleInfo.type !== messaging_1.MessageType.SingleOracleInfo) {
744
+ throw new Error('Only SingleOracleInfo supported in this context');
745
+ }
746
+ const oracleAnnouncement = oracleInfo
747
+ .announcement;
748
+ const oracleEventSigs = isOfferer
749
+ ? dlcAccept.cetAdaptorSignatures.sigs
750
+ : dlcSign.cetAdaptorSignatures.sigs;
751
+ const sigsValidity = [];
752
+ const tempMessagesList = messagesList;
753
+ const tempSigs = oracleEventSigs;
754
+ const tempAdaptorPairs = tempSigs.map((sig) => {
755
+ return {
756
+ signature: sig.encryptedSig,
757
+ proof: sig.dleqProof,
758
+ };
759
+ });
760
+ // Create the correct P2WSH multisig funding script for verification
761
+ const network = await this.getConnectedNetwork();
762
+ const verifyFundingPubKeys = Buffer.compare(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey) === -1
763
+ ? [dlcOffer.fundingPubkey, dlcAccept.fundingPubkey]
764
+ : [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
765
+ const verifyP2ms = bitcoinjs_lib_1.payments.p2ms({
766
+ m: 2,
767
+ pubkeys: verifyFundingPubKeys,
768
+ network,
769
+ });
770
+ // We need the redeem script (multisig), not the P2WSH output
771
+ const fundingSPK = verifyP2ms.output;
772
+ const ddkOracleInfo = {
773
+ publicKey: oracleAnnouncement.oraclePubkey,
774
+ nonces: oracleAnnouncement.oracleEvent.oracleNonces,
775
+ };
776
+ const pubkey = isOfferer
777
+ ? dlcAccept.fundingPubkey
778
+ : dlcOffer.fundingPubkey;
779
+ const messagesForDdk = this.convertMessagesForDdk(tempMessagesList);
780
+ sigsValidity.push((async () => {
781
+ const response = this._ddk.verifyCetAdaptorSigsFromOracleInfo(tempAdaptorPairs, dlcTxs.cets.map((cet) => this.convertTxToDdkTransaction(cet)), [ddkOracleInfo], pubkey, fundingSPK, this.getFundOutputValueSats(dlcTxs), messagesForDdk);
782
+ return response;
783
+ })());
784
+ let areSigsValid = (await Promise.all(sigsValidity)).every((b) => b);
785
+ await this.VerifyRefundSignatureAlt(dlcOffer, dlcAccept, dlcSign, dlcTxs, isOfferer);
786
+ if (!areSigsValid) {
787
+ throw new Error('Invalid signatures received');
788
+ }
789
+ }
790
+ }
791
+ else {
792
+ const chunk = 100;
793
+ const indices = this.GetIndicesFromPayouts(this.GetPayouts(dlcOffer));
794
+ for (const [index, { oracleInfo }] of contractOraclePairs.entries()) {
795
+ if (oracleInfo.type !== messaging_1.MessageType.SingleOracleInfo) {
796
+ throw new Error('Only SingleOracleInfo supported in this context');
797
+ }
798
+ const oracleAnnouncement = oracleInfo
799
+ .announcement;
800
+ const startingIndex = indices[index].startingMessagesIndex, endingIndex = indices[index + 1].startingMessagesIndex;
801
+ const oracleEventMessagesList = messagesList.slice(startingIndex, endingIndex);
802
+ const oracleEventCetsHex = cetsHex.slice(startingIndex, endingIndex);
803
+ const oracleEventSigs = (isOfferer
804
+ ? dlcAccept.cetAdaptorSignatures.sigs
805
+ : dlcSign.cetAdaptorSignatures.sigs).slice(startingIndex, endingIndex);
806
+ const sigsValidity = [];
807
+ for (let i = 0, j = oracleEventMessagesList.length; i < j; i += chunk) {
808
+ const tempMessagesList = oracleEventMessagesList.slice(i, i + chunk);
809
+ const tempCetsHex = oracleEventCetsHex.slice(i, i + chunk);
810
+ const tempSigs = oracleEventSigs.slice(i, i + chunk);
811
+ const tempAdaptorPairs = tempSigs.map((sig) => {
812
+ return {
813
+ signature: sig.encryptedSig,
814
+ proof: sig.dleqProof,
815
+ };
816
+ });
817
+ // Create the correct P2WSH multisig funding script
818
+ const network = await this.getConnectedNetwork();
819
+ const nonEnumFundingPubKeys = Buffer.compare(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey) ===
820
+ -1
821
+ ? [dlcOffer.fundingPubkey, dlcAccept.fundingPubkey]
822
+ : [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
823
+ const nonEnumP2ms = bitcoinjs_lib_1.payments.p2ms({
824
+ m: 2,
825
+ pubkeys: nonEnumFundingPubKeys,
826
+ network,
827
+ });
828
+ // We need the redeem script (multisig), not the P2WSH output
829
+ const fundingSPK = nonEnumP2ms.output;
830
+ const ddkOracleInfo = {
831
+ publicKey: oracleAnnouncement.oraclePubkey,
832
+ nonces: oracleAnnouncement.oracleEvent.oracleNonces,
833
+ };
834
+ const pubkey = isOfferer
835
+ ? dlcAccept.fundingPubkey
836
+ : dlcOffer.fundingPubkey;
837
+ const messagesForDdk = this.convertMessagesForDdk(tempMessagesList);
838
+ sigsValidity.push((async () => {
839
+ const response = this._ddk.verifyCetAdaptorSigsFromOracleInfo(tempAdaptorPairs, tempCetsHex.map((cet) => this.convertTxToDdkTransaction(bitcoin_1.Tx.decode(bufio_1.StreamReader.fromHex(cet)))), [ddkOracleInfo], pubkey, fundingSPK, this.getFundOutputValueSats(dlcTxs), messagesForDdk);
840
+ return response;
841
+ })());
842
+ }
843
+ let areSigsValid = (await Promise.all(sigsValidity)).every((b) => b);
844
+ // Verify refund signature using PSBT approach
845
+ await this.VerifyRefundSignatureAlt(dlcOffer, dlcAccept, dlcSign, dlcTxs, isOfferer);
846
+ if (!areSigsValid) {
847
+ throw new Error('Invalid CET adaptor signatures received');
848
+ }
849
+ }
850
+ }
851
+ }
852
+ async CreateFundingSigsAlt(dlcOffer, dlcAccept, dlcTxs, isOfferer) {
853
+ const transaction = bitcoinjs_lib_1.Transaction.fromBuffer(dlcTxs.fundTx.serialize());
854
+ const network = await this.getConnectedNetwork();
855
+ const psbt = new bitcoinjs_lib_1.Psbt({ network });
856
+ // Combine all funding inputs from both parties
857
+ const allFundingInputs = [
858
+ ...dlcOffer.fundingInputs,
859
+ ...dlcAccept.fundingInputs,
860
+ ];
861
+ // Sort by inputSerialId to reconstruct proper transaction order
862
+ allFundingInputs.sort((a, b) => Number(a.inputSerialId - b.inputSerialId));
863
+ // Add all inputs to PSBT with proper witnessUtxo
864
+ for (const fundingInput of allFundingInputs) {
865
+ const prevOut = fundingInput.prevTx.outputs[fundingInput.prevTxVout];
866
+ // Use the same pattern as existing code - slice(1) to remove length prefix
867
+ const witnessUtxo = {
868
+ script: prevOut.scriptPubKey.serialize().subarray(1),
869
+ value: Number(prevOut.value.sats),
870
+ };
871
+ // Use sequence from the original transaction to ensure consistency
872
+ const originalInput = transaction.ins.find((input) => input.hash.reverse().toString('hex') ===
873
+ fundingInput.prevTx.txId.toString() &&
874
+ input.index === fundingInput.prevTxVout);
875
+ const sequenceValue = originalInput
876
+ ? originalInput.sequence
877
+ : Number(fundingInput.sequence);
878
+ psbt.addInput({
879
+ hash: fundingInput.prevTx.txId.toString(),
880
+ index: fundingInput.prevTxVout,
881
+ sequence: sequenceValue,
882
+ witnessUtxo,
883
+ });
884
+ }
885
+ // Add all outputs to PSBT (maintains transaction structure)
886
+ for (const output of transaction.outs) {
887
+ psbt.addOutput({
888
+ address: bitcoinjs_lib_1.address.fromOutputScript(output.script, network),
889
+ value: output.value,
890
+ });
891
+ }
892
+ // Determine which inputs belong to this party
893
+ const partyFundingInputs = isOfferer
894
+ ? dlcOffer.fundingInputs
895
+ : dlcAccept.fundingInputs;
896
+ // Convert party's funding inputs to Input objects and get private keys
897
+ const partyInputs = await Promise.all(partyFundingInputs.map(async (fundingInput) => {
898
+ return this.fundingInputToInput(fundingInput);
899
+ }));
900
+ const inputPrivKeys = await this.GetPrivKeysForInputs(partyInputs);
901
+ // Create map of this party's inputs for efficient lookup
902
+ const partyInputMap = new Map();
903
+ partyInputs.forEach((input, index) => {
904
+ const key = `${input.txid}:${input.vout}`;
905
+ partyInputMap.set(key, { input, privKey: inputPrivKeys[index] });
906
+ });
907
+ // Initialize witness elements array to match all inputs
908
+ const witnessElements = [];
909
+ // Sign only this party's inputs
910
+ for (let inputIndex = 0; inputIndex < allFundingInputs.length; inputIndex++) {
911
+ const fundingInput = allFundingInputs[inputIndex];
912
+ const inputKey = `${fundingInput.prevTx.txId.toString()}:${fundingInput.prevTxVout}`;
913
+ if (partyInputMap.has(inputKey)) {
914
+ // This input belongs to this party - sign it
915
+ const { privKey } = partyInputMap.get(inputKey);
916
+ const keyPair = ECPair.fromPrivateKey(Buffer.from(privKey, 'hex'));
917
+ // Check if this is a DLC input (2-of-2 multisig from previous DLC)
918
+ if (fundingInput.dlcInput) {
919
+ // For DLC inputs, we'll need to handle 2-of-2 multisig signing differently
920
+ // For now, throw an error as this requires implementing multisig support
921
+ throw new Error('DLC input signing not yet implemented in CreateFundingSigsAlt');
922
+ }
923
+ else {
924
+ // For P2WPKH inputs, use PSBT signing
925
+ psbt.signInput(inputIndex, keyPair);
926
+ // Extract signature from partial signatures (more reliable than finalization)
927
+ const inputData = psbt.data.inputs[inputIndex];
928
+ const partialSigs = inputData.partialSig;
929
+ if (!partialSigs || partialSigs.length === 0) {
930
+ throw new Error(`No signatures found for input ${inputIndex} after signing`);
931
+ }
932
+ // For P2WPKH, create witness manually: [signature, publicKey]
933
+ const sigWitness = new messaging_1.ScriptWitnessV0();
934
+ sigWitness.witness = partialSigs[0].signature;
935
+ const pubKeyWitness = new messaging_1.ScriptWitnessV0();
936
+ pubKeyWitness.witness = keyPair.publicKey;
937
+ witnessElements.push([sigWitness, pubKeyWitness]);
938
+ }
939
+ }
940
+ // Note: We don't add anything to witnessElements for other party's inputs
941
+ }
942
+ const fundingSignatures = new messaging_1.FundingSignatures();
943
+ fundingSignatures.witnessElements = witnessElements;
944
+ return fundingSignatures;
945
+ }
946
+ async VerifyFundingSigsAlt(dlcOffer, dlcAccept, dlcSign, dlcTxs, isOfferer) {
947
+ const network = await this.getConnectedNetwork();
948
+ const transaction = bitcoinjs_lib_1.Transaction.fromBuffer(dlcTxs.fundTx.serialize());
949
+ // Get the party whose signatures we're verifying
950
+ // If we're the offerer, we verify accepter's signatures (from dlcSign)
951
+ // If we're the accepter, we verify offerer's signatures (from dlcSign)
952
+ const signingPartyInputs = isOfferer
953
+ ? dlcAccept.fundingInputs
954
+ : dlcOffer.fundingInputs;
955
+ // Combine all inputs to get the correct ordering
956
+ const allFundingInputs = [
957
+ ...dlcOffer.fundingInputs,
958
+ ...dlcAccept.fundingInputs,
959
+ ];
960
+ allFundingInputs.sort((a, b) => Number(a.inputSerialId - b.inputSerialId));
961
+ // Compare transaction IDs
962
+ const dlcFundTxId = dlcTxs.fundTx.txId.toString();
963
+ const psbtBuilderTxId = transaction.getId();
964
+ (0, assert_1.default)(dlcFundTxId === psbtBuilderTxId, 'Transaction IDs do not match');
965
+ // Create a PSBT for signature verification
966
+ const psbt = new bitcoinjs_lib_1.Psbt({ network });
967
+ // Add all inputs (needed for proper sighash calculation)
968
+ for (const input of allFundingInputs) {
969
+ const prevOutput = input.prevTx.outputs[input.prevTxVout];
970
+ psbt.addInput({
971
+ hash: input.prevTx.txId.toString(),
972
+ index: input.prevTxVout,
973
+ sequence: 0,
974
+ witnessUtxo: {
975
+ script: prevOutput.scriptPubKey.serialize().subarray(1),
976
+ value: Number(prevOutput.value.sats),
977
+ },
978
+ });
979
+ }
980
+ // Add all outputs
981
+ for (const output of transaction.outs) {
982
+ psbt.addOutput({
983
+ address: bitcoinjs_lib_1.address.fromOutputScript(output.script, network),
984
+ value: output.value,
985
+ });
986
+ }
987
+ if (psbt.inputCount !== transaction.ins.length ||
988
+ psbt.txOutputs.length !== transaction.outs.length) {
989
+ throw new Error(`PSBT structure doesn't match original transaction`);
990
+ }
991
+ // Add the funding signatures to the PSBT as partial signatures
992
+ let witnessIndex = 0;
993
+ for (const fundingInput of signingPartyInputs) {
994
+ // Skip DLC inputs for now (same as CreateFundingSigsAlt)
995
+ if (fundingInput.dlcInput) {
996
+ continue;
997
+ }
998
+ if (witnessIndex >= dlcSign.fundingSignatures.witnessElements.length) {
999
+ throw new Error(`Not enough witness elements: expected at least ${witnessIndex + 1}, got ${dlcSign.fundingSignatures.witnessElements.length}`);
1000
+ }
1001
+ const witnessElement = dlcSign.fundingSignatures.witnessElements[witnessIndex];
1002
+ const signature = witnessElement[0].witness;
1003
+ const publicKey = witnessElement[1].witness;
1004
+ // Find this input's index in the sorted transaction
1005
+ const inputIndex = allFundingInputs.findIndex((input) => input.prevTx.txId.toString() ===
1006
+ fundingInput.prevTx.txId.toString() &&
1007
+ input.prevTxVout === fundingInput.prevTxVout);
1008
+ if (inputIndex === -1) {
1009
+ throw new Error(`Input not found in transaction: ${fundingInput.prevTx.txId.toString()}:${fundingInput.prevTxVout}`);
1010
+ }
1011
+ psbt.updateInput(inputIndex, {
1012
+ partialSig: [{ pubkey: publicKey, signature: signature }],
1013
+ });
1014
+ psbt.validateSignaturesOfInput(inputIndex, (pubkey, msghash, signature) => {
1015
+ return ecc.verify(msghash, pubkey, signature);
1016
+ });
1017
+ witnessIndex++;
1018
+ }
1019
+ }
1020
+ async VerifyRefundSignatureAlt(dlcOffer, dlcAccept, dlcSign, dlcTxs, isOfferer) {
1021
+ const network = await this.getConnectedNetwork();
1022
+ // Get the refund signature we need to verify
1023
+ // If we're the offerer, we verify accepter's refund signature (from dlcAccept)
1024
+ // If we're the accepter, we verify offerer's refund signature (from dlcSign)
1025
+ const refundSignature = isOfferer
1026
+ ? dlcAccept.refundSignature
1027
+ : dlcSign.refundSignature;
1028
+ const signingPubkey = isOfferer
1029
+ ? dlcAccept.fundingPubkey
1030
+ : dlcOffer.fundingPubkey;
1031
+ // Verify refund transaction locktime matches expected
1032
+ if (Number(dlcTxs.refundTx.locktime) !== dlcOffer.refundLocktime) {
1033
+ throw new Error(`Refund transaction locktime ${dlcTxs.refundTx.locktime} does not match expected ${dlcOffer.refundLocktime}`);
1034
+ }
1035
+ // Create a PSBT for the refund transaction verification
1036
+ const psbt = new bitcoinjs_lib_1.Psbt({ network });
1037
+ // Add the funding input
1038
+ const fundingOutput = dlcTxs.fundTx.outputs[dlcTxs.fundTxVout];
1039
+ psbt.addInput({
1040
+ hash: dlcTxs.fundTx.txId.toString(),
1041
+ index: dlcTxs.fundTxVout,
1042
+ sequence: 0,
1043
+ witnessUtxo: {
1044
+ script: fundingOutput.scriptPubKey.serialize().subarray(1), // Remove length prefix
1045
+ value: Number(fundingOutput.value.sats),
1046
+ },
1047
+ witnessScript: this.CreateFundingScript(dlcOffer, dlcAccept), // 2-of-2 multisig script
1048
+ });
1049
+ // Add the refund output
1050
+ const refundOutput = dlcTxs.refundTx.outputs[0];
1051
+ psbt.addOutput({
1052
+ address: bitcoinjs_lib_1.address.fromOutputScript(refundOutput.scriptPubKey.serialize().subarray(1), network),
1053
+ value: Number(refundOutput.value.sats),
1054
+ });
1055
+ // Set the locktime to match the refund transaction
1056
+ psbt.setLocktime(Number(dlcTxs.refundTx.locktime));
1057
+ // Add the refund signature as a partial signature
1058
+ psbt.updateInput(0, {
1059
+ partialSig: [{ pubkey: signingPubkey, signature: refundSignature }],
1060
+ });
1061
+ // Validate the refund signature
1062
+ try {
1063
+ psbt.validateSignaturesOfInput(0, (pubkey, msghash, signature) => {
1064
+ return ecc.verify(msghash, pubkey, signature);
1065
+ });
1066
+ }
1067
+ catch (error) {
1068
+ throw new Error(`Refund signature validation failed for ${isOfferer ? 'accepter' : 'offerer'}: ${error.message}`);
1069
+ }
1070
+ }
1071
+ CreateFundingScript(dlcOffer, dlcAccept) {
1072
+ const network = this.getBitcoinJsNetwork();
1073
+ // Sort funding pubkeys in lexicographical order as per DLC spec
1074
+ const fundingPubKeys = Buffer.compare(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey) === -1
1075
+ ? [dlcOffer.fundingPubkey, dlcAccept.fundingPubkey]
1076
+ : [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
1077
+ // Create 2-of-2 multisig script
1078
+ const p2ms = bitcoinjs_lib_1.payments.p2ms({
1079
+ m: 2,
1080
+ pubkeys: fundingPubKeys,
1081
+ network,
1082
+ });
1083
+ const paymentVariant = bitcoinjs_lib_1.payments.p2wsh({
1084
+ redeem: p2ms,
1085
+ network,
1086
+ });
1087
+ return paymentVariant.redeem.output;
1088
+ }
1089
+ async CreateFundingTx(dlcOffer, dlcAccept, dlcSign, dlcTxs, fundingSignatures) {
1090
+ const network = await this.getConnectedNetwork();
1091
+ const psbt = new bitcoinjs_lib_1.Psbt({ network });
1092
+ // Combine and sort all funding inputs by serial ID (same as CreateFundingSigsAlt)
1093
+ const allFundingInputs = [
1094
+ ...dlcOffer.fundingInputs,
1095
+ ...dlcAccept.fundingInputs,
1096
+ ];
1097
+ allFundingInputs.sort((a, b) => Number(a.inputSerialId - b.inputSerialId));
1098
+ // Create a map of input txid:vout to witness elements
1099
+ const witnessMap = new Map();
1100
+ // Map witness elements correctly - CreateFundingSigsAlt only creates witness elements
1101
+ // for the party's own inputs, so we need to map them correctly
1102
+ // For dlcSign (offerer's signatures), map to offerer's inputs only
1103
+ let offererWitnessIndex = 0;
1104
+ dlcOffer.fundingInputs.forEach((fundingInput) => {
1105
+ // Skip DLC inputs for now as in CreateFundingSigsAlt
1106
+ if (fundingInput.dlcInput) {
1107
+ return;
1108
+ }
1109
+ if (offererWitnessIndex < dlcSign.fundingSignatures.witnessElements.length) {
1110
+ const key = `${fundingInput.prevTx.txId.toString()}:${fundingInput.prevTxVout}`;
1111
+ witnessMap.set(key, dlcSign.fundingSignatures.witnessElements[offererWitnessIndex]);
1112
+ offererWitnessIndex++;
1113
+ }
1114
+ });
1115
+ // For fundingSignatures (accepter's signatures), map to accepter's inputs only
1116
+ let accepterWitnessIndex = 0;
1117
+ dlcAccept.fundingInputs.forEach((fundingInput) => {
1118
+ // Skip DLC inputs for now as in CreateFundingSigsAlt
1119
+ if (fundingInput.dlcInput) {
1120
+ return;
1121
+ }
1122
+ if (accepterWitnessIndex < fundingSignatures.witnessElements.length) {
1123
+ const key = `${fundingInput.prevTx.txId.toString()}:${fundingInput.prevTxVout}`;
1124
+ witnessMap.set(key, fundingSignatures.witnessElements[accepterWitnessIndex]);
1125
+ accepterWitnessIndex++;
1126
+ }
1127
+ });
1128
+ // Add all inputs to PSBT with proper sequence values
1129
+ const originalTransaction = bitcoinjs_lib_1.Transaction.fromBuffer(dlcTxs.fundTx.serialize());
1130
+ for (const fundingInput of allFundingInputs) {
1131
+ const prevOut = fundingInput.prevTx.outputs[fundingInput.prevTxVout];
1132
+ // Use same script handling as CreateFundingSigsAlt
1133
+ const witnessUtxo = {
1134
+ script: prevOut.scriptPubKey.serialize().subarray(1),
1135
+ value: Number(prevOut.value.sats),
1136
+ };
1137
+ // Use sequence from the original transaction (same as CreateFundingSigsAlt)
1138
+ const originalInput = originalTransaction.ins.find((input) => input.hash.reverse().toString('hex') ===
1139
+ fundingInput.prevTx.txId.toString() &&
1140
+ input.index === fundingInput.prevTxVout);
1141
+ const sequenceValue = originalInput
1142
+ ? originalInput.sequence
1143
+ : Number(fundingInput.sequence);
1144
+ psbt.addInput({
1145
+ hash: fundingInput.prevTx.txId.toString(),
1146
+ index: fundingInput.prevTxVout,
1147
+ sequence: sequenceValue,
1148
+ witnessUtxo,
1149
+ });
1150
+ }
1151
+ // Add all outputs from the funding transaction
1152
+ for (const output of originalTransaction.outs) {
1153
+ psbt.addOutput({
1154
+ address: bitcoinjs_lib_1.address.fromOutputScript(output.script, network),
1155
+ value: output.value,
1156
+ });
1157
+ }
1158
+ // Finalize inputs with their witness data
1159
+ for (let inputIndex = 0; inputIndex < allFundingInputs.length; inputIndex++) {
1160
+ const fundingInput = allFundingInputs[inputIndex];
1161
+ const inputKey = `${fundingInput.prevTx.txId.toString()}:${fundingInput.prevTxVout}`;
1162
+ const witnessElements = witnessMap.get(inputKey);
1163
+ if (witnessElements && witnessElements.length === 2) {
1164
+ // Skip DLC inputs for now as requested
1165
+ if (fundingInput.dlcInput) {
1166
+ continue;
1167
+ }
1168
+ // For P2WPKH inputs, finalize the input with witness data
1169
+ const signature = witnessElements[0].witness;
1170
+ const publicKey = witnessElements[1].witness;
1171
+ // Try a simpler approach - let bitcoinjs-lib handle witness construction
1172
+ psbt.finalizeInput(inputIndex, () => ({
1173
+ finalScriptSig: Buffer.alloc(0),
1174
+ finalScriptWitness: Buffer.concat([
1175
+ Buffer.from([0x02]), // witness stack count
1176
+ Buffer.from([signature.length]),
1177
+ signature,
1178
+ Buffer.from([publicKey.length]),
1179
+ publicKey,
1180
+ ]),
1181
+ }));
1182
+ }
1183
+ }
1184
+ // Extract the final transaction
1185
+ const finalTx = psbt.extractTransaction();
1186
+ // Convert back to the expected Tx format
1187
+ return bitcoin_1.Tx.decode(bufio_1.StreamReader.fromBuffer(finalTx.toBuffer()));
1188
+ }
1189
+ async FindOutcomeIndexFromPolynomialPayoutCurvePiece(dlcOffer, contractDescriptor, contractOraclePairIndex, polynomialPayoutCurvePiece, oracleAttestation, outcome) {
1190
+ const polynomialCurve = core_2.PolynomialPayoutCurve.fromPayoutCurvePiece(polynomialPayoutCurvePiece);
1191
+ const payouts = polynomialPayoutCurvePiece.points.map((point) => Number(point.outcomePayout));
1192
+ const minPayout = Math.min(...payouts);
1193
+ const maxPayout = Math.max(...payouts);
1194
+ const clampBN = (val) => bignumber_js_1.default.max(minPayout, bignumber_js_1.default.min(val, maxPayout));
1195
+ const payout = clampBN(polynomialCurve.getPayout(outcome));
1196
+ const payoutResponses = this.GetPayouts(dlcOffer);
1197
+ const payoutIndexOffset = this.GetIndicesFromPayouts(payoutResponses)[contractOraclePairIndex]
1198
+ .startingMessagesIndex;
1199
+ const { payoutGroups } = payoutResponses[contractOraclePairIndex];
1200
+ const intervalsSorted = [
1201
+ ...contractDescriptor.roundingIntervals.intervals,
1202
+ ].sort((a, b) => Number(b.beginInterval) - Number(a.beginInterval));
1203
+ const interval = intervalsSorted.find((interval) => Number(outcome) >= Number(interval.beginInterval));
1204
+ const roundedPayout = BigInt(clampBN(new bignumber_js_1.default((0, core_2.roundPayout)(payout, interval.roundingMod).toString())).toString());
1205
+ const outcomesFormatted = oracleAttestation.outcomes.map((outcome) => parseInt(outcome));
1206
+ let index = 0;
1207
+ let groupIndex = -1;
1208
+ let groupLength = 0;
1209
+ for (const payoutGroup of payoutGroups) {
1210
+ if (payoutGroup.payout === roundedPayout) {
1211
+ groupIndex = payoutGroup.groups.findIndex((group) => {
1212
+ return group.every((msg, i) => msg === outcomesFormatted[i]);
1213
+ });
1214
+ if (groupIndex === -1)
1215
+ throw Error('Failed to Find OutcomeIndex From PolynomialPayoutCurvePiece. \
1216
+ Payout Group found but incorrect group index');
1217
+ index += groupIndex;
1218
+ groupLength = payoutGroup.groups[groupIndex].length;
1219
+ break;
1220
+ }
1221
+ else {
1222
+ index += payoutGroup.groups.length;
1223
+ }
1224
+ }
1225
+ if (groupIndex === -1)
1226
+ throw Error('Failed to Find OutcomeIndex From PolynomialPayoutCurvePiece. \
1227
+ Payout Group not found');
1228
+ return { index: payoutIndexOffset + index, groupLength };
1229
+ }
1230
+ async FindOutcomeIndexFromHyperbolaPayoutCurvePiece(_dlcOffer, contractDescriptor, contractOraclePairIndex, hyperbolaPayoutCurvePiece, oracleAttestation, outcome) {
1231
+ const { dlcOffer } = (0, Utils_1.checkTypes)({ _dlcOffer });
1232
+ const hyperbolaCurve = core_2.HyperbolaPayoutCurve.fromPayoutCurvePiece(hyperbolaPayoutCurvePiece);
1233
+ const clampBN = (val) => bignumber_js_1.default.max(0, bignumber_js_1.default.min(val, dlcOffer.contractInfo.totalCollateral.toString()));
1234
+ const payout = clampBN(hyperbolaCurve.getPayout(outcome));
1235
+ const payoutResponses = this.GetPayouts(dlcOffer);
1236
+ const payoutIndexOffset = this.GetIndicesFromPayouts(payoutResponses)[contractOraclePairIndex]
1237
+ .startingMessagesIndex;
1238
+ const { payoutGroups } = payoutResponses[contractOraclePairIndex];
1239
+ const intervalsSorted = [
1240
+ ...contractDescriptor.roundingIntervals.intervals,
1241
+ ].sort((a, b) => Number(b.beginInterval) - Number(a.beginInterval));
1242
+ const interval = intervalsSorted.find((interval) => Number(outcome) >= Number(interval.beginInterval));
1243
+ const roundedPayout = BigInt(clampBN(new bignumber_js_1.default((0, core_2.roundPayout)(payout, interval.roundingMod).toString())).toString());
1244
+ const outcomesFormatted = oracleAttestation.outcomes.map((outcome) => parseInt(outcome));
1245
+ let index = 0;
1246
+ let groupIndex = -1;
1247
+ let groupLength = 0;
1248
+ for (const [i, payoutGroup] of payoutGroups.entries()) {
1249
+ if (payoutGroup.payout === roundedPayout) {
1250
+ groupIndex = payoutGroup.groups.findIndex((group) => {
1251
+ return group.every((msg, i) => msg === outcomesFormatted[i]);
1252
+ });
1253
+ if (groupIndex !== -1) {
1254
+ index += groupIndex;
1255
+ groupLength = payoutGroup.groups[groupIndex].length;
1256
+ break;
1257
+ }
1258
+ }
1259
+ else if (payoutGroup.payout === BigInt(Math.round(Number(payout.toString()))) &&
1260
+ i !== 0) {
1261
+ // Edge case to account for case where payout is maximum payout for DLC
1262
+ // But rounded payout does not round down
1263
+ if (payoutGroups[i - 1].payout === roundedPayout) {
1264
+ // Ensure that the previous payout group causes index to be incremented
1265
+ index += payoutGroups[i - 1].groups.length;
1266
+ }
1267
+ groupIndex = payoutGroup.groups.findIndex((group) => {
1268
+ return group.every((msg, i) => msg === outcomesFormatted[i]);
1269
+ });
1270
+ if (groupIndex !== -1) {
1271
+ index += groupIndex;
1272
+ groupLength = payoutGroup.groups[groupIndex].length;
1273
+ break;
1274
+ }
1275
+ }
1276
+ else {
1277
+ index += payoutGroup.groups.length;
1278
+ }
1279
+ }
1280
+ if (groupIndex === -1) {
1281
+ // Fallback to brute force search if payout-based search fails
1282
+ index = 0;
1283
+ groupLength = 0;
1284
+ for (const [, payoutGroup] of payoutGroups.entries()) {
1285
+ groupIndex = payoutGroup.groups.findIndex((group) => {
1286
+ return group.every((msg, j) => msg === outcomesFormatted[j]);
1287
+ });
1288
+ if (groupIndex !== -1) {
1289
+ index += groupIndex;
1290
+ groupLength = payoutGroup.groups[groupIndex].length;
1291
+ break;
1292
+ }
1293
+ else {
1294
+ index += payoutGroup.groups.length;
1295
+ }
1296
+ }
1297
+ if (groupIndex === -1) {
1298
+ throw Error('Failed to Find OutcomeIndex From HyperbolaPayoutCurvePiece. \
1299
+ Payout Group not found even with brute force search');
1300
+ }
1301
+ }
1302
+ return { index: payoutIndexOffset + index, groupLength };
1303
+ }
1304
+ async FindOutcomeIndex(dlcOffer, oracleAttestation) {
1305
+ const contractOraclePairs = this.GetContractOraclePairs(dlcOffer.contractInfo);
1306
+ const contractOraclePairIndex = contractOraclePairs.findIndex(({ oracleInfo }) => {
1307
+ if (oracleInfo.type !== messaging_1.MessageType.SingleOracleInfo)
1308
+ return false;
1309
+ const singleOracleInfo = oracleInfo;
1310
+ return (singleOracleInfo.announcement.oracleEvent.eventId ===
1311
+ oracleAttestation.eventId);
1312
+ });
1313
+ (0, assert_1.default)(contractOraclePairIndex !== -1, 'OracleAttestation must be for an existing OracleEvent');
1314
+ const contractOraclePair = contractOraclePairs[contractOraclePairIndex];
1315
+ const { contractDescriptor: _contractDescriptor, oracleInfo } = contractOraclePair;
1316
+ (0, assert_1.default)(_contractDescriptor.contractDescriptorType ===
1317
+ messaging_1.ContractDescriptorType.NumericOutcome, 'ContractDescriptor must be NumericOutcome');
1318
+ const contractDescriptor = _contractDescriptor;
1319
+ const _payoutFunction = contractDescriptor.payoutFunction;
1320
+ (0, assert_1.default)(_payoutFunction.type === messaging_1.MessageType.PayoutFunction, 'PayoutFunction must be V0');
1321
+ if (oracleInfo.type !== messaging_1.MessageType.SingleOracleInfo) {
1322
+ throw new Error('Only SingleOracleInfo supported in this context');
1323
+ }
1324
+ const singleOracleInfo = oracleInfo;
1325
+ const eventDescriptor = singleOracleInfo.announcement.oracleEvent
1326
+ .eventDescriptor;
1327
+ const payoutFunction = _payoutFunction;
1328
+ const base = eventDescriptor.base;
1329
+ const outcome = [...oracleAttestation.outcomes]
1330
+ .reverse()
1331
+ .reduce((acc, val, i) => acc + Number(val) * base ** i, 0);
1332
+ const piecesSorted = payoutFunction.payoutFunctionPieces.sort((a, b) => Number(a.endPoint.eventOutcome) - Number(b.endPoint.eventOutcome));
1333
+ const piece = piecesSorted.find((piece) => outcome < piece.endPoint.eventOutcome);
1334
+ switch (piece.payoutCurvePiece.type) {
1335
+ case messaging_1.MessageType.PolynomialPayoutCurvePiece:
1336
+ return this.FindOutcomeIndexFromPolynomialPayoutCurvePiece(dlcOffer, contractDescriptor, contractOraclePairIndex, piece.payoutCurvePiece, oracleAttestation, BigInt(outcome));
1337
+ case messaging_1.MessageType.HyperbolaPayoutCurvePiece:
1338
+ return this.FindOutcomeIndexFromHyperbolaPayoutCurvePiece(dlcOffer, contractDescriptor, contractOraclePairIndex, piece.payoutCurvePiece, oracleAttestation, BigInt(outcome));
1339
+ case messaging_1.MessageType.OldHyperbolaPayoutCurvePiece:
1340
+ return this.FindOutcomeIndexFromHyperbolaPayoutCurvePiece(dlcOffer, contractDescriptor, contractOraclePairIndex, piece.payoutCurvePiece, oracleAttestation, BigInt(outcome));
1341
+ default:
1342
+ throw Error('Must be Hyperbola or Polynomial curve piece');
1343
+ }
1344
+ }
1345
+ ValidateEvent(dlcOffer, oracleAttestation) {
1346
+ switch (dlcOffer.contractInfo.contractInfoType) {
1347
+ case messaging_1.ContractInfoType.Single: {
1348
+ const contractInfo = dlcOffer.contractInfo;
1349
+ switch (contractInfo.contractDescriptor.contractDescriptorType) {
1350
+ case messaging_1.ContractDescriptorType.Enumerated: {
1351
+ const oracleInfo = contractInfo.oracleInfo;
1352
+ if (oracleInfo.type !== messaging_1.MessageType.SingleOracleInfo) {
1353
+ throw Error('Only SingleOracleInfo supported in this context');
1354
+ }
1355
+ const singleOracleInfo = oracleInfo;
1356
+ if (singleOracleInfo.announcement.oracleEvent.eventId !==
1357
+ oracleAttestation.eventId)
1358
+ throw Error('Incorrect Oracle Attestation. Event Id must match.');
1359
+ break;
1360
+ }
1361
+ case messaging_1.ContractDescriptorType.NumericOutcome: {
1362
+ const oracleInfo = contractInfo.oracleInfo;
1363
+ if (oracleInfo.type !== messaging_1.MessageType.SingleOracleInfo) {
1364
+ throw Error('Only SingleOracleInfo supported in this context');
1365
+ }
1366
+ const singleOracleInfo = oracleInfo;
1367
+ if (singleOracleInfo.announcement.oracleEvent.eventId !==
1368
+ oracleAttestation.eventId)
1369
+ throw Error('Incorrect Oracle Attestation. Event Id must match.');
1370
+ break;
1371
+ }
1372
+ default:
1373
+ throw Error('ConractDescriptor must be V0 or V1');
1374
+ }
1375
+ break;
1376
+ }
1377
+ case messaging_1.ContractInfoType.Disjoint: {
1378
+ const contractInfo = dlcOffer.contractInfo;
1379
+ const attestedOracleEvent = contractInfo.contractOraclePairs.find(({ oracleInfo }) => {
1380
+ if (oracleInfo.type !== messaging_1.MessageType.SingleOracleInfo)
1381
+ return false;
1382
+ const singleOracleInfo = oracleInfo;
1383
+ return (singleOracleInfo.announcement.oracleEvent.eventId ===
1384
+ oracleAttestation.eventId);
1385
+ });
1386
+ if (!attestedOracleEvent)
1387
+ throw Error('Oracle event of attestation not found.');
1388
+ break;
1389
+ }
1390
+ default:
1391
+ throw Error('ContractInfo must be V0 or V1');
1392
+ }
1393
+ }
1394
+ async FindAndSignCet(dlcOffer, dlcAccept, dlcSign, dlcTxs, oracleAttestation, isOfferer) {
1395
+ if (isOfferer === undefined)
1396
+ isOfferer = await this.isOfferer(dlcOffer, dlcAccept);
1397
+ const fundPrivateKey = await this.GetFundPrivateKey(dlcOffer, dlcAccept, isOfferer);
1398
+ let finalCet;
1399
+ if (dlcOffer.contractInfo.contractInfoType === messaging_1.ContractInfoType.Single &&
1400
+ dlcOffer.contractInfo.contractDescriptor
1401
+ .contractDescriptorType === messaging_1.ContractDescriptorType.Enumerated) {
1402
+ const contractDescriptor = dlcOffer.contractInfo
1403
+ .contractDescriptor;
1404
+ // Handle different contract descriptor outcome formats
1405
+ const attestedOutcome = oracleAttestation.outcomes[0];
1406
+ const outcomeIndex = contractDescriptor.outcomes.findIndex((outcome) => {
1407
+ // Try direct string match first (for DDK2 test: '1', '2', '3')
1408
+ if (outcome.outcome === attestedOutcome) {
1409
+ return true;
1410
+ }
1411
+ // Try sha256 hash match (for other tests with hashed outcomes)
1412
+ const attestedOutcomeHash = (0, crypto_1.sha256)(Buffer.from(attestedOutcome, 'utf8')).toString('hex');
1413
+ return outcome.outcome === attestedOutcomeHash;
1414
+ });
1415
+ finalCet = this._ddk
1416
+ .signCet(this.convertTxToDdkTransaction(dlcTxs.cets[outcomeIndex]), isOfferer
1417
+ ? dlcAccept.cetAdaptorSignatures.sigs[outcomeIndex].encryptedSig
1418
+ : dlcSign.cetAdaptorSignatures.sigs[outcomeIndex].encryptedSig, oracleAttestation.signatures, Buffer.from(fundPrivateKey, 'hex'), isOfferer ? dlcAccept.fundingPubkey : dlcOffer.fundingPubkey, isOfferer ? dlcOffer.fundingPubkey : dlcAccept.fundingPubkey, this.getFundOutputValueSats(dlcTxs))
1419
+ .rawBytes.toString('hex');
1420
+ }
1421
+ else {
1422
+ const { index: outcomeIndex, groupLength } = await this.FindOutcomeIndex(dlcOffer, oracleAttestation);
1423
+ const sliceIndex = -(oracleAttestation.signatures.length - groupLength);
1424
+ const oracleSignatures = sliceIndex === 0
1425
+ ? oracleAttestation.signatures
1426
+ : oracleAttestation.signatures.slice(0, sliceIndex);
1427
+ finalCet = this._ddk
1428
+ .signCet(this.convertTxToDdkTransaction(dlcTxs.cets[outcomeIndex]), isOfferer
1429
+ ? dlcAccept.cetAdaptorSignatures.sigs[outcomeIndex].encryptedSig
1430
+ : dlcSign.cetAdaptorSignatures.sigs[outcomeIndex].encryptedSig, oracleSignatures, Buffer.from(fundPrivateKey, 'hex'), isOfferer ? dlcAccept.fundingPubkey : dlcOffer.fundingPubkey, isOfferer ? dlcOffer.fundingPubkey : dlcAccept.fundingPubkey, this.getFundOutputValueSats(dlcTxs))
1431
+ .rawBytes.toString('hex');
1432
+ }
1433
+ // const finalCet = (await this.SignCet(signCetRequest)).hex;
1434
+ return bitcoin_1.Tx.decode(bufio_1.StreamReader.fromHex(finalCet));
1435
+ }
1436
+ async GetFundAddress(dlcOffer, dlcAccept, isOfferer) {
1437
+ const network = await this.getConnectedNetwork();
1438
+ const fundingSPK = bitcoin_1.Script.p2wpkhLock((0, crypto_1.hash160)(isOfferer ? dlcOffer.fundingPubkey : dlcAccept.fundingPubkey))
1439
+ .serialize()
1440
+ .slice(1);
1441
+ const fundingAddress = bitcoinjs_lib_1.address.fromOutputScript(fundingSPK, network);
1442
+ return fundingAddress;
1443
+ }
1444
+ async GetFundKeyPair(dlcOffer, dlcAccept, isOfferer) {
1445
+ const fundingAddress = await this.GetFundAddress(dlcOffer, dlcAccept, isOfferer);
1446
+ const { derivationPath } = await this.getMethod('getWalletAddress')(fundingAddress);
1447
+ const keyPair = await this.getMethod('keyPair')(derivationPath);
1448
+ return keyPair;
1449
+ }
1450
+ async GetFundPrivateKey(dlcOffer, dlcAccept, isOfferer) {
1451
+ const fundPrivateKeyPair = await this.GetFundKeyPair(dlcOffer, dlcAccept, isOfferer);
1452
+ return Buffer.from(fundPrivateKeyPair.privateKey).toString('hex');
1453
+ }
1454
+ async CreateCloseRawTxs(dlcOffer, dlcAccept, dlcTxs, closeInputAmount, isOfferer, _dlcCloses = [], fundingInputs, initiatorPayouts) {
1455
+ const network = await this.getConnectedNetwork();
1456
+ let finalizer;
1457
+ if (_dlcCloses.length === 0) {
1458
+ finalizer = new core_2.DualClosingTxFinalizer(fundingInputs, dlcOffer.payoutSpk, dlcAccept.payoutSpk, dlcOffer.feeRatePerVb);
1459
+ }
1460
+ const rawTransactionRequestPromises = [];
1461
+ const rawCloseTxs = [];
1462
+ const numPayouts = _dlcCloses.length === 0 ? initiatorPayouts.length : _dlcCloses.length;
1463
+ for (let i = 0; i < numPayouts; i++) {
1464
+ let offerPayoutValue = BigInt(0);
1465
+ let acceptPayoutValue = BigInt(0);
1466
+ if (_dlcCloses.length === 0) {
1467
+ const payout = initiatorPayouts[i];
1468
+ const payoutMinusOfferFees = finalizer.offerInitiatorFees > payout
1469
+ ? BigInt(0)
1470
+ : payout - finalizer.offerInitiatorFees;
1471
+ const collateralMinusPayout = payout > dlcOffer.contractInfo.totalCollateral
1472
+ ? BigInt(0)
1473
+ : dlcOffer.contractInfo.totalCollateral - payout;
1474
+ offerPayoutValue = isOfferer
1475
+ ? closeInputAmount + payoutMinusOfferFees
1476
+ : collateralMinusPayout;
1477
+ acceptPayoutValue = isOfferer
1478
+ ? collateralMinusPayout
1479
+ : closeInputAmount + payoutMinusOfferFees;
1480
+ }
1481
+ else {
1482
+ const dlcClose = (0, Utils_1.checkTypes)({ _dlcClose: _dlcCloses[i] }).dlcClose;
1483
+ offerPayoutValue = dlcClose.offerPayoutSatoshis;
1484
+ acceptPayoutValue = dlcClose.acceptPayoutSatoshis;
1485
+ }
1486
+ const txOuts = [];
1487
+ if (Number(offerPayoutValue) > 0) {
1488
+ txOuts.push({
1489
+ address: bitcoinjs_lib_1.address.fromOutputScript(dlcOffer.payoutSpk, network),
1490
+ amount: Number(offerPayoutValue),
1491
+ });
1492
+ }
1493
+ if (Number(acceptPayoutValue) > 0) {
1494
+ txOuts.push({
1495
+ address: bitcoinjs_lib_1.address.fromOutputScript(dlcAccept.payoutSpk, network),
1496
+ amount: Number(acceptPayoutValue),
1497
+ });
1498
+ }
1499
+ if (dlcOffer.payoutSerialId > dlcAccept.payoutSerialId)
1500
+ txOuts.reverse();
1501
+ const rawTransactionRequest = {
1502
+ version: 2,
1503
+ locktime: 0,
1504
+ txins: [
1505
+ {
1506
+ txid: dlcTxs.fundTx.txId.serialize().reverse().toString('hex'),
1507
+ vout: dlcTxs.fundTxVout,
1508
+ sequence: 0,
1509
+ },
1510
+ ],
1511
+ txouts: txOuts,
1512
+ };
1513
+ rawTransactionRequestPromises.push((async () => {
1514
+ const response = await this.getMethod('CreateRawTransaction')(rawTransactionRequest);
1515
+ return response.hex;
1516
+ })());
1517
+ }
1518
+ const hexs = await Promise.all(rawTransactionRequestPromises);
1519
+ rawCloseTxs.push(hexs);
1520
+ return rawCloseTxs.flat();
1521
+ }
1522
+ async CreateSignatureHashes(_dlcOffer, _dlcAccept, _dlcTxs, rawCloseTxs) {
1523
+ const { dlcOffer, dlcAccept, dlcTxs } = (0, Utils_1.checkTypes)({
1524
+ _dlcOffer,
1525
+ _dlcAccept,
1526
+ _dlcTxs,
1527
+ });
1528
+ const network = await this.getConnectedNetwork();
1529
+ const fundingPubKeys = Buffer.compare(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey) === -1
1530
+ ? [dlcOffer.fundingPubkey, dlcAccept.fundingPubkey]
1531
+ : [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
1532
+ const p2ms = bitcoinjs_lib_1.payments.p2ms({
1533
+ m: 2,
1534
+ pubkeys: fundingPubKeys,
1535
+ network,
1536
+ });
1537
+ const paymentVariant = bitcoinjs_lib_1.payments.p2wsh({
1538
+ redeem: p2ms,
1539
+ network,
1540
+ });
1541
+ const sigHashRequestPromises = [];
1542
+ const sigHashes = [];
1543
+ for (let i = 0; i < rawCloseTxs.length; i++) {
1544
+ const rawTx = rawCloseTxs[i];
1545
+ const sigHashRequest = {
1546
+ tx: rawTx,
1547
+ txin: {
1548
+ txid: dlcTxs.fundTx.txId.serialize().reverse().toString('hex'),
1549
+ vout: dlcTxs.fundTxVout,
1550
+ keyData: {
1551
+ hex: paymentVariant.redeem.output.toString('hex'),
1552
+ type: 'redeem_script',
1553
+ },
1554
+ amount: Number(this.getFundOutputValueSats(dlcTxs)),
1555
+ hashType: 'p2wsh',
1556
+ sighashType: 'all',
1557
+ sighashAnyoneCanPay: false,
1558
+ },
1559
+ };
1560
+ sigHashRequestPromises.push((async () => {
1561
+ const response = await this.getMethod('CreateSignatureHash')(sigHashRequest);
1562
+ return response.sighash;
1563
+ })());
1564
+ }
1565
+ const sighashes = await Promise.all(sigHashRequestPromises);
1566
+ sigHashes.push(sighashes);
1567
+ return sigHashes.flat();
1568
+ }
1569
+ async CalculateEcSignatureHashes(sigHashes, privKey) {
1570
+ const cfdNetwork = await this.GetCfdNetwork();
1571
+ const sigsRequestPromises = [];
1572
+ for (let i = 0; i < sigHashes.length; i++) {
1573
+ const sigHash = sigHashes[i];
1574
+ const calculateEcSignatureRequest = {
1575
+ sighash: sigHash,
1576
+ privkeyData: {
1577
+ privkey: privKey,
1578
+ wif: false,
1579
+ network: cfdNetwork,
1580
+ },
1581
+ isGrindR: true,
1582
+ };
1583
+ sigsRequestPromises.push((async () => {
1584
+ const response = await this.getMethod('CalculateEcSignature')(calculateEcSignatureRequest);
1585
+ return response.signature;
1586
+ })());
1587
+ }
1588
+ const sigs = await Promise.all(sigsRequestPromises);
1589
+ return sigs.flat();
1590
+ }
1591
+ async VerifySignatures(_dlcOffer, _dlcAccept, _dlcTxs, _dlcCloses, rawCloseTxs, isOfferer) {
1592
+ const { dlcOffer, dlcAccept, dlcTxs } = (0, Utils_1.checkTypes)({
1593
+ _dlcOffer,
1594
+ _dlcAccept,
1595
+ _dlcTxs,
1596
+ });
1597
+ const dlcCloses = _dlcCloses.map((_dlcClose) => (0, Utils_1.checkTypes)({ _dlcClose }).dlcClose);
1598
+ const network = await this.getConnectedNetwork();
1599
+ const fundingPubKeys = Buffer.compare(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey) === -1
1600
+ ? [dlcOffer.fundingPubkey, dlcAccept.fundingPubkey]
1601
+ : [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
1602
+ const p2ms = bitcoinjs_lib_1.payments.p2ms({
1603
+ m: 2,
1604
+ pubkeys: fundingPubKeys,
1605
+ network,
1606
+ });
1607
+ const paymentVariant = bitcoinjs_lib_1.payments.p2wsh({
1608
+ redeem: p2ms,
1609
+ network,
1610
+ });
1611
+ const pubkey = isOfferer ? dlcAccept.fundingPubkey : dlcOffer.fundingPubkey;
1612
+ const sigsValidity = [];
1613
+ for (let i = 0; i < rawCloseTxs.length; i++) {
1614
+ const rawTx = rawCloseTxs[i];
1615
+ const dlcClose = dlcCloses[i];
1616
+ const verifySignatureRequest = {
1617
+ tx: rawTx,
1618
+ txin: {
1619
+ txid: dlcTxs.fundTx.txId.serialize().reverse().toString('hex'),
1620
+ vout: dlcTxs.fundTxVout,
1621
+ signature: dlcClose.closeSignature.toString('hex'),
1622
+ pubkey: pubkey.toString('hex'),
1623
+ redeemScript: paymentVariant.redeem.output.toString('hex'),
1624
+ hashType: 'p2wsh',
1625
+ sighashType: 'all',
1626
+ sighashAnyoneCanPay: false,
1627
+ amount: Number(this.getFundOutputValueSats(dlcTxs)),
1628
+ },
1629
+ };
1630
+ sigsValidity.push((async () => {
1631
+ const response = await this.getMethod('VerifySignature')(verifySignatureRequest);
1632
+ return response.success;
1633
+ })());
1634
+ }
1635
+ const areSigsValid = (await Promise.all(sigsValidity)).every((b) => b);
1636
+ return areSigsValid;
1637
+ }
1638
+ /**
1639
+ * Check whether wallet is offerer of DlcOffer or DlcAccept
1640
+ * @param dlcOffer Dlc Offer Message
1641
+ * @param dlcAccept Dlc Accept Message
1642
+ * @returns {Promise<boolean>}
1643
+ */
1644
+ async isOfferer(_dlcOffer, _dlcAccept) {
1645
+ const { dlcOffer, dlcAccept } = (0, Utils_1.checkTypes)({
1646
+ _dlcOffer,
1647
+ _dlcAccept,
1648
+ });
1649
+ const network = await this.getConnectedNetwork();
1650
+ const offerFundingSPK = bitcoin_1.Script.p2wpkhLock((0, crypto_1.hash160)(dlcOffer.fundingPubkey))
1651
+ .serialize()
1652
+ .slice(1);
1653
+ const acceptFundingSPK = bitcoin_1.Script.p2wpkhLock((0, crypto_1.hash160)(dlcAccept.fundingPubkey))
1654
+ .serialize()
1655
+ .slice(1);
1656
+ const offerFundingAddress = bitcoinjs_lib_1.address.fromOutputScript(offerFundingSPK, network);
1657
+ const acceptFundingAddress = bitcoinjs_lib_1.address.fromOutputScript(acceptFundingSPK, network);
1658
+ let walletAddress = await this.client.wallet.findAddress([
1659
+ offerFundingAddress,
1660
+ ]);
1661
+ if (walletAddress)
1662
+ return true;
1663
+ walletAddress = await this.client.wallet.findAddress([
1664
+ acceptFundingAddress,
1665
+ ]);
1666
+ if (walletAddress)
1667
+ return false;
1668
+ throw Error('Wallet Address not found for DlcOffer or DlcAccept');
1669
+ }
1670
+ /**
1671
+ * Create DLC Offer Message
1672
+ * @param contractInfo ContractInfo TLV (V0 or V1)
1673
+ * @param offerCollateralSatoshis Amount DLC Initiator is putting into the contract
1674
+ * @param feeRatePerVb Fee rate in satoshi per virtual byte that both sides use to compute fees in funding tx
1675
+ * @param cetLocktime The nLockTime to be put on CETs
1676
+ * @param refundLocktime The nLockTime to be put on the refund transaction
1677
+ * @param fixedInputs Optional fixed inputs - can be Input[] for regular inputs or FundingInput[] for DLC inputs
1678
+ * @returns {Promise<DlcOffer>}
1679
+ */
1680
+ async createDlcOffer(contractInfo, offerCollateralSatoshis, feeRatePerVb, cetLocktime, refundLocktime, fixedInputs, inputSupplementationMode) {
1681
+ contractInfo.validate();
1682
+ const network = await this.getConnectedNetwork();
1683
+ const dlcOffer = new messaging_1.DlcOffer();
1684
+ // Generate a random 32-byte temporary contract ID
1685
+ dlcOffer.temporaryContractId = crypto_2.default.randomBytes(32);
1686
+ // Check if we have FundingInput[] (DLC inputs) or Input[] (regular inputs)
1687
+ const hasFundingInputs = fixedInputs && fixedInputs.length > 0 && 'prevTx' in fixedInputs[0]; // FundingInput has prevTx, Input doesn't
1688
+ let fundingPubKey;
1689
+ let payoutSPK;
1690
+ let payoutSerialId;
1691
+ let _fundingInputs;
1692
+ let changeSPK;
1693
+ let changeSerialId;
1694
+ if (hasFundingInputs) {
1695
+ // Handle FundingInput[] directly (for DLC inputs)
1696
+ const fundingInputs = fixedInputs;
1697
+ // Generate addresses directly since we're bypassing Initialize()
1698
+ const payoutAddress = await this.client.wallet.getUnusedAddress(false);
1699
+ payoutSPK = bitcoinjs_lib_1.address.toOutputScript(payoutAddress.address, network);
1700
+ const changeAddress = await this.client.wallet.getUnusedAddress(true);
1701
+ changeSPK = bitcoinjs_lib_1.address.toOutputScript(changeAddress.address, network);
1702
+ const fundingAddress = await this.client.wallet.getUnusedAddress(false);
1703
+ fundingPubKey = Buffer.from(fundingAddress.publicKey, 'hex');
1704
+ if (fundingAddress.address === payoutAddress.address)
1705
+ throw Error('Address reuse');
1706
+ payoutSerialId = (0, Utils_1.generateSerialId)();
1707
+ changeSerialId = (0, Utils_1.generateSerialId)();
1708
+ _fundingInputs = fundingInputs;
1709
+ }
1710
+ else {
1711
+ // Handle Input[] through existing Initialize() flow
1712
+ const initResult = await this.Initialize(offerCollateralSatoshis, feeRatePerVb, fixedInputs, inputSupplementationMode || types_1.InputSupplementationMode.Required);
1713
+ fundingPubKey = initResult.fundingPubKey;
1714
+ payoutSPK = initResult.payoutSPK;
1715
+ payoutSerialId = initResult.payoutSerialId;
1716
+ _fundingInputs = initResult.fundingInputs;
1717
+ changeSPK = initResult.changeSPK;
1718
+ changeSerialId = initResult.changeSerialId;
1719
+ }
1720
+ _fundingInputs.forEach((input) => (0, assert_1.default)(input.type === messaging_1.MessageType.FundingInput, 'FundingInput must be V0'));
1721
+ const fundingInputs = _fundingInputs.map((input) => input);
1722
+ fundingInputs.sort((a, b) => Number(a.inputSerialId) - Number(b.inputSerialId));
1723
+ const fundOutputSerialId = (0, Utils_1.generateSerialId)();
1724
+ (0, assert_1.default)(changeSerialId !== fundOutputSerialId, 'changeSerialId cannot equal the fundOutputSerialId');
1725
+ dlcOffer.contractFlags = Buffer.from('00', 'hex');
1726
+ dlcOffer.chainHash = (0, bitcoin_networks_1.chainHashFromNetwork)(network);
1727
+ dlcOffer.contractInfo = contractInfo;
1728
+ dlcOffer.fundingPubkey = fundingPubKey;
1729
+ dlcOffer.payoutSpk = payoutSPK;
1730
+ dlcOffer.payoutSerialId = payoutSerialId;
1731
+ dlcOffer.offerCollateral = offerCollateralSatoshis;
1732
+ dlcOffer.fundingInputs = fundingInputs;
1733
+ dlcOffer.changeSpk = changeSPK;
1734
+ dlcOffer.changeSerialId = changeSerialId;
1735
+ dlcOffer.fundOutputSerialId = dlcOffer.fundOutputSerialId =
1736
+ fundOutputSerialId;
1737
+ dlcOffer.feeRatePerVb = feeRatePerVb;
1738
+ dlcOffer.cetLocktime = cetLocktime;
1739
+ dlcOffer.refundLocktime = refundLocktime;
1740
+ if (offerCollateralSatoshis === dlcOffer.contractInfo.totalCollateral) {
1741
+ dlcOffer.markAsSingleFunded();
1742
+ }
1743
+ (0, assert_1.default)((() => {
1744
+ const finalizer = new core_2.DualFundingTxFinalizer(dlcOffer.fundingInputs, dlcOffer.payoutSpk, dlcOffer.changeSpk, null, null, null, dlcOffer.feeRatePerVb);
1745
+ const funding = fundingInputs.reduce((total, input) => {
1746
+ return total + input.prevTx.outputs[input.prevTxVout].value.sats;
1747
+ }, BigInt(0));
1748
+ return funding >= offerCollateralSatoshis + finalizer.offerFees;
1749
+ })(), 'fundingInputs for dlcOffer must be greater than offerCollateralSatoshis plus offerFees');
1750
+ dlcOffer.validate();
1751
+ return dlcOffer;
1752
+ }
1753
+ /**
1754
+ * Accept DLC Offer (supports single-funded DLCs when accept collateral is 0)
1755
+ * @param _dlcOffer Dlc Offer Message
1756
+ * @param fixedInputs Optional inputs to use for Funding Inputs
1757
+ * @returns {Promise<AcceptDlcOfferResponse}
1758
+ */
1759
+ async acceptDlcOffer(_dlcOffer, fixedInputs) {
1760
+ const { dlcOffer } = (0, Utils_1.checkTypes)({ _dlcOffer });
1761
+ dlcOffer.validate();
1762
+ const acceptCollateralSatoshis = dlcOffer.contractInfo.totalCollateral - dlcOffer.offerCollateral;
1763
+ (0, assert_1.default)(acceptCollateralSatoshis ===
1764
+ dlcOffer.contractInfo.totalCollateral - dlcOffer.offerCollateral, 'acceptCollaterialSatoshis should equal totalCollateral - offerCollateralSatoshis');
1765
+ let fundingPubKey;
1766
+ let payoutSPK;
1767
+ let payoutSerialId;
1768
+ let fundingInputs;
1769
+ let changeSPK;
1770
+ let changeSerialId;
1771
+ if (acceptCollateralSatoshis === BigInt(0)) {
1772
+ // Single-funded DLC: accept side provides no funding
1773
+ const network = await this.getConnectedNetwork();
1774
+ // Still need payout address for receiving DLC outcomes
1775
+ const payoutAddress = await this.client.wallet.getUnusedAddress(false);
1776
+ payoutSPK = bitcoinjs_lib_1.address.toOutputScript(payoutAddress.address, network);
1777
+ // Generate funding pubkey for DLC contract construction
1778
+ const fundingAddress = await this.client.wallet.getUnusedAddress(false);
1779
+ fundingPubKey = Buffer.from(fundingAddress.publicKey, 'hex');
1780
+ // Generate change address (even though not used)
1781
+ const changeAddress = await this.client.wallet.getUnusedAddress(true);
1782
+ changeSPK = bitcoinjs_lib_1.address.toOutputScript(changeAddress.address, network);
1783
+ if (fundingAddress.address === payoutAddress.address)
1784
+ throw Error('Address reuse');
1785
+ // Generate serial IDs
1786
+ payoutSerialId = (0, Utils_1.generateSerialId)();
1787
+ changeSerialId = (0, Utils_1.generateSerialId)();
1788
+ // No funding inputs for single-funded DLC
1789
+ fundingInputs = [];
1790
+ }
1791
+ else {
1792
+ // Standard DLC: accept side provides funding
1793
+ // Check if we have FundingInput[] (DLC inputs) or Input[] (regular inputs)
1794
+ const hasFundingInputs = fixedInputs && fixedInputs.length > 0 && 'prevTx' in fixedInputs[0]; // FundingInput has prevTx, Input doesn't
1795
+ let initResult;
1796
+ if (hasFundingInputs) {
1797
+ // Handle FundingInput[] directly (for DLC inputs)
1798
+ const fundingInputs = fixedInputs;
1799
+ const network = await this.getConnectedNetwork();
1800
+ // Generate addresses directly since we're bypassing Initialize()
1801
+ const payoutAddress = await this.client.wallet.getUnusedAddress(false);
1802
+ const payoutSPK = bitcoinjs_lib_1.address.toOutputScript(payoutAddress.address, network);
1803
+ const changeAddress = await this.client.wallet.getUnusedAddress(true);
1804
+ const changeSPK = bitcoinjs_lib_1.address.toOutputScript(changeAddress.address, network);
1805
+ const fundingAddress = await this.client.wallet.getUnusedAddress(false);
1806
+ const fundingPubKey = Buffer.from(fundingAddress.publicKey, 'hex');
1807
+ if (fundingAddress.address === payoutAddress.address)
1808
+ throw Error('Address reuse');
1809
+ const payoutSerialId = (0, Utils_1.generateSerialId)();
1810
+ const changeSerialId = (0, Utils_1.generateSerialId)();
1811
+ initResult = {
1812
+ fundingPubKey,
1813
+ payoutSPK,
1814
+ payoutSerialId,
1815
+ fundingInputs,
1816
+ changeSPK,
1817
+ changeSerialId,
1818
+ };
1819
+ }
1820
+ else {
1821
+ // Handle Input[] through existing Initialize() flow
1822
+ // Use InputSupplementationMode.None when fixed inputs are provided
1823
+ // to avoid wallet lookup issues with unusual addresses
1824
+ const supplementationMode = fixedInputs && fixedInputs.length > 0
1825
+ ? types_1.InputSupplementationMode.None
1826
+ : types_1.InputSupplementationMode.Required;
1827
+ initResult = await this.Initialize(acceptCollateralSatoshis, dlcOffer.feeRatePerVb, fixedInputs, supplementationMode);
1828
+ }
1829
+ fundingPubKey = initResult.fundingPubKey;
1830
+ payoutSPK = initResult.payoutSPK;
1831
+ payoutSerialId = initResult.payoutSerialId;
1832
+ changeSPK = initResult.changeSPK;
1833
+ changeSerialId = initResult.changeSerialId;
1834
+ const _fundingInputs = initResult.fundingInputs;
1835
+ _fundingInputs.forEach((input) => (0, assert_1.default)(input.type === messaging_1.MessageType.FundingInput, 'FundingInput must be V0'));
1836
+ fundingInputs = _fundingInputs.map((input) => input);
1837
+ fundingInputs.sort((a, b) => Number(a.inputSerialId) - Number(b.inputSerialId));
1838
+ }
1839
+ (0, assert_1.default)(Buffer.compare(dlcOffer.fundingPubkey, fundingPubKey) !== 0, 'DlcOffer and DlcAccept FundingPubKey cannot be the same');
1840
+ const dlcAccept = new messaging_1.DlcAccept();
1841
+ dlcAccept.temporaryContractId = (0, crypto_1.sha256)(dlcOffer.serialize());
1842
+ dlcAccept.acceptCollateral = acceptCollateralSatoshis;
1843
+ dlcAccept.fundingPubkey = fundingPubKey;
1844
+ dlcAccept.payoutSpk = payoutSPK;
1845
+ dlcAccept.payoutSerialId = payoutSerialId;
1846
+ dlcAccept.fundingInputs = fundingInputs;
1847
+ dlcAccept.changeSpk = changeSPK;
1848
+ dlcAccept.changeSerialId = changeSerialId;
1849
+ (0, assert_1.default)(dlcAccept.changeSerialId !== dlcOffer.fundOutputSerialId, 'changeSerialId cannot equal the fundOutputSerialId');
1850
+ (0, assert_1.default)(dlcOffer.payoutSerialId !== dlcAccept.payoutSerialId, 'offer.payoutSerialId cannot equal accept.payoutSerialId');
1851
+ (0, assert_1.default)((() => {
1852
+ const ids = [
1853
+ dlcOffer.changeSerialId,
1854
+ dlcAccept.changeSerialId,
1855
+ dlcOffer.fundOutputSerialId,
1856
+ ];
1857
+ return new Set(ids).size === ids.length;
1858
+ })(), 'offer.changeSerialID, accept.changeSerialId and fundOutputSerialId must be unique');
1859
+ if (dlcOffer.singleFunded)
1860
+ dlcAccept.markAsSingleFunded();
1861
+ dlcAccept.validate();
1862
+ // Only validate funding requirements if accept side is providing collateral
1863
+ if (acceptCollateralSatoshis > BigInt(0)) {
1864
+ (0, assert_1.default)((() => {
1865
+ const finalizer = new core_2.DualFundingTxFinalizer(dlcOffer.fundingInputs, dlcOffer.payoutSpk, dlcOffer.changeSpk, dlcAccept.fundingInputs, dlcAccept.payoutSpk, dlcAccept.changeSpk, dlcOffer.feeRatePerVb);
1866
+ const funding = fundingInputs.reduce((total, input) => {
1867
+ return total + input.prevTx.outputs[input.prevTxVout].value.sats;
1868
+ }, BigInt(0));
1869
+ return funding >= acceptCollateralSatoshis + finalizer.acceptFees;
1870
+ })(), 'fundingInputs for dlcAccept must be greater than acceptCollateralSatoshis plus acceptFees');
1871
+ }
1872
+ const { dlcTransactions, messagesList } = await this.createDlcTxs(dlcOffer, dlcAccept);
1873
+ const { cetSignatures, refundSignature } = await this.CreateCetAdaptorAndRefundSigs(dlcOffer, dlcAccept, dlcTransactions, messagesList, false);
1874
+ const _dlcTransactions = dlcTransactions;
1875
+ const contractId = (0, crypto_1.xor)(_dlcTransactions.fundTx.txId.serialize(), dlcAccept.temporaryContractId);
1876
+ _dlcTransactions.contractId = contractId;
1877
+ dlcAccept.cetAdaptorSignatures = cetSignatures;
1878
+ dlcAccept.refundSignature = refundSignature;
1879
+ return { dlcAccept, dlcTransactions: _dlcTransactions };
1880
+ }
1881
+ /**
1882
+ * Sign Dlc Accept Message
1883
+ * @param _dlcOffer Dlc Offer Message
1884
+ * @param _dlcAccept Dlc Accept Message
1885
+ * @returns {Promise<SignDlcAcceptResponse}
1886
+ */
1887
+ async signDlcAccept(dlcOffer, dlcAccept) {
1888
+ dlcOffer.validate();
1889
+ dlcAccept.validate();
1890
+ (0, assert_1.default)(Buffer.compare(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey) !== 0, 'DlcOffer and DlcAccept FundingPubKey cannot be the same');
1891
+ const dlcSign = new messaging_1.DlcSign();
1892
+ const { dlcTransactions, messagesList } = await this.createDlcTxs(dlcOffer, dlcAccept);
1893
+ await this.VerifyCetAdaptorAndRefundSigs(dlcOffer, dlcAccept, dlcSign, dlcTransactions, messagesList, true);
1894
+ const { cetSignatures, refundSignature } = await this.CreateCetAdaptorAndRefundSigs(dlcOffer, dlcAccept, dlcTransactions, messagesList, true);
1895
+ const fundingSignatures = await this.CreateFundingSigsAlt(dlcOffer, dlcAccept, dlcTransactions, true);
1896
+ const dlcTxs = dlcTransactions;
1897
+ const contractId = (0, crypto_1.xor)(dlcTxs.fundTx.txId.serialize(), dlcAccept.temporaryContractId);
1898
+ (0, assert_1.default)(Buffer.compare(contractId, (0, crypto_1.xor)(dlcTxs.fundTx.txId.serialize(), dlcAccept.temporaryContractId)) === 0, 'contractId must be the xor of funding txid, fundingOutputIndex and the tempContractId');
1899
+ dlcTxs.contractId = contractId;
1900
+ dlcSign.contractId = contractId;
1901
+ dlcSign.cetAdaptorSignatures = cetSignatures;
1902
+ dlcSign.refundSignature = refundSignature;
1903
+ dlcSign.fundingSignatures = fundingSignatures;
1904
+ return { dlcSign, dlcTransactions: dlcTxs };
1905
+ }
1906
+ /**
1907
+ * Finalize Dlc Sign
1908
+ * @param dlcOffer Dlc Offer Message
1909
+ * @param dlcAccept Dlc Accept Message
1910
+ * @param dlcSign Dlc Sign Message
1911
+ * @param dlcTxs Dlc Transactions Message
1912
+ * @returns {Promise<Tx>}
1913
+ */
1914
+ async finalizeDlcSign(dlcOffer, dlcAccept, dlcSign, dlcTxs) {
1915
+ let messagesList = [];
1916
+ if (dlcOffer.contractInfo.type === messaging_1.MessageType.SingleContractInfo &&
1917
+ dlcOffer.contractInfo.contractDescriptor.type ===
1918
+ messaging_1.MessageType.SingleContractInfo) {
1919
+ for (const outcome of dlcOffer.contractInfo
1920
+ .contractDescriptor.outcomes) {
1921
+ messagesList.push({ messages: [outcome.outcome] });
1922
+ }
1923
+ }
1924
+ else {
1925
+ const payoutResponses = this.GetPayouts(dlcOffer);
1926
+ const { messagesList: oracleEventMessagesList } = this.FlattenPayouts(payoutResponses);
1927
+ messagesList = oracleEventMessagesList;
1928
+ }
1929
+ await this.VerifyCetAdaptorAndRefundSigs(dlcOffer, dlcAccept, dlcSign, dlcTxs, messagesList, false);
1930
+ await this.VerifyFundingSigsAlt(dlcOffer, dlcAccept, dlcSign, dlcTxs, false);
1931
+ const fundingSignatures = await this.CreateFundingSigsAlt(dlcOffer, dlcAccept, dlcTxs, false);
1932
+ const fundTx = await this.CreateFundingTx(dlcOffer, dlcAccept, dlcSign, dlcTxs, fundingSignatures);
1933
+ return fundTx;
1934
+ }
1935
+ /**
1936
+ * Execute DLC
1937
+ * @param _dlcOffer Dlc Offer Message
1938
+ * @param _dlcAccept Dlc Accept Message
1939
+ * @param _dlcSign Dlc Sign Message
1940
+ * @param _dlcTxs Dlc Transactions Message
1941
+ * @param oracleAttestation Oracle Attestations TLV (V0)
1942
+ * @param isOfferer Whether party is Dlc Offerer
1943
+ * @returns {Promise<Tx>}
1944
+ */
1945
+ async execute(dlcOffer, dlcAccept, dlcSign, dlcTxs, oracleAttestation, isOfferer) {
1946
+ if (isOfferer === undefined)
1947
+ isOfferer = await this.isOfferer(dlcOffer, dlcAccept);
1948
+ this.ValidateEvent(dlcOffer, oracleAttestation);
1949
+ return this.FindAndSignCet(dlcOffer, dlcAccept, dlcSign, dlcTxs, oracleAttestation, isOfferer);
1950
+ }
1951
+ /**
1952
+ * Refund DLC
1953
+ * @param dlcOffer Dlc Offer Message
1954
+ * @param dlcAccept Dlc Accept Message
1955
+ * @param dlcSign Dlc Sign Message
1956
+ * @param dlcTxs Dlc Transactions message
1957
+ * @returns {Promise<Tx>}
1958
+ */
1959
+ async refund(dlcOffer, dlcAccept, dlcSign, dlcTxs) {
1960
+ const network = await this.getConnectedNetwork();
1961
+ const psbt = new bitcoinjs_lib_1.Psbt({ network });
1962
+ // Verify refund transaction locktime matches expected
1963
+ if (Number(dlcTxs.refundTx.locktime) !== dlcOffer.refundLocktime) {
1964
+ throw new Error(`Refund transaction locktime ${dlcTxs.refundTx.locktime} does not match expected ${dlcOffer.refundLocktime}`);
1965
+ }
1966
+ // Add the funding input
1967
+ const fundingOutput = dlcTxs.fundTx.outputs[dlcTxs.fundTxVout];
1968
+ psbt.addInput({
1969
+ hash: dlcTxs.fundTx.txId.toString(),
1970
+ index: dlcTxs.fundTxVout,
1971
+ sequence: 0,
1972
+ witnessUtxo: {
1973
+ script: fundingOutput.scriptPubKey.serialize().subarray(1), // Remove length prefix
1974
+ value: Number(fundingOutput.value.sats),
1975
+ },
1976
+ witnessScript: this.CreateFundingScript(dlcOffer, dlcAccept), // 2-of-2 multisig script
1977
+ });
1978
+ // Add the refund output
1979
+ const refundOutput = dlcTxs.refundTx.outputs[0];
1980
+ psbt.addOutput({
1981
+ address: bitcoinjs_lib_1.address.fromOutputScript(refundOutput.scriptPubKey.serialize().subarray(1), network),
1982
+ value: Number(refundOutput.value.sats),
1983
+ });
1984
+ // Set the locktime to match the refund transaction
1985
+ psbt.setLocktime(Number(dlcTxs.refundTx.locktime));
1986
+ // Sort funding pubkeys for consistent signature ordering
1987
+ const fundingPubKeys = Buffer.compare(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey) === -1
1988
+ ? [dlcOffer.fundingPubkey, dlcAccept.fundingPubkey]
1989
+ : [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
1990
+ // Add both refund signatures as partial signatures
1991
+ psbt.updateInput(0, {
1992
+ partialSig: [
1993
+ { pubkey: fundingPubKeys[0], signature: dlcSign.refundSignature },
1994
+ { pubkey: fundingPubKeys[1], signature: dlcAccept.refundSignature },
1995
+ ],
1996
+ });
1997
+ // Validate all signatures
1998
+ psbt.validateSignaturesOfInput(0, (pubkey, msghash, signature) => {
1999
+ return ecc.verify(msghash, pubkey, signature);
2000
+ });
2001
+ // Finalize the input - this will create the final witness script
2002
+ psbt.finalizeInput(0);
2003
+ // Extract the final transaction
2004
+ const finalTx = psbt.extractTransaction();
2005
+ // Convert to the expected Tx format
2006
+ return bitcoin_1.Tx.decode(bufio_1.StreamReader.fromBuffer(finalTx.toBuffer()));
2007
+ }
2008
+ /**
2009
+ * Goal of createDlcClose is for alice (the initiator) to
2010
+ * 1. take dlcoffer, accept, and sign messages. Create a dlcClose message.
2011
+ * 2. Build a close tx, sign.
2012
+ * 3. return dlcClose message (no psbt)
2013
+ */
2014
+ /**
2015
+ * Generate DlcClose messagetype for closing DLC with Mutual Consent
2016
+ * @param _dlcOffer DlcOffer TLV (V0)
2017
+ * @param _dlcAccept DlcAccept TLV (V0)
2018
+ * @param _dlcTxs DlcTransactions TLV (V0)
2019
+ * @param initiatorPayoutSatoshis Amount initiator expects as a payout
2020
+ * @param isOfferer Whether offerer or not
2021
+ * @param _inputs Optionally specified closing inputs
2022
+ * @returns {Promise<DlcClose>}
2023
+ */
2024
+ async createDlcClose(_dlcOffer, _dlcAccept, _dlcTxs, initiatorPayoutSatoshis, isOfferer, _inputs) {
2025
+ const { dlcOffer, dlcAccept, dlcTxs } = (0, Utils_1.checkTypes)({
2026
+ _dlcOffer,
2027
+ _dlcAccept,
2028
+ _dlcTxs,
2029
+ });
2030
+ if (isOfferer === undefined)
2031
+ isOfferer = await this.isOfferer(dlcOffer, dlcAccept);
2032
+ const network = await this.getConnectedNetwork();
2033
+ const psbt = new bitcoinjs_lib_1.Psbt({ network });
2034
+ const fundingPubKeys = Buffer.compare(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey) === -1
2035
+ ? [dlcOffer.fundingPubkey, dlcAccept.fundingPubkey]
2036
+ : [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
2037
+ const p2ms = bitcoinjs_lib_1.payments.p2ms({
2038
+ m: 2,
2039
+ pubkeys: fundingPubKeys,
2040
+ network,
2041
+ });
2042
+ const paymentVariant = bitcoinjs_lib_1.payments.p2wsh({
2043
+ redeem: p2ms,
2044
+ network,
2045
+ });
2046
+ // Initiate and build PSBT
2047
+ let inputs = _inputs;
2048
+ if (!_inputs) {
2049
+ const tempInputs = await this.GetInputsForAmountWithMode([BigInt(20000)], dlcOffer.feeRatePerVb, _inputs || [], types_1.InputSupplementationMode.Required);
2050
+ _inputs = tempInputs;
2051
+ }
2052
+ // Ensure all inputs have derivation paths by fetching from wallet
2053
+ const inputsWithPaths = await Promise.all(_inputs.map(async (input) => {
2054
+ const address = await this.getMethod('getWalletAddress')(input.address);
2055
+ const inputWithPath = new types_1.Input(input.txid, input.vout, input.address, input.amount, input.value, input.derivationPath || address.derivationPath, // Use derivationPath from wallet if not set
2056
+ input.maxWitnessLength, input.redeemScript, input.inputSerialId || (0, Utils_1.generateSerialId)(), input.scriptPubKey, input.label, input.confirmations, input.spendable, input.solvable, input.safe, input.dlcInput);
2057
+ return { input: inputWithPath, address };
2058
+ }));
2059
+ inputs = inputsWithPaths.map((item) => item.input);
2060
+ const pubkeys = inputsWithPaths.map((item) => Buffer.from(item.address.publicKey, 'hex'));
2061
+ const fundingInputSerialId = (0, Utils_1.generateSerialId)();
2062
+ // Make temporary array to hold all inputs and then sort them
2063
+ // this method can be improved later
2064
+ const psbtInputs = [];
2065
+ psbtInputs.push({
2066
+ hash: dlcTxs.fundTx.txId.serialize(),
2067
+ index: dlcTxs.fundTxVout,
2068
+ sequence: 0,
2069
+ witnessUtxo: {
2070
+ script: paymentVariant.output,
2071
+ value: Number(this.getFundOutputValueSats(dlcTxs)),
2072
+ },
2073
+ witnessScript: paymentVariant.redeem.output,
2074
+ inputSerialId: fundingInputSerialId,
2075
+ derivationPath: null,
2076
+ });
2077
+ // add all dlc close inputs
2078
+ inputs.forEach((input, i) => {
2079
+ const paymentVariant = bitcoinjs_lib_1.payments.p2wpkh({ pubkey: pubkeys[i], network });
2080
+ psbtInputs.push({
2081
+ hash: input.txid,
2082
+ index: input.vout,
2083
+ sequence: 0,
2084
+ witnessUtxo: {
2085
+ script: paymentVariant.output,
2086
+ value: input.value,
2087
+ },
2088
+ inputSerialId: input.inputSerialId,
2089
+ derivationPath: input.derivationPath,
2090
+ });
2091
+ });
2092
+ // sort all inputs in ascending order by serial ID
2093
+ // The only reason we are doing this is for privacy. If the fundingInput is
2094
+ // always first, it is very obvious. Hence, a serialId is randomly generated
2095
+ // and the inputs are sorted by that instead.
2096
+ const sortedPsbtInputs = psbtInputs.sort((a, b) => Number(a.inputSerialId - b.inputSerialId));
2097
+ // Get index of fundingInput
2098
+ const fundingInputIndex = sortedPsbtInputs.findIndex((input) => input.inputSerialId === fundingInputSerialId);
2099
+ // add to psbt
2100
+ sortedPsbtInputs.forEach((input) => psbt.addInput(input));
2101
+ const fundingInputs = await Promise.all(inputs.map(async (input) => {
2102
+ return this.inputToFundingInput(input);
2103
+ }));
2104
+ const finalizer = new core_2.DualClosingTxFinalizer(fundingInputs, dlcOffer.payoutSpk, dlcAccept.payoutSpk, dlcOffer.feeRatePerVb);
2105
+ const closeInputAmount = BigInt(inputs.reduce((acc, val) => acc + val.value, 0));
2106
+ const offerPayoutValue = isOfferer
2107
+ ? closeInputAmount +
2108
+ initiatorPayoutSatoshis -
2109
+ finalizer.offerInitiatorFees
2110
+ : dlcOffer.contractInfo.totalCollateral - initiatorPayoutSatoshis;
2111
+ const acceptPayoutValue = isOfferer
2112
+ ? dlcOffer.contractInfo.totalCollateral - initiatorPayoutSatoshis
2113
+ : closeInputAmount +
2114
+ initiatorPayoutSatoshis -
2115
+ finalizer.offerInitiatorFees;
2116
+ const offerFirst = dlcOffer.payoutSerialId < dlcAccept.payoutSerialId;
2117
+ psbt.addOutput({
2118
+ value: Number(offerFirst ? offerPayoutValue : acceptPayoutValue),
2119
+ address: bitcoinjs_lib_1.address.fromOutputScript(offerFirst ? dlcOffer.payoutSpk : dlcAccept.payoutSpk, network),
2120
+ });
2121
+ psbt.addOutput({
2122
+ value: Number(offerFirst ? acceptPayoutValue : offerPayoutValue),
2123
+ address: bitcoinjs_lib_1.address.fromOutputScript(offerFirst ? dlcAccept.payoutSpk : dlcOffer.payoutSpk, network),
2124
+ });
2125
+ // Generate keypair to sign inputs
2126
+ const fundPrivateKeyPair = await this.GetFundKeyPair(dlcOffer, dlcAccept, isOfferer);
2127
+ // Sign dlc fundinginput
2128
+ psbt.signInput(fundingInputIndex, fundPrivateKeyPair);
2129
+ // Sign dlcclose inputs
2130
+ await Promise.all(sortedPsbtInputs.map(async (input, i) => {
2131
+ if (i === fundingInputIndex)
2132
+ return;
2133
+ // derive keypair
2134
+ if (!input.derivationPath) {
2135
+ throw new Error(`Missing derivation path for input ${i}`);
2136
+ }
2137
+ const keyPair = await this.getMethod('keyPair')(input.derivationPath);
2138
+ psbt.signInput(i, keyPair);
2139
+ }));
2140
+ // Validate signatures
2141
+ psbt.validateSignaturesOfAllInputs((pubkey, msghash, signature) => {
2142
+ return ecc.verify(msghash, pubkey, signature);
2143
+ });
2144
+ // Extract close signature from psbt and decode it to only extract r and s values
2145
+ const closeSignature = await bitcoinjs_lib_1.script.signature.decode(psbt.data.inputs[fundingInputIndex].partialSig[0].signature).signature;
2146
+ // Extract funding signatures from psbt
2147
+ const inputSigs = psbt.data.inputs
2148
+ .filter((input) => input !== fundingInputIndex)
2149
+ .map((input) => input.partialSig[0]);
2150
+ // create fundingSignatures
2151
+ const witnessElements = [];
2152
+ for (let i = 0; i < inputSigs.length; i++) {
2153
+ const sigWitness = new messaging_1.ScriptWitnessV0();
2154
+ sigWitness.witness = inputSigs[i].signature;
2155
+ const pubKeyWitness = new messaging_1.ScriptWitnessV0();
2156
+ pubKeyWitness.witness = inputSigs[i].pubkey;
2157
+ witnessElements.push([sigWitness, pubKeyWitness]);
2158
+ }
2159
+ const fundingSignatures = new messaging_1.FundingSignatures();
2160
+ fundingSignatures.witnessElements = witnessElements;
2161
+ // Create DlcClose
2162
+ const dlcClose = new messaging_1.DlcClose();
2163
+ dlcClose.contractId = dlcTxs.contractId;
2164
+ dlcClose.offerPayoutSatoshis = BigInt(psbt.txOutputs[offerFirst ? 0 : 1].value); // You give collateral back to users
2165
+ dlcClose.acceptPayoutSatoshis = BigInt(psbt.txOutputs[offerFirst ? 1 : 0].value); // give collateral back to users
2166
+ dlcClose.fundInputSerialId = fundingInputSerialId; // randomly generated serial id
2167
+ dlcClose.closeSignature = closeSignature;
2168
+ dlcClose.fundingSignatures = fundingSignatures;
2169
+ dlcClose.fundingInputs = fundingInputs;
2170
+ dlcClose.validate();
2171
+ return dlcClose;
2172
+ }
2173
+ /**
2174
+ * Generate multiple DlcClose messagetypes for closing DLC with Mutual Consent
2175
+ * @param _dlcOffer DlcOffer TLV (V0)
2176
+ * @param _dlcAccept DlcAccept TLV (V0)
2177
+ * @param _dlcTxs DlcTransactions TLV (V0)
2178
+ * @param initiatorPayouts Array of amounts initiator expects as payouts
2179
+ * @param isOfferer Whether offerer or not
2180
+ * @param _inputs Optionally specified closing inputs
2181
+ * @returns {Promise<DlcClose[]>}
2182
+ */
2183
+ async createBatchDlcClose(_dlcOffer, _dlcAccept, _dlcTxs, initiatorPayouts, isOfferer, _inputs) {
2184
+ const { dlcOffer, dlcAccept, dlcTxs } = (0, Utils_1.checkTypes)({
2185
+ _dlcOffer,
2186
+ _dlcAccept,
2187
+ _dlcTxs,
2188
+ });
2189
+ if (isOfferer === undefined)
2190
+ isOfferer = await this.isOfferer(dlcOffer, dlcAccept);
2191
+ if (_inputs && _inputs.length > 0)
2192
+ throw Error('funding inputs not supported on BatchDlcClose'); // TODO support multiple funding inputs
2193
+ const fundingInputSerialId = (0, Utils_1.generateSerialId)();
2194
+ const fundingInputs = []; // TODO: support multiple funding inputs
2195
+ const finalizer = new core_2.DualClosingTxFinalizer(fundingInputs, dlcOffer.payoutSpk, dlcAccept.payoutSpk, dlcOffer.feeRatePerVb);
2196
+ // Generate keypair to sign inputs
2197
+ const fundPrivateKeyPair = await this.GetFundKeyPair(dlcOffer, dlcAccept, isOfferer);
2198
+ const closeInputAmount = BigInt(0); // TODO support multiple funding inputs
2199
+ const privKey = Buffer.from(fundPrivateKeyPair.privateKey).toString('hex');
2200
+ const rawCloseTxs = await this.CreateCloseRawTxs(dlcOffer, dlcAccept, dlcTxs, closeInputAmount, isOfferer, [], fundingInputs, initiatorPayouts);
2201
+ const sigHashes = await this.CreateSignatureHashes(dlcOffer, dlcAccept, dlcTxs, rawCloseTxs);
2202
+ const signatures = await this.CalculateEcSignatureHashes(sigHashes, privKey);
2203
+ const dlcCloses = [];
2204
+ signatures.forEach((sig, i) => {
2205
+ const payout = initiatorPayouts[i];
2206
+ const payoutMinusOfferFees = finalizer.offerInitiatorFees > payout
2207
+ ? BigInt(0)
2208
+ : payout - finalizer.offerInitiatorFees;
2209
+ const collateralMinusPayout = payout > dlcOffer.contractInfo.totalCollateral
2210
+ ? BigInt(0)
2211
+ : dlcOffer.contractInfo.totalCollateral - payout;
2212
+ const offerPayoutValue = isOfferer
2213
+ ? closeInputAmount + payoutMinusOfferFees
2214
+ : collateralMinusPayout;
2215
+ const acceptPayoutValue = isOfferer
2216
+ ? collateralMinusPayout
2217
+ : closeInputAmount + payoutMinusOfferFees;
2218
+ const fundingSignatures = new messaging_1.FundingSignatures();
2219
+ const dlcClose = new messaging_1.DlcClose();
2220
+ dlcClose.contractId = dlcTxs.contractId;
2221
+ dlcClose.offerPayoutSatoshis = offerPayoutValue;
2222
+ dlcClose.acceptPayoutSatoshis = acceptPayoutValue;
2223
+ dlcClose.fundInputSerialId = fundingInputSerialId;
2224
+ dlcClose.closeSignature = Buffer.from(sig, 'hex');
2225
+ dlcClose.fundingSignatures = fundingSignatures;
2226
+ dlcClose.validate();
2227
+ dlcCloses.push(dlcClose);
2228
+ });
2229
+ return dlcCloses;
2230
+ }
2231
+ async verifyBatchDlcCloseUsingMetadata(dlcCloseMetadata, _dlcCloses, isOfferer) {
2232
+ const { dlcOffer, dlcAccept, dlcTxs } = dlcCloseMetadata.toDlcMessages();
2233
+ await this.verifyBatchDlcClose(dlcOffer, dlcAccept, dlcTxs, _dlcCloses, isOfferer);
2234
+ }
2235
+ /**
2236
+ * Verify multiple DlcClose messagetypes for closing DLC with Mutual Consent
2237
+ * @param _dlcOffer DlcOffer TLV (V0)
2238
+ * @param _dlcAccept DlcAccept TLV (V0)
2239
+ * @param _dlcTxs DlcTransactions TLV (V0)
2240
+ * @param _dlcCloses DlcClose[] TLV (V0)
2241
+ * @param isOfferer Whether offerer or not
2242
+ * @returns {Promise<void>}
2243
+ */
2244
+ async verifyBatchDlcClose(_dlcOffer, _dlcAccept, _dlcTxs, _dlcCloses, isOfferer) {
2245
+ const { dlcOffer, dlcAccept, dlcTxs } = (0, Utils_1.checkTypes)({
2246
+ _dlcOffer,
2247
+ _dlcAccept,
2248
+ _dlcTxs,
2249
+ });
2250
+ const dlcCloses = _dlcCloses.map((_dlcClose) => (0, Utils_1.checkTypes)({ _dlcClose }).dlcClose);
2251
+ if (isOfferer === undefined)
2252
+ isOfferer = await this.isOfferer(dlcOffer, dlcAccept);
2253
+ (0, assert_1.default)(dlcCloses.every((dlcClose) => dlcClose.fundingInputs.length === 0), 'funding inputs not supported on verify BatchDlcClose'); // TODO support multiple funding inputs
2254
+ const closeInputAmount = BigInt(0); // TODO support multiple funding inputs
2255
+ const rawCloseTxs = await this.CreateCloseRawTxs(dlcOffer, dlcAccept, dlcTxs, closeInputAmount, isOfferer, dlcCloses);
2256
+ const areSigsValid = await this.VerifySignatures(dlcOffer, dlcAccept, dlcTxs, dlcCloses, rawCloseTxs, isOfferer);
2257
+ (0, assert_1.default)(areSigsValid, 'Signatures invalid in Verify Batch DlcClose');
2258
+ }
2259
+ /**
2260
+ * Goal of finalize Dlc Close is for bob to
2261
+ * 1. take the dlcClose created by alice using createDlcClose,
2262
+ * 2. Build a psbt using Alice's dlcClose message
2263
+ * 3. Sign psbt with bob's privkey
2264
+ * 4. return a tx ready to be broadcast
2265
+ */
2266
+ /**
2267
+ * Finalize Dlc Close
2268
+ * @param _dlcOffer Dlc Offer Message
2269
+ * @param _dlcAccept Dlc Accept Message
2270
+ * @param _dlcClose Dlc Close Message
2271
+ * @param _dlcTxs Dlc Transactions Message
2272
+ * @returns {Promise<Tx>}
2273
+ */
2274
+ async finalizeDlcClose(_dlcOffer, _dlcAccept, _dlcClose, _dlcTxs) {
2275
+ const { dlcOffer, dlcAccept, dlcClose, dlcTxs } = (0, Utils_1.checkTypes)({
2276
+ _dlcOffer,
2277
+ _dlcAccept,
2278
+ _dlcClose,
2279
+ _dlcTxs,
2280
+ });
2281
+ dlcOffer.validate();
2282
+ dlcAccept.validate();
2283
+ dlcClose.validate();
2284
+ const network = await this.getConnectedNetwork();
2285
+ const psbt = new bitcoinjs_lib_1.Psbt({ network });
2286
+ const fundingPubKeys = Buffer.compare(dlcOffer.fundingPubkey, dlcAccept.fundingPubkey) === -1
2287
+ ? [dlcOffer.fundingPubkey, dlcAccept.fundingPubkey]
2288
+ : [dlcAccept.fundingPubkey, dlcOffer.fundingPubkey];
2289
+ const p2ms = bitcoinjs_lib_1.payments.p2ms({
2290
+ m: 2,
2291
+ pubkeys: fundingPubKeys,
2292
+ network,
2293
+ });
2294
+ const paymentVariant = bitcoinjs_lib_1.payments.p2wsh({
2295
+ redeem: p2ms,
2296
+ network,
2297
+ });
2298
+ // Make temporary array to hold all inputs and then sort them
2299
+ // this method can be improved later
2300
+ const psbtInputs = [];
2301
+ psbtInputs.push({
2302
+ hash: dlcTxs.fundTx.txId.serialize(),
2303
+ index: dlcTxs.fundTxVout,
2304
+ sequence: 0,
2305
+ witnessUtxo: {
2306
+ script: paymentVariant.output,
2307
+ value: Number(this.getFundOutputValueSats(dlcTxs)),
2308
+ },
2309
+ witnessScript: paymentVariant.redeem.output,
2310
+ inputSerialId: dlcClose.fundInputSerialId,
2311
+ });
2312
+ // add all dlc close inputs
2313
+ dlcClose.fundingInputs.forEach((input) => {
2314
+ psbtInputs.push({
2315
+ hash: input.prevTx.txId.serialize(),
2316
+ index: input.prevTxVout,
2317
+ sequence: 0,
2318
+ witnessUtxo: {
2319
+ script: input.prevTx.outputs[input.prevTxVout].scriptPubKey
2320
+ .serialize()
2321
+ .slice(1),
2322
+ value: Number(input.prevTx.outputs[input.prevTxVout].value.sats),
2323
+ },
2324
+ inputSerialId: input.inputSerialId,
2325
+ });
2326
+ });
2327
+ // sort all inputs in ascending order by serial ID
2328
+ // The only reason we are doing this is for privacy. If the fundingInput is
2329
+ // always first, it is very obvious. Hence, a serialId is randomly generated
2330
+ // and the inputs are sorted by that instead.
2331
+ const sortedPsbtInputs = psbtInputs.sort((a, b) => Number(a.inputSerialId - b.inputSerialId));
2332
+ // Get index of fundingInput
2333
+ const fundingInputIndex = sortedPsbtInputs.findIndex((input) => input.inputSerialId === dlcClose.fundInputSerialId);
2334
+ const offerFirst = dlcOffer.payoutSerialId < dlcAccept.payoutSerialId;
2335
+ psbt.addOutput({
2336
+ value: Number(offerFirst
2337
+ ? dlcClose.offerPayoutSatoshis
2338
+ : dlcClose.acceptPayoutSatoshis),
2339
+ address: bitcoinjs_lib_1.address.fromOutputScript(offerFirst ? dlcOffer.payoutSpk : dlcAccept.payoutSpk, network),
2340
+ });
2341
+ psbt.addOutput({
2342
+ value: Number(offerFirst
2343
+ ? dlcClose.acceptPayoutSatoshis
2344
+ : dlcClose.offerPayoutSatoshis),
2345
+ address: bitcoinjs_lib_1.address.fromOutputScript(offerFirst ? dlcAccept.payoutSpk : dlcOffer.payoutSpk, network),
2346
+ });
2347
+ // add to psbt
2348
+ sortedPsbtInputs.forEach((input) => psbt.addInput(input));
2349
+ const offerer = await this.isOfferer(dlcOffer, dlcAccept);
2350
+ // Generate keypair to sign inputs
2351
+ const fundPrivateKeyPair = await this.GetFundKeyPair(dlcOffer, dlcAccept, offerer);
2352
+ // Sign dlc fundinginput
2353
+ psbt.signInput(fundingInputIndex, fundPrivateKeyPair);
2354
+ const partialSig = [
2355
+ {
2356
+ pubkey: offerer ? dlcAccept.fundingPubkey : dlcOffer.fundingPubkey,
2357
+ signature: bitcoinjs_lib_1.script.signature.encode(dlcClose.closeSignature, 1), // encode using SIGHASH_ALL
2358
+ },
2359
+ ];
2360
+ psbt.updateInput(fundingInputIndex, { partialSig });
2361
+ for (let i = 0; i < psbt.data.inputs.length; ++i) {
2362
+ if (i === fundingInputIndex)
2363
+ continue;
2364
+ if (!psbt.data.inputs[i].partialSig)
2365
+ psbt.data.inputs[i].partialSig = [];
2366
+ const witnessI = dlcClose.fundingSignatures.witnessElements.findIndex((el) => Buffer.compare(bitcoin_1.Script.p2wpkhLock((0, crypto_1.hash160)(el[1].witness)).serialize().subarray(1), psbt.data.inputs[i].witnessUtxo.script) === 0);
2367
+ const partialSig = [
2368
+ {
2369
+ pubkey: dlcClose.fundingSignatures.witnessElements[witnessI][1].witness,
2370
+ signature: dlcClose.fundingSignatures.witnessElements[witnessI][0].witness,
2371
+ },
2372
+ ];
2373
+ psbt.updateInput(i, { partialSig });
2374
+ }
2375
+ psbt.validateSignaturesOfAllInputs((pubkey, msghash, signature) => {
2376
+ return ecc.verify(msghash, pubkey, signature);
2377
+ });
2378
+ psbt.finalizeAllInputs();
2379
+ return psbt.extractTransaction().toHex();
2380
+ }
2381
+ async fundingInputToInput(_input, findDerivationPath = true) {
2382
+ (0, assert_1.default)(_input.type === messaging_1.MessageType.FundingInput, 'FundingInput must be V0');
2383
+ const network = await this.getConnectedNetwork();
2384
+ const input = _input;
2385
+ const prevTx = input.prevTx;
2386
+ const prevTxOut = prevTx.outputs[input.prevTxVout];
2387
+ const scriptPubKey = prevTxOut.scriptPubKey.serialize().subarray(1);
2388
+ const _address = bitcoinjs_lib_1.address.fromOutputScript(scriptPubKey, network);
2389
+ let derivationPath;
2390
+ if (findDerivationPath) {
2391
+ const inputAddress = await this.client.wallet.findAddress([
2392
+ _address,
2393
+ ]);
2394
+ if (inputAddress) {
2395
+ derivationPath = inputAddress.derivationPath;
2396
+ }
2397
+ }
2398
+ // Check if this FundingInput has DLC input information to preserve
2399
+ const dlcInputMessage = input.dlcInput;
2400
+ let dlcInputInfo;
2401
+ if (dlcInputMessage) {
2402
+ dlcInputInfo = {
2403
+ localFundPubkey: dlcInputMessage.localFundPubkey.toString('hex'),
2404
+ remoteFundPubkey: dlcInputMessage.remoteFundPubkey.toString('hex'),
2405
+ contractId: dlcInputMessage.contractId.toString('hex'),
2406
+ };
2407
+ }
2408
+ return new types_1.Input(prevTx.txId.toString(), input.prevTxVout, _address, prevTxOut.value.bitcoin, Number(prevTxOut.value.sats), derivationPath, input.maxWitnessLen, input.redeemScript ? input.redeemScript.toString('hex') : '', input.inputSerialId, scriptPubKey.toString('hex'), undefined, // label
2409
+ undefined, // confirmations
2410
+ undefined, // spendable
2411
+ undefined, // solvable
2412
+ undefined, // safe
2413
+ dlcInputInfo);
2414
+ }
2415
+ async inputToFundingInput(input) {
2416
+ const fundingInput = new messaging_1.FundingInput();
2417
+ fundingInput.prevTxVout = input.vout;
2418
+ let txRaw = '';
2419
+ try {
2420
+ txRaw = await this.getMethod('getRawTransactionByHash')(input.txid);
2421
+ }
2422
+ catch {
2423
+ try {
2424
+ txRaw = (await this.getMethod('jsonrpc')('gettransaction', input.txid))
2425
+ .hex;
2426
+ }
2427
+ catch {
2428
+ throw Error(`Cannot find tx ${input.txid} in inputToFundingInput using getrawtransactionbyhash or gettransaction`);
2429
+ }
2430
+ }
2431
+ const tx = bitcoin_1.Tx.decode(bufio_1.StreamReader.fromHex(txRaw));
2432
+ fundingInput.prevTx = tx;
2433
+ fundingInput.sequence = bitcoin_1.Sequence.default();
2434
+ fundingInput.maxWitnessLen = input.maxWitnessLength
2435
+ ? input.maxWitnessLength
2436
+ : 108;
2437
+ fundingInput.redeemScript = input.redeemScript
2438
+ ? Buffer.from(input.redeemScript, 'hex')
2439
+ : Buffer.from('', 'hex');
2440
+ fundingInput.inputSerialId = input.inputSerialId
2441
+ ? input.inputSerialId
2442
+ : (0, Utils_1.generateSerialId)();
2443
+ // Preserve DLC input information if present
2444
+ if (input.isDlcInput()) {
2445
+ const dlcInputInfo = input.dlcInput;
2446
+ const dlcInput = new messaging_1.DlcInput();
2447
+ dlcInput.localFundPubkey = Buffer.from(dlcInputInfo.localFundPubkey, 'hex');
2448
+ dlcInput.remoteFundPubkey = Buffer.from(dlcInputInfo.remoteFundPubkey, 'hex');
2449
+ dlcInput.contractId = Buffer.alloc(32); // Placeholder contract ID
2450
+ fundingInput.dlcInput = dlcInput;
2451
+ }
2452
+ return fundingInput;
2453
+ }
2454
+ async getConnectedNetwork() {
2455
+ return this._network;
2456
+ }
2457
+ /**
2458
+ * Convert BitcoinNetwork to bitcoinjs-lib network format
2459
+ */
2460
+ getBitcoinJsNetwork() {
2461
+ const network = this._network;
2462
+ if (network.name === 'bitcoin') {
2463
+ return bitcoinjs_lib_1.networks.bitcoin;
2464
+ }
2465
+ else if (network.name === 'testnet') {
2466
+ return bitcoinjs_lib_1.networks.testnet;
2467
+ }
2468
+ else if (network.name === 'regtest') {
2469
+ return bitcoinjs_lib_1.networks.regtest;
2470
+ }
2471
+ else {
2472
+ // Default to mainnet if unknown
2473
+ return bitcoinjs_lib_1.networks.bitcoin;
2474
+ }
2475
+ }
2476
+ /**
2477
+ * Calculate the maximum collateral possible with given inputs
2478
+ * @param inputs Array of Input objects to use for funding
2479
+ * @param feeRatePerVb Fee rate in satoshis per virtual byte
2480
+ * @param contractCount Number of DLC contracts (default: 1)
2481
+ * @returns Maximum collateral amount in satoshis
2482
+ */
2483
+ async calculateMaxCollateral(inputs, feeRatePerVb, contractCount = 1) {
2484
+ if (inputs.length === 0) {
2485
+ return BigInt(0);
2486
+ }
2487
+ try {
2488
+ // Convert Input[] to FundingInput[]
2489
+ const fundingInputs = await Promise.all(inputs.map((input) => this.inputToFundingInput(input)));
2490
+ // Use node-dlc's calculateMaxCollateral function
2491
+ // For single-funded DLC, pass only offerer inputs and fee rate
2492
+ return core_1.BatchDlcTxBuilder.calculateMaxCollateral(fundingInputs, feeRatePerVb, contractCount);
2493
+ }
2494
+ catch (error) {
2495
+ // If calculation fails, return 0 to indicate insufficient funds
2496
+ console.warn('calculateMaxCollateral failed:', error);
2497
+ return BigInt(0);
2498
+ }
2499
+ }
2500
+ /**
2501
+ * Create a funding input with DLC input information for splicing
2502
+ * @param dlcInputInfo DLC input information
2503
+ * @param fundingTxHex Raw transaction hex of the funding transaction
2504
+ */
2505
+ async createDlcFundingInput(dlcInputInfo, fundingTxHex) {
2506
+ const fundingInput = new messaging_1.FundingInput();
2507
+ const tx = bitcoin_1.Tx.decode(bufio_1.StreamReader.fromHex(fundingTxHex));
2508
+ fundingInput.prevTx = tx;
2509
+ fundingInput.prevTxVout = dlcInputInfo.fundVout;
2510
+ fundingInput.sequence = bitcoin_1.Sequence.default();
2511
+ fundingInput.maxWitnessLen = dlcInputInfo.maxWitnessLength || 220;
2512
+ fundingInput.redeemScript = Buffer.from('', 'hex'); // Empty for P2WSH
2513
+ fundingInput.inputSerialId = BigInt(dlcInputInfo.inputSerialId || (0, Utils_1.generateSerialId)());
2514
+ // Create the DLC multisig script for address generation
2515
+ const localPubkey = Buffer.from(dlcInputInfo.localFundPubkey, 'hex');
2516
+ const remotePubkey = Buffer.from(dlcInputInfo.remoteFundPubkey, 'hex');
2517
+ // Use the same deterministic ordering as cfd-dlc-js: lexicographic by hex
2518
+ // This matches GetOrderedPubkeys() in cfddlc_transactions.cpp
2519
+ const orderedPubkeys = dlcInputInfo.localFundPubkey < dlcInputInfo.remoteFundPubkey
2520
+ ? [localPubkey, remotePubkey]
2521
+ : [remotePubkey, localPubkey];
2522
+ const network = await this.getConnectedNetwork();
2523
+ // Create 2-of-2 multisig payment using deterministic ordering
2524
+ const p2ms = bitcoinjs_lib_1.payments.p2ms({
2525
+ m: 2,
2526
+ pubkeys: orderedPubkeys,
2527
+ network,
2528
+ });
2529
+ const paymentVariant = bitcoinjs_lib_1.payments.p2wsh({
2530
+ redeem: p2ms,
2531
+ network,
2532
+ });
2533
+ const multisigAddress = paymentVariant.address;
2534
+ // Verify this matches the actual funding output address
2535
+ const actualFundingOutput = tx.outputs[dlcInputInfo.fundVout];
2536
+ const actualFundingAddress = bitcoinjs_lib_1.address.fromOutputScript(actualFundingOutput.scriptPubKey.serialize().subarray(1), network);
2537
+ if (actualFundingAddress !== multisigAddress) {
2538
+ throw new Error(`DLC funding address mismatch. ` +
2539
+ `Expected: ${actualFundingAddress}, ` +
2540
+ `Constructed: ${multisigAddress}`);
2541
+ }
2542
+ // Add toUtxo method that's expected by GetInputsForAmount
2543
+ fundingInput.toUtxo = () => {
2544
+ return new types_1.Utxo(dlcInputInfo.fundTxid, dlcInputInfo.fundVout, types_1.Amount.FromSatoshis(Number(dlcInputInfo.fundAmount)), multisigAddress, dlcInputInfo.maxWitnessLength || 220, undefined, // DLC inputs don't have derivation paths
2545
+ fundingInput.inputSerialId);
2546
+ };
2547
+ // Create proper DlcInput object for splicing detection and signing
2548
+ const dlcInput = new messaging_1.DlcInput();
2549
+ dlcInput.localFundPubkey = Buffer.from(dlcInputInfo.localFundPubkey, 'hex');
2550
+ dlcInput.remoteFundPubkey = Buffer.from(dlcInputInfo.remoteFundPubkey, 'hex');
2551
+ dlcInput.contractId = Buffer.from(dlcInputInfo.contractId, 'hex');
2552
+ fundingInput.dlcInput = dlcInput;
2553
+ return fundingInput;
2554
+ }
2555
+ }
2556
+ exports.default = BitcoinDdkProvider;
2557
+ //# sourceMappingURL=BitcoinDdkProvider.js.map