@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
@@ -1,7 +1,6 @@
1
1
  import { schnorr, secp256k1 } from "@noble/curves/secp256k1";
2
2
  import { sha256 } from "@noble/hashes/sha2";
3
3
  import { hexToBytes } from "@noble/hashes/utils";
4
- import * as btc from "@scure/btc-signer";
5
4
  import { p2tr, Transaction } from "@scure/btc-signer";
6
5
  import { equalBytes } from "@scure/btc-signer/utils";
7
6
  import { NetworkError, ValidationError } from "../errors/types.js";
@@ -13,15 +12,16 @@ import {
13
12
  StartDepositTreeCreationResponse,
14
13
  } from "../proto/spark.js";
15
14
  import { KeyDerivation } from "../signer/types.js";
16
- import {
17
- getP2TRAddressFromPublicKey,
18
- getSigHashFromTx,
19
- getTxId,
20
- } from "../utils/bitcoin.js";
15
+ import { getSigHashFromTx, getTxId } from "../utils/bitcoin.js";
21
16
  import { subtractPublicKeys } from "../utils/keys.js";
22
17
  import { getNetwork } from "../utils/network.js";
23
18
  import { proofOfPossessionMessageHashForDepositAddress } from "../utils/proof.js";
24
- import { getEphemeralAnchorOutput } from "../utils/transaction.js";
19
+ import {
20
+ createRefundTxs,
21
+ createRootTx,
22
+ INITIAL_DIRECT_SEQUENCE,
23
+ INITIAL_SEQUENCE,
24
+ } from "../utils/transaction.js";
25
25
  import { WalletConfigService } from "./config.js";
26
26
  import { ConnectionManager } from "./connection.js";
27
27
 
@@ -185,8 +185,7 @@ export class DepositService {
185
185
  depositTx,
186
186
  vout,
187
187
  }: CreateTreeRootParams) {
188
- // Create a root tx
189
- const rootTx = new Transaction({ version: 3 });
188
+ // Create root transactions (CPFP and direct)
190
189
  const output = depositTx.getOutput(vout);
191
190
  if (!output) {
192
191
  throw new ValidationError("Invalid deposit transaction output", {
@@ -205,63 +204,461 @@ export class DepositService {
205
204
  });
206
205
  }
207
206
 
208
- // Calculate fee and adjust output amount
209
- let outputAmount = amount;
210
- /*if (outputAmount > DEFAULT_FEE_SATS) {
211
- outputAmount = outputAmount - BigInt(DEFAULT_FEE_SATS);
212
- }*/
213
-
214
- // Create new output with fee-adjusted amount
215
- rootTx.addInput({
216
- txid: getTxId(depositTx),
207
+ const depositOutPoint = {
208
+ txid: hexToBytes(getTxId(depositTx)),
217
209
  index: vout,
218
- });
219
-
220
- rootTx.addOutput({
210
+ };
211
+ const depositTxOut = {
221
212
  script,
222
- amount: outputAmount,
223
- });
213
+ amount,
214
+ };
224
215
 
225
- rootTx.addOutput(getEphemeralAnchorOutput());
216
+ const [cpfpRootTx, directRootTx] = createRootTx(
217
+ depositOutPoint,
218
+ depositTxOut,
219
+ );
226
220
 
227
- const rootNonceCommitment =
221
+ // Create nonce commitments for root transactions
222
+ const cpfpRootNonceCommitment =
223
+ await this.config.signer.getRandomSigningCommitment();
224
+ const directRootNonceCommitment =
228
225
  await this.config.signer.getRandomSigningCommitment();
229
- const rootTxSighash = getSigHashFromTx(rootTx, 0, output);
230
-
231
- // Create a refund tx
232
- const refundTx = new Transaction({ version: 3 });
233
- const sequence = (1 << 30) | INITIAL_TIME_LOCK;
234
- /* if (outputAmount > DEFAULT_FEE_SATS) {
235
- outputAmount = outputAmount - BigInt(DEFAULT_FEE_SATS);
236
- }*/
237
- refundTx.addInput({
238
- txid: getTxId(rootTx),
239
- index: 0,
240
- sequence,
241
- });
226
+
227
+ // Get sighashes for root transactions
228
+ const cpfpRootTxSighash = getSigHashFromTx(cpfpRootTx, 0, output);
229
+ const directRootTxSighash = getSigHashFromTx(directRootTx, 0, output);
242
230
 
243
231
  const signingPubKey =
244
232
  await this.config.signer.getPublicKeyFromDerivation(keyDerivation);
245
233
 
246
- const refundP2trAddress = getP2TRAddressFromPublicKey(
247
- signingPubKey,
248
- this.config.getNetwork(),
234
+ // Create refund transactions (CPFP and direct)
235
+ const { cpfpRefundTx, directRefundTx, directFromCpfpRefundTx } =
236
+ createRefundTxs({
237
+ sequence: INITIAL_SEQUENCE,
238
+ directSequence: INITIAL_DIRECT_SEQUENCE,
239
+ input: { txid: hexToBytes(getTxId(cpfpRootTx)), index: 0 },
240
+ directInput: { txid: hexToBytes(getTxId(directRootTx)), index: 0 },
241
+ amountSats: amount,
242
+ receivingPubkey: signingPubKey,
243
+ network: this.config.getNetwork(),
244
+ });
245
+
246
+ // Create nonce commitments for refund transactions
247
+ const cpfpRefundNonceCommitment =
248
+ await this.config.signer.getRandomSigningCommitment();
249
+ const directRefundNonceCommitment =
250
+ await this.config.signer.getRandomSigningCommitment();
251
+ const directFromCpfpRefundNonceCommitment =
252
+ await this.config.signer.getRandomSigningCommitment();
253
+
254
+ // Get sighashes for refund transactions
255
+ const cpfpRefundTxSighash = getSigHashFromTx(
256
+ cpfpRefundTx,
257
+ 0,
258
+ cpfpRootTx.getOutput(0),
259
+ );
260
+
261
+ if (!directRefundTx || !directFromCpfpRefundTx) {
262
+ throw new ValidationError(
263
+ "Expected direct refund transactions for tree creation",
264
+ {
265
+ field: "directRefundTx",
266
+ value: directRefundTx,
267
+ },
268
+ );
269
+ }
270
+
271
+ const directRefundTxSighash = getSigHashFromTx(
272
+ directRefundTx,
273
+ 0,
274
+ directRootTx.getOutput(0),
275
+ );
276
+ const directFromCpfpRefundTxSighash = getSigHashFromTx(
277
+ directFromCpfpRefundTx,
278
+ 0,
279
+ cpfpRootTx.getOutput(0),
280
+ );
281
+
282
+ const sparkClient = await this.connectionManager.createSparkClient(
283
+ this.config.getCoordinatorAddress(),
249
284
  );
250
- const refundAddress = btc
251
- .Address(getNetwork(this.config.getNetwork()))
252
- .decode(refundP2trAddress);
253
- const refundPkScript = btc.OutScript.encode(refundAddress);
254
-
255
- refundTx.addOutput({
256
- script: refundPkScript,
257
- amount: outputAmount,
285
+
286
+ let treeResp: StartDepositTreeCreationResponse;
287
+
288
+ try {
289
+ treeResp = await sparkClient.start_deposit_tree_creation({
290
+ identityPublicKey: await this.config.signer.getIdentityPublicKey(),
291
+ onChainUtxo: {
292
+ vout: vout,
293
+ rawTx: depositTx.toBytes(true),
294
+ network: this.config.getNetworkProto(),
295
+ },
296
+ rootTxSigningJob: {
297
+ rawTx: cpfpRootTx.toBytes(),
298
+ signingPublicKey: signingPubKey,
299
+ signingNonceCommitment: cpfpRootNonceCommitment.commitment,
300
+ },
301
+ refundTxSigningJob: {
302
+ rawTx: cpfpRefundTx.toBytes(),
303
+ signingPublicKey: signingPubKey,
304
+ signingNonceCommitment: cpfpRefundNonceCommitment.commitment,
305
+ },
306
+ directRootTxSigningJob: {
307
+ rawTx: directRootTx.toBytes(),
308
+ signingPublicKey: signingPubKey,
309
+ signingNonceCommitment: directRootNonceCommitment.commitment,
310
+ },
311
+ directRefundTxSigningJob: {
312
+ rawTx: directRefundTx.toBytes(),
313
+ signingPublicKey: signingPubKey,
314
+ signingNonceCommitment: directRefundNonceCommitment.commitment,
315
+ },
316
+ directFromCpfpRefundTxSigningJob: {
317
+ rawTx: directFromCpfpRefundTx.toBytes(),
318
+ signingPublicKey: signingPubKey,
319
+ signingNonceCommitment:
320
+ directFromCpfpRefundNonceCommitment.commitment,
321
+ },
322
+ });
323
+ } catch (error) {
324
+ throw new NetworkError(
325
+ "Failed to start deposit tree creation",
326
+ {
327
+ operation: "start_deposit_tree_creation",
328
+ errorCount: 1,
329
+ errors: error instanceof Error ? error.message : String(error),
330
+ },
331
+ error as Error,
332
+ );
333
+ }
334
+
335
+ if (!treeResp.rootNodeSignatureShares?.verifyingKey) {
336
+ throw new ValidationError("No verifying key found in tree response", {
337
+ field: "verifyingKey",
338
+ value: treeResp.rootNodeSignatureShares,
339
+ expected: "Non-null verifying key",
340
+ });
341
+ }
342
+
343
+ if (
344
+ !treeResp.rootNodeSignatureShares.nodeTxSigningResult
345
+ ?.signingNonceCommitments
346
+ ) {
347
+ throw new ValidationError(
348
+ "No signing nonce commitments found in tree response",
349
+ {
350
+ field: "nodeTxSigningResult.signingNonceCommitments",
351
+ value: treeResp.rootNodeSignatureShares.nodeTxSigningResult,
352
+ expected: "Non-null signing nonce commitments",
353
+ },
354
+ );
355
+ }
356
+
357
+ if (
358
+ !treeResp.rootNodeSignatureShares.refundTxSigningResult
359
+ ?.signingNonceCommitments
360
+ ) {
361
+ throw new ValidationError(
362
+ "No signing nonce commitments found in tree response",
363
+ {
364
+ field: "refundTxSigningResult.signingNonceCommitments",
365
+ },
366
+ );
367
+ }
368
+
369
+ if (
370
+ !treeResp.rootNodeSignatureShares.directNodeTxSigningResult
371
+ ?.signingNonceCommitments
372
+ ) {
373
+ throw new ValidationError(
374
+ "No direct node signing nonce commitments found in tree response",
375
+ {
376
+ field: "directNodeTxSigningResult.signingNonceCommitments",
377
+ },
378
+ );
379
+ }
380
+
381
+ if (
382
+ !treeResp.rootNodeSignatureShares.directRefundTxSigningResult
383
+ ?.signingNonceCommitments
384
+ ) {
385
+ throw new ValidationError(
386
+ "No direct refund signing nonce commitments found in tree response",
387
+ {
388
+ field: "directRefundTxSigningResult.signingNonceCommitments",
389
+ },
390
+ );
391
+ }
392
+
393
+ if (
394
+ !treeResp.rootNodeSignatureShares.directFromCpfpRefundTxSigningResult
395
+ ?.signingNonceCommitments
396
+ ) {
397
+ throw new ValidationError(
398
+ "No direct from CPFP refund signing nonce commitments found in tree response",
399
+ {
400
+ field: "directFromCpfpRefundTxSigningResult.signingNonceCommitments",
401
+ },
402
+ );
403
+ }
404
+
405
+ if (
406
+ !equalBytes(treeResp.rootNodeSignatureShares.verifyingKey, verifyingKey)
407
+ ) {
408
+ throw new ValidationError("Verifying key mismatch", {
409
+ field: "verifyingKey",
410
+ value: treeResp.rootNodeSignatureShares.verifyingKey,
411
+ expected: verifyingKey,
412
+ });
413
+ }
414
+
415
+ // Sign all four transactions
416
+ const cpfpRootSignature = await this.config.signer.signFrost({
417
+ message: cpfpRootTxSighash,
418
+ publicKey: signingPubKey,
419
+ keyDerivation,
420
+ verifyingKey,
421
+ selfCommitment: cpfpRootNonceCommitment,
422
+ statechainCommitments:
423
+ treeResp.rootNodeSignatureShares.nodeTxSigningResult
424
+ .signingNonceCommitments,
425
+ adaptorPubKey: new Uint8Array(),
426
+ });
427
+
428
+ const directRootSignature = await this.config.signer.signFrost({
429
+ message: directRootTxSighash,
430
+ publicKey: signingPubKey,
431
+ keyDerivation,
432
+ verifyingKey,
433
+ selfCommitment: directRootNonceCommitment,
434
+ statechainCommitments:
435
+ treeResp.rootNodeSignatureShares.directNodeTxSigningResult
436
+ .signingNonceCommitments,
437
+ adaptorPubKey: new Uint8Array(),
438
+ });
439
+
440
+ const cpfpRefundSignature = await this.config.signer.signFrost({
441
+ message: cpfpRefundTxSighash,
442
+ publicKey: signingPubKey,
443
+ keyDerivation,
444
+ verifyingKey: treeResp.rootNodeSignatureShares.verifyingKey,
445
+ selfCommitment: cpfpRefundNonceCommitment,
446
+ statechainCommitments:
447
+ treeResp.rootNodeSignatureShares.refundTxSigningResult
448
+ .signingNonceCommitments,
449
+ adaptorPubKey: new Uint8Array(),
450
+ });
451
+
452
+ const directRefundSignature = await this.config.signer.signFrost({
453
+ message: directRefundTxSighash,
454
+ publicKey: signingPubKey,
455
+ keyDerivation,
456
+ verifyingKey: treeResp.rootNodeSignatureShares.verifyingKey,
457
+ selfCommitment: directRefundNonceCommitment,
458
+ statechainCommitments:
459
+ treeResp.rootNodeSignatureShares.directRefundTxSigningResult
460
+ .signingNonceCommitments,
461
+ adaptorPubKey: new Uint8Array(),
258
462
  });
259
463
 
260
- refundTx.addOutput(getEphemeralAnchorOutput());
464
+ const directFromCpfpRefundSignature = await this.config.signer.signFrost({
465
+ message: directFromCpfpRefundTxSighash,
466
+ publicKey: signingPubKey,
467
+ keyDerivation,
468
+ verifyingKey: treeResp.rootNodeSignatureShares.verifyingKey,
469
+ selfCommitment: directFromCpfpRefundNonceCommitment,
470
+ statechainCommitments:
471
+ treeResp.rootNodeSignatureShares.directFromCpfpRefundTxSigningResult
472
+ .signingNonceCommitments,
473
+ adaptorPubKey: new Uint8Array(),
474
+ });
261
475
 
262
- const refundNonceCommitment =
476
+ // Aggregate all four signatures
477
+ const cpfpRootAggregate = await this.config.signer.aggregateFrost({
478
+ message: cpfpRootTxSighash,
479
+ statechainSignatures:
480
+ treeResp.rootNodeSignatureShares.nodeTxSigningResult.signatureShares,
481
+ statechainPublicKeys:
482
+ treeResp.rootNodeSignatureShares.nodeTxSigningResult.publicKeys,
483
+ verifyingKey: treeResp.rootNodeSignatureShares.verifyingKey,
484
+ statechainCommitments:
485
+ treeResp.rootNodeSignatureShares.nodeTxSigningResult
486
+ .signingNonceCommitments,
487
+ selfCommitment: cpfpRootNonceCommitment,
488
+ publicKey: signingPubKey,
489
+ selfSignature: cpfpRootSignature!,
490
+ adaptorPubKey: new Uint8Array(),
491
+ });
492
+
493
+ const directRootAggregate = await this.config.signer.aggregateFrost({
494
+ message: directRootTxSighash,
495
+ statechainSignatures:
496
+ treeResp.rootNodeSignatureShares.directNodeTxSigningResult
497
+ .signatureShares,
498
+ statechainPublicKeys:
499
+ treeResp.rootNodeSignatureShares.directNodeTxSigningResult.publicKeys,
500
+ verifyingKey: treeResp.rootNodeSignatureShares.verifyingKey,
501
+ statechainCommitments:
502
+ treeResp.rootNodeSignatureShares.directNodeTxSigningResult
503
+ .signingNonceCommitments,
504
+ selfCommitment: directRootNonceCommitment,
505
+ publicKey: signingPubKey,
506
+ selfSignature: directRootSignature!,
507
+ adaptorPubKey: new Uint8Array(),
508
+ });
509
+
510
+ const cpfpRefundAggregate = await this.config.signer.aggregateFrost({
511
+ message: cpfpRefundTxSighash,
512
+ statechainSignatures:
513
+ treeResp.rootNodeSignatureShares.refundTxSigningResult.signatureShares,
514
+ statechainPublicKeys:
515
+ treeResp.rootNodeSignatureShares.refundTxSigningResult.publicKeys,
516
+ verifyingKey: treeResp.rootNodeSignatureShares.verifyingKey,
517
+ statechainCommitments:
518
+ treeResp.rootNodeSignatureShares.refundTxSigningResult
519
+ .signingNonceCommitments,
520
+ selfCommitment: cpfpRefundNonceCommitment,
521
+ publicKey: signingPubKey,
522
+ selfSignature: cpfpRefundSignature!,
523
+ adaptorPubKey: new Uint8Array(),
524
+ });
525
+
526
+ const directRefundAggregate = await this.config.signer.aggregateFrost({
527
+ message: directRefundTxSighash,
528
+ statechainSignatures:
529
+ treeResp.rootNodeSignatureShares.directRefundTxSigningResult
530
+ .signatureShares,
531
+ statechainPublicKeys:
532
+ treeResp.rootNodeSignatureShares.directRefundTxSigningResult.publicKeys,
533
+ verifyingKey: treeResp.rootNodeSignatureShares.verifyingKey,
534
+ statechainCommitments:
535
+ treeResp.rootNodeSignatureShares.directRefundTxSigningResult
536
+ .signingNonceCommitments,
537
+ selfCommitment: directRefundNonceCommitment,
538
+ publicKey: signingPubKey,
539
+ selfSignature: directRefundSignature!,
540
+ adaptorPubKey: new Uint8Array(),
541
+ });
542
+
543
+ const directFromCpfpRefundAggregate =
544
+ await this.config.signer.aggregateFrost({
545
+ message: directFromCpfpRefundTxSighash,
546
+ statechainSignatures:
547
+ treeResp.rootNodeSignatureShares.directFromCpfpRefundTxSigningResult
548
+ .signatureShares,
549
+ statechainPublicKeys:
550
+ treeResp.rootNodeSignatureShares.directFromCpfpRefundTxSigningResult
551
+ .publicKeys,
552
+ verifyingKey: treeResp.rootNodeSignatureShares.verifyingKey,
553
+ statechainCommitments:
554
+ treeResp.rootNodeSignatureShares.directFromCpfpRefundTxSigningResult
555
+ .signingNonceCommitments,
556
+ selfCommitment: directFromCpfpRefundNonceCommitment,
557
+ publicKey: signingPubKey,
558
+ selfSignature: directFromCpfpRefundSignature!,
559
+ adaptorPubKey: new Uint8Array(),
560
+ });
561
+
562
+ let finalizeResp: FinalizeNodeSignaturesResponse;
563
+ try {
564
+ finalizeResp = await sparkClient.finalize_node_signatures_v2({
565
+ intent: SignatureIntent.CREATION,
566
+ nodeSignatures: [
567
+ {
568
+ nodeId: treeResp.rootNodeSignatureShares.nodeId,
569
+ nodeTxSignature: cpfpRootAggregate,
570
+ refundTxSignature: cpfpRefundAggregate,
571
+ directNodeTxSignature: directRootAggregate,
572
+ directRefundTxSignature: directRefundAggregate,
573
+ directFromCpfpRefundTxSignature: directFromCpfpRefundAggregate,
574
+ },
575
+ ],
576
+ });
577
+ } catch (error) {
578
+ throw new NetworkError(
579
+ "Failed to finalize node signatures",
580
+ {
581
+ operation: "finalize_node_signatures",
582
+ errorCount: 1,
583
+ errors: error instanceof Error ? error.message : String(error),
584
+ },
585
+ error as Error,
586
+ );
587
+ }
588
+
589
+ return finalizeResp;
590
+ }
591
+
592
+ /**
593
+ * @deprecated
594
+ * Use createTreeRoot instead.
595
+ * This is currently only used to test backwards compatibility.
596
+ */
597
+ async createTreeWithoutDirectTx({
598
+ keyDerivation,
599
+ verifyingKey,
600
+ depositTx,
601
+ vout,
602
+ }: CreateTreeRootParams) {
603
+ // Create root transactions (CPFP and direct)
604
+ const output = depositTx.getOutput(vout);
605
+ if (!output) {
606
+ throw new ValidationError("Invalid deposit transaction output", {
607
+ field: "vout",
608
+ value: vout,
609
+ expected: "Valid output index",
610
+ });
611
+ }
612
+ const script = output.script;
613
+ const amount = output.amount;
614
+ if (!script || !amount) {
615
+ throw new ValidationError("No script or amount found in deposit tx", {
616
+ field: "output",
617
+ value: output,
618
+ expected: "Output with script and amount",
619
+ });
620
+ }
621
+
622
+ const depositOutPoint = {
623
+ txid: hexToBytes(getTxId(depositTx)),
624
+ index: vout,
625
+ };
626
+ const depositTxOut = {
627
+ script,
628
+ amount,
629
+ };
630
+
631
+ const [cpfpRootTx, _] = createRootTx(depositOutPoint, depositTxOut);
632
+
633
+ // Create nonce commitments for root transactions
634
+ const cpfpRootNonceCommitment =
263
635
  await this.config.signer.getRandomSigningCommitment();
264
- const refundTxSighash = getSigHashFromTx(refundTx, 0, rootTx.getOutput(0));
636
+
637
+ // Get sighashes for root transactions
638
+ const cpfpRootTxSighash = getSigHashFromTx(cpfpRootTx, 0, output);
639
+
640
+ const signingPubKey =
641
+ await this.config.signer.getPublicKeyFromDerivation(keyDerivation);
642
+
643
+ // Create refund transactions (CPFP and direct)
644
+ const { cpfpRefundTx } = createRefundTxs({
645
+ sequence: INITIAL_SEQUENCE,
646
+ input: { txid: hexToBytes(getTxId(cpfpRootTx)), index: 0 },
647
+ amountSats: amount,
648
+ receivingPubkey: signingPubKey,
649
+ network: this.config.getNetwork(),
650
+ });
651
+
652
+ // Create nonce commitments for refund transactions
653
+ const cpfpRefundNonceCommitment =
654
+ await this.config.signer.getRandomSigningCommitment();
655
+
656
+ // Get sighashes for refund transactions
657
+ const cpfpRefundTxSighash = getSigHashFromTx(
658
+ cpfpRefundTx,
659
+ 0,
660
+ cpfpRootTx.getOutput(0),
661
+ );
265
662
 
266
663
  const sparkClient = await this.connectionManager.createSparkClient(
267
664
  this.config.getCoordinatorAddress(),
@@ -278,14 +675,14 @@ export class DepositService {
278
675
  network: this.config.getNetworkProto(),
279
676
  },
280
677
  rootTxSigningJob: {
281
- rawTx: rootTx.toBytes(),
678
+ rawTx: cpfpRootTx.toBytes(),
282
679
  signingPublicKey: signingPubKey,
283
- signingNonceCommitment: rootNonceCommitment.commitment,
680
+ signingNonceCommitment: cpfpRootNonceCommitment.commitment,
284
681
  },
285
682
  refundTxSigningJob: {
286
- rawTx: refundTx.toBytes(),
683
+ rawTx: cpfpRefundTx.toBytes(),
287
684
  signingPublicKey: signingPubKey,
288
- signingNonceCommitment: refundNonceCommitment.commitment,
685
+ signingNonceCommitment: cpfpRefundNonceCommitment.commitment,
289
686
  },
290
687
  });
291
688
  } catch (error) {
@@ -344,32 +741,32 @@ export class DepositService {
344
741
  });
345
742
  }
346
743
 
347
- const rootSignature = await this.config.signer.signFrost({
348
- message: rootTxSighash,
744
+ const cpfpRootSignature = await this.config.signer.signFrost({
745
+ message: cpfpRootTxSighash,
349
746
  publicKey: signingPubKey,
350
747
  keyDerivation,
351
748
  verifyingKey,
352
- selfCommitment: rootNonceCommitment,
749
+ selfCommitment: cpfpRootNonceCommitment,
353
750
  statechainCommitments:
354
751
  treeResp.rootNodeSignatureShares.nodeTxSigningResult
355
752
  .signingNonceCommitments,
356
753
  adaptorPubKey: new Uint8Array(),
357
754
  });
358
755
 
359
- const refundSignature = await this.config.signer.signFrost({
360
- message: refundTxSighash,
756
+ const cpfpRefundSignature = await this.config.signer.signFrost({
757
+ message: cpfpRefundTxSighash,
361
758
  publicKey: signingPubKey,
362
759
  keyDerivation,
363
- verifyingKey,
364
- selfCommitment: refundNonceCommitment,
760
+ verifyingKey: treeResp.rootNodeSignatureShares.verifyingKey,
761
+ selfCommitment: cpfpRefundNonceCommitment,
365
762
  statechainCommitments:
366
763
  treeResp.rootNodeSignatureShares.refundTxSigningResult
367
764
  .signingNonceCommitments,
368
765
  adaptorPubKey: new Uint8Array(),
369
766
  });
370
767
 
371
- const rootAggregate = await this.config.signer.aggregateFrost({
372
- message: rootTxSighash,
768
+ const cpfpRootAggregate = await this.config.signer.aggregateFrost({
769
+ message: cpfpRootTxSighash,
373
770
  statechainSignatures:
374
771
  treeResp.rootNodeSignatureShares.nodeTxSigningResult.signatureShares,
375
772
  statechainPublicKeys:
@@ -378,14 +775,14 @@ export class DepositService {
378
775
  statechainCommitments:
379
776
  treeResp.rootNodeSignatureShares.nodeTxSigningResult
380
777
  .signingNonceCommitments,
381
- selfCommitment: rootNonceCommitment,
778
+ selfCommitment: cpfpRootNonceCommitment,
382
779
  publicKey: signingPubKey,
383
- selfSignature: rootSignature!,
780
+ selfSignature: cpfpRootSignature!,
384
781
  adaptorPubKey: new Uint8Array(),
385
782
  });
386
783
 
387
- const refundAggregate = await this.config.signer.aggregateFrost({
388
- message: refundTxSighash,
784
+ const cpfpRefundAggregate = await this.config.signer.aggregateFrost({
785
+ message: cpfpRefundTxSighash,
389
786
  statechainSignatures:
390
787
  treeResp.rootNodeSignatureShares.refundTxSigningResult.signatureShares,
391
788
  statechainPublicKeys:
@@ -394,21 +791,21 @@ export class DepositService {
394
791
  statechainCommitments:
395
792
  treeResp.rootNodeSignatureShares.refundTxSigningResult
396
793
  .signingNonceCommitments,
397
- selfCommitment: refundNonceCommitment,
794
+ selfCommitment: cpfpRefundNonceCommitment,
398
795
  publicKey: signingPubKey,
399
- selfSignature: refundSignature,
796
+ selfSignature: cpfpRefundSignature!,
400
797
  adaptorPubKey: new Uint8Array(),
401
798
  });
402
799
 
403
800
  let finalizeResp: FinalizeNodeSignaturesResponse;
404
801
  try {
405
- finalizeResp = await sparkClient.finalize_node_signatures({
802
+ finalizeResp = await sparkClient.finalize_node_signatures_v2({
406
803
  intent: SignatureIntent.CREATION,
407
804
  nodeSignatures: [
408
805
  {
409
806
  nodeId: treeResp.rootNodeSignatureShares.nodeId,
410
- nodeTxSignature: rootAggregate,
411
- refundTxSignature: refundAggregate,
807
+ nodeTxSignature: cpfpRootAggregate,
808
+ refundTxSignature: cpfpRefundAggregate,
412
809
  },
413
810
  ],
414
811
  });