@buildonspark/spark-sdk 0.1.39 → 0.1.41

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.
Files changed (125) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -1
  3. package/dist/{RequestLightningSendInput-39_zGri6.d.cts → RequestLightningSendInput-DXcLoiCe.d.cts} +10 -2
  4. package/dist/{RequestLightningSendInput-B4JdzclX.d.ts → RequestLightningSendInput-mXUWn_cp.d.ts} +10 -2
  5. package/dist/address/index.cjs +138 -6
  6. package/dist/address/index.d.cts +18 -6
  7. package/dist/address/index.d.ts +18 -6
  8. package/dist/address/index.js +5 -2
  9. package/dist/{chunk-FWQPAPXK.js → chunk-2ZXXLPG2.js} +1 -1
  10. package/dist/{chunk-S7KD6DDL.js → chunk-6YVPOQ2A.js} +41 -20
  11. package/dist/{chunk-ZUVYYR5T.js → chunk-7EFSUADA.js} +1 -0
  12. package/dist/{chunk-NS4UZRQ7.js → chunk-ABZA6R5S.js} +1 -1
  13. package/dist/{chunk-57XLH3ZR.js → chunk-ATEHMLKP.js} +23 -23
  14. package/dist/{chunk-VJTDG4BQ.js → chunk-HK6LPV6Z.js} +10 -1
  15. package/dist/{chunk-W3EC5XSA.js → chunk-J5W5Q2ZP.js} +337 -72
  16. package/dist/{chunk-TKYOYOYJ.js → chunk-KKSU7OZO.js} +653 -76
  17. package/dist/chunk-L3EHBOUX.js +0 -0
  18. package/dist/{chunk-C5LTJBI7.js → chunk-M6A4KFIG.js} +125 -226
  19. package/dist/{chunk-A74XSEW3.js → chunk-MIVX3GHD.js} +1 -1
  20. package/dist/{chunk-RGWBSZIO.js → chunk-ROKY5KS4.js} +23 -3
  21. package/dist/{chunk-LIP2K6KR.js → chunk-TM4TOEOX.js} +26 -8
  22. package/dist/{chunk-RAPBVYJY.js → chunk-UKT6OFLO.js} +125 -35
  23. package/dist/chunk-VA7MV4MZ.js +1073 -0
  24. package/dist/chunk-YEZDPUFY.js +840 -0
  25. package/dist/{chunk-DI7QXUQJ.js → chunk-ZXDE2XMU.js} +8 -5
  26. package/dist/graphql/objects/index.cjs +6 -3
  27. package/dist/graphql/objects/index.d.cts +6 -5
  28. package/dist/graphql/objects/index.d.ts +6 -5
  29. package/dist/graphql/objects/index.js +1 -1
  30. package/dist/{index-DEo_hdN3.d.cts → index-CFh4uWzi.d.cts} +60 -6
  31. package/dist/{index-BVY0yH_H.d.ts → index-OSDtPMmC.d.ts} +60 -6
  32. package/dist/index.cjs +3316 -954
  33. package/dist/index.d.cts +9 -8
  34. package/dist/index.d.ts +9 -8
  35. package/dist/index.js +48 -26
  36. package/dist/index.node.cjs +3316 -954
  37. package/dist/index.node.d.cts +9 -8
  38. package/dist/index.node.d.ts +9 -8
  39. package/dist/index.node.js +48 -26
  40. package/dist/native/index.cjs +3323 -961
  41. package/dist/native/index.d.cts +542 -260
  42. package/dist/native/index.d.ts +542 -260
  43. package/dist/native/index.js +3192 -838
  44. package/dist/{network-DobHpaV6.d.ts → network-BF2GYPye.d.ts} +9 -2
  45. package/dist/{network-GFGEHkS4.d.cts → network-BiwBmoOg.d.cts} +9 -2
  46. package/dist/proto/lrc20.d.cts +1 -1
  47. package/dist/proto/lrc20.d.ts +1 -1
  48. package/dist/proto/lrc20.js +2 -2
  49. package/dist/proto/spark.cjs +125 -226
  50. package/dist/proto/spark.d.cts +1 -1
  51. package/dist/proto/spark.d.ts +1 -1
  52. package/dist/proto/spark.js +3 -5
  53. package/dist/proto/spark_token.cjs +1364 -0
  54. package/dist/proto/spark_token.d.cts +209 -0
  55. package/dist/proto/spark_token.d.ts +209 -0
  56. package/dist/proto/spark_token.js +32 -0
  57. package/dist/{sdk-types-BuVMn2rX.d.cts → sdk-types-CfhdFnsA.d.cts} +1 -1
  58. package/dist/{sdk-types-BeI6DM_M.d.ts → sdk-types-MnQrHolg.d.ts} +1 -1
  59. package/dist/services/config.cjs +64 -40
  60. package/dist/services/config.d.cts +6 -5
  61. package/dist/services/config.d.ts +6 -5
  62. package/dist/services/config.js +7 -7
  63. package/dist/services/connection.cjs +1108 -306
  64. package/dist/services/connection.d.cts +10 -5
  65. package/dist/services/connection.d.ts +10 -5
  66. package/dist/services/connection.js +3 -2
  67. package/dist/services/index.cjs +1702 -488
  68. package/dist/services/index.d.cts +6 -5
  69. package/dist/services/index.d.ts +6 -5
  70. package/dist/services/index.js +16 -14
  71. package/dist/services/lrc-connection.d.cts +5 -5
  72. package/dist/services/lrc-connection.d.ts +5 -5
  73. package/dist/services/lrc-connection.js +3 -3
  74. package/dist/services/token-transactions.cjs +637 -247
  75. package/dist/services/token-transactions.d.cts +19 -8
  76. package/dist/services/token-transactions.d.ts +19 -8
  77. package/dist/services/token-transactions.js +5 -4
  78. package/dist/services/wallet-config.cjs +1 -0
  79. package/dist/services/wallet-config.d.cts +6 -5
  80. package/dist/services/wallet-config.d.ts +6 -5
  81. package/dist/services/wallet-config.js +1 -1
  82. package/dist/signer/signer.cjs +122 -35
  83. package/dist/signer/signer.d.cts +4 -3
  84. package/dist/signer/signer.d.ts +4 -3
  85. package/dist/signer/signer.js +8 -4
  86. package/dist/{signer-C1t40Wus.d.cts → signer-BhLS7SYR.d.cts} +35 -14
  87. package/dist/{signer-DFGw9RRp.d.ts → signer-CylxIujU.d.ts} +35 -14
  88. package/dist/{spark-DXYE9gMM.d.ts → spark-DjR1b3TC.d.cts} +13 -21
  89. package/dist/{spark-DXYE9gMM.d.cts → spark-DjR1b3TC.d.ts} +13 -21
  90. package/dist/types/index.cjs +130 -227
  91. package/dist/types/index.d.cts +6 -5
  92. package/dist/types/index.d.ts +6 -5
  93. package/dist/types/index.js +3 -3
  94. package/dist/utils/index.cjs +1169 -3
  95. package/dist/utils/index.d.cts +66 -6
  96. package/dist/utils/index.d.ts +66 -6
  97. package/dist/utils/index.js +35 -14
  98. package/package.json +6 -2
  99. package/src/address/address.ts +41 -6
  100. package/src/graphql/client.ts +15 -0
  101. package/src/graphql/objects/Transfer.ts +7 -0
  102. package/src/graphql/queries/Transfer.ts +10 -0
  103. package/src/proto/spark.ts +215 -337
  104. package/src/proto/spark_token.ts +1407 -0
  105. package/src/services/config.ts +4 -0
  106. package/src/services/connection.ts +37 -1
  107. package/src/services/deposit.ts +23 -5
  108. package/src/services/token-transactions.ts +426 -75
  109. package/src/services/transfer.ts +182 -11
  110. package/src/services/tree-creation.ts +29 -14
  111. package/src/services/wallet-config.ts +2 -0
  112. package/src/signer/signer.ts +190 -48
  113. package/src/spark-wallet/spark-wallet.ts +510 -6
  114. package/src/tests/integration/transfer.test.ts +186 -214
  115. package/src/tests/integration/tree-creation.test.ts +5 -1
  116. package/src/tests/signer.test.ts +34 -0
  117. package/src/tests/transaction.test.ts +12 -0
  118. package/src/tests/xchain-address.test.ts +28 -0
  119. package/src/utils/index.ts +2 -0
  120. package/src/utils/mempool.ts +26 -1
  121. package/src/utils/network.ts +15 -0
  122. package/src/utils/transaction.ts +51 -3
  123. package/src/utils/unilateral-exit.ts +729 -0
  124. package/src/utils/xchain-address.ts +36 -0
  125. package/dist/chunk-E5SL7XTO.js +0 -301
@@ -0,0 +1,840 @@
1
+ import {
2
+ getNetwork,
3
+ getNetworkFromAddress
4
+ } from "./chunk-HK6LPV6Z.js";
5
+ import {
6
+ ELECTRS_CREDENTIALS,
7
+ getElectrsUrl
8
+ } from "./chunk-7EFSUADA.js";
9
+ import {
10
+ BitcoinNetwork_default
11
+ } from "./chunk-HMLOC6TE.js";
12
+ import {
13
+ encodeSparkAddress
14
+ } from "./chunk-TM4TOEOX.js";
15
+ import {
16
+ ValidationError
17
+ } from "./chunk-TWF35O6M.js";
18
+ import {
19
+ TreeNode
20
+ } from "./chunk-M6A4KFIG.js";
21
+ import {
22
+ Buffer
23
+ } from "./chunk-MVRQ5US7.js";
24
+
25
+ // src/utils/bitcoin.ts
26
+ import {
27
+ bytesToHex,
28
+ bytesToNumberBE,
29
+ hexToBytes
30
+ } from "@noble/curves/abstract/utils";
31
+ import { schnorr, secp256k1 } from "@noble/curves/secp256k1";
32
+ import { sha256 } from "@noble/hashes/sha2";
33
+ import * as btc from "@scure/btc-signer";
34
+ function computeTaprootKeyNoScript(pubkey) {
35
+ if (pubkey.length !== 32) {
36
+ throw new ValidationError("Public key must be 32 bytes", {
37
+ field: "pubkey",
38
+ value: pubkey.length,
39
+ expected: 32
40
+ });
41
+ }
42
+ const taggedHash = schnorr.utils.taggedHash("TapTweak", pubkey);
43
+ const tweak = bytesToNumberBE(taggedHash);
44
+ const P = schnorr.utils.lift_x(schnorr.utils.bytesToNumberBE(pubkey));
45
+ const Q = P.add(secp256k1.ProjectivePoint.fromPrivateKey(tweak));
46
+ return Q.toRawBytes();
47
+ }
48
+ function getP2TRScriptFromPublicKey(pubKey, network) {
49
+ if (pubKey.length !== 33) {
50
+ throw new ValidationError("Public key must be 33 bytes", {
51
+ field: "pubKey",
52
+ value: pubKey.length,
53
+ expected: 33
54
+ });
55
+ }
56
+ const internalKey = secp256k1.ProjectivePoint.fromHex(pubKey);
57
+ const script = btc.p2tr(
58
+ internalKey.toRawBytes().slice(1, 33),
59
+ void 0,
60
+ getNetwork(network)
61
+ ).script;
62
+ if (!script) {
63
+ throw new ValidationError("Failed to get P2TR script", {
64
+ field: "script",
65
+ value: "null"
66
+ });
67
+ }
68
+ return script;
69
+ }
70
+ function getP2TRAddressFromPublicKey(pubKey, network) {
71
+ if (pubKey.length !== 33) {
72
+ throw new ValidationError("Public key must be 33 bytes", {
73
+ field: "pubKey",
74
+ value: pubKey.length,
75
+ expected: 33
76
+ });
77
+ }
78
+ const internalKey = secp256k1.ProjectivePoint.fromHex(pubKey);
79
+ const address = btc.p2tr(
80
+ internalKey.toRawBytes().slice(1, 33),
81
+ void 0,
82
+ getNetwork(network)
83
+ ).address;
84
+ if (!address) {
85
+ throw new ValidationError("Failed to get P2TR address", {
86
+ field: "address",
87
+ value: "null"
88
+ });
89
+ }
90
+ return address;
91
+ }
92
+ function getP2TRAddressFromPkScript(pkScript, network) {
93
+ if (pkScript.length !== 34 || pkScript[0] !== 81 || pkScript[1] !== 32) {
94
+ throw new ValidationError("Invalid pkscript", {
95
+ field: "pkScript",
96
+ value: bytesToHex(pkScript),
97
+ expected: "34 bytes starting with 0x51 0x20"
98
+ });
99
+ }
100
+ const parsedScript = btc.OutScript.decode(pkScript);
101
+ return btc.Address(getNetwork(network)).encode(parsedScript);
102
+ }
103
+ function getP2WPKHAddressFromPublicKey(pubKey, network) {
104
+ if (pubKey.length !== 33) {
105
+ throw new ValidationError("Public key must be 33 bytes", {
106
+ field: "pubKey",
107
+ value: pubKey.length,
108
+ expected: 33
109
+ });
110
+ }
111
+ const address = btc.p2wpkh(pubKey, getNetwork(network)).address;
112
+ if (!address) {
113
+ throw new ValidationError("Failed to get P2WPKH address", {
114
+ field: "address",
115
+ value: "null"
116
+ });
117
+ }
118
+ return address;
119
+ }
120
+ function getTxFromRawTxHex(rawTxHex) {
121
+ const txBytes = hexToBytes(rawTxHex);
122
+ const tx = btc.Transaction.fromRaw(txBytes, {
123
+ allowUnknownOutputs: true
124
+ });
125
+ if (!tx) {
126
+ throw new ValidationError("Failed to parse transaction", {
127
+ field: "tx",
128
+ value: "null"
129
+ });
130
+ }
131
+ return tx;
132
+ }
133
+ function getTxFromRawTxBytes(rawTxBytes) {
134
+ const tx = btc.Transaction.fromRaw(rawTxBytes, {
135
+ allowUnknownOutputs: true
136
+ });
137
+ if (!tx) {
138
+ throw new ValidationError("Failed to parse transaction", {
139
+ field: "tx",
140
+ value: "null"
141
+ });
142
+ }
143
+ return tx;
144
+ }
145
+ function getSigHashFromTx(tx, inputIndex, prevOutput) {
146
+ const prevScript = prevOutput.script;
147
+ if (!prevScript) {
148
+ throw new ValidationError("No script found in prevOutput", {
149
+ field: "prevScript",
150
+ value: "null"
151
+ });
152
+ }
153
+ const amount = prevOutput.amount;
154
+ if (!amount) {
155
+ throw new ValidationError("No amount found in prevOutput", {
156
+ field: "amount",
157
+ value: "null"
158
+ });
159
+ }
160
+ return tx.preimageWitnessV1(
161
+ inputIndex,
162
+ new Array(tx.inputsLength).fill(prevScript),
163
+ btc.SigHash.DEFAULT,
164
+ new Array(tx.inputsLength).fill(amount)
165
+ );
166
+ }
167
+ function getTxId(tx) {
168
+ return bytesToHex(sha256(sha256(tx.toBytes(true))).reverse());
169
+ }
170
+ function getTxIdNoReverse(tx) {
171
+ return bytesToHex(sha256(sha256(tx.toBytes(true))));
172
+ }
173
+
174
+ // src/utils/mempool.ts
175
+ async function getLatestDepositTxId(address) {
176
+ const network = getNetworkFromAddress(address);
177
+ const baseUrl = network === BitcoinNetwork_default.REGTEST ? getElectrsUrl("REGTEST") : getElectrsUrl("MAINNET");
178
+ const headers = {};
179
+ if (network === BitcoinNetwork_default.REGTEST) {
180
+ const auth = btoa(
181
+ `${ELECTRS_CREDENTIALS.username}:${ELECTRS_CREDENTIALS.password}`
182
+ );
183
+ headers["Authorization"] = `Basic ${auth}`;
184
+ }
185
+ const response = await fetch(`${baseUrl}/address/${address}/txs`, {
186
+ headers
187
+ });
188
+ const addressTxs = await response.json();
189
+ if (addressTxs && addressTxs.length > 0) {
190
+ const latestTx = addressTxs[0];
191
+ const outputIndex = latestTx.vout.findIndex(
192
+ (output) => output.scriptpubkey_address === address
193
+ );
194
+ if (outputIndex === -1) {
195
+ return null;
196
+ }
197
+ return latestTx.txid;
198
+ }
199
+ return null;
200
+ }
201
+ async function isTxBroadcast(txid, baseUrl, network) {
202
+ const headers = {};
203
+ if (network === 3 /* REGTEST */) {
204
+ const auth = btoa(
205
+ `${ELECTRS_CREDENTIALS.username}:${ELECTRS_CREDENTIALS.password}`
206
+ );
207
+ headers["Authorization"] = `Basic ${auth}`;
208
+ }
209
+ const response = await fetch(`${baseUrl}/tx/${txid}`, {
210
+ headers
211
+ });
212
+ const tx = await response.json();
213
+ if (tx.error) {
214
+ return false;
215
+ }
216
+ return true;
217
+ }
218
+
219
+ // src/utils/proof.ts
220
+ import { sha256 as sha2562 } from "@noble/hashes/sha2";
221
+ function proofOfPossessionMessageHashForDepositAddress(userPubkey, operatorPubkey, depositAddress) {
222
+ const encoder = new TextEncoder();
223
+ const depositAddressBytes = encoder.encode(depositAddress);
224
+ const proofMsg = new Uint8Array([
225
+ ...userPubkey,
226
+ ...operatorPubkey,
227
+ ...depositAddressBytes
228
+ ]);
229
+ return sha2562(proofMsg);
230
+ }
231
+
232
+ // src/utils/transfer_package.ts
233
+ import { hexToBytes as hexToBytes2 } from "@noble/curves/abstract/utils";
234
+ import { sha256 as sha2563 } from "@noble/hashes/sha2";
235
+ function getTransferPackageSigningPayload(transferID, transferPackage) {
236
+ const encryptedPayload = transferPackage.keyTweakPackage;
237
+ const pairs = Object.entries(
238
+ encryptedPayload
239
+ ).map(([key, value]) => ({ key, value }));
240
+ pairs.sort((a, b) => a.key.localeCompare(b.key));
241
+ const encoder = new TextEncoder();
242
+ let message = hexToBytes2(transferID.replaceAll("-", ""));
243
+ for (const pair of pairs) {
244
+ const keyPart = encoder.encode(pair.key + ":");
245
+ const separator = encoder.encode(";");
246
+ message = new Uint8Array([
247
+ ...message,
248
+ ...keyPart,
249
+ ...pair.value,
250
+ ...separator
251
+ ]);
252
+ }
253
+ return sha2563(message);
254
+ }
255
+
256
+ // src/utils/transaction.ts
257
+ import { Transaction as Transaction2 } from "@scure/btc-signer";
258
+ var TIME_LOCK_INTERVAL = 100;
259
+ var ESTIMATED_TX_SIZE = 191;
260
+ var DEFAULT_SATS_PER_VBYTE = 5;
261
+ var DEFAULT_FEE_SATS = ESTIMATED_TX_SIZE * DEFAULT_SATS_PER_VBYTE;
262
+ function maybeApplyFee(amount) {
263
+ if (amount > BigInt(DEFAULT_FEE_SATS)) {
264
+ return amount - BigInt(DEFAULT_FEE_SATS);
265
+ }
266
+ return amount;
267
+ }
268
+ function createRefundTx(sequence, nodeOutPoint, amountSats, receivingPubkey, network) {
269
+ const newRefundTx = new Transaction2({
270
+ version: 3,
271
+ allowUnknownOutputs: true
272
+ });
273
+ newRefundTx.addInput({
274
+ ...nodeOutPoint,
275
+ sequence
276
+ });
277
+ const refundPkScript = getP2TRScriptFromPublicKey(receivingPubkey, network);
278
+ newRefundTx.addOutput({
279
+ script: refundPkScript,
280
+ amount: amountSats
281
+ });
282
+ newRefundTx.addOutput(getEphemeralAnchorOutput());
283
+ return newRefundTx;
284
+ }
285
+ function getCurrentTimelock(currSequence) {
286
+ return (currSequence || 0) & 65535;
287
+ }
288
+ function getTransactionSequence(currSequence) {
289
+ const timelock = getCurrentTimelock(currSequence);
290
+ return 1 << 30 | timelock;
291
+ }
292
+ function checkIfValidSequence(currSequence) {
293
+ const TIME_LOCK_ACTIVE = (currSequence || 0) & 2147483648;
294
+ if (TIME_LOCK_ACTIVE !== 0) {
295
+ throw new ValidationError("Timelock not active", {
296
+ field: "currSequence",
297
+ value: currSequence
298
+ });
299
+ }
300
+ const RELATIVE_TIME_LOCK_ACTIVE = (currSequence || 0) & 4194304;
301
+ if (RELATIVE_TIME_LOCK_ACTIVE !== 0) {
302
+ throw new ValidationError("Block based timelock not active", {
303
+ field: "currSequence",
304
+ value: currSequence
305
+ });
306
+ }
307
+ }
308
+ function getNextTransactionSequence(currSequence, forRefresh) {
309
+ checkIfValidSequence(currSequence);
310
+ const currentTimelock = getCurrentTimelock(currSequence);
311
+ const nextTimelock = currentTimelock - TIME_LOCK_INTERVAL;
312
+ checkIfValidSequence(nextTimelock);
313
+ if (forRefresh && nextTimelock <= 100 && currentTimelock > 0) {
314
+ return {
315
+ nextSequence: 1 << 30 | nextTimelock,
316
+ needRefresh: true
317
+ };
318
+ }
319
+ if (nextTimelock < 100) {
320
+ throw new ValidationError("timelock interval is less than 100", {
321
+ field: "nextTimelock",
322
+ value: nextTimelock
323
+ });
324
+ }
325
+ return {
326
+ nextSequence: 1 << 30 | nextTimelock,
327
+ needRefresh: nextTimelock <= 100
328
+ };
329
+ }
330
+ function getEphemeralAnchorOutput() {
331
+ return {
332
+ script: new Uint8Array([81, 2, 78, 115]),
333
+ // Pay-to-anchor (P2A) ephemeral anchor output
334
+ amount: 0n
335
+ };
336
+ }
337
+
338
+ // src/utils/unilateral-exit.ts
339
+ import { bytesToHex as bytesToHex2, hexToBytes as hexToBytes3 } from "@noble/curves/abstract/utils";
340
+ import { ripemd160 } from "@noble/hashes/legacy";
341
+ import { sha256 as sha2564 } from "@noble/hashes/sha2";
342
+ import * as btc2 from "@scure/btc-signer";
343
+ function isEphemeralAnchorOutput(script, amount) {
344
+ return Boolean(
345
+ amount === 0n && script && // Pattern 1: Bare OP_TRUE (single byte 0x51)
346
+ (script.length === 1 && script[0] === 81 || // Pattern 2: Push OP_TRUE (two bytes 0x01 0x51) - MALFORMED but we detect it
347
+ script.length === 2 && script[0] === 1 && script[1] === 81 || // Pattern 3: Bitcoin v29 ephemeral anchor script (7 bytes: 015152014e0173)
348
+ script.length === 7 && script[0] === 1 && script[1] === 81 && script[2] === 82 && script[3] === 1 && script[4] === 78 && script[5] === 1 && script[6] === 115 || // Pattern 4: Bitcoin ephemeral anchor OP_1 + push 2 bytes (4 bytes: 51024e73)
349
+ script.length === 4 && script[0] === 81 && script[1] === 2 && script[2] === 78 && script[3] === 115)
350
+ );
351
+ }
352
+ async function constructUnilateralExitTxs(nodeHexStrings, sparkClient, network) {
353
+ const result = [];
354
+ const nodes = nodeHexStrings.map((hex) => TreeNode.decode(hexToBytes3(hex)));
355
+ const nodeMap = /* @__PURE__ */ new Map();
356
+ for (const node of nodes) {
357
+ nodeMap.set(node.id, node);
358
+ }
359
+ for (const node of nodes) {
360
+ const transactions = [];
361
+ const chain = [];
362
+ let currentNode = node;
363
+ while (currentNode) {
364
+ chain.unshift(currentNode);
365
+ if (currentNode.parentNodeId) {
366
+ let parentNode = nodeMap.get(currentNode.parentNodeId);
367
+ if (!parentNode && sparkClient) {
368
+ try {
369
+ const response = await sparkClient.query_nodes({
370
+ source: {
371
+ $case: "nodeIds",
372
+ nodeIds: {
373
+ nodeIds: [currentNode.parentNodeId]
374
+ }
375
+ },
376
+ includeParents: true,
377
+ network: network || 0
378
+ // Default to mainnet if not provided
379
+ });
380
+ parentNode = response.nodes[currentNode.parentNodeId];
381
+ if (parentNode) {
382
+ nodeMap.set(currentNode.parentNodeId, parentNode);
383
+ }
384
+ } catch (error) {
385
+ console.warn(
386
+ `Failed to query parent node ${currentNode.parentNodeId}: ${error}`
387
+ );
388
+ break;
389
+ }
390
+ }
391
+ if (parentNode) {
392
+ currentNode = parentNode;
393
+ } else {
394
+ if (!sparkClient) {
395
+ console.warn(
396
+ `Parent node ${currentNode.parentNodeId} not found. Provide a sparkClient to fetch missing parents.`
397
+ );
398
+ } else {
399
+ console.warn(
400
+ `Parent node ${currentNode.parentNodeId} not found in database. Chain may be incomplete.`
401
+ );
402
+ }
403
+ break;
404
+ }
405
+ } else {
406
+ break;
407
+ }
408
+ }
409
+ for (const chainNode of chain) {
410
+ const nodeTx = bytesToHex2(chainNode.nodeTx);
411
+ transactions.push(nodeTx);
412
+ if (chainNode.id === node.id) {
413
+ const refundTx = bytesToHex2(chainNode.refundTx);
414
+ transactions.push(refundTx);
415
+ }
416
+ }
417
+ result.push({
418
+ leafId: node.id,
419
+ transactions
420
+ });
421
+ }
422
+ return result;
423
+ }
424
+ async function constructUnilateralExitFeeBumpPackages(nodeHexStrings, utxos, feeRate, electrsUrl, sparkClient, network) {
425
+ const result = [];
426
+ const availableUtxos = [...utxos].sort((a, b) => {
427
+ if (a.value > b.value) return -1;
428
+ if (a.value < b.value) return 1;
429
+ return 0;
430
+ });
431
+ const nodes = [];
432
+ for (let i = 0; i < nodeHexStrings.length; i++) {
433
+ const hex = nodeHexStrings[i];
434
+ try {
435
+ if (!hex || hex.length === 0) {
436
+ throw new Error(`Node hex string at index ${i} is empty`);
437
+ }
438
+ if (hex.startsWith("03000000") || hex.startsWith("02000000") || hex.startsWith("01000000")) {
439
+ throw new Error(
440
+ `Node hex string at index ${i} appears to be a raw transaction hex, not a TreeNode protobuf. Use 'leafidtohex' command to convert node IDs to proper hex strings.`
441
+ );
442
+ }
443
+ const nodeBytes = hexToBytes3(hex);
444
+ const node = TreeNode.decode(nodeBytes);
445
+ if (!node.id) {
446
+ throw new Error(
447
+ `Decoded TreeNode at index ${i} is missing required 'id' field`
448
+ );
449
+ }
450
+ if (!node.nodeTx || node.nodeTx.length === 0) {
451
+ throw new Error(
452
+ `Decoded TreeNode at index ${i} is missing required 'nodeTx' field`
453
+ );
454
+ }
455
+ nodes.push(node);
456
+ } catch (decodeError) {
457
+ throw new Error(
458
+ `Failed to decode TreeNode hex string at index ${i}: ${decodeError}. Make sure you're providing TreeNode protobuf hex strings, not raw transaction hex. Use 'leafidtohex' command to get proper hex strings.`
459
+ );
460
+ }
461
+ }
462
+ const nodeMap = /* @__PURE__ */ new Map();
463
+ for (const node of nodes) {
464
+ nodeMap.set(node.id, node);
465
+ }
466
+ const broadcastTxs = /* @__PURE__ */ new Map();
467
+ for (const node of nodes) {
468
+ const txPackages = [];
469
+ let previousFeeBumpTx;
470
+ const chain = [];
471
+ let currentNode = node;
472
+ while (currentNode) {
473
+ chain.unshift(currentNode);
474
+ if (currentNode.parentNodeId) {
475
+ let parentNode = nodeMap.get(currentNode.parentNodeId);
476
+ if (!parentNode && sparkClient) {
477
+ try {
478
+ const response = await sparkClient.query_nodes({
479
+ source: {
480
+ $case: "nodeIds",
481
+ nodeIds: {
482
+ nodeIds: [currentNode.parentNodeId]
483
+ }
484
+ },
485
+ includeParents: true,
486
+ network: network || 0
487
+ // Default to mainnet if not provided
488
+ });
489
+ parentNode = response.nodes[currentNode.parentNodeId];
490
+ if (parentNode) {
491
+ nodeMap.set(currentNode.parentNodeId, parentNode);
492
+ }
493
+ } catch (error) {
494
+ console.warn(
495
+ `Failed to query parent node ${currentNode.parentNodeId}: ${error}`
496
+ );
497
+ break;
498
+ }
499
+ }
500
+ if (parentNode) {
501
+ currentNode = parentNode;
502
+ } else {
503
+ if (!sparkClient) {
504
+ console.warn(
505
+ `Parent node ${currentNode.parentNodeId} not found. Provide a sparkClient to fetch missing parents.`
506
+ );
507
+ } else {
508
+ console.warn(
509
+ `Parent node ${currentNode.parentNodeId} not found in database. Chain may be incomplete.`
510
+ );
511
+ }
512
+ break;
513
+ }
514
+ } else {
515
+ break;
516
+ }
517
+ }
518
+ for (const chainNode of chain) {
519
+ let nodeTxHex = bytesToHex2(chainNode.nodeTx);
520
+ try {
521
+ const txObj = getTxFromRawTxHex(nodeTxHex);
522
+ const txid = getTxId(txObj);
523
+ if (broadcastTxs.get(txid)) {
524
+ continue;
525
+ }
526
+ broadcastTxs.set(txid, true);
527
+ const isBroadcast = await isTxBroadcast(txid, electrsUrl, network);
528
+ if (isBroadcast) {
529
+ continue;
530
+ } else {
531
+ }
532
+ let anchorOutputScriptHex;
533
+ for (let i = txObj.outputsLength - 1; i >= 0; i--) {
534
+ const output = txObj.getOutput(i);
535
+ if (output?.amount === 0n && output.script) {
536
+ anchorOutputScriptHex = bytesToHex2(output.script);
537
+ break;
538
+ }
539
+ }
540
+ } catch (parseError) {
541
+ console.error(
542
+ `\u274C Error parsing nodeTx for anchor check (node ${chainNode.id}): ${parseError}`
543
+ );
544
+ console.log(
545
+ ` This may indicate a corrupted transaction in the TreeNode.`
546
+ );
547
+ console.log(` Transaction hex: ${nodeTxHex}`);
548
+ console.log(
549
+ ` Attempting to continue with original hex, but fee bump may fail.`
550
+ );
551
+ }
552
+ const {
553
+ feeBumpPsbt: nodeFeeBumpPsbt,
554
+ usedUtxos,
555
+ correctedParentTx
556
+ } = constructFeeBumpTx(nodeTxHex, availableUtxos, feeRate, void 0);
557
+ const feeBumpTx = btc2.Transaction.fromPSBT(hexToBytes3(nodeFeeBumpPsbt));
558
+ var feeBumpOut = feeBumpTx.outputsLength === 1 ? feeBumpTx.getOutput(0) : null;
559
+ var feeBumpOutPubKey = null;
560
+ for (const usedUtxo of usedUtxos) {
561
+ if (feeBumpOut && bytesToHex2(feeBumpOut.script) == usedUtxo.script) {
562
+ feeBumpOutPubKey = usedUtxo.publicKey;
563
+ }
564
+ const index = availableUtxos.findIndex(
565
+ (u) => u.txid === usedUtxo.txid && u.vout === usedUtxo.vout
566
+ );
567
+ if (index !== -1) {
568
+ availableUtxos.splice(index, 1);
569
+ }
570
+ }
571
+ if (feeBumpOut)
572
+ availableUtxos.unshift({
573
+ txid: getTxId(feeBumpTx),
574
+ vout: 0,
575
+ value: feeBumpOut.amount,
576
+ script: bytesToHex2(feeBumpOut.script),
577
+ publicKey: feeBumpOutPubKey
578
+ });
579
+ const finalNodeTx = correctedParentTx || nodeTxHex;
580
+ txPackages.push({ tx: finalNodeTx, feeBumpPsbt: nodeFeeBumpPsbt });
581
+ if (chainNode.id === node.id) {
582
+ let refundTxHex = bytesToHex2(chainNode.refundTx);
583
+ try {
584
+ const txObj = getTxFromRawTxHex(refundTxHex);
585
+ let anchorOutputScriptHex;
586
+ for (let i = txObj.outputsLength - 1; i >= 0; i--) {
587
+ const output = txObj.getOutput(i);
588
+ if (output?.amount === 0n && output.script) {
589
+ anchorOutputScriptHex = bytesToHex2(output.script);
590
+ break;
591
+ }
592
+ }
593
+ } catch (parseError) {
594
+ console.error(
595
+ `\u274C Error parsing refundTx for anchor check (node ${chainNode.id}): ${parseError}`
596
+ );
597
+ console.log(
598
+ ` This may indicate a corrupted refund transaction in the TreeNode.`
599
+ );
600
+ console.log(` Refund transaction hex: ${refundTxHex}`);
601
+ console.log(
602
+ ` Attempting to continue with original refund hex, but this transaction may be invalid.`
603
+ );
604
+ }
605
+ const refundFeeBump = constructFeeBumpTx(
606
+ refundTxHex,
607
+ availableUtxos,
608
+ feeRate,
609
+ void 0
610
+ );
611
+ txPackages.push({
612
+ tx: refundTxHex,
613
+ feeBumpPsbt: refundFeeBump.feeBumpPsbt
614
+ });
615
+ }
616
+ }
617
+ result.push({
618
+ leafId: node.id,
619
+ txPackages
620
+ });
621
+ }
622
+ return result;
623
+ }
624
+ function hash160(data) {
625
+ const sha256Hash = sha2564(data);
626
+ return ripemd160(sha256Hash);
627
+ }
628
+ function constructFeeBumpTx(txHex, utxos, feeRate, previousFeeBumpTx) {
629
+ if (!txHex || txHex.length === 0) {
630
+ throw new Error("Transaction hex string is empty or undefined");
631
+ }
632
+ if (utxos.length === 0) {
633
+ throw new Error("No UTXOs available for fee bump");
634
+ }
635
+ let correctedTxHex = txHex;
636
+ let parentTx;
637
+ try {
638
+ parentTx = getTxFromRawTxHex(correctedTxHex);
639
+ if (!parentTx) {
640
+ throw new Error("getTxFromRawTxHex returned null/undefined");
641
+ }
642
+ } catch (parseError) {
643
+ throw new Error(
644
+ `Failed to parse parent transaction hex: ${parseError}. Transaction hex: ${correctedTxHex}`
645
+ );
646
+ }
647
+ try {
648
+ const outputsLength = parentTx.outputsLength;
649
+ const inputsLength = parentTx.inputsLength;
650
+ if (typeof outputsLength !== "number" || outputsLength < 0) {
651
+ throw new Error(
652
+ "Invalid transaction: outputsLength is not a valid number"
653
+ );
654
+ }
655
+ if (typeof inputsLength !== "number" || inputsLength < 0) {
656
+ throw new Error(
657
+ "Invalid transaction: inputsLength is not a valid number"
658
+ );
659
+ }
660
+ } catch (validationError) {
661
+ throw new Error(
662
+ `Transaction validation failed: ${validationError}. This may indicate a corrupted or malformed transaction.`
663
+ );
664
+ }
665
+ const parentTxIdFromLib = parentTx.id;
666
+ let ephemeralAnchorIndex = -1;
667
+ for (let i = 0; i < parentTx.outputsLength; i++) {
668
+ const output = parentTx.getOutput(i);
669
+ const isEphemeralAnchor = isEphemeralAnchorOutput(
670
+ output?.script,
671
+ output?.amount
672
+ );
673
+ if (isEphemeralAnchor) {
674
+ ephemeralAnchorIndex = i;
675
+ break;
676
+ }
677
+ }
678
+ if (ephemeralAnchorIndex === -1) {
679
+ throw new Error(
680
+ "No ephemeral anchor output found in parent transaction. Expected a 0-value output with OP_TRUE script (0x51), malformed OP_TRUE (0x0151), Bitcoin v29 ephemeral anchor script (015152014e0173), or Bitcoin OP_1 + push 2 bytes script (51024e73)."
681
+ );
682
+ }
683
+ const ephemeralAnchorOutput = parentTx.getOutput(ephemeralAnchorIndex);
684
+ if (!ephemeralAnchorOutput)
685
+ throw new Error("No ephemeral anchor output found");
686
+ if (!ephemeralAnchorOutput.script)
687
+ throw new Error("No script found in ephemeral anchor output");
688
+ if (utxos.length === 0) {
689
+ throw new Error("No UTXOs available for fee bump");
690
+ }
691
+ const builder = new btc2.Transaction({
692
+ version: 3,
693
+ allowUnknown: true,
694
+ allowLegacyWitnessUtxo: true
695
+ });
696
+ let totalValue = 0n;
697
+ const processedUtxos = [];
698
+ for (let i = 0; i < utxos.length; i++) {
699
+ const fundingUtxo = utxos[i];
700
+ if (!fundingUtxo) {
701
+ throw new Error(`UTXO at index ${i} is undefined`);
702
+ }
703
+ const pubKeyHash = hash160(hexToBytes3(fundingUtxo.publicKey));
704
+ const scriptToUse = new Uint8Array([0, 20, ...pubKeyHash]);
705
+ const providedScript = hexToBytes3(fundingUtxo.script);
706
+ if (bytesToHex2(scriptToUse) !== bytesToHex2(providedScript)) {
707
+ throw new Error(
708
+ `\u274C Derived script doesn't match provided script for UTXO ${i + 1}.`
709
+ );
710
+ }
711
+ builder.addInput({
712
+ txid: fundingUtxo.txid,
713
+ index: fundingUtxo.vout,
714
+ sequence: 4294967295,
715
+ witnessUtxo: {
716
+ script: scriptToUse,
717
+ // Always P2WPKH
718
+ amount: fundingUtxo.value
719
+ }
720
+ });
721
+ totalValue += fundingUtxo.value;
722
+ processedUtxos.push({
723
+ utxo: fundingUtxo,
724
+ p2wpkhScript: scriptToUse
725
+ });
726
+ }
727
+ builder.addInput({
728
+ txid: parentTxIdFromLib,
729
+ index: ephemeralAnchorIndex,
730
+ sequence: 4294967295,
731
+ witnessUtxo: {
732
+ script: ephemeralAnchorOutput.script,
733
+ // Use the original script directly (not P2WSH wrapped)
734
+ amount: 0n
735
+ }
736
+ });
737
+ const fee = 1500n;
738
+ const remainingValue = totalValue - fee;
739
+ if (remainingValue <= 0n) {
740
+ throw new Error(
741
+ `Insufficient funds for fee bump. Required fee: ${fee} sats, Available: ${totalValue} sats`
742
+ );
743
+ }
744
+ if (processedUtxos.length === 0) {
745
+ throw new Error("No processed UTXOs available for change output");
746
+ }
747
+ const firstProcessedUtxo = processedUtxos[0];
748
+ if (!firstProcessedUtxo) {
749
+ throw new Error("First processed UTXO is undefined");
750
+ }
751
+ builder.addOutput({
752
+ script: firstProcessedUtxo.p2wpkhScript,
753
+ amount: remainingValue
754
+ });
755
+ for (let i = 0; i < processedUtxos.length; i++) {
756
+ const processed = processedUtxos[i];
757
+ if (!processed) {
758
+ throw new Error(`Processed UTXO at index ${i} is undefined`);
759
+ }
760
+ try {
761
+ builder.updateInput(i, {
762
+ witnessScript: processed.p2wpkhScript
763
+ });
764
+ builder.signIdx;
765
+ } catch (error) {
766
+ throw new Error(`Failed to handle funding UTXO input ${i + 1}: ${error}`);
767
+ }
768
+ }
769
+ let psbtHex;
770
+ try {
771
+ psbtHex = bytesToHex2(builder.toPSBT());
772
+ } catch (error) {
773
+ throw new Error(`Failed to extract transaction: ${error}`);
774
+ }
775
+ return {
776
+ feeBumpPsbt: psbtHex,
777
+ usedUtxos: utxos,
778
+ correctedParentTx: correctedTxHex !== txHex ? correctedTxHex : void 0
779
+ };
780
+ }
781
+
782
+ // src/utils/xchain-address.ts
783
+ import * as btc3 from "@scure/btc-signer";
784
+ var networkByType = {
785
+ MAINNET: btc3.NETWORK,
786
+ TESTNET: btc3.TEST_NETWORK,
787
+ REGTEST: {
788
+ ...btc3.TEST_NETWORK,
789
+ bech32: "bcrt"
790
+ }
791
+ };
792
+ function getSparkAddressFromTaproot(taprootAddress) {
793
+ for (const networkType of ["MAINNET", "TESTNET", "REGTEST"]) {
794
+ try {
795
+ const result = btc3.Address(networkByType[networkType]).decode(taprootAddress);
796
+ if (result.type === "tr") {
797
+ const outputPublicKey = result.pubkey;
798
+ return encodeSparkAddress({
799
+ identityPublicKey: Buffer.concat([
800
+ Buffer.from([2]),
801
+ outputPublicKey
802
+ ]).toString("hex"),
803
+ network: networkType
804
+ });
805
+ }
806
+ } catch (_) {
807
+ }
808
+ }
809
+ throw new ValidationError("Invalid taproot address");
810
+ }
811
+
812
+ export {
813
+ computeTaprootKeyNoScript,
814
+ getP2TRScriptFromPublicKey,
815
+ getP2TRAddressFromPublicKey,
816
+ getP2TRAddressFromPkScript,
817
+ getP2WPKHAddressFromPublicKey,
818
+ getTxFromRawTxHex,
819
+ getTxFromRawTxBytes,
820
+ getSigHashFromTx,
821
+ getTxId,
822
+ getTxIdNoReverse,
823
+ DEFAULT_FEE_SATS,
824
+ maybeApplyFee,
825
+ createRefundTx,
826
+ getCurrentTimelock,
827
+ getTransactionSequence,
828
+ checkIfValidSequence,
829
+ getNextTransactionSequence,
830
+ getEphemeralAnchorOutput,
831
+ getTransferPackageSigningPayload,
832
+ proofOfPossessionMessageHashForDepositAddress,
833
+ getLatestDepositTxId,
834
+ isTxBroadcast,
835
+ isEphemeralAnchorOutput,
836
+ constructUnilateralExitTxs,
837
+ constructUnilateralExitFeeBumpPackages,
838
+ constructFeeBumpTx,
839
+ getSparkAddressFromTaproot
840
+ };