@buildonspark/spark-sdk 0.2.2 → 0.2.4

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 (94) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/{chunk-TM6CHQXC.js → chunk-3SEOTO43.js} +1 -1
  3. package/dist/{chunk-2ENZX6LT.js → chunk-AAZWSPUK.js} +84 -8
  4. package/dist/{chunk-4JD4HIAN.js → chunk-G4MSZ6DE.js} +299 -1
  5. package/dist/{chunk-S2AL73MZ.js → chunk-TVUMSHWA.js} +1 -1
  6. package/dist/{chunk-2TUM3R6C.js → chunk-W4ZRBSWM.js} +2351 -797
  7. package/dist/{chunk-CDLETEDT.js → chunk-WAQKYSDI.js} +13 -1
  8. package/dist/{client-CGTRS23n.d.ts → client-BF4cn8F4.d.ts} +15 -3
  9. package/dist/{client-CcYzmpmj.d.cts → client-KhNkrXz4.d.cts} +15 -3
  10. package/dist/debug.cjs +2948 -1023
  11. package/dist/debug.d.cts +19 -6
  12. package/dist/debug.d.ts +19 -6
  13. package/dist/debug.js +5 -5
  14. package/dist/graphql/objects/index.cjs +13 -1
  15. package/dist/graphql/objects/index.d.cts +2 -2
  16. package/dist/graphql/objects/index.d.ts +2 -2
  17. package/dist/graphql/objects/index.js +1 -1
  18. package/dist/index.cjs +2794 -858
  19. package/dist/index.d.cts +190 -9
  20. package/dist/index.d.ts +190 -9
  21. package/dist/index.js +32 -6
  22. package/dist/index.node.cjs +2931 -892
  23. package/dist/index.node.d.cts +10 -188
  24. package/dist/index.node.d.ts +10 -188
  25. package/dist/index.node.js +134 -6
  26. package/dist/native/index.cjs +2794 -858
  27. package/dist/native/index.d.cts +148 -40
  28. package/dist/native/index.d.ts +148 -40
  29. package/dist/native/index.js +2799 -877
  30. package/dist/proto/lrc20.d.cts +1 -1
  31. package/dist/proto/lrc20.d.ts +1 -1
  32. package/dist/proto/lrc20.js +1 -1
  33. package/dist/proto/spark.cjs +84 -8
  34. package/dist/proto/spark.d.cts +1 -1
  35. package/dist/proto/spark.d.ts +1 -1
  36. package/dist/proto/spark.js +1 -1
  37. package/dist/proto/spark_token.cjs +301 -0
  38. package/dist/proto/spark_token.d.cts +35 -2
  39. package/dist/proto/spark_token.d.ts +35 -2
  40. package/dist/proto/spark_token.js +8 -2
  41. package/dist/{sdk-types-DJ2ve9YY.d.cts → sdk-types-CB9HrW5O.d.cts} +1 -1
  42. package/dist/{sdk-types-DCIVdKUT.d.ts → sdk-types-CkRNraXT.d.ts} +1 -1
  43. package/dist/{spark-BUOx3U7Q.d.cts → spark-B_7nZx6T.d.cts} +112 -10
  44. package/dist/{spark-BUOx3U7Q.d.ts → spark-B_7nZx6T.d.ts} +112 -10
  45. package/dist/{spark-wallet-B_96y9BS.d.ts → spark-wallet-C1Tr_VKI.d.ts} +38 -28
  46. package/dist/{spark-wallet-CHwKQYJu.d.cts → spark-wallet-DG3x2obf.d.cts} +38 -28
  47. package/dist/spark-wallet.node-CGxoeCpH.d.ts +13 -0
  48. package/dist/spark-wallet.node-CN9LoB_O.d.cts +13 -0
  49. package/dist/tests/test-utils.cjs +1086 -218
  50. package/dist/tests/test-utils.d.cts +13 -13
  51. package/dist/tests/test-utils.d.ts +13 -13
  52. package/dist/tests/test-utils.js +56 -19
  53. package/dist/types/index.cjs +97 -9
  54. package/dist/types/index.d.cts +3 -3
  55. package/dist/types/index.d.ts +3 -3
  56. package/dist/types/index.js +3 -3
  57. package/dist/{xchain-address-D5MIHCDL.d.cts → xchain-address-BHu6CpZC.d.ts} +55 -8
  58. package/dist/{xchain-address-DLbW1iDh.d.ts → xchain-address-HBr6isnc.d.cts} +55 -8
  59. package/package.json +1 -1
  60. package/src/graphql/client.ts +8 -0
  61. package/src/graphql/mutations/CompleteLeavesSwap.ts +9 -1
  62. package/src/graphql/mutations/RequestSwapLeaves.ts +4 -0
  63. package/src/graphql/objects/CompleteLeavesSwapInput.ts +34 -34
  64. package/src/graphql/objects/LeavesSwapRequest.ts +4 -0
  65. package/src/graphql/objects/RequestLeavesSwapInput.ts +48 -47
  66. package/src/graphql/objects/SwapLeaf.ts +40 -32
  67. package/src/graphql/objects/UserLeafInput.ts +24 -0
  68. package/src/graphql/objects/UserRequest.ts +4 -0
  69. package/src/index.node.ts +1 -1
  70. package/src/native/index.ts +4 -5
  71. package/src/proto/spark.ts +172 -16
  72. package/src/proto/spark_token.ts +369 -0
  73. package/src/services/coop-exit.ts +171 -36
  74. package/src/services/deposit.ts +471 -74
  75. package/src/services/lightning.ts +18 -5
  76. package/src/services/signing.ts +162 -50
  77. package/src/services/token-transactions.ts +6 -2
  78. package/src/services/transfer.ts +950 -384
  79. package/src/services/tree-creation.ts +342 -121
  80. package/src/spark-wallet/spark-wallet.node.ts +71 -66
  81. package/src/spark-wallet/spark-wallet.ts +459 -166
  82. package/src/tests/integration/coop-exit.test.ts +3 -8
  83. package/src/tests/integration/deposit.test.ts +3 -3
  84. package/src/tests/integration/lightning.test.ts +521 -466
  85. package/src/tests/integration/swap.test.ts +559 -307
  86. package/src/tests/integration/transfer.test.ts +625 -623
  87. package/src/tests/integration/wallet.test.ts +2 -2
  88. package/src/tests/integration/watchtower.test.ts +211 -0
  89. package/src/tests/test-utils.ts +63 -14
  90. package/src/tests/utils/test-faucet.ts +4 -2
  91. package/src/utils/adaptor-signature.ts +15 -5
  92. package/src/utils/fetch.ts +75 -0
  93. package/src/utils/mempool.ts +9 -4
  94. package/src/utils/transaction.ts +388 -26
@@ -4,7 +4,18 @@ import { ValidationError } from "../errors/types.js";
4
4
  import { getP2TRScriptFromPublicKey } from "./bitcoin.js";
5
5
  import { Network } from "./network.js";
6
6
 
7
+ const INITIAL_TIMELOCK = 2000;
8
+ const TEST_UNILATERAL_TIMELOCK = 100;
9
+
7
10
  const TIME_LOCK_INTERVAL = 100;
11
+ export const DIRECT_TIMELOCK_OFFSET = 50;
12
+
13
+ export const INITIAL_SEQUENCE = (1 << 30) | INITIAL_TIMELOCK;
14
+ export const INITIAL_DIRECT_SEQUENCE =
15
+ (1 << 30) | (INITIAL_TIMELOCK + DIRECT_TIMELOCK_OFFSET);
16
+ export const TEST_UNILATERAL_SEQUENCE = (1 << 30) | TEST_UNILATERAL_TIMELOCK;
17
+ export const TEST_UNILATERAL_DIRECT_SEQUENCE =
18
+ (1 << 30) | (TEST_UNILATERAL_TIMELOCK + DIRECT_TIMELOCK_OFFSET);
8
19
 
9
20
  // Default fee constants matching Go implementation
10
21
  const ESTIMATED_TX_SIZE = 191;
@@ -22,41 +33,383 @@ export function maybeApplyFee(amount: bigint): bigint {
22
33
  return amount;
23
34
  }
24
35
 
25
- export function createRefundTx(
36
+ export function createRootTx(
37
+ depositOutPoint: TransactionInput,
38
+ depositTxOut: TransactionOutput,
39
+ ): [Transaction, Transaction] {
40
+ // Create CPFP-friendly root tx (with ephemeral anchor, no fee)
41
+ const cpfpRootTx = new Transaction({
42
+ version: 3,
43
+ allowUnknownOutputs: true,
44
+ });
45
+ cpfpRootTx.addInput(depositOutPoint);
46
+ cpfpRootTx.addOutput(depositTxOut);
47
+ cpfpRootTx.addOutput(getEphemeralAnchorOutput());
48
+
49
+ // Create direct root tx (with fee, no anchor)
50
+ const directRootTx = new Transaction({
51
+ version: 3,
52
+ allowUnknownOutputs: true,
53
+ });
54
+ directRootTx.addInput(depositOutPoint);
55
+ directRootTx.addOutput({
56
+ script: depositTxOut.script,
57
+ amount: maybeApplyFee(depositTxOut.amount ?? 0n),
58
+ });
59
+
60
+ return [cpfpRootTx, directRootTx];
61
+ }
62
+
63
+ export function createSplitTx(
64
+ parentOutPoint: TransactionInput,
65
+ childTxOuts: TransactionOutput[],
66
+ ): [Transaction, Transaction] {
67
+ // Create CPFP-friendly split tx (with ephemeral anchor, no fee)
68
+ const cpfpSplitTx = new Transaction({
69
+ version: 3,
70
+ allowUnknownOutputs: true,
71
+ });
72
+ cpfpSplitTx.addInput(parentOutPoint);
73
+ for (const txOut of childTxOuts) {
74
+ cpfpSplitTx.addOutput(txOut);
75
+ }
76
+ cpfpSplitTx.addOutput(getEphemeralAnchorOutput());
77
+
78
+ // Create direct split tx (with fee, no anchor)
79
+ const directSplitTx = new Transaction({
80
+ version: 3,
81
+ allowUnknownOutputs: true,
82
+ });
83
+ directSplitTx.addInput(parentOutPoint);
84
+
85
+ // Adjust output amounts to account for fee
86
+ let totalOutputAmount = 0n;
87
+ for (const txOut of childTxOuts) {
88
+ totalOutputAmount += txOut.amount ?? 0n;
89
+ }
90
+
91
+ if (totalOutputAmount > BigInt(DEFAULT_FEE_SATS)) {
92
+ // Distribute fee proportionally across outputs
93
+ const feeRatio = Number(DEFAULT_FEE_SATS) / Number(totalOutputAmount);
94
+ for (const txOut of childTxOuts) {
95
+ const adjustedAmount = BigInt(
96
+ Math.floor(Number(txOut.amount ?? 0n) * (1 - feeRatio)),
97
+ );
98
+ directSplitTx.addOutput({
99
+ script: txOut.script,
100
+ amount: adjustedAmount,
101
+ });
102
+ }
103
+ } else {
104
+ // If fee is larger than total output, just pass through original amounts
105
+ for (const txOut of childTxOuts) {
106
+ directSplitTx.addOutput(txOut);
107
+ }
108
+ }
109
+
110
+ return [cpfpSplitTx, directSplitTx];
111
+ }
112
+
113
+ interface CreateNodeTxInput {
114
+ txOut: TransactionOutput;
115
+ parentOutPoint: TransactionInput;
116
+ applyFee?: boolean;
117
+ includeAnchor?: boolean;
118
+ }
119
+ // createNodeTx creates a node transaction.
120
+ // This stands in between a split tx and a leaf node tx,
121
+ // and has no timelock.
122
+ export function createNodeTx({
123
+ txOut,
124
+ parentOutPoint,
125
+ applyFee,
126
+ includeAnchor,
127
+ }: CreateNodeTxInput): Transaction {
128
+ const nodeTx = new Transaction({
129
+ version: 3,
130
+ allowUnknownOutputs: true,
131
+ });
132
+ nodeTx.addInput(parentOutPoint);
133
+
134
+ if (applyFee) {
135
+ nodeTx.addOutput({
136
+ script: txOut.script,
137
+ amount: maybeApplyFee(txOut.amount ?? 0n),
138
+ });
139
+ } else {
140
+ nodeTx.addOutput(txOut);
141
+ }
142
+
143
+ if (includeAnchor) {
144
+ nodeTx.addOutput(getEphemeralAnchorOutput());
145
+ }
146
+
147
+ return nodeTx;
148
+ }
149
+
150
+ export function createNodeTxs(
151
+ txOut: TransactionOutput,
152
+ txIn: TransactionInput,
153
+ directTxIn?: TransactionInput,
154
+ ): {
155
+ cpfpNodeTx: Transaction;
156
+ directNodeTx?: Transaction;
157
+ } {
158
+ const cpfpNodeTx = createNodeTx({
159
+ txOut,
160
+ parentOutPoint: txIn,
161
+ includeAnchor: true,
162
+ });
163
+
164
+ let directNodeTx: Transaction | undefined;
165
+ if (directTxIn) {
166
+ directNodeTx = createNodeTx({
167
+ txOut,
168
+ parentOutPoint: directTxIn,
169
+ includeAnchor: false,
170
+ applyFee: true,
171
+ });
172
+ }
173
+
174
+ return { cpfpNodeTx, directNodeTx };
175
+ }
176
+
177
+ // createLeafNodeTx creates a leaf node transaction.
178
+ // This transaction provides an intermediate transaction
179
+ // to allow the timelock of the final refund transaction
180
+ // to be extended. E.g. when the refund tx timelock reaches
181
+ // 0, the leaf node tx can be re-signed with a decremented
182
+ // timelock, and the refund tx can be reset it's timelock.
183
+ export function createLeafNodeTx(
26
184
  sequence: number,
27
- nodeOutPoint: TransactionInput,
28
- amountSats: bigint,
29
- receivingPubkey: Uint8Array,
30
- network: Network,
31
- ): Transaction {
32
- const newRefundTx = new Transaction({
185
+ directSequence: number,
186
+ parentOutPoint: TransactionInput,
187
+ txOut: TransactionOutput,
188
+ shouldCalculateFee: boolean,
189
+ ): [Transaction, Transaction] {
190
+ // Create CPFP-friendly leaf node tx (with ephemeral anchor, no fee)
191
+ const cpfpLeafTx = new Transaction({
33
192
  version: 3,
34
193
  allowUnknownOutputs: true,
35
194
  });
36
- newRefundTx.addInput({
37
- ...nodeOutPoint,
195
+ cpfpLeafTx.addInput({
196
+ ...parentOutPoint,
197
+ sequence,
198
+ });
199
+ cpfpLeafTx.addOutput(txOut);
200
+ cpfpLeafTx.addOutput(getEphemeralAnchorOutput());
201
+
202
+ // Create direct leaf node tx (with fee, no anchor)
203
+ const directLeafTx = new Transaction({
204
+ version: 3,
205
+ allowUnknownOutputs: true,
206
+ });
207
+ directLeafTx.addInput({
208
+ ...parentOutPoint,
209
+ sequence: directSequence,
210
+ });
211
+ const amountSats = txOut.amount ?? 0n;
212
+ let outputAmount = amountSats;
213
+ if (shouldCalculateFee) {
214
+ outputAmount = maybeApplyFee(amountSats);
215
+ }
216
+ directLeafTx.addOutput({
217
+ script: txOut.script,
218
+ amount: outputAmount,
219
+ });
220
+
221
+ return [cpfpLeafTx, directLeafTx];
222
+ }
223
+
224
+ interface CreateRefundTxInput {
225
+ sequence: number;
226
+ input: TransactionInput;
227
+ amountSats: bigint;
228
+ receivingPubkey: Uint8Array;
229
+ network: Network;
230
+ shouldCalculateFee: boolean;
231
+ includeAnchor: boolean;
232
+ }
233
+ export function createRefundTx({
234
+ sequence,
235
+ input,
236
+ amountSats,
237
+ receivingPubkey,
238
+ network,
239
+ shouldCalculateFee,
240
+ includeAnchor,
241
+ }: CreateRefundTxInput): Transaction {
242
+ const refundTx = new Transaction({
243
+ version: 3,
244
+ allowUnknownOutputs: true,
245
+ });
246
+ refundTx.addInput({
247
+ ...input,
38
248
  sequence,
39
249
  });
40
250
 
41
251
  const refundPkScript = getP2TRScriptFromPublicKey(receivingPubkey, network);
42
252
 
43
- newRefundTx.addOutput({
253
+ let outputAmount = amountSats;
254
+ if (shouldCalculateFee) {
255
+ outputAmount = maybeApplyFee(amountSats);
256
+ }
257
+
258
+ refundTx.addOutput({
44
259
  script: refundPkScript,
260
+ amount: outputAmount,
261
+ });
262
+
263
+ if (includeAnchor) {
264
+ refundTx.addOutput(getEphemeralAnchorOutput());
265
+ }
266
+
267
+ return refundTx;
268
+ }
269
+
270
+ interface CreateRefundTxsInput {
271
+ sequence: number;
272
+ directSequence?: number;
273
+ input: TransactionInput;
274
+ directInput?: TransactionInput;
275
+ amountSats: bigint;
276
+ receivingPubkey: Uint8Array;
277
+ network: Network;
278
+ }
279
+ export function createRefundTxs({
280
+ sequence,
281
+ directSequence,
282
+ input,
283
+ directInput,
284
+ amountSats,
285
+ receivingPubkey,
286
+ network,
287
+ }: CreateRefundTxsInput): {
288
+ cpfpRefundTx: Transaction;
289
+ directRefundTx?: Transaction;
290
+ directFromCpfpRefundTx?: Transaction;
291
+ } {
292
+ const cpfpRefundTx = createRefundTx({
293
+ sequence,
294
+ input,
295
+ amountSats,
296
+ receivingPubkey,
297
+ network,
298
+ shouldCalculateFee: false,
299
+ includeAnchor: true,
300
+ });
301
+
302
+ let directRefundTx: Transaction | undefined;
303
+ let directFromCpfpRefundTx: Transaction | undefined;
304
+ if (directSequence && directInput) {
305
+ directRefundTx = createRefundTx({
306
+ sequence: directSequence,
307
+ input: directInput,
308
+ amountSats,
309
+ receivingPubkey,
310
+ network,
311
+ shouldCalculateFee: true,
312
+ includeAnchor: false,
313
+ });
314
+ directFromCpfpRefundTx = createRefundTx({
315
+ sequence: directSequence,
316
+ input,
317
+ amountSats,
318
+ receivingPubkey,
319
+ network,
320
+ shouldCalculateFee: true,
321
+ includeAnchor: false,
322
+ });
323
+ } else if (directInput && !directSequence) {
324
+ throw new ValidationError(
325
+ "directSequence must be provided if directInput is",
326
+ {
327
+ field: "directSequence",
328
+ value: directSequence,
329
+ },
330
+ );
331
+ }
332
+
333
+ return { cpfpRefundTx, directRefundTx, directFromCpfpRefundTx };
334
+ }
335
+
336
+ export function createConnectorRefundTransactions(
337
+ sequence: number,
338
+ cpfpNodeOutPoint: TransactionInput,
339
+ directNodeOutPoint: TransactionInput,
340
+ connectorOutput: TransactionInput,
341
+ amountSats: bigint,
342
+ receiverPubKey: Uint8Array,
343
+ network: Network,
344
+ shouldCalculateFee: boolean,
345
+ ): [Transaction, Transaction, Transaction] {
346
+ // Create CPFP-friendly connector refund tx (with ephemeral anchor, no fee)
347
+ const cpfpRefundTx = new Transaction({
348
+ version: 3,
349
+ allowUnknownOutputs: true,
350
+ });
351
+ cpfpRefundTx.addInput({
352
+ ...cpfpNodeOutPoint,
353
+ sequence,
354
+ });
355
+ cpfpRefundTx.addInput(connectorOutput);
356
+ const receiverScript = getP2TRScriptFromPublicKey(receiverPubKey, network);
357
+ cpfpRefundTx.addOutput({
358
+ script: receiverScript,
45
359
  amount: amountSats,
46
360
  });
47
361
 
48
- newRefundTx.addOutput(getEphemeralAnchorOutput());
362
+ // Create direct connector refund tx (with fee, no anchor)
363
+ const directRefundTx = new Transaction({
364
+ version: 3,
365
+ allowUnknownOutputs: true,
366
+ });
367
+ directRefundTx.addInput({
368
+ ...directNodeOutPoint,
369
+ sequence,
370
+ });
371
+ directRefundTx.addInput(connectorOutput);
372
+
373
+ let outputAmount = amountSats;
374
+ if (shouldCalculateFee) {
375
+ outputAmount = maybeApplyFee(amountSats);
376
+ }
377
+ directRefundTx.addOutput({
378
+ script: receiverScript,
379
+ amount: outputAmount,
380
+ });
381
+
382
+ // Create direct-style refund tx that spends from CPFP outpoint (with fee, no anchor)
383
+ const directFromCpfpTx = new Transaction({
384
+ version: 3,
385
+ allowUnknownOutputs: true,
386
+ });
387
+ directFromCpfpTx.addInput({
388
+ ...cpfpNodeOutPoint,
389
+ sequence,
390
+ });
391
+ directFromCpfpTx.addInput(connectorOutput);
392
+ directFromCpfpTx.addOutput({
393
+ script: receiverScript,
394
+ amount: outputAmount,
395
+ });
49
396
 
50
- return newRefundTx;
397
+ return [cpfpRefundTx, directRefundTx, directFromCpfpTx];
51
398
  }
52
399
 
53
400
  export function getCurrentTimelock(currSequence?: number): number {
54
401
  return (currSequence || 0) & 0xffff;
55
402
  }
56
403
 
57
- export function getTransactionSequence(currSequence?: number): number {
404
+ export function getTransactionSequence(currSequence?: number): {
405
+ nextSequence: number;
406
+ nextDirectSequence: number;
407
+ } {
58
408
  const timelock = getCurrentTimelock(currSequence);
59
- return (1 << 30) | timelock;
409
+ return {
410
+ nextSequence: (1 << 30) | timelock,
411
+ nextDirectSequence: (1 << 30) | (timelock + DIRECT_TIMELOCK_OFFSET),
412
+ };
60
413
  }
61
414
 
62
415
  export function checkIfValidSequence(currSequence?: number) {
@@ -79,34 +432,43 @@ export function checkIfValidSequence(currSequence?: number) {
79
432
  }
80
433
  }
81
434
 
435
+ export function doesLeafNeedRefresh(currSequence: number, isNodeTx?: boolean) {
436
+ const currentTimelock = getCurrentTimelock(currSequence);
437
+
438
+ if (isNodeTx) {
439
+ return currentTimelock === 0;
440
+ }
441
+ return currentTimelock <= 100;
442
+ }
443
+
82
444
  // make sure that the leaves are ok before sending or else next user could lose funds
83
445
  export function getNextTransactionSequence(
84
- currSequence?: number,
85
- forRefresh?: boolean,
446
+ currSequence: number,
447
+ isNodeTx?: boolean,
86
448
  ): {
87
449
  nextSequence: number;
88
- needRefresh: boolean;
450
+ nextDirectSequence: number;
89
451
  } {
90
452
  const currentTimelock = getCurrentTimelock(currSequence);
91
453
  const nextTimelock = currentTimelock - TIME_LOCK_INTERVAL;
92
454
 
93
- if (forRefresh && nextTimelock <= 100 && currentTimelock > 0) {
94
- return {
95
- nextSequence: (1 << 30) | nextTimelock,
96
- needRefresh: true,
97
- };
98
- }
99
-
100
- if (nextTimelock < 0) {
455
+ if (isNodeTx && nextTimelock < 0) {
101
456
  throw new ValidationError("timelock interval is less than 0", {
102
457
  field: "nextTimelock",
103
458
  value: nextTimelock,
459
+ expected: "Non-negative timelock interval",
460
+ });
461
+ } else if (!isNodeTx && nextTimelock <= 0) {
462
+ throw new ValidationError("timelock interval is less than or equal to 0", {
463
+ field: "nextTimelock",
464
+ value: nextTimelock,
465
+ expected: "Timelock greater than 0",
104
466
  });
105
467
  }
106
468
 
107
469
  return {
108
470
  nextSequence: (1 << 30) | nextTimelock,
109
- needRefresh: nextTimelock <= 100,
471
+ nextDirectSequence: (1 << 30) | (nextTimelock + DIRECT_TIMELOCK_OFFSET),
110
472
  };
111
473
  }
112
474