@buildonspark/spark-sdk 0.2.3 → 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 (73) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/{chunk-PTRXJS7Q.js → chunk-TVUMSHWA.js} +1 -1
  3. package/dist/{chunk-PLLJIZC3.js → chunk-W4ZRBSWM.js} +2298 -778
  4. package/dist/{chunk-CDLETEDT.js → chunk-WAQKYSDI.js} +13 -1
  5. package/dist/{client-CGTRS23n.d.ts → client-BF4cn8F4.d.ts} +15 -3
  6. package/dist/{client-CcYzmpmj.d.cts → client-KhNkrXz4.d.cts} +15 -3
  7. package/dist/debug.cjs +2282 -762
  8. package/dist/debug.d.cts +17 -4
  9. package/dist/debug.d.ts +17 -4
  10. package/dist/debug.js +2 -2
  11. package/dist/graphql/objects/index.cjs +13 -1
  12. package/dist/graphql/objects/index.d.cts +2 -2
  13. package/dist/graphql/objects/index.d.ts +2 -2
  14. package/dist/graphql/objects/index.js +1 -1
  15. package/dist/index.cjs +2283 -752
  16. package/dist/index.d.cts +189 -8
  17. package/dist/index.d.ts +189 -8
  18. package/dist/index.js +29 -3
  19. package/dist/index.node.cjs +2387 -753
  20. package/dist/index.node.d.cts +9 -189
  21. package/dist/index.node.d.ts +9 -189
  22. package/dist/index.node.js +131 -3
  23. package/dist/native/index.cjs +2283 -752
  24. package/dist/native/index.d.cts +95 -30
  25. package/dist/native/index.d.ts +95 -30
  26. package/dist/native/index.js +2284 -767
  27. package/dist/{spark-wallet-CxcGPXRB.d.ts → spark-wallet-C1Tr_VKI.d.ts} +31 -25
  28. package/dist/{spark-wallet-DJJm19BP.d.cts → spark-wallet-DG3x2obf.d.cts} +31 -25
  29. package/dist/spark-wallet.node-CGxoeCpH.d.ts +13 -0
  30. package/dist/spark-wallet.node-CN9LoB_O.d.cts +13 -0
  31. package/dist/tests/test-utils.cjs +570 -73
  32. package/dist/tests/test-utils.d.cts +11 -11
  33. package/dist/tests/test-utils.d.ts +11 -11
  34. package/dist/tests/test-utils.js +53 -16
  35. package/dist/types/index.cjs +13 -1
  36. package/dist/types/index.d.cts +1 -1
  37. package/dist/types/index.d.ts +1 -1
  38. package/dist/types/index.js +1 -1
  39. package/dist/{xchain-address-Bh9w1SeC.d.ts → xchain-address-BHu6CpZC.d.ts} +54 -7
  40. package/dist/{xchain-address-SZ7dkVUE.d.cts → xchain-address-HBr6isnc.d.cts} +54 -7
  41. package/package.json +1 -1
  42. package/src/graphql/client.ts +8 -0
  43. package/src/graphql/mutations/CompleteLeavesSwap.ts +9 -1
  44. package/src/graphql/mutations/RequestSwapLeaves.ts +4 -0
  45. package/src/graphql/objects/CompleteLeavesSwapInput.ts +34 -34
  46. package/src/graphql/objects/LeavesSwapRequest.ts +4 -0
  47. package/src/graphql/objects/RequestLeavesSwapInput.ts +48 -47
  48. package/src/graphql/objects/SwapLeaf.ts +40 -32
  49. package/src/graphql/objects/UserLeafInput.ts +24 -0
  50. package/src/graphql/objects/UserRequest.ts +4 -0
  51. package/src/index.node.ts +1 -1
  52. package/src/native/index.ts +4 -5
  53. package/src/services/coop-exit.ts +171 -36
  54. package/src/services/deposit.ts +471 -74
  55. package/src/services/lightning.ts +18 -5
  56. package/src/services/signing.ts +162 -50
  57. package/src/services/transfer.ts +950 -384
  58. package/src/services/tree-creation.ts +342 -121
  59. package/src/spark-wallet/spark-wallet.node.ts +71 -66
  60. package/src/spark-wallet/spark-wallet.ts +405 -153
  61. package/src/tests/integration/coop-exit.test.ts +3 -8
  62. package/src/tests/integration/deposit.test.ts +3 -3
  63. package/src/tests/integration/lightning.test.ts +521 -466
  64. package/src/tests/integration/swap.test.ts +559 -307
  65. package/src/tests/integration/transfer.test.ts +625 -623
  66. package/src/tests/integration/wallet.test.ts +2 -2
  67. package/src/tests/integration/watchtower.test.ts +211 -0
  68. package/src/tests/test-utils.ts +63 -14
  69. package/src/tests/utils/test-faucet.ts +4 -2
  70. package/src/utils/adaptor-signature.ts +15 -5
  71. package/src/utils/fetch.ts +75 -0
  72. package/src/utils/mempool.ts +9 -4
  73. package/src/utils/transaction.ts +388 -26
@@ -1,63 +1,64 @@
1
-
2
1
  // Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved
3
2
 
4
-
5
- import {UserLeafInputFromJson} from './UserLeafInput.js';
6
- import {UserLeafInputToJson} from './UserLeafInput.js';
7
- import UserLeafInput from './UserLeafInput.js';
8
-
3
+ import UserLeafInput, {
4
+ UserLeafInputFromJson,
5
+ UserLeafInputToJson,
6
+ } from "./UserLeafInput.js";
9
7
 
10
8
  interface RequestLeavesSwapInput {
9
+ adaptorPubkey: string;
11
10
 
11
+ directAdaptorPubkey?: string;
12
12
 
13
- adaptorPubkey: string;
14
-
15
- totalAmountSats: number;
16
-
17
- targetAmountSats: number;
18
-
19
- feeSats: number;
20
-
21
- userLeaves: UserLeafInput[];
22
-
23
- idempotencyKey: string;
13
+ directFromCpfpAdaptorPubkey?: string;
24
14
 
25
- targetAmountSatsList?: number[] | undefined;
15
+ totalAmountSats: number;
26
16
 
17
+ targetAmountSats: number;
27
18
 
19
+ feeSats: number;
28
20
 
21
+ userLeaves: UserLeafInput[];
29
22
 
30
- }
31
-
32
- export const RequestLeavesSwapInputFromJson = (obj: any): RequestLeavesSwapInput => {
33
- return {
34
- adaptorPubkey: obj["request_leaves_swap_input_adaptor_pubkey"],
35
- totalAmountSats: obj["request_leaves_swap_input_total_amount_sats"],
36
- targetAmountSats: obj["request_leaves_swap_input_target_amount_sats"],
37
- feeSats: obj["request_leaves_swap_input_fee_sats"],
38
- userLeaves: obj["request_leaves_swap_input_user_leaves"].map((e) => UserLeafInputFromJson(e)),
39
- idempotencyKey: obj["request_leaves_swap_input_idempotency_key"],
40
- targetAmountSatsList: obj["request_leaves_swap_input_target_amount_sats_list"],
41
-
42
- } as RequestLeavesSwapInput;
43
-
44
- }
45
- export const RequestLeavesSwapInputToJson = (obj: RequestLeavesSwapInput): any => {
46
- return {
47
- request_leaves_swap_input_adaptor_pubkey: obj.adaptorPubkey,
48
- request_leaves_swap_input_total_amount_sats: obj.totalAmountSats,
49
- request_leaves_swap_input_target_amount_sats: obj.targetAmountSats,
50
- request_leaves_swap_input_fee_sats: obj.feeSats,
51
- request_leaves_swap_input_user_leaves: obj.userLeaves.map((e) => UserLeafInputToJson(e)),
52
- request_leaves_swap_input_idempotency_key: obj.idempotencyKey,
53
- request_leaves_swap_input_target_amount_sats_list: obj.targetAmountSatsList,
54
-
55
- }
23
+ idempotencyKey: string;
56
24
 
25
+ targetAmountSatsList?: number[] | undefined;
57
26
  }
58
27
 
59
-
60
-
61
-
28
+ export const RequestLeavesSwapInputFromJson = (
29
+ obj: any,
30
+ ): RequestLeavesSwapInput => {
31
+ return {
32
+ adaptorPubkey: obj["request_leaves_swap_input_adaptor_pubkey"],
33
+ directAdaptorPubkey: obj["request_leaves_swap_input_direct_adaptor_pubkey"],
34
+ directFromCpfpAdaptorPubkey: obj["request_leaves_swap_input_direct_from_cpfp_adaptor_pubkey"],
35
+ totalAmountSats: obj["request_leaves_swap_input_total_amount_sats"],
36
+ targetAmountSats: obj["request_leaves_swap_input_target_amount_sats"],
37
+ feeSats: obj["request_leaves_swap_input_fee_sats"],
38
+ userLeaves: obj["request_leaves_swap_input_user_leaves"].map((e) =>
39
+ UserLeafInputFromJson(e),
40
+ ),
41
+ idempotencyKey: obj["request_leaves_swap_input_idempotency_key"],
42
+ targetAmountSatsList:
43
+ obj["request_leaves_swap_input_target_amount_sats_list"],
44
+ } as RequestLeavesSwapInput;
45
+ };
46
+ export const RequestLeavesSwapInputToJson = (
47
+ obj: RequestLeavesSwapInput,
48
+ ): any => {
49
+ return {
50
+ request_leaves_swap_input_adaptor_pubkey: obj.adaptorPubkey,
51
+ request_leaves_swap_input_direct_adaptor_pubkey: obj.directAdaptorPubkey,
52
+ request_leaves_swap_input_direct_from_cpfp_adaptor_pubkey: obj.directFromCpfpAdaptorPubkey,
53
+ request_leaves_swap_input_total_amount_sats: obj.totalAmountSats,
54
+ request_leaves_swap_input_target_amount_sats: obj.targetAmountSats,
55
+ request_leaves_swap_input_fee_sats: obj.feeSats,
56
+ request_leaves_swap_input_user_leaves: obj.userLeaves.map((e) =>
57
+ UserLeafInputToJson(e),
58
+ ),
59
+ request_leaves_swap_input_idempotency_key: obj.idempotencyKey,
60
+ request_leaves_swap_input_target_amount_sats_list: obj.targetAmountSatsList,
61
+ };
62
+ };
62
63
 
63
64
  export default RequestLeavesSwapInput;
@@ -1,53 +1,61 @@
1
-
2
1
  // Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved
3
2
 
4
-
5
-
6
-
7
-
8
3
  interface SwapLeaf {
4
+ leafId: string;
9
5
 
6
+ rawUnsignedRefundTransaction: string;
10
7
 
11
- leafId: string;
12
-
13
- rawUnsignedRefundTransaction: string;
14
-
15
- adaptorSignedSignature: string;
8
+ adaptorSignedSignature: string;
16
9
 
10
+ directRawUnsignedRefundTransaction?: string;
17
11
 
12
+ directAdaptorSignedSignature?: string;
18
13
 
14
+ directFromCpfpRawUnsignedRefundTransaction?: string;
19
15
 
16
+ directFromCpfpAdaptorSignedSignature?: string;
20
17
  }
21
18
 
22
19
  export const SwapLeafFromJson = (obj: any): SwapLeaf => {
23
- return {
24
- leafId: obj["swap_leaf_leaf_id"],
25
- rawUnsignedRefundTransaction: obj["swap_leaf_raw_unsigned_refund_transaction"],
26
- adaptorSignedSignature: obj["swap_leaf_adaptor_signed_signature"],
27
-
28
- } as SwapLeaf;
29
-
30
- }
20
+ return {
21
+ leafId: obj["swap_leaf_leaf_id"],
22
+ rawUnsignedRefundTransaction:
23
+ obj["swap_leaf_raw_unsigned_refund_transaction"],
24
+ adaptorSignedSignature: obj["swap_leaf_adaptor_signed_signature"],
25
+ directRawUnsignedRefundTransaction:
26
+ obj["swap_leaf_direct_raw_unsigned_refund_transaction"],
27
+ directAdaptorSignedSignature:
28
+ obj["swap_leaf_direct_adaptor_signed_signature"],
29
+ directFromCpfpRawUnsignedRefundTransaction:
30
+ obj["swap_leaf_direct_from_cpfp_raw_unsigned_refund_transaction"],
31
+ directFromCpfpAdaptorSignedSignature:
32
+ obj["swap_leaf_direct_from_cpfp_adaptor_signed_signature"],
33
+ } as SwapLeaf;
34
+ };
31
35
  export const SwapLeafToJson = (obj: SwapLeaf): any => {
32
- return {
33
- swap_leaf_leaf_id: obj.leafId,
34
- swap_leaf_raw_unsigned_refund_transaction: obj.rawUnsignedRefundTransaction,
35
- swap_leaf_adaptor_signed_signature: obj.adaptorSignedSignature,
36
-
37
- }
38
-
39
- }
40
-
41
-
42
- export const FRAGMENT = `
36
+ return {
37
+ swap_leaf_leaf_id: obj.leafId,
38
+ swap_leaf_raw_unsigned_refund_transaction: obj.rawUnsignedRefundTransaction,
39
+ swap_leaf_adaptor_signed_signature: obj.adaptorSignedSignature,
40
+ swap_leaf_direct_raw_unsigned_refund_transaction:
41
+ obj.directRawUnsignedRefundTransaction,
42
+ swap_leaf_direct_adaptor_signed_signature: obj.directAdaptorSignedSignature,
43
+ swap_leaf_direct_from_cpfp_raw_unsigned_refund_transaction:
44
+ obj.directFromCpfpRawUnsignedRefundTransaction,
45
+ swap_leaf_direct_from_cpfp_adaptor_signed_signature: obj.directFromCpfpAdaptorSignedSignature,
46
+ };
47
+ };
48
+
49
+ export const FRAGMENT = `
43
50
  fragment SwapLeafFragment on SwapLeaf {
44
51
  __typename
45
52
  swap_leaf_leaf_id: leaf_id
46
53
  swap_leaf_raw_unsigned_refund_transaction: raw_unsigned_refund_transaction
47
54
  swap_leaf_adaptor_signed_signature: adaptor_signed_signature
55
+ swap_leaf_direct_raw_unsigned_refund_transaction: direct_raw_unsigned_refund_transaction
56
+ swap_leaf_direct_adaptor_signed_signature: direct_adaptor_signed_signature
57
+ swap_leaf_direct_from_cpfp_raw_unsigned_refund_transaction: direct_from_cpfp_raw_unsigned_refund_transaction
58
+ swap_leaf_direct_from_cpfp_adaptor_signed_signature: direct_from_cpfp_adaptor_signed_signature
48
59
  }`;
49
60
 
50
-
51
-
52
-
53
61
  export default SwapLeaf;
@@ -5,7 +5,15 @@ interface UserLeafInput {
5
5
 
6
6
  raw_unsigned_refund_transaction: string;
7
7
 
8
+ direct_raw_unsigned_refund_transaction: string;
9
+
10
+ direct_from_cpfp_raw_unsigned_refund_transaction: string;
11
+
8
12
  adaptor_added_signature: string;
13
+
14
+ direct_adaptor_added_signature: string;
15
+
16
+ direct_from_cpfp_adaptor_added_signature: string;
9
17
  }
10
18
 
11
19
  export const UserLeafInputFromJson = (obj: any): UserLeafInput => {
@@ -13,7 +21,15 @@ export const UserLeafInputFromJson = (obj: any): UserLeafInput => {
13
21
  leaf_id: obj["user_leaf_input_leaf_id"],
14
22
  raw_unsigned_refund_transaction:
15
23
  obj["user_leaf_input_raw_unsigned_refund_transaction"],
24
+ direct_raw_unsigned_refund_transaction:
25
+ obj["user_leaf_input_direct_raw_unsigned_refund_transaction"],
26
+ direct_from_cpfp_raw_unsigned_refund_transaction:
27
+ obj["user_leaf_input_direct_from_cpfp_unsigned_refund_transaction"],
16
28
  adaptor_added_signature: obj["user_leaf_input_adaptor_added_signature"],
29
+ direct_adaptor_added_signature:
30
+ obj["user_leaf_input_direct_adaptor_added_signature"],
31
+ direct_from_cpfp_adaptor_added_signature:
32
+ obj["user_leaf_input_direct_from_cpfp_adaptor_added_signature"],
17
33
  } as UserLeafInput;
18
34
  };
19
35
  export const UserLeafInputToJson = (obj: UserLeafInput): any => {
@@ -21,7 +37,15 @@ export const UserLeafInputToJson = (obj: UserLeafInput): any => {
21
37
  user_leaf_input_leaf_id: obj.leaf_id,
22
38
  user_leaf_input_raw_unsigned_refund_transaction:
23
39
  obj.raw_unsigned_refund_transaction,
40
+ user_leaf_input_direct_raw_unsigned_refund_transaction:
41
+ obj.direct_raw_unsigned_refund_transaction,
42
+ user_leaf_input_direct_from_cpfp_raw_unsigned_refund_transaction:
43
+ obj.direct_from_cpfp_raw_unsigned_refund_transaction,
24
44
  user_leaf_input_adaptor_added_signature: obj.adaptor_added_signature,
45
+ user_leaf_input_direct_adaptor_added_signature:
46
+ obj.direct_adaptor_added_signature,
47
+ user_leaf_input_direct_from_cpfp_adaptor_added_signature:
48
+ obj.direct_from_cpfp_adaptor_added_signature,
25
49
  };
26
50
  };
27
51
 
@@ -448,6 +448,10 @@ fragment UserRequestFragment on UserRequest {
448
448
  swap_leaf_leaf_id: leaf_id
449
449
  swap_leaf_raw_unsigned_refund_transaction: raw_unsigned_refund_transaction
450
450
  swap_leaf_adaptor_signed_signature: adaptor_signed_signature
451
+ swap_leaf_direct_raw_unsigned_refund_transaction: direct_raw_unsigned_refund_transaction
452
+ swap_leaf_direct_adaptor_signed_signature: direct_adaptor_signed_signature
453
+ swap_leaf_direct_from_cpfp_raw_unsigned_refund_transaction: direct_from_cpfp_raw_unsigned_refund_transaction
454
+ swap_leaf_direct_from_cpfp_adaptor_signed_signature: direct_from_cpfp_adaptor_signed_signature
451
455
  }
452
456
  }
453
457
  ... on LightningReceiveRequest {
package/src/index.node.ts CHANGED
@@ -21,7 +21,7 @@ export {
21
21
  } from "./signer/signer.js";
22
22
  export * from "./signer/types.js";
23
23
 
24
- export { SparkWallet } from "./spark-wallet/spark-wallet.js";
24
+ export { SparkWallet } from "./spark-wallet/spark-wallet.node.js";
25
25
  export * from "./spark-wallet/types.js";
26
26
 
27
27
  export { type WalletConfigService } from "./services/config.js";
@@ -1,6 +1,6 @@
1
1
  /* Root React Native entrypoint */
2
2
 
3
- import { setCrypto, SparkCrypto } from "../utils/crypto.js";
3
+ import { setCrypto } from "../utils/crypto.js";
4
4
 
5
5
  setCrypto(globalThis.crypto);
6
6
 
@@ -14,8 +14,7 @@ export { ReactNativeSparkSigner as DefaultSparkSigner } from "../signer/signer.r
14
14
  export { SparkWallet } from "../spark-wallet/spark-wallet.js";
15
15
  export * from "../spark-wallet/types.js";
16
16
 
17
- export { WalletConfig } from "../services/wallet-config.js";
18
- export { TokenTransactionService } from "../services/token-transactions.js";
19
- export { type ConnectionManager } from "../services/connection.js";
20
17
  export { type WalletConfigService } from "../services/config.js";
21
- export { type ConfigOptions } from "../services/wallet-config.js";
18
+ export { type ConnectionManager } from "../services/connection.js";
19
+ export { TokenTransactionService } from "../services/token-transactions.js";
20
+ export { WalletConfig, type ConfigOptions } from "../services/wallet-config.js";
@@ -12,7 +12,10 @@ import {
12
12
  getTxFromRawTxBytes,
13
13
  } from "../utils/bitcoin.js";
14
14
  import { Network } from "../utils/network.js";
15
- import { getNextTransactionSequence } from "../utils/transaction.js";
15
+ import {
16
+ getNextTransactionSequence,
17
+ maybeApplyFee,
18
+ } from "../utils/transaction.js";
16
19
  import { WalletConfigService } from "./config.js";
17
20
  import { ConnectionManager } from "./connection.js";
18
21
  import { SigningService } from "./signing.js";
@@ -43,8 +46,15 @@ export class CoopExitService extends BaseTransferService {
43
46
  }: GetConnectorRefundSignaturesParams): Promise<{
44
47
  transfer: Transfer;
45
48
  signaturesMap: Map<string, Uint8Array>;
49
+ directSignaturesMap: Map<string, Uint8Array>;
50
+ directFromCpfpSignaturesMap: Map<string, Uint8Array>;
46
51
  }> {
47
- const { transfer, signaturesMap } = await this.signCoopExitRefunds(
52
+ const {
53
+ transfer,
54
+ signaturesMap,
55
+ directSignaturesMap,
56
+ directFromCpfpSignaturesMap,
57
+ } = await this.signCoopExitRefunds(
48
58
  leaves,
49
59
  exitTxId,
50
60
  connectorOutputs,
@@ -55,51 +65,116 @@ export class CoopExitService extends BaseTransferService {
55
65
  transfer,
56
66
  leaves,
57
67
  signaturesMap,
68
+ directSignaturesMap,
69
+ directFromCpfpSignaturesMap,
58
70
  );
59
71
 
60
- return { transfer: transferTweak, signaturesMap };
72
+ return {
73
+ transfer: transferTweak,
74
+ signaturesMap,
75
+ directSignaturesMap,
76
+ directFromCpfpSignaturesMap,
77
+ };
61
78
  }
62
79
 
63
- private createConnectorRefundTransaction(
80
+ private createConnectorRefundTransactions(
64
81
  sequence: number,
65
- nodeOutPoint: TransactionInput,
82
+ directSequence: number,
83
+ cpfpNodeOutPoint: TransactionInput,
84
+ directNodeOutPoint: TransactionInput | undefined,
66
85
  connectorOutput: TransactionInput,
67
86
  amountSats: bigint,
68
87
  receiverPubKey: Uint8Array,
69
- ): Transaction {
70
- const refundTx = new Transaction();
71
- if (!nodeOutPoint.txid || nodeOutPoint.index === undefined) {
72
- throw new ValidationError("Invalid node outpoint", {
73
- field: "nodeOutPoint",
74
- value: { txid: nodeOutPoint.txid, index: nodeOutPoint.index },
88
+ ): {
89
+ cpfpRefundTx: Transaction;
90
+ directRefundTx?: Transaction;
91
+ directFromCpfpRefundTx?: Transaction;
92
+ } {
93
+ // Create CPFP refund transaction
94
+ const cpfpRefundTx = new Transaction();
95
+ if (!cpfpNodeOutPoint.txid || cpfpNodeOutPoint.index === undefined) {
96
+ throw new ValidationError("Invalid CPFP node outpoint", {
97
+ field: "cpfpNodeOutPoint",
98
+ value: { txid: cpfpNodeOutPoint.txid, index: cpfpNodeOutPoint.index },
75
99
  expected: "Both txid and index must be defined",
76
100
  });
77
101
  }
78
- refundTx.addInput({
79
- txid: nodeOutPoint.txid,
80
- index: nodeOutPoint.index,
102
+ cpfpRefundTx.addInput({
103
+ txid: cpfpNodeOutPoint.txid,
104
+ index: cpfpNodeOutPoint.index,
81
105
  sequence,
82
106
  });
83
107
 
84
- refundTx.addInput(connectorOutput);
108
+ cpfpRefundTx.addInput(connectorOutput);
85
109
  const receiverScript = getP2TRScriptFromPublicKey(
86
110
  receiverPubKey,
87
111
  this.config.getNetwork(),
88
112
  );
89
113
 
90
- refundTx.addOutput({
114
+ cpfpRefundTx.addOutput({
91
115
  script: receiverScript,
92
116
  amount: amountSats,
93
117
  });
94
118
 
95
- return refundTx;
119
+ // Create direct refund transaction
120
+ let directRefundTx: Transaction | undefined;
121
+ let directFromCpfpRefundTx: Transaction | undefined;
122
+ if (directNodeOutPoint) {
123
+ if (!directNodeOutPoint.txid || directNodeOutPoint.index === undefined) {
124
+ throw new ValidationError("Invalid direct node outpoint", {
125
+ field: "directNodeOutPoint",
126
+ value: {
127
+ txid: directNodeOutPoint.txid,
128
+ index: directNodeOutPoint.index,
129
+ },
130
+ expected: "Both txid and index must be defined",
131
+ });
132
+ }
133
+ directRefundTx = new Transaction();
134
+ directRefundTx.addInput({
135
+ txid: directNodeOutPoint.txid,
136
+ index: directNodeOutPoint.index,
137
+ sequence: directSequence,
138
+ });
139
+
140
+ directRefundTx.addInput(connectorOutput);
141
+ directRefundTx.addOutput({
142
+ script: receiverScript,
143
+ amount: maybeApplyFee(amountSats),
144
+ });
145
+
146
+ directFromCpfpRefundTx = new Transaction();
147
+ directFromCpfpRefundTx.addInput({
148
+ txid: cpfpNodeOutPoint.txid,
149
+ index: cpfpNodeOutPoint.index,
150
+ sequence: directSequence,
151
+ });
152
+
153
+ directFromCpfpRefundTx.addInput(connectorOutput);
154
+ directFromCpfpRefundTx.addOutput({
155
+ script: receiverScript,
156
+ amount: maybeApplyFee(amountSats),
157
+ });
158
+ }
159
+
160
+ return {
161
+ cpfpRefundTx,
162
+ directRefundTx,
163
+ directFromCpfpRefundTx,
164
+ };
96
165
  }
166
+
97
167
  private async signCoopExitRefunds(
98
168
  leaves: LeafKeyTweak[],
99
169
  exitTxId: Uint8Array,
100
170
  connectorOutputs: TransactionInput[],
101
171
  receiverPubKey: Uint8Array,
102
- ): Promise<{ transfer: Transfer; signaturesMap: Map<string, Uint8Array> }> {
172
+ ): Promise<{
173
+ transfer: Transfer;
174
+ signaturesMap: Map<string, Uint8Array>;
175
+ directSignaturesMap: Map<string, Uint8Array>;
176
+ directFromCpfpSignaturesMap: Map<string, Uint8Array>;
177
+ }> {
103
178
  if (leaves.length !== connectorOutputs.length) {
104
179
  throw new ValidationError(
105
180
  "Mismatch between leaves and connector outputs",
@@ -136,43 +211,88 @@ export class CoopExitService extends BaseTransferService {
136
211
  }
137
212
  const currentRefundTx = getTxFromRawTxBytes(leaf.leaf.refundTx);
138
213
 
139
- const { nextSequence } = getNextTransactionSequence(
140
- currentRefundTx.getInput(0).sequence,
141
- );
214
+ const sequence = currentRefundTx.getInput(0).sequence;
215
+ if (!sequence) {
216
+ throw new ValidationError("Invalid refund transaction", {
217
+ field: "sequence",
218
+ value: currentRefundTx.getInput(0),
219
+ expected: "Non-null sequence",
220
+ });
221
+ }
222
+ const { nextSequence, nextDirectSequence } =
223
+ getNextTransactionSequence(sequence);
142
224
 
143
- const refundTx = this.createConnectorRefundTransaction(
144
- nextSequence,
145
- currentRefundTx.getInput(0),
146
- connectorOutput,
147
- BigInt(leaf.leaf.value),
148
- receiverPubKey,
149
- );
225
+ let currentDirectRefundTx: Transaction | undefined;
226
+ if (leaf.leaf.directRefundTx.length > 0) {
227
+ currentDirectRefundTx = getTxFromRawTxBytes(leaf.leaf.directRefundTx);
228
+ }
229
+
230
+ const { cpfpRefundTx, directRefundTx, directFromCpfpRefundTx } =
231
+ this.createConnectorRefundTransactions(
232
+ nextSequence,
233
+ nextDirectSequence,
234
+ currentRefundTx.getInput(0),
235
+ currentDirectRefundTx?.getInput(0),
236
+ connectorOutput,
237
+ BigInt(leaf.leaf.value),
238
+ receiverPubKey,
239
+ );
150
240
 
151
241
  const signingNonceCommitment =
152
242
  await this.config.signer.getRandomSigningCommitment();
243
+ const directSigningNonceCommitment =
244
+ await this.config.signer.getRandomSigningCommitment();
245
+ const directFromCpfpSigningNonceCommitment =
246
+ await this.config.signer.getRandomSigningCommitment();
247
+ const signingPublicKey =
248
+ await this.config.signer.getPublicKeyFromDerivation(leaf.keyDerivation);
249
+
153
250
  const signingJob: LeafRefundTxSigningJob = {
154
251
  leafId: leaf.leaf.id,
155
252
  refundTxSigningJob: {
156
253
  signingPublicKey: await this.config.signer.getPublicKeyFromDerivation(
157
254
  leaf.keyDerivation,
158
255
  ),
159
- rawTx: refundTx.toBytes(),
256
+ rawTx: cpfpRefundTx.toBytes(),
160
257
  signingNonceCommitment: signingNonceCommitment.commitment,
161
258
  },
162
- // TODO: Add direct refund signature
163
- directRefundTxSigningJob: undefined,
164
- directFromCpfpRefundTxSigningJob: undefined,
259
+ directRefundTxSigningJob: directRefundTx
260
+ ? {
261
+ signingPublicKey,
262
+ rawTx: directRefundTx.toBytes(),
263
+ signingNonceCommitment: directSigningNonceCommitment.commitment,
264
+ }
265
+ : undefined,
266
+ directFromCpfpRefundTxSigningJob: directFromCpfpRefundTx
267
+ ? {
268
+ signingPublicKey,
269
+ rawTx: directFromCpfpRefundTx.toBytes(),
270
+ signingNonceCommitment:
271
+ directFromCpfpSigningNonceCommitment.commitment,
272
+ }
273
+ : undefined,
165
274
  };
166
275
 
167
276
  signingJobs.push(signingJob);
168
277
  const tx = getTxFromRawTxBytes(leaf.leaf.nodeTx);
278
+ const directTx =
279
+ leaf.leaf.directTx.length > 0
280
+ ? getTxFromRawTxBytes(leaf.leaf.directTx)
281
+ : undefined;
282
+
169
283
  leafDataMap.set(leaf.leaf.id, {
170
284
  keyDerivation: leaf.keyDerivation,
171
- refundTx,
285
+ receivingPubkey: receiverPubKey,
172
286
  signingNonceCommitment,
287
+ directSigningNonceCommitment,
173
288
  tx,
289
+ directTx,
290
+ refundTx: cpfpRefundTx,
291
+ directRefundTx: directRefundTx,
292
+ directFromCpfpRefundTx: directFromCpfpRefundTx,
293
+ directFromCpfpRefundSigningNonceCommitment:
294
+ directFromCpfpSigningNonceCommitment,
174
295
  vout: leaf.leaf.vout,
175
- receivingPubkey: receiverPubKey,
176
296
  });
177
297
  }
178
298
 
@@ -182,7 +302,7 @@ export class CoopExitService extends BaseTransferService {
182
302
 
183
303
  let response: CooperativeExitResponse;
184
304
  try {
185
- response = await sparkClient.cooperative_exit({
305
+ response = await sparkClient.cooperative_exit_v2({
186
306
  transfer: {
187
307
  transferId: uuidv7(),
188
308
  leavesToSend: signingJobs,
@@ -222,10 +342,25 @@ export class CoopExitService extends BaseTransferService {
222
342
  );
223
343
 
224
344
  const signaturesMap: Map<string, Uint8Array> = new Map();
345
+ const directSignaturesMap: Map<string, Uint8Array> = new Map();
346
+ const directFromCpfpSignaturesMap: Map<string, Uint8Array> = new Map();
225
347
  for (const signature of signatures) {
226
348
  signaturesMap.set(signature.nodeId, signature.refundTxSignature);
349
+ directSignaturesMap.set(
350
+ signature.nodeId,
351
+ signature.directRefundTxSignature,
352
+ );
353
+ directFromCpfpSignaturesMap.set(
354
+ signature.nodeId,
355
+ signature.directFromCpfpRefundTxSignature,
356
+ );
227
357
  }
228
358
 
229
- return { transfer: response.transfer, signaturesMap };
359
+ return {
360
+ transfer: response.transfer,
361
+ signaturesMap,
362
+ directSignaturesMap,
363
+ directFromCpfpSignaturesMap,
364
+ };
230
365
  }
231
366
  }