@buildonspark/spark-sdk 0.5.0 → 0.5.1
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.
- package/CHANGELOG.md +26 -0
- package/dist/bare/index.cjs +1608 -3635
- package/dist/bare/index.d.cts +27 -435
- package/dist/bare/index.d.ts +27 -435
- package/dist/bare/index.js +1608 -3634
- package/dist/{chunk-RU434ZAE.js → chunk-F3BFSHVR.js} +357 -391
- package/dist/{chunk-UYEB2VPG.js → chunk-IOIEBLMK.js} +7 -1
- package/dist/{chunk-EU3I7GFB.js → chunk-STB6WMU7.js} +1 -1
- package/dist/{chunk-JE3MXMPW.js → chunk-UTECVGQQ.js} +93 -202
- package/dist/{chunk-ZP6Z6DFX.js → chunk-YFVVYZCS.js} +37 -5
- package/dist/{client-D1dLzWu0.d.ts → client-C9kc4cog.d.cts} +9 -3
- package/dist/{client-CVn0R_eM.d.cts → client-eyjf4knu.d.ts} +9 -3
- package/dist/graphql/objects/index.cjs +7 -1
- package/dist/graphql/objects/index.d.cts +3 -3
- package/dist/graphql/objects/index.d.ts +3 -3
- package/dist/graphql/objects/index.js +1 -1
- package/dist/index.browser.d.ts +27 -435
- package/dist/index.browser.js +1613 -3639
- package/dist/index.node.cjs +1613 -3640
- package/dist/index.node.d.cts +7 -8
- package/dist/index.node.d.ts +7 -8
- package/dist/index.node.js +5 -7
- package/dist/native/index.react-native.cjs +1613 -3640
- package/dist/native/index.react-native.d.cts +27 -435
- package/dist/native/index.react-native.d.ts +27 -435
- package/dist/native/index.react-native.js +1613 -3639
- package/dist/proto/spark.cjs +93 -202
- package/dist/proto/spark.d.cts +1 -1
- package/dist/proto/spark.d.ts +1 -1
- package/dist/proto/spark.js +1 -1
- package/dist/proto/spark_token.cjs +36 -4
- package/dist/proto/spark_token.d.cts +4 -1
- package/dist/proto/spark_token.d.ts +4 -1
- package/dist/proto/spark_token.js +2 -2
- package/dist/{spark-2Fxnvl8K.d.cts → spark-d6w3BLGZ.d.cts} +10 -328
- package/dist/{spark-2Fxnvl8K.d.ts → spark-d6w3BLGZ.d.ts} +10 -328
- package/dist/{spark-wallet.node-DlhZiDgY.d.ts → spark-wallet.node-MReThHBY.d.ts} +6 -7
- package/dist/{spark-wallet.node-xKJXzAEd.d.cts → spark-wallet.node-eR0svGws.d.cts} +6 -7
- package/dist/tests/test-utils.cjs +409 -2429
- package/dist/tests/test-utils.d.cts +3 -3
- package/dist/tests/test-utils.d.ts +3 -3
- package/dist/tests/test-utils.js +5 -5
- package/dist/types/index.cjs +100 -203
- package/dist/types/index.d.cts +2 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +3 -3
- package/package.json +3 -3
- package/src/graphql/client.ts +36 -1
- package/src/graphql/objects/LightningSendRequestStatus.ts +25 -13
- package/src/proto/common.ts +1 -1
- package/src/proto/google/protobuf/descriptor.ts +1 -1
- package/src/proto/google/protobuf/duration.ts +1 -1
- package/src/proto/google/protobuf/empty.ts +1 -1
- package/src/proto/google/protobuf/timestamp.ts +1 -1
- package/src/proto/mock.ts +1 -1
- package/src/proto/spark.ts +113 -446
- package/src/proto/spark_authn.ts +1 -1
- package/src/proto/spark_token.ts +41 -2
- package/src/proto/validate/validate.ts +1 -1
- package/src/services/connection/connection.ts +23 -60
- package/src/services/coop-exit.ts +3 -5
- package/src/services/deposit.ts +1 -1
- package/src/services/lightning.ts +1 -1
- package/src/services/signing.ts +5 -6
- package/src/services/transfer.ts +250 -240
- package/src/services/wallet-config.ts +22 -5
- package/src/spark-wallet/proto-descriptors.ts +1 -1
- package/src/spark-wallet/proto-reflection.ts +0 -2
- package/src/spark-wallet/spark-wallet.ts +2 -2
- package/src/spark_descriptors.pb +0 -0
- package/src/tests/bufbuild-reflection.test.ts +2 -3
- package/src/tests/integration/coop-exit.test.ts +6 -1
- package/src/tests/integration/htlc.test.ts +5 -0
- package/src/tests/integration/lightning.test.ts +24 -4
- package/src/tests/integration/time-sync.test.ts +18 -0
- package/src/tests/integration/transfer.test.ts +42 -7
- package/src/tests/ssp-client-retry.test.ts +161 -0
- package/src/tests/token-hashing.test.ts +92 -0
- package/src/utils/token-hashing.ts +4 -51
- package/src/utils/transaction.ts +1 -2
- package/src/utils/unilateral-exit.ts +139 -142
|
@@ -7,7 +7,11 @@ import * as btc from "@scure/btc-signer";
|
|
|
7
7
|
import * as psbt from "@scure/btc-signer/psbt";
|
|
8
8
|
import type { SparkServiceClient } from "../proto/spark.js";
|
|
9
9
|
import { TreeNode } from "../proto/spark.js";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
getTxFromRawTxHex,
|
|
12
|
+
getTxId,
|
|
13
|
+
getTxEstimatedVbytesSizeByNumberOfInputsOutputs,
|
|
14
|
+
} from "../utils/bitcoin.js";
|
|
11
15
|
import { isTxBroadcast } from "../utils/mempool.js";
|
|
12
16
|
|
|
13
17
|
// Types
|
|
@@ -66,132 +70,29 @@ export function isEphemeralAnchorOutput(
|
|
|
66
70
|
): boolean {
|
|
67
71
|
return Boolean(
|
|
68
72
|
amount === 0n &&
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
73
|
+
script &&
|
|
74
|
+
// Pattern 1: Bare OP_TRUE (single byte 0x51)
|
|
75
|
+
((script.length === 1 && script[0] === 0x51) ||
|
|
76
|
+
// Pattern 2: Push OP_TRUE (two bytes 0x01 0x51) - MALFORMED but we detect it
|
|
77
|
+
(script.length === 2 && script[0] === 0x01 && script[1] === 0x51) ||
|
|
78
|
+
// Pattern 3: Bitcoin v29 ephemeral anchor script (7 bytes: 015152014e0173)
|
|
79
|
+
(script.length === 7 &&
|
|
80
|
+
script[0] === 0x01 &&
|
|
81
|
+
script[1] === 0x51 &&
|
|
82
|
+
script[2] === 0x52 &&
|
|
83
|
+
script[3] === 0x01 &&
|
|
84
|
+
script[4] === 0x4e &&
|
|
85
|
+
script[5] === 0x01 &&
|
|
86
|
+
script[6] === 0x73) ||
|
|
87
|
+
// Pattern 4: Bitcoin ephemeral anchor OP_1 + push 2 bytes (4 bytes: 51024e73)
|
|
88
|
+
(script.length === 4 &&
|
|
89
|
+
script[0] === 0x51 &&
|
|
90
|
+
script[1] === 0x02 &&
|
|
91
|
+
script[2] === 0x4e &&
|
|
92
|
+
script[3] === 0x73)),
|
|
89
93
|
);
|
|
90
94
|
}
|
|
91
95
|
|
|
92
|
-
// Main function to generate unilateral exit tx chains for broadcasting non-CPFP transactions
|
|
93
|
-
export async function constructUnilateralExitTxs(
|
|
94
|
-
nodeHexStrings: string[],
|
|
95
|
-
sparkClient?: SparkServiceClient,
|
|
96
|
-
network?: any, // Network enum from the proto
|
|
97
|
-
): Promise<TxChain[]> {
|
|
98
|
-
const result: TxChain[] = [];
|
|
99
|
-
|
|
100
|
-
// Convert hex strings to TreeNode objects
|
|
101
|
-
const nodes = nodeHexStrings.map((hex) => TreeNode.decode(hexToBytes(hex)));
|
|
102
|
-
|
|
103
|
-
// Create a map of nodes by ID for easy lookup
|
|
104
|
-
const nodeMap = new Map<string, TreeNode>();
|
|
105
|
-
for (const node of nodes) {
|
|
106
|
-
nodeMap.set(node.id, node);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// For each provided node, build its complete chain to the root
|
|
110
|
-
for (const node of nodes) {
|
|
111
|
-
const transactions: string[] = [];
|
|
112
|
-
|
|
113
|
-
// Build the chain from this node to the root
|
|
114
|
-
const chain: TreeNode[] = [];
|
|
115
|
-
let currentNode = node;
|
|
116
|
-
|
|
117
|
-
// Walk up the chain to find the root, querying for each parent
|
|
118
|
-
while (currentNode) {
|
|
119
|
-
chain.unshift(currentNode);
|
|
120
|
-
|
|
121
|
-
if (currentNode.parentNodeId) {
|
|
122
|
-
// Check if we already have the parent in our map (from previous queries)
|
|
123
|
-
let parentNode = nodeMap.get(currentNode.parentNodeId);
|
|
124
|
-
|
|
125
|
-
if (!parentNode && sparkClient) {
|
|
126
|
-
// Query for the parent node
|
|
127
|
-
try {
|
|
128
|
-
const response = await sparkClient.query_nodes({
|
|
129
|
-
source: {
|
|
130
|
-
$case: "nodeIds",
|
|
131
|
-
nodeIds: {
|
|
132
|
-
nodeIds: [currentNode.parentNodeId],
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
includeParents: true,
|
|
136
|
-
network: network || 0, // Default to mainnet if not provided
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
parentNode = response.nodes[currentNode.parentNodeId];
|
|
140
|
-
|
|
141
|
-
if (parentNode) {
|
|
142
|
-
// Add to our map for future use
|
|
143
|
-
nodeMap.set(currentNode.parentNodeId, parentNode);
|
|
144
|
-
}
|
|
145
|
-
} catch (error) {
|
|
146
|
-
console.warn(
|
|
147
|
-
`Failed to query parent node ${currentNode.parentNodeId}: ${error}`,
|
|
148
|
-
);
|
|
149
|
-
break;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (parentNode) {
|
|
154
|
-
currentNode = parentNode;
|
|
155
|
-
} else {
|
|
156
|
-
if (!sparkClient) {
|
|
157
|
-
console.warn(
|
|
158
|
-
`Parent node ${currentNode.parentNodeId} not found. Provide a sparkClient to fetch missing parents.`,
|
|
159
|
-
);
|
|
160
|
-
} else {
|
|
161
|
-
console.warn(
|
|
162
|
-
`Parent node ${currentNode.parentNodeId} not found in database. Chain may be incomplete.`,
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
167
|
-
} else {
|
|
168
|
-
// We've reached the root
|
|
169
|
-
break;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Now walk down the chain from root to leaf to build transactions
|
|
174
|
-
for (const chainNode of chain) {
|
|
175
|
-
// Add node tx
|
|
176
|
-
const nodeTx = bytesToHex(chainNode.nodeTx);
|
|
177
|
-
transactions.push(nodeTx);
|
|
178
|
-
|
|
179
|
-
// If this is the original node we started with, also add its refund tx
|
|
180
|
-
if (chainNode.id === node.id) {
|
|
181
|
-
const refundTx = bytesToHex(chainNode.refundTx);
|
|
182
|
-
transactions.push(refundTx);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
result.push({
|
|
187
|
-
leafId: node.id,
|
|
188
|
-
transactions,
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return result;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
96
|
// Main function to generate unilateral exit tx chains for broadcasting CPFP transactions
|
|
196
97
|
export async function constructUnilateralExitFeeBumpPackages(
|
|
197
98
|
nodeHexStrings: string[],
|
|
@@ -279,6 +180,10 @@ export async function constructUnilateralExitFeeBumpPackages(
|
|
|
279
180
|
|
|
280
181
|
// Walk up the chain to find the root, querying for each parent
|
|
281
182
|
while (currentNode) {
|
|
183
|
+
// Only proceed with nodes that are available for exit
|
|
184
|
+
if (currentNode.status !== "AVAILABLE") {
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
282
187
|
chain.unshift(currentNode);
|
|
283
188
|
|
|
284
189
|
if (currentNode.parentNodeId) {
|
|
@@ -379,7 +284,7 @@ export async function constructUnilateralExitFeeBumpPackages(
|
|
|
379
284
|
const {
|
|
380
285
|
feeBumpPsbt: nodeFeeBumpPsbt,
|
|
381
286
|
usedUtxos,
|
|
382
|
-
|
|
287
|
+
parentTx,
|
|
383
288
|
} = constructFeeBumpTx(nodeTxHex, availableUtxos, feeRate, undefined);
|
|
384
289
|
|
|
385
290
|
const feeBumpTx = btc.Transaction.fromPSBT(hexToBytes(nodeFeeBumpPsbt));
|
|
@@ -414,7 +319,7 @@ export async function constructUnilateralExitFeeBumpPackages(
|
|
|
414
319
|
});
|
|
415
320
|
|
|
416
321
|
// Use the corrected parent transaction if it was fixed
|
|
417
|
-
const finalNodeTx =
|
|
322
|
+
const finalNodeTx = parentTx || nodeTxHex;
|
|
418
323
|
txPackages.push({ tx: finalNodeTx, feeBumpPsbt: nodeFeeBumpPsbt });
|
|
419
324
|
|
|
420
325
|
// If this is the original node we started with, also add its refund tx
|
|
@@ -509,13 +414,89 @@ export function hash160(data: Uint8Array): Uint8Array {
|
|
|
509
414
|
return ripemd160(sha256Hash);
|
|
510
415
|
}
|
|
511
416
|
|
|
417
|
+
// Helper function to calculate transaction vSize from hex
|
|
418
|
+
function calculateTransactionVSize(txHex: string): number {
|
|
419
|
+
try {
|
|
420
|
+
const tx = getTxFromRawTxHex(txHex);
|
|
421
|
+
const numInputs = tx.inputsLength;
|
|
422
|
+
const numOutputs = tx.outputsLength;
|
|
423
|
+
|
|
424
|
+
return getTxEstimatedVbytesSizeByNumberOfInputsOutputs(
|
|
425
|
+
numInputs,
|
|
426
|
+
numOutputs,
|
|
427
|
+
);
|
|
428
|
+
} catch (error) {
|
|
429
|
+
console.warn(
|
|
430
|
+
`Failed to calculate transaction vSize: ${error}, falling back to default estimate`,
|
|
431
|
+
);
|
|
432
|
+
// Fall back to default for typical transactions
|
|
433
|
+
return 191;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Helper function to select optimal UTXOs for fee payment
|
|
438
|
+
function selectUtxosForFee(
|
|
439
|
+
utxos: Utxo[],
|
|
440
|
+
parentTxSize: number,
|
|
441
|
+
feeRate?: FeeRate,
|
|
442
|
+
): Utxo[] {
|
|
443
|
+
if (utxos.length === 0) {
|
|
444
|
+
throw new Error("No UTXOs available for selection");
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Sort UTXOs by value in descending order (largest first)
|
|
448
|
+
const sortedUtxos = [...utxos].sort((a, b) => {
|
|
449
|
+
if (a.value > b.value) return -1;
|
|
450
|
+
if (a.value < b.value) return 1;
|
|
451
|
+
return 0;
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
const selectedUtxos: Utxo[] = [];
|
|
455
|
+
let totalValue = 0n;
|
|
456
|
+
|
|
457
|
+
// If no fee rate provided, use all available UTXOs (fallback behavior)
|
|
458
|
+
if (!feeRate?.satPerVbyte) {
|
|
459
|
+
return sortedUtxos;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Try to find the minimum number of UTXOs needed to cover the fee
|
|
463
|
+
for (let i = 0; i < sortedUtxos.length; i++) {
|
|
464
|
+
const utxo = sortedUtxos[i];
|
|
465
|
+
if (!utxo) {
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
selectedUtxos.push(utxo);
|
|
469
|
+
totalValue += utxo.value;
|
|
470
|
+
|
|
471
|
+
// Calculate child transaction size with current number of selected UTXOs
|
|
472
|
+
const childTxSize = getTxEstimatedVbytesSizeByNumberOfInputsOutputs(
|
|
473
|
+
selectedUtxos.length + 1, // selected UTXOs + ephemeral anchor
|
|
474
|
+
1, // single change output
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
// Calculate total fee needed for CPFP package
|
|
478
|
+
const totalVbytes = parentTxSize + childTxSize;
|
|
479
|
+
const requiredFee = BigInt(Math.ceil(totalVbytes * feeRate.satPerVbyte));
|
|
480
|
+
|
|
481
|
+
// Minimum change amount (dust threshold)
|
|
482
|
+
const minChange = 546n;
|
|
483
|
+
|
|
484
|
+
// Check if we have enough to cover fee + minimum change
|
|
485
|
+
if (totalValue >= requiredFee + minChange) {
|
|
486
|
+
return selectedUtxos;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return sortedUtxos;
|
|
491
|
+
}
|
|
492
|
+
|
|
512
493
|
// Helper function to construct a fee bump tx for a given tx using available UTXOs
|
|
513
494
|
export function constructFeeBumpTx(
|
|
514
495
|
txHex: string,
|
|
515
496
|
utxos: Utxo[],
|
|
516
497
|
feeRate: FeeRate,
|
|
517
498
|
previousFeeBumpTx?: string, // Optional previous fee bump tx to chain from
|
|
518
|
-
): { feeBumpPsbt: string; usedUtxos: Utxo[];
|
|
499
|
+
): { feeBumpPsbt: string; usedUtxos: Utxo[]; parentTx?: string } {
|
|
519
500
|
// Validate inputs first
|
|
520
501
|
if (!txHex || txHex.length === 0) {
|
|
521
502
|
throw new Error("Transaction hex string is empty or undefined");
|
|
@@ -526,18 +507,17 @@ export function constructFeeBumpTx(
|
|
|
526
507
|
}
|
|
527
508
|
|
|
528
509
|
// Check for and fix malformed ephemeral anchor BEFORE parsing
|
|
529
|
-
let correctedTxHex = txHex;
|
|
530
510
|
|
|
531
511
|
// Decode the parent tx using the utility function with error handling
|
|
532
512
|
let parentTx: any;
|
|
533
513
|
try {
|
|
534
|
-
parentTx = getTxFromRawTxHex(
|
|
514
|
+
parentTx = getTxFromRawTxHex(txHex);
|
|
535
515
|
if (!parentTx) {
|
|
536
516
|
throw new Error("getTxFromRawTxHex returned null/undefined");
|
|
537
517
|
}
|
|
538
518
|
} catch (parseError) {
|
|
539
519
|
throw new Error(
|
|
540
|
-
`Failed to parse parent transaction hex: ${parseError}. Transaction hex: ${
|
|
520
|
+
`Failed to parse parent transaction hex: ${parseError}. Transaction hex: ${txHex}`,
|
|
541
521
|
);
|
|
542
522
|
}
|
|
543
523
|
|
|
@@ -591,12 +571,16 @@ export function constructFeeBumpTx(
|
|
|
591
571
|
throw new Error("No ephemeral anchor output found");
|
|
592
572
|
if (!ephemeralAnchorOutput.script)
|
|
593
573
|
throw new Error("No script found in ephemeral anchor output");
|
|
594
|
-
|
|
595
|
-
// Use all available UTXOs for funding
|
|
596
574
|
if (utxos.length === 0) {
|
|
597
575
|
throw new Error("No UTXOs available for fee bump");
|
|
598
576
|
}
|
|
599
577
|
|
|
578
|
+
// Calculate parent transaction size for CPFP fee calculation
|
|
579
|
+
const parentTxSize = calculateTransactionVSize(txHex);
|
|
580
|
+
|
|
581
|
+
// Select optimal UTXOs based on fee requirements
|
|
582
|
+
const selectedUtxos = selectUtxosForFee(utxos, parentTxSize, feeRate);
|
|
583
|
+
|
|
600
584
|
// Create a new transaction using the builder pattern
|
|
601
585
|
const builder = new btc.Transaction({
|
|
602
586
|
version: 3,
|
|
@@ -604,15 +588,15 @@ export function constructFeeBumpTx(
|
|
|
604
588
|
allowLegacyWitnessUtxo: true,
|
|
605
589
|
}); // ✅ set v3 in constructor
|
|
606
590
|
|
|
607
|
-
// Track total value and process each funding UTXO
|
|
591
|
+
// Track total value and process each selected funding UTXO
|
|
608
592
|
let totalValue = 0n;
|
|
609
593
|
const processedUtxos: Array<{
|
|
610
594
|
utxo: Utxo;
|
|
611
595
|
p2wpkhScript: Uint8Array;
|
|
612
596
|
}> = [];
|
|
613
597
|
|
|
614
|
-
for (let i = 0; i <
|
|
615
|
-
const fundingUtxo =
|
|
598
|
+
for (let i = 0; i < selectedUtxos.length; i++) {
|
|
599
|
+
const fundingUtxo = selectedUtxos[i];
|
|
616
600
|
if (!fundingUtxo) {
|
|
617
601
|
throw new Error(`UTXO at index ${i} is undefined`);
|
|
618
602
|
}
|
|
@@ -648,20 +632,33 @@ export function constructFeeBumpTx(
|
|
|
648
632
|
});
|
|
649
633
|
}
|
|
650
634
|
|
|
651
|
-
// Add ephemeral anchor output as the last input - use direct script spend
|
|
635
|
+
// Add ephemeral anchor output as the last input - use direct script spend
|
|
652
636
|
builder.addInput({
|
|
653
637
|
txid: parentTxIdFromLib,
|
|
654
638
|
index: ephemeralAnchorIndex,
|
|
655
639
|
sequence: 0xffffffff,
|
|
656
640
|
witnessUtxo: {
|
|
657
|
-
script: ephemeralAnchorOutput.script,
|
|
641
|
+
script: ephemeralAnchorOutput.script,
|
|
658
642
|
amount: 0n,
|
|
659
643
|
},
|
|
660
644
|
});
|
|
661
645
|
|
|
662
|
-
//
|
|
663
|
-
|
|
664
|
-
|
|
646
|
+
// Calculate child transaction size based on number of inputs and outputs
|
|
647
|
+
const childTxSize = getTxEstimatedVbytesSizeByNumberOfInputsOutputs(
|
|
648
|
+
selectedUtxos.length + 1, // funding UTXOs + ephemeral anchor
|
|
649
|
+
1, // single change output
|
|
650
|
+
);
|
|
651
|
+
|
|
652
|
+
const totalVbytes = parentTxSize + childTxSize;
|
|
653
|
+
|
|
654
|
+
// If no fee rate provided, fall back to fixed 1500 satoshis
|
|
655
|
+
let fee: bigint;
|
|
656
|
+
if (feeRate?.satPerVbyte) {
|
|
657
|
+
// Calculate total fee needed for the entire package at target rate
|
|
658
|
+
fee = BigInt(Math.ceil(totalVbytes * feeRate.satPerVbyte));
|
|
659
|
+
} else {
|
|
660
|
+
fee = 1500n;
|
|
661
|
+
}
|
|
665
662
|
|
|
666
663
|
// Minimum change amount (546 satoshis for a standard output)
|
|
667
664
|
const remainingValue = totalValue - fee;
|
|
@@ -716,7 +713,7 @@ export function constructFeeBumpTx(
|
|
|
716
713
|
// Return both the fee bump transaction hex and the UTXOs used
|
|
717
714
|
return {
|
|
718
715
|
feeBumpPsbt: psbtHex,
|
|
719
|
-
usedUtxos:
|
|
720
|
-
|
|
716
|
+
usedUtxos: selectedUtxos,
|
|
717
|
+
parentTx: txHex !== txHex ? txHex : undefined,
|
|
721
718
|
};
|
|
722
719
|
}
|