@buildonspark/spark-sdk 0.1.45 → 0.1.47

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 (146) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/{chunk-I54FARY2.js → chunk-EAP3U3CW.js} +14 -14
  3. package/dist/chunk-GWFQ7EBA.js +3773 -0
  4. package/dist/{chunk-J2IE4Z7Y.js → chunk-NNX4OK44.js} +3487 -934
  5. package/dist/{RequestLightningSendInput-Du0z7Om7.d.cts → client-CvpTRpcw.d.cts} +422 -212
  6. package/dist/{RequestLightningSendInput-DEPd_fPO.d.ts → client-D7KgLN44.d.ts} +422 -212
  7. package/dist/graphql/objects/index.d.cts +5 -9
  8. package/dist/graphql/objects/index.d.ts +5 -9
  9. package/dist/graphql/objects/index.js +1 -1
  10. package/dist/index.cjs +20461 -23377
  11. package/dist/index.d.cts +15 -769
  12. package/dist/index.d.ts +15 -769
  13. package/dist/index.js +81 -71
  14. package/dist/index.node.cjs +21994 -25018
  15. package/dist/index.node.d.cts +312 -34
  16. package/dist/index.node.d.ts +312 -34
  17. package/dist/index.node.js +82 -176
  18. package/dist/native/index.cjs +22847 -25841
  19. package/dist/native/index.d.cts +974 -1138
  20. package/dist/native/index.d.ts +974 -1138
  21. package/dist/native/index.js +10604 -13592
  22. package/dist/proto/lrc20.d.cts +2 -2
  23. package/dist/proto/lrc20.d.ts +2 -2
  24. package/dist/proto/lrc20.js +3098 -46
  25. package/dist/proto/spark.d.cts +1 -1
  26. package/dist/proto/spark.d.ts +1 -1
  27. package/dist/proto/spark_token.d.cts +1 -1
  28. package/dist/proto/spark_token.d.ts +1 -1
  29. package/dist/{sdk-types-Cc4l4kb1.d.ts → sdk-types-BGCeea0G.d.ts} +1 -1
  30. package/dist/{sdk-types-B0SwjolI.d.cts → sdk-types-XUeQMLFP.d.cts} +1 -1
  31. package/dist/{spark-dM7EYXYQ.d.cts → spark-BbUrbvZz.d.cts} +1 -1
  32. package/dist/{spark-dM7EYXYQ.d.ts → spark-BbUrbvZz.d.ts} +1 -1
  33. package/dist/spark-wallet-BAFPpPtY.d.cts +923 -0
  34. package/dist/spark-wallet-CJkQW8pK.d.ts +923 -0
  35. package/dist/spark_bindings/native/index.d.cts +1 -1
  36. package/dist/spark_bindings/native/index.d.ts +1 -1
  37. package/dist/spark_bindings/wasm/index.d.cts +1 -1
  38. package/dist/spark_bindings/wasm/index.d.ts +1 -1
  39. package/dist/{services/index.cjs → tests/test-utils.cjs} +2512 -4380
  40. package/dist/tests/test-utils.d.cts +79 -0
  41. package/dist/tests/test-utils.d.ts +79 -0
  42. package/dist/tests/test-utils.js +85 -0
  43. package/dist/types/index.d.cts +5 -9
  44. package/dist/types/index.d.ts +5 -9
  45. package/dist/types/index.js +5 -5
  46. package/dist/{types-C-Rp0Oo7.d.cts → types-BADxR3bm.d.cts} +1 -1
  47. package/dist/{types-C-Rp0Oo7.d.ts → types-BADxR3bm.d.ts} +1 -1
  48. package/package.json +7 -35
  49. package/src/graphql/client.ts +59 -20
  50. package/src/index.node.ts +28 -2
  51. package/src/index.ts +31 -1
  52. package/src/native/index.ts +16 -2
  53. package/src/services/config.ts +4 -6
  54. package/src/services/connection.ts +131 -64
  55. package/src/services/lightning.ts +1 -2
  56. package/src/services/token-transactions.ts +7 -7
  57. package/src/services/transfer.ts +1 -1
  58. package/src/services/tree-creation.ts +1 -1
  59. package/src/services/wallet-config.ts +18 -10
  60. package/src/signer/signer.react-native.ts +2 -5
  61. package/src/signer/signer.ts +138 -64
  62. package/src/signer/types.ts +52 -0
  63. package/src/spark-wallet/spark-wallet.ts +79 -36
  64. package/src/spark-wallet/types.ts +4 -4
  65. package/src/tests/integration/coop-exit.test.ts +2 -1
  66. package/src/tests/integration/lightning.test.ts +2 -2
  67. package/src/tests/integration/swap.test.ts +1 -1
  68. package/src/tests/integration/transfer.test.ts +5 -5
  69. package/src/tests/integration/tree-creation.test.ts +1 -1
  70. package/src/tests/integration/wallet.test.ts +1 -0
  71. package/src/tests/isHermeticTest.ts +3 -24
  72. package/src/tests/{test-util.ts → test-utils.ts} +3 -7
  73. package/src/tests/wrapWithOtelSpan.test.ts +1 -1
  74. package/src/{address → utils}/address.ts +1 -1
  75. package/src/utils/crypto.ts +19 -9
  76. package/src/utils/index.ts +2 -0
  77. package/src/utils/network.ts +17 -0
  78. package/src/utils/secret-sharing.ts +1 -2
  79. package/src/utils/signing.ts +1 -1
  80. package/src/utils/token-transactions.ts +3 -3
  81. package/src/utils/unilateral-exit.ts +32 -0
  82. package/src/utils/xchain-address.ts +1 -1
  83. package/dist/BitcoinNetwork-TnABML0T.d.cts +0 -18
  84. package/dist/BitcoinNetwork-TnABML0T.d.ts +0 -18
  85. package/dist/LightningSendFeeEstimateInput-BgOhEAI-.d.cts +0 -10
  86. package/dist/LightningSendFeeEstimateInput-BgOhEAI-.d.ts +0 -10
  87. package/dist/address/index.cjs +0 -458
  88. package/dist/address/index.d.cts +0 -32
  89. package/dist/address/index.d.ts +0 -32
  90. package/dist/address/index.js +0 -17
  91. package/dist/chunk-5FUB65LX.js +0 -838
  92. package/dist/chunk-6264CGDM.js +0 -113
  93. package/dist/chunk-7V6N75CC.js +0 -24
  94. package/dist/chunk-C2S227QR.js +0 -2336
  95. package/dist/chunk-GSI4OLXZ.js +0 -117
  96. package/dist/chunk-GZ5IPPJ2.js +0 -170
  97. package/dist/chunk-HWJWKEIU.js +0 -75
  98. package/dist/chunk-KMUMFYFX.js +0 -137
  99. package/dist/chunk-L3EHBOUX.js +0 -0
  100. package/dist/chunk-NSJF5F5O.js +0 -325
  101. package/dist/chunk-NTFKFRQ2.js +0 -3146
  102. package/dist/chunk-PQN3C2MF.js +0 -1122
  103. package/dist/chunk-QNNSEJ4P.js +0 -232
  104. package/dist/chunk-R5PXJZQS.js +0 -277
  105. package/dist/chunk-VTUGIIWI.js +0 -0
  106. package/dist/chunk-YUPMXTCJ.js +0 -622
  107. package/dist/chunk-Z5HIAYFT.js +0 -84
  108. package/dist/index-B2AwKW5J.d.cts +0 -214
  109. package/dist/index-CJDi1HWc.d.ts +0 -214
  110. package/dist/network-BTJl-Sul.d.ts +0 -46
  111. package/dist/network-CqgsdUF2.d.cts +0 -46
  112. package/dist/services/config.cjs +0 -2354
  113. package/dist/services/config.d.cts +0 -42
  114. package/dist/services/config.d.ts +0 -42
  115. package/dist/services/config.js +0 -17
  116. package/dist/services/connection.cjs +0 -17691
  117. package/dist/services/connection.d.cts +0 -95
  118. package/dist/services/connection.d.ts +0 -95
  119. package/dist/services/connection.js +0 -11
  120. package/dist/services/index.d.cts +0 -21
  121. package/dist/services/index.d.ts +0 -21
  122. package/dist/services/index.js +0 -58
  123. package/dist/services/lrc-connection.cjs +0 -4713
  124. package/dist/services/lrc-connection.d.cts +0 -34
  125. package/dist/services/lrc-connection.d.ts +0 -34
  126. package/dist/services/lrc-connection.js +0 -11
  127. package/dist/services/token-transactions.cjs +0 -2877
  128. package/dist/services/token-transactions.d.cts +0 -75
  129. package/dist/services/token-transactions.d.ts +0 -75
  130. package/dist/services/token-transactions.js +0 -15
  131. package/dist/services/wallet-config.cjs +0 -340
  132. package/dist/services/wallet-config.d.cts +0 -56
  133. package/dist/services/wallet-config.d.ts +0 -56
  134. package/dist/services/wallet-config.js +0 -33
  135. package/dist/signer/signer.cjs +0 -2004
  136. package/dist/signer/signer.d.cts +0 -10
  137. package/dist/signer/signer.d.ts +0 -10
  138. package/dist/signer/signer.js +0 -24
  139. package/dist/signer-BocS_J6B.d.ts +0 -187
  140. package/dist/signer-DKS0AJkw.d.cts +0 -187
  141. package/dist/utils/index.cjs +0 -2947
  142. package/dist/utils/index.d.cts +0 -18
  143. package/dist/utils/index.d.ts +0 -18
  144. package/dist/utils/index.js +0 -157
  145. package/src/address/index.ts +0 -1
  146. package/src/services/lrc-connection.ts +0 -215
@@ -1,838 +0,0 @@
1
- import {
2
- encodeSparkAddress
3
- } from "./chunk-KMUMFYFX.js";
4
- import {
5
- getNetwork,
6
- getNetworkFromAddress
7
- } from "./chunk-Z5HIAYFT.js";
8
- import {
9
- ELECTRS_CREDENTIALS,
10
- getElectrsUrl
11
- } from "./chunk-R5PXJZQS.js";
12
- import {
13
- BitcoinNetwork_default
14
- } from "./chunk-HMLOC6TE.js";
15
- import {
16
- ValidationError
17
- } from "./chunk-GSI4OLXZ.js";
18
- import {
19
- TreeNode
20
- } from "./chunk-BGGEVUJK.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
- const currentTimelock = getCurrentTimelock(currSequence);
310
- const nextTimelock = currentTimelock - TIME_LOCK_INTERVAL;
311
- if (forRefresh && nextTimelock <= 100 && currentTimelock > 0) {
312
- return {
313
- nextSequence: 1 << 30 | nextTimelock,
314
- needRefresh: true
315
- };
316
- }
317
- if (nextTimelock < 0) {
318
- throw new ValidationError("timelock interval is less than 0", {
319
- field: "nextTimelock",
320
- value: nextTimelock
321
- });
322
- }
323
- return {
324
- nextSequence: 1 << 30 | nextTimelock,
325
- needRefresh: nextTimelock <= 100
326
- };
327
- }
328
- function getEphemeralAnchorOutput() {
329
- return {
330
- script: new Uint8Array([81, 2, 78, 115]),
331
- // Pay-to-anchor (P2A) ephemeral anchor output
332
- amount: 0n
333
- };
334
- }
335
-
336
- // src/utils/unilateral-exit.ts
337
- import { bytesToHex as bytesToHex2, hexToBytes as hexToBytes3 } from "@noble/curves/abstract/utils";
338
- import { ripemd160 } from "@noble/hashes/legacy";
339
- import { sha256 as sha2564 } from "@noble/hashes/sha2";
340
- import * as btc2 from "@scure/btc-signer";
341
- function isEphemeralAnchorOutput(script, amount) {
342
- return Boolean(
343
- amount === 0n && script && // Pattern 1: Bare OP_TRUE (single byte 0x51)
344
- (script.length === 1 && script[0] === 81 || // Pattern 2: Push OP_TRUE (two bytes 0x01 0x51) - MALFORMED but we detect it
345
- script.length === 2 && script[0] === 1 && script[1] === 81 || // Pattern 3: Bitcoin v29 ephemeral anchor script (7 bytes: 015152014e0173)
346
- 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)
347
- script.length === 4 && script[0] === 81 && script[1] === 2 && script[2] === 78 && script[3] === 115)
348
- );
349
- }
350
- async function constructUnilateralExitTxs(nodeHexStrings, sparkClient, network) {
351
- const result = [];
352
- const nodes = nodeHexStrings.map((hex) => TreeNode.decode(hexToBytes3(hex)));
353
- const nodeMap = /* @__PURE__ */ new Map();
354
- for (const node of nodes) {
355
- nodeMap.set(node.id, node);
356
- }
357
- for (const node of nodes) {
358
- const transactions = [];
359
- const chain = [];
360
- let currentNode = node;
361
- while (currentNode) {
362
- chain.unshift(currentNode);
363
- if (currentNode.parentNodeId) {
364
- let parentNode = nodeMap.get(currentNode.parentNodeId);
365
- if (!parentNode && sparkClient) {
366
- try {
367
- const response = await sparkClient.query_nodes({
368
- source: {
369
- $case: "nodeIds",
370
- nodeIds: {
371
- nodeIds: [currentNode.parentNodeId]
372
- }
373
- },
374
- includeParents: true,
375
- network: network || 0
376
- // Default to mainnet if not provided
377
- });
378
- parentNode = response.nodes[currentNode.parentNodeId];
379
- if (parentNode) {
380
- nodeMap.set(currentNode.parentNodeId, parentNode);
381
- }
382
- } catch (error) {
383
- console.warn(
384
- `Failed to query parent node ${currentNode.parentNodeId}: ${error}`
385
- );
386
- break;
387
- }
388
- }
389
- if (parentNode) {
390
- currentNode = parentNode;
391
- } else {
392
- if (!sparkClient) {
393
- console.warn(
394
- `Parent node ${currentNode.parentNodeId} not found. Provide a sparkClient to fetch missing parents.`
395
- );
396
- } else {
397
- console.warn(
398
- `Parent node ${currentNode.parentNodeId} not found in database. Chain may be incomplete.`
399
- );
400
- }
401
- break;
402
- }
403
- } else {
404
- break;
405
- }
406
- }
407
- for (const chainNode of chain) {
408
- const nodeTx = bytesToHex2(chainNode.nodeTx);
409
- transactions.push(nodeTx);
410
- if (chainNode.id === node.id) {
411
- const refundTx = bytesToHex2(chainNode.refundTx);
412
- transactions.push(refundTx);
413
- }
414
- }
415
- result.push({
416
- leafId: node.id,
417
- transactions
418
- });
419
- }
420
- return result;
421
- }
422
- async function constructUnilateralExitFeeBumpPackages(nodeHexStrings, utxos, feeRate, electrsUrl, sparkClient, network) {
423
- const result = [];
424
- const availableUtxos = [...utxos].sort((a, b) => {
425
- if (a.value > b.value) return -1;
426
- if (a.value < b.value) return 1;
427
- return 0;
428
- });
429
- const nodes = [];
430
- for (let i = 0; i < nodeHexStrings.length; i++) {
431
- const hex = nodeHexStrings[i];
432
- try {
433
- if (!hex || hex.length === 0) {
434
- throw new Error(`Node hex string at index ${i} is empty`);
435
- }
436
- if (hex.startsWith("03000000") || hex.startsWith("02000000") || hex.startsWith("01000000")) {
437
- throw new Error(
438
- `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.`
439
- );
440
- }
441
- const nodeBytes = hexToBytes3(hex);
442
- const node = TreeNode.decode(nodeBytes);
443
- if (!node.id) {
444
- throw new Error(
445
- `Decoded TreeNode at index ${i} is missing required 'id' field`
446
- );
447
- }
448
- if (!node.nodeTx || node.nodeTx.length === 0) {
449
- throw new Error(
450
- `Decoded TreeNode at index ${i} is missing required 'nodeTx' field`
451
- );
452
- }
453
- nodes.push(node);
454
- } catch (decodeError) {
455
- throw new Error(
456
- `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.`
457
- );
458
- }
459
- }
460
- const nodeMap = /* @__PURE__ */ new Map();
461
- for (const node of nodes) {
462
- nodeMap.set(node.id, node);
463
- }
464
- const broadcastTxs = /* @__PURE__ */ new Map();
465
- for (const node of nodes) {
466
- const txPackages = [];
467
- let previousFeeBumpTx;
468
- const chain = [];
469
- let currentNode = node;
470
- while (currentNode) {
471
- chain.unshift(currentNode);
472
- if (currentNode.parentNodeId) {
473
- let parentNode = nodeMap.get(currentNode.parentNodeId);
474
- if (!parentNode && sparkClient) {
475
- try {
476
- const response = await sparkClient.query_nodes({
477
- source: {
478
- $case: "nodeIds",
479
- nodeIds: {
480
- nodeIds: [currentNode.parentNodeId]
481
- }
482
- },
483
- includeParents: true,
484
- network: network || 0
485
- // Default to mainnet if not provided
486
- });
487
- parentNode = response.nodes[currentNode.parentNodeId];
488
- if (parentNode) {
489
- nodeMap.set(currentNode.parentNodeId, parentNode);
490
- }
491
- } catch (error) {
492
- console.warn(
493
- `Failed to query parent node ${currentNode.parentNodeId}: ${error}`
494
- );
495
- break;
496
- }
497
- }
498
- if (parentNode) {
499
- currentNode = parentNode;
500
- } else {
501
- if (!sparkClient) {
502
- console.warn(
503
- `Parent node ${currentNode.parentNodeId} not found. Provide a sparkClient to fetch missing parents.`
504
- );
505
- } else {
506
- console.warn(
507
- `Parent node ${currentNode.parentNodeId} not found in database. Chain may be incomplete.`
508
- );
509
- }
510
- break;
511
- }
512
- } else {
513
- break;
514
- }
515
- }
516
- for (const chainNode of chain) {
517
- let nodeTxHex = bytesToHex2(chainNode.nodeTx);
518
- try {
519
- const txObj = getTxFromRawTxHex(nodeTxHex);
520
- const txid = getTxId(txObj);
521
- if (broadcastTxs.get(txid)) {
522
- continue;
523
- }
524
- broadcastTxs.set(txid, true);
525
- const isBroadcast = await isTxBroadcast(txid, electrsUrl, network);
526
- if (isBroadcast) {
527
- continue;
528
- } else {
529
- }
530
- let anchorOutputScriptHex;
531
- for (let i = txObj.outputsLength - 1; i >= 0; i--) {
532
- const output = txObj.getOutput(i);
533
- if (output?.amount === 0n && output.script) {
534
- anchorOutputScriptHex = bytesToHex2(output.script);
535
- break;
536
- }
537
- }
538
- } catch (parseError) {
539
- console.error(
540
- `\u274C Error parsing nodeTx for anchor check (node ${chainNode.id}): ${parseError}`
541
- );
542
- console.log(
543
- ` This may indicate a corrupted transaction in the TreeNode.`
544
- );
545
- console.log(` Transaction hex: ${nodeTxHex}`);
546
- console.log(
547
- ` Attempting to continue with original hex, but fee bump may fail.`
548
- );
549
- }
550
- const {
551
- feeBumpPsbt: nodeFeeBumpPsbt,
552
- usedUtxos,
553
- correctedParentTx
554
- } = constructFeeBumpTx(nodeTxHex, availableUtxos, feeRate, void 0);
555
- const feeBumpTx = btc2.Transaction.fromPSBT(hexToBytes3(nodeFeeBumpPsbt));
556
- var feeBumpOut = feeBumpTx.outputsLength === 1 ? feeBumpTx.getOutput(0) : null;
557
- var feeBumpOutPubKey = null;
558
- for (const usedUtxo of usedUtxos) {
559
- if (feeBumpOut && bytesToHex2(feeBumpOut.script) == usedUtxo.script) {
560
- feeBumpOutPubKey = usedUtxo.publicKey;
561
- }
562
- const index = availableUtxos.findIndex(
563
- (u) => u.txid === usedUtxo.txid && u.vout === usedUtxo.vout
564
- );
565
- if (index !== -1) {
566
- availableUtxos.splice(index, 1);
567
- }
568
- }
569
- if (feeBumpOut)
570
- availableUtxos.unshift({
571
- txid: getTxId(feeBumpTx),
572
- vout: 0,
573
- value: feeBumpOut.amount,
574
- script: bytesToHex2(feeBumpOut.script),
575
- publicKey: feeBumpOutPubKey
576
- });
577
- const finalNodeTx = correctedParentTx || nodeTxHex;
578
- txPackages.push({ tx: finalNodeTx, feeBumpPsbt: nodeFeeBumpPsbt });
579
- if (chainNode.id === node.id) {
580
- let refundTxHex = bytesToHex2(chainNode.refundTx);
581
- try {
582
- const txObj = getTxFromRawTxHex(refundTxHex);
583
- let anchorOutputScriptHex;
584
- for (let i = txObj.outputsLength - 1; i >= 0; i--) {
585
- const output = txObj.getOutput(i);
586
- if (output?.amount === 0n && output.script) {
587
- anchorOutputScriptHex = bytesToHex2(output.script);
588
- break;
589
- }
590
- }
591
- } catch (parseError) {
592
- console.error(
593
- `\u274C Error parsing refundTx for anchor check (node ${chainNode.id}): ${parseError}`
594
- );
595
- console.log(
596
- ` This may indicate a corrupted refund transaction in the TreeNode.`
597
- );
598
- console.log(` Refund transaction hex: ${refundTxHex}`);
599
- console.log(
600
- ` Attempting to continue with original refund hex, but this transaction may be invalid.`
601
- );
602
- }
603
- const refundFeeBump = constructFeeBumpTx(
604
- refundTxHex,
605
- availableUtxos,
606
- feeRate,
607
- void 0
608
- );
609
- txPackages.push({
610
- tx: refundTxHex,
611
- feeBumpPsbt: refundFeeBump.feeBumpPsbt
612
- });
613
- }
614
- }
615
- result.push({
616
- leafId: node.id,
617
- txPackages
618
- });
619
- }
620
- return result;
621
- }
622
- function hash160(data) {
623
- const sha256Hash = sha2564(data);
624
- return ripemd160(sha256Hash);
625
- }
626
- function constructFeeBumpTx(txHex, utxos, feeRate, previousFeeBumpTx) {
627
- if (!txHex || txHex.length === 0) {
628
- throw new Error("Transaction hex string is empty or undefined");
629
- }
630
- if (utxos.length === 0) {
631
- throw new Error("No UTXOs available for fee bump");
632
- }
633
- let correctedTxHex = txHex;
634
- let parentTx;
635
- try {
636
- parentTx = getTxFromRawTxHex(correctedTxHex);
637
- if (!parentTx) {
638
- throw new Error("getTxFromRawTxHex returned null/undefined");
639
- }
640
- } catch (parseError) {
641
- throw new Error(
642
- `Failed to parse parent transaction hex: ${parseError}. Transaction hex: ${correctedTxHex}`
643
- );
644
- }
645
- try {
646
- const outputsLength = parentTx.outputsLength;
647
- const inputsLength = parentTx.inputsLength;
648
- if (typeof outputsLength !== "number" || outputsLength < 0) {
649
- throw new Error(
650
- "Invalid transaction: outputsLength is not a valid number"
651
- );
652
- }
653
- if (typeof inputsLength !== "number" || inputsLength < 0) {
654
- throw new Error(
655
- "Invalid transaction: inputsLength is not a valid number"
656
- );
657
- }
658
- } catch (validationError) {
659
- throw new Error(
660
- `Transaction validation failed: ${validationError}. This may indicate a corrupted or malformed transaction.`
661
- );
662
- }
663
- const parentTxIdFromLib = parentTx.id;
664
- let ephemeralAnchorIndex = -1;
665
- for (let i = 0; i < parentTx.outputsLength; i++) {
666
- const output = parentTx.getOutput(i);
667
- const isEphemeralAnchor = isEphemeralAnchorOutput(
668
- output?.script,
669
- output?.amount
670
- );
671
- if (isEphemeralAnchor) {
672
- ephemeralAnchorIndex = i;
673
- break;
674
- }
675
- }
676
- if (ephemeralAnchorIndex === -1) {
677
- throw new Error(
678
- "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)."
679
- );
680
- }
681
- const ephemeralAnchorOutput = parentTx.getOutput(ephemeralAnchorIndex);
682
- if (!ephemeralAnchorOutput)
683
- throw new Error("No ephemeral anchor output found");
684
- if (!ephemeralAnchorOutput.script)
685
- throw new Error("No script found in ephemeral anchor output");
686
- if (utxos.length === 0) {
687
- throw new Error("No UTXOs available for fee bump");
688
- }
689
- const builder = new btc2.Transaction({
690
- version: 3,
691
- allowUnknown: true,
692
- allowLegacyWitnessUtxo: true
693
- });
694
- let totalValue = 0n;
695
- const processedUtxos = [];
696
- for (let i = 0; i < utxos.length; i++) {
697
- const fundingUtxo = utxos[i];
698
- if (!fundingUtxo) {
699
- throw new Error(`UTXO at index ${i} is undefined`);
700
- }
701
- const pubKeyHash = hash160(hexToBytes3(fundingUtxo.publicKey));
702
- const scriptToUse = new Uint8Array([0, 20, ...pubKeyHash]);
703
- const providedScript = hexToBytes3(fundingUtxo.script);
704
- if (bytesToHex2(scriptToUse) !== bytesToHex2(providedScript)) {
705
- throw new Error(
706
- `\u274C Derived script doesn't match provided script for UTXO ${i + 1}.`
707
- );
708
- }
709
- builder.addInput({
710
- txid: fundingUtxo.txid,
711
- index: fundingUtxo.vout,
712
- sequence: 4294967295,
713
- witnessUtxo: {
714
- script: scriptToUse,
715
- // Always P2WPKH
716
- amount: fundingUtxo.value
717
- }
718
- });
719
- totalValue += fundingUtxo.value;
720
- processedUtxos.push({
721
- utxo: fundingUtxo,
722
- p2wpkhScript: scriptToUse
723
- });
724
- }
725
- builder.addInput({
726
- txid: parentTxIdFromLib,
727
- index: ephemeralAnchorIndex,
728
- sequence: 4294967295,
729
- witnessUtxo: {
730
- script: ephemeralAnchorOutput.script,
731
- // Use the original script directly (not P2WSH wrapped)
732
- amount: 0n
733
- }
734
- });
735
- const fee = 1500n;
736
- const remainingValue = totalValue - fee;
737
- if (remainingValue <= 0n) {
738
- throw new Error(
739
- `Insufficient funds for fee bump. Required fee: ${fee} sats, Available: ${totalValue} sats`
740
- );
741
- }
742
- if (processedUtxos.length === 0) {
743
- throw new Error("No processed UTXOs available for change output");
744
- }
745
- const firstProcessedUtxo = processedUtxos[0];
746
- if (!firstProcessedUtxo) {
747
- throw new Error("First processed UTXO is undefined");
748
- }
749
- builder.addOutput({
750
- script: firstProcessedUtxo.p2wpkhScript,
751
- amount: remainingValue
752
- });
753
- for (let i = 0; i < processedUtxos.length; i++) {
754
- const processed = processedUtxos[i];
755
- if (!processed) {
756
- throw new Error(`Processed UTXO at index ${i} is undefined`);
757
- }
758
- try {
759
- builder.updateInput(i, {
760
- witnessScript: processed.p2wpkhScript
761
- });
762
- builder.signIdx;
763
- } catch (error) {
764
- throw new Error(`Failed to handle funding UTXO input ${i + 1}: ${error}`);
765
- }
766
- }
767
- let psbtHex;
768
- try {
769
- psbtHex = bytesToHex2(builder.toPSBT());
770
- } catch (error) {
771
- throw new Error(`Failed to extract transaction: ${error}`);
772
- }
773
- return {
774
- feeBumpPsbt: psbtHex,
775
- usedUtxos: utxos,
776
- correctedParentTx: correctedTxHex !== txHex ? correctedTxHex : void 0
777
- };
778
- }
779
-
780
- // src/utils/xchain-address.ts
781
- import * as btc3 from "@scure/btc-signer";
782
- var networkByType = {
783
- MAINNET: btc3.NETWORK,
784
- TESTNET: btc3.TEST_NETWORK,
785
- REGTEST: {
786
- ...btc3.TEST_NETWORK,
787
- bech32: "bcrt"
788
- }
789
- };
790
- function getSparkAddressFromTaproot(taprootAddress) {
791
- for (const networkType of ["MAINNET", "TESTNET", "REGTEST"]) {
792
- try {
793
- const result = btc3.Address(networkByType[networkType]).decode(taprootAddress);
794
- if (result.type === "tr") {
795
- const outputPublicKey = result.pubkey;
796
- return encodeSparkAddress({
797
- identityPublicKey: Buffer.concat([
798
- Buffer.from([2]),
799
- outputPublicKey
800
- ]).toString("hex"),
801
- network: networkType
802
- });
803
- }
804
- } catch (_) {
805
- }
806
- }
807
- throw new ValidationError("Invalid taproot address");
808
- }
809
-
810
- export {
811
- computeTaprootKeyNoScript,
812
- getP2TRScriptFromPublicKey,
813
- getP2TRAddressFromPublicKey,
814
- getP2TRAddressFromPkScript,
815
- getP2WPKHAddressFromPublicKey,
816
- getTxFromRawTxHex,
817
- getTxFromRawTxBytes,
818
- getSigHashFromTx,
819
- getTxId,
820
- getTxIdNoReverse,
821
- DEFAULT_FEE_SATS,
822
- maybeApplyFee,
823
- createRefundTx,
824
- getCurrentTimelock,
825
- getTransactionSequence,
826
- checkIfValidSequence,
827
- getNextTransactionSequence,
828
- getEphemeralAnchorOutput,
829
- getTransferPackageSigningPayload,
830
- proofOfPossessionMessageHashForDepositAddress,
831
- getLatestDepositTxId,
832
- isTxBroadcast,
833
- isEphemeralAnchorOutput,
834
- constructUnilateralExitTxs,
835
- constructUnilateralExitFeeBumpPackages,
836
- constructFeeBumpTx,
837
- getSparkAddressFromTaproot
838
- };