@buildonspark/spark-sdk 0.1.43 → 0.1.45

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 (153) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/{RequestLightningSendInput-D7fZdT4A.d.ts → RequestLightningSendInput-DEPd_fPO.d.ts} +43 -4
  3. package/dist/{RequestLightningSendInput-Na1mHdWg.d.cts → RequestLightningSendInput-Du0z7Om7.d.cts} +43 -4
  4. package/dist/address/index.cjs +2 -2
  5. package/dist/address/index.d.cts +2 -2
  6. package/dist/address/index.d.ts +2 -2
  7. package/dist/address/index.js +2 -2
  8. package/dist/{chunk-IRW5TWMH.js → chunk-5FUB65LX.js} +7 -9
  9. package/dist/{chunk-BUTZWYBW.js → chunk-6264CGDM.js} +4 -4
  10. package/dist/{chunk-VFJQNBFX.js → chunk-7V6N75CC.js} +5 -2
  11. package/dist/{chunk-M6A4KFIG.js → chunk-BGGEVUJK.js} +1505 -445
  12. package/dist/{chunk-DQYKQJRZ.js → chunk-C2S227QR.js} +675 -52
  13. package/dist/{chunk-GYQR4B4P.js → chunk-GZ5IPPJ2.js} +2 -2
  14. package/dist/{chunk-6AFUC5M2.js → chunk-HWJWKEIU.js} +8 -2
  15. package/dist/{chunk-TOSP3INR.js → chunk-I54FARY2.js} +4 -2
  16. package/dist/{chunk-WWOTVNPP.js → chunk-J2IE4Z7Y.js} +544 -431
  17. package/dist/{chunk-O4RYNJNB.js → chunk-KMUMFYFX.js} +3 -3
  18. package/dist/chunk-LHRD2WT6.js +2374 -0
  19. package/dist/{chunk-ABZA6R5S.js → chunk-NTFKFRQ2.js} +1 -1
  20. package/dist/{chunk-MIVX3GHD.js → chunk-OBFKIEMP.js} +1 -1
  21. package/dist/{chunk-HRQRRDSS.js → chunk-PQN3C2MF.js} +15 -15
  22. package/dist/{chunk-DOA6QXYQ.js → chunk-R5PXJZQS.js} +3 -1
  23. package/dist/{chunk-TIUBYNN5.js → chunk-YUPMXTCJ.js} +4 -4
  24. package/dist/graphql/objects/index.d.cts +6 -43
  25. package/dist/graphql/objects/index.d.ts +6 -43
  26. package/dist/graphql/objects/index.js +1 -1
  27. package/dist/index-B2AwKW5J.d.cts +214 -0
  28. package/dist/index-CJDi1HWc.d.ts +214 -0
  29. package/dist/index.cjs +4150 -1026
  30. package/dist/index.d.cts +764 -19
  31. package/dist/index.d.ts +764 -19
  32. package/dist/index.js +17 -21
  33. package/dist/index.node.cjs +4153 -1033
  34. package/dist/index.node.d.cts +10 -8
  35. package/dist/index.node.d.ts +10 -8
  36. package/dist/index.node.js +20 -28
  37. package/dist/native/index.cjs +4166 -1042
  38. package/dist/native/index.d.cts +369 -108
  39. package/dist/native/index.d.ts +369 -108
  40. package/dist/native/index.js +4138 -1015
  41. package/dist/{network-xkBSpaTn.d.ts → network-BTJl-Sul.d.ts} +1 -1
  42. package/dist/{network-D5lKssVl.d.cts → network-CqgsdUF2.d.cts} +1 -1
  43. package/dist/proto/lrc20.cjs +222 -19
  44. package/dist/proto/lrc20.d.cts +1 -1
  45. package/dist/proto/lrc20.d.ts +1 -1
  46. package/dist/proto/lrc20.js +2 -2
  47. package/dist/proto/spark.cjs +1502 -442
  48. package/dist/proto/spark.d.cts +1 -1
  49. package/dist/proto/spark.d.ts +1 -1
  50. package/dist/proto/spark.js +5 -5
  51. package/dist/proto/spark_token.cjs +1515 -56
  52. package/dist/proto/spark_token.d.cts +153 -15
  53. package/dist/proto/spark_token.d.ts +153 -15
  54. package/dist/proto/spark_token.js +40 -4
  55. package/dist/{sdk-types-B-q9py_P.d.cts → sdk-types-B0SwjolI.d.cts} +1 -1
  56. package/dist/{sdk-types-BPoPgzda.d.ts → sdk-types-Cc4l4kb1.d.ts} +1 -1
  57. package/dist/services/config.cjs +7 -3
  58. package/dist/services/config.d.cts +5 -4
  59. package/dist/services/config.d.ts +5 -4
  60. package/dist/services/config.js +6 -6
  61. package/dist/services/connection.cjs +2938 -646
  62. package/dist/services/connection.d.cts +5 -4
  63. package/dist/services/connection.d.ts +5 -4
  64. package/dist/services/connection.js +4 -4
  65. package/dist/services/index.cjs +6381 -3461
  66. package/dist/services/index.d.cts +7 -6
  67. package/dist/services/index.d.ts +7 -6
  68. package/dist/services/index.js +15 -13
  69. package/dist/services/lrc-connection.cjs +227 -21
  70. package/dist/services/lrc-connection.d.cts +5 -4
  71. package/dist/services/lrc-connection.d.ts +5 -4
  72. package/dist/services/lrc-connection.js +4 -4
  73. package/dist/services/token-transactions.cjs +868 -244
  74. package/dist/services/token-transactions.d.cts +25 -7
  75. package/dist/services/token-transactions.d.ts +25 -7
  76. package/dist/services/token-transactions.js +5 -4
  77. package/dist/services/wallet-config.cjs +4 -1
  78. package/dist/services/wallet-config.d.cts +7 -5
  79. package/dist/services/wallet-config.d.ts +7 -5
  80. package/dist/services/wallet-config.js +3 -1
  81. package/dist/signer/signer.cjs +5 -2
  82. package/dist/signer/signer.d.cts +3 -2
  83. package/dist/signer/signer.d.ts +3 -2
  84. package/dist/signer/signer.js +2 -2
  85. package/dist/{signer-wqesWifN.d.ts → signer-BocS_J6B.d.ts} +2 -6
  86. package/dist/{signer-IO3oMRNj.d.cts → signer-DKS0AJkw.d.cts} +2 -6
  87. package/dist/{spark-CDm4gqS6.d.cts → spark-dM7EYXYQ.d.cts} +138 -42
  88. package/dist/{spark-CDm4gqS6.d.ts → spark-dM7EYXYQ.d.ts} +138 -42
  89. package/dist/spark_bindings/native/index.cjs +183 -0
  90. package/dist/spark_bindings/native/index.d.cts +14 -0
  91. package/dist/spark_bindings/native/index.d.ts +14 -0
  92. package/dist/spark_bindings/native/index.js +141 -0
  93. package/dist/spark_bindings/wasm/index.cjs +1093 -0
  94. package/dist/spark_bindings/wasm/index.d.cts +47 -0
  95. package/dist/spark_bindings/wasm/index.d.ts +47 -0
  96. package/dist/{chunk-K4C4W5FC.js → spark_bindings/wasm/index.js} +7 -6
  97. package/dist/types/index.cjs +1503 -443
  98. package/dist/types/index.d.cts +6 -5
  99. package/dist/types/index.d.ts +6 -5
  100. package/dist/types/index.js +3 -3
  101. package/dist/types-C-Rp0Oo7.d.cts +46 -0
  102. package/dist/types-C-Rp0Oo7.d.ts +46 -0
  103. package/dist/utils/index.cjs +358 -36
  104. package/dist/utils/index.d.cts +14 -134
  105. package/dist/utils/index.d.ts +14 -134
  106. package/dist/utils/index.js +8 -8
  107. package/package.json +21 -1
  108. package/src/constants.ts +5 -1
  109. package/src/graphql/client.ts +28 -0
  110. package/src/graphql/mutations/RequestCoopExit.ts +6 -0
  111. package/src/graphql/mutations/RequestSwapLeaves.ts +2 -0
  112. package/src/graphql/queries/GetCoopExitFeeQuote.ts +20 -0
  113. package/src/index.node.ts +0 -1
  114. package/src/index.ts +0 -1
  115. package/src/native/index.ts +1 -2
  116. package/src/proto/common.ts +5 -5
  117. package/src/proto/google/protobuf/descriptor.ts +34 -34
  118. package/src/proto/google/protobuf/duration.ts +2 -2
  119. package/src/proto/google/protobuf/empty.ts +2 -2
  120. package/src/proto/google/protobuf/timestamp.ts +2 -2
  121. package/src/proto/mock.ts +4 -4
  122. package/src/proto/spark.ts +1924 -525
  123. package/src/proto/spark_authn.ts +7 -7
  124. package/src/proto/spark_token.ts +1668 -105
  125. package/src/proto/validate/validate.ts +24 -24
  126. package/src/services/bolt11-spark.ts +62 -187
  127. package/src/services/coop-exit.ts +3 -0
  128. package/src/services/lrc20.ts +1 -1
  129. package/src/services/token-transactions.ts +209 -9
  130. package/src/services/transfer.ts +22 -3
  131. package/src/services/tree-creation.ts +13 -0
  132. package/src/services/wallet-config.ts +2 -1
  133. package/src/spark-wallet/spark-wallet.node.ts +3 -7
  134. package/src/spark-wallet/spark-wallet.ts +376 -232
  135. package/src/spark-wallet/types.ts +39 -3
  136. package/src/tests/bolt11-spark.test.ts +7 -15
  137. package/src/tests/integration/deposit.test.ts +16 -0
  138. package/src/tests/integration/ssp/coop-exit.test.ts +85 -21
  139. package/src/tests/integration/ssp/swap.test.ts +47 -0
  140. package/src/tests/integration/swap.test.ts +453 -433
  141. package/src/tests/integration/transfer.test.ts +261 -248
  142. package/src/tests/token-identifier.test.ts +54 -0
  143. package/src/tests/tokens.test.ts +218 -22
  144. package/src/utils/token-hashing.ts +346 -52
  145. package/src/utils/token-identifier.ts +88 -0
  146. package/src/utils/token-transaction-validation.ts +350 -5
  147. package/src/utils/token-transactions.ts +12 -8
  148. package/src/utils/transaction.ts +2 -8
  149. package/dist/chunk-VA7MV4MZ.js +0 -1073
  150. package/dist/index-7RYRH5wc.d.ts +0 -815
  151. package/dist/index-BJOc8Ur-.d.cts +0 -815
  152. package/dist/wasm-7OWFHDMS.js +0 -21
  153. package/src/logger.ts +0 -3
@@ -23,7 +23,7 @@ import SspClient from "../graphql/client.js";
23
23
  import {
24
24
  BitcoinNetwork,
25
25
  ClaimStaticDepositOutput,
26
- CoopExitFeeEstimatesOutput,
26
+ CoopExitFeeQuote,
27
27
  CoopExitRequest,
28
28
  ExitSpeed,
29
29
  LeavesSwapFeeEstimateOutput,
@@ -31,8 +31,8 @@ import {
31
31
  LightningReceiveRequest,
32
32
  LightningSendFeeEstimateInput,
33
33
  LightningSendRequest,
34
+ RequestCoopExitInput,
34
35
  StaticDepositQuoteOutput,
35
- SwapLeaf,
36
36
  UserLeafInput,
37
37
  } from "../graphql/objects/index.js";
38
38
  import GraphQLTransferObj from "../graphql/objects/Transfer.js";
@@ -43,7 +43,6 @@ import {
43
43
  QueryNodesResponse,
44
44
  SigningJob,
45
45
  SubscribeToEventsResponse,
46
- TokenTransactionWithStatus,
47
46
  Transfer,
48
47
  TransferStatus,
49
48
  TransferType,
@@ -102,7 +101,7 @@ import {
102
101
  SparkAddressFormat,
103
102
  } from "../address/index.js";
104
103
  import { isReactNative } from "../constants.js";
105
- import { networkToJSON } from "../proto/spark.js";
104
+ import { Network as NetworkProto, networkToJSON } from "../proto/spark.js";
106
105
  import {
107
106
  decodeInvoice,
108
107
  getNetworkFromInvoice,
@@ -125,9 +124,12 @@ import type {
125
124
  InitWalletResponse,
126
125
  PayLightningInvoiceParams,
127
126
  SparkWalletProps,
128
- TokenInfo,
127
+ TokenMetadata,
129
128
  TransferParams,
129
+ TokenBalanceMap,
130
130
  } from "./types.js";
131
+ import { encodeHumanReadableTokenIdentifier } from "../utils/token-identifier.js";
132
+ import { TokenTransactionWithStatus } from "../proto/spark_token.js";
131
133
 
132
134
  /**
133
135
  * The SparkWallet class is the primary interface for interacting with the Spark network.
@@ -164,6 +166,7 @@ export class SparkWallet extends EventEmitter {
164
166
  private streamController: AbortController | null = null;
165
167
 
166
168
  protected leaves: TreeNode[] = [];
169
+
167
170
  protected tokenOutputs: Map<string, OutputWithPreviousTransactionData[]> =
168
171
  new Map();
169
172
 
@@ -486,8 +489,7 @@ export class SparkWallet extends EventEmitter {
486
489
  leaf.signingKeyshare.publicKey,
487
490
  operatorLeaf.signingKeyshare.publicKey,
488
491
  ) ||
489
- !equalBytes(leaf.nodeTx, operatorLeaf.nodeTx) ||
490
- !equalBytes(leaf.refundTx, operatorLeaf.refundTx)
492
+ !equalBytes(leaf.nodeTx, operatorLeaf.nodeTx)
491
493
  ) {
492
494
  leavesToIgnore.add(nodeId);
493
495
  }
@@ -524,14 +526,40 @@ export class SparkWallet extends EventEmitter {
524
526
  .map(([_, node]) => node);
525
527
  }
526
528
 
527
- private async selectLeaves(targetAmount: number): Promise<TreeNode[]> {
528
- if (targetAmount <= 0) {
529
+ private async selectLeaves(
530
+ targetAmounts: number[],
531
+ ): Promise<Map<number, TreeNode[]>> {
532
+ if (targetAmounts.length === 0) {
533
+ throw new ValidationError("Target amounts must be non-empty", {
534
+ field: "targetAmounts",
535
+ value: targetAmounts,
536
+ });
537
+ }
538
+
539
+ if (targetAmounts.some((amount) => amount <= 0)) {
529
540
  throw new ValidationError("Target amount must be positive", {
530
- field: "targetAmount",
531
- value: targetAmount,
541
+ field: "targetAmounts",
542
+ value: targetAmounts,
532
543
  });
533
544
  }
534
545
 
546
+ const totalTargetAmount = targetAmounts.reduce(
547
+ (acc, amount) => acc + amount,
548
+ 0,
549
+ );
550
+ const totalBalance = this.getInternalBalance();
551
+
552
+ if (totalTargetAmount > totalBalance) {
553
+ throw new ValidationError(
554
+ "Total target amount exceeds available balance",
555
+ {
556
+ field: "targetAmounts",
557
+ value: totalTargetAmount,
558
+ expected: `less than or equal to ${totalBalance}`,
559
+ },
560
+ );
561
+ }
562
+
535
563
  const leaves = await this.getLeaves();
536
564
  if (leaves.length === 0) {
537
565
  throw new ValidationError("No owned leaves found", {
@@ -541,37 +569,63 @@ export class SparkWallet extends EventEmitter {
541
569
 
542
570
  leaves.sort((a, b) => b.value - a.value);
543
571
 
544
- let amount = 0;
545
- let nodes: TreeNode[] = [];
546
- for (const leaf of leaves) {
547
- if (targetAmount - amount >= leaf.value) {
548
- amount += leaf.value;
549
- nodes.push(leaf);
550
- }
551
- }
572
+ const selectLeavesForTargets = (
573
+ targetAmounts: number[],
574
+ leaves: TreeNode[],
575
+ ) => {
576
+ const usedLeaves = new Set<string>();
577
+ const results: Map<number, TreeNode[]> = new Map();
578
+ let totalAmount = 0;
552
579
 
553
- if (amount !== targetAmount) {
554
- await this.requestLeavesSwap({ targetAmount });
580
+ for (const targetAmount of targetAmounts) {
581
+ const nodes: TreeNode[] = [];
582
+ let amount = 0;
555
583
 
556
- amount = 0;
557
- nodes = [];
558
- const newLeaves = await this.getLeaves();
559
- newLeaves.sort((a, b) => b.value - a.value);
560
- for (const leaf of newLeaves) {
561
- if (targetAmount - amount >= leaf.value) {
562
- amount += leaf.value;
563
- nodes.push(leaf);
584
+ for (const leaf of leaves) {
585
+ if (usedLeaves.has(leaf.id)) {
586
+ continue;
587
+ }
588
+
589
+ if (targetAmount - amount >= leaf.value) {
590
+ amount += leaf.value;
591
+ nodes.push(leaf);
592
+ usedLeaves.add(leaf.id);
593
+ }
564
594
  }
595
+
596
+ totalAmount += amount;
597
+ results.set(targetAmount, nodes);
565
598
  }
599
+
600
+ return {
601
+ results,
602
+ foundSelections: totalAmount === totalTargetAmount,
603
+ };
604
+ };
605
+
606
+ let { results, foundSelections } = selectLeavesForTargets(
607
+ targetAmounts,
608
+ leaves,
609
+ );
610
+
611
+ if (!foundSelections) {
612
+ const newLeaves = await this.requestLeavesSwap({ targetAmounts });
613
+
614
+ newLeaves.sort((a, b) => b.value - a.value);
615
+
616
+ ({ results, foundSelections } = selectLeavesForTargets(
617
+ targetAmounts,
618
+ newLeaves,
619
+ ));
566
620
  }
567
621
 
568
- if (nodes.reduce((acc, leaf) => acc + leaf.value, 0) !== targetAmount) {
622
+ if (!foundSelections) {
569
623
  throw new Error(
570
- `Failed to select leaves for target amount ${targetAmount}`,
624
+ `Failed to select leaves for target amount ${totalTargetAmount}`,
571
625
  );
572
626
  }
573
627
 
574
- return nodes;
628
+ return results;
575
629
  }
576
630
 
577
631
  private async selectLeavesForSwap(targetAmount: number) {
@@ -853,32 +907,57 @@ export class SparkWallet extends EventEmitter {
853
907
  * @private
854
908
  */
855
909
  private async requestLeavesSwap({
856
- targetAmount,
910
+ targetAmounts,
857
911
  leaves,
858
912
  }: {
859
- targetAmount?: number;
913
+ targetAmounts?: number[];
860
914
  leaves?: TreeNode[];
861
- }) {
862
- if (targetAmount && targetAmount <= 0) {
863
- throw new Error("targetAmount must be positive");
915
+ }): Promise<TreeNode[]> {
916
+ if (targetAmounts && targetAmounts.some((amount) => amount <= 0)) {
917
+ throw new Error("specified targetAmount must be positive");
864
918
  }
865
919
 
866
- if (targetAmount && !Number.isSafeInteger(targetAmount)) {
920
+ if (
921
+ targetAmounts &&
922
+ targetAmounts.some((amount) => !Number.isSafeInteger(amount))
923
+ ) {
867
924
  throw new ValidationError("targetAmount must be less than 2^53", {
868
- field: "targetAmount",
869
- value: targetAmount,
925
+ field: "targetAmounts",
926
+ value: targetAmounts,
870
927
  expected: "smaller or equal to " + Number.MAX_SAFE_INTEGER,
871
928
  });
872
929
  }
873
930
 
874
931
  let leavesToSwap: TreeNode[];
875
- if (targetAmount && leaves && leaves.length > 0) {
876
- if (targetAmount < leaves.reduce((acc, leaf) => acc + leaf.value, 0)) {
932
+ const totalTargetAmount = targetAmounts?.reduce(
933
+ (acc, amount) => acc + amount,
934
+ 0,
935
+ );
936
+
937
+ if (totalTargetAmount) {
938
+ const totalBalance = this.getInternalBalance();
939
+
940
+ if (totalTargetAmount > totalBalance) {
941
+ throw new ValidationError(
942
+ "Total target amount exceeds available balance",
943
+ {
944
+ field: "targetAmounts",
945
+ value: totalTargetAmount,
946
+ expected: `less than or equal to ${totalBalance}`,
947
+ },
948
+ );
949
+ }
950
+ }
951
+
952
+ if (totalTargetAmount && leaves && leaves.length > 0) {
953
+ if (
954
+ totalTargetAmount < leaves.reduce((acc, leaf) => acc + leaf.value, 0)
955
+ ) {
877
956
  throw new Error("targetAmount is less than the sum of leaves");
878
957
  }
879
958
  leavesToSwap = leaves;
880
- } else if (targetAmount) {
881
- leavesToSwap = await this.selectLeavesForSwap(targetAmount);
959
+ } else if (totalTargetAmount) {
960
+ leavesToSwap = await this.selectLeavesForSwap(totalTargetAmount);
882
961
  } else if (leaves && leaves.length > 0) {
883
962
  leavesToSwap = leaves;
884
963
  } else {
@@ -889,10 +968,10 @@ export class SparkWallet extends EventEmitter {
889
968
 
890
969
  const batches = chunkArray(leavesToSwap, 100);
891
970
 
892
- const results: SwapLeaf[] = [];
971
+ const results: TreeNode[] = [];
893
972
  for (const batch of batches) {
894
- const result = await this.processSwapBatch(batch, targetAmount);
895
- results.push(...result.swapLeaves);
973
+ const result = await this.processSwapBatch(batch, targetAmounts);
974
+ results.push(...result);
896
975
  }
897
976
 
898
977
  return results;
@@ -903,8 +982,8 @@ export class SparkWallet extends EventEmitter {
903
982
  */
904
983
  private async processSwapBatch(
905
984
  leavesBatch: TreeNode[],
906
- targetAmount?: number,
907
- ): Promise<LeavesSwapRequest> {
985
+ targetAmounts?: number[],
986
+ ): Promise<TreeNode[]> {
908
987
  const leafKeyTweaks = await Promise.all(
909
988
  leavesBatch.map(async (leaf) => ({
910
989
  leaf,
@@ -981,9 +1060,10 @@ export class SparkWallet extends EventEmitter {
981
1060
  userLeaves,
982
1061
  adaptorPubkey,
983
1062
  targetAmountSats:
984
- targetAmount ||
1063
+ targetAmounts?.reduce((acc, amount) => acc + amount, 0) ||
985
1064
  leavesBatch.reduce((acc, leaf) => acc + leaf.value, 0),
986
1065
  totalAmountSats: leavesBatch.reduce((acc, leaf) => acc + leaf.value, 0),
1066
+ targetAmountSatsList: targetAmounts,
987
1067
  // TODO: Request fee from SSP
988
1068
  feeSats: 0,
989
1069
  idempotencyKey: uuidv7(),
@@ -1051,13 +1131,24 @@ export class SparkWallet extends EventEmitter {
1051
1131
  leavesSwapRequestId: request.id,
1052
1132
  });
1053
1133
 
1054
- if (!completeResponse) {
1134
+ if (!completeResponse || !completeResponse.inboundTransfer?.sparkId) {
1055
1135
  throw new Error("Failed to complete leaves swap");
1056
1136
  }
1057
1137
 
1058
- await this.claimTransfers(TransferType.COUNTER_SWAP);
1138
+ const incomingTransfer = await this.transferService.queryTransfer(
1139
+ completeResponse.inboundTransfer.sparkId,
1140
+ );
1059
1141
 
1060
- return completeResponse;
1142
+ if (!incomingTransfer) {
1143
+ throw new Error("Failed to get incoming transfer");
1144
+ }
1145
+
1146
+ return await this.claimTransfer({
1147
+ transfer: incomingTransfer,
1148
+ emit: false,
1149
+ retryCount: 0,
1150
+ optimize: false,
1151
+ });
1061
1152
  } catch (e) {
1062
1153
  await this.cancelAllSenderInitiatedTransfers();
1063
1154
  throw new Error(`Failed to request leaves swap: ${e}`);
@@ -1093,32 +1184,6 @@ export class SparkWallet extends EventEmitter {
1093
1184
  };
1094
1185
  }
1095
1186
 
1096
- /**
1097
- * Gets the held token info for the wallet.
1098
- *
1099
- * @deprecated The information is returned in getBalance
1100
- */
1101
- public async getTokenInfo(): Promise<TokenInfo[]> {
1102
- console.warn("getTokenInfo is deprecated. Use getBalance instead.");
1103
-
1104
- await this.syncTokenOutputs();
1105
-
1106
- const lrc20Client = await this.lrc20ConnectionManager.createLrc20Client();
1107
- const { balance, tokenBalances } = await this.getBalance();
1108
-
1109
- const tokenInfo = await lrc20Client.getTokenPubkeyInfo({
1110
- publicKeys: Array.from(tokenBalances.keys()).map(hexToBytes),
1111
- });
1112
-
1113
- return tokenInfo.tokenPubkeyInfos.map((info) => ({
1114
- tokenPublicKey: bytesToHex(info.announcement!.publicKey!.publicKey),
1115
- tokenName: info.announcement!.name,
1116
- tokenSymbol: info.announcement!.symbol,
1117
- tokenDecimals: Number(bytesToNumberBE(info.announcement!.decimal)),
1118
- maxSupply: bytesToNumberBE(info.announcement!.maxSupply),
1119
- }));
1120
- }
1121
-
1122
1187
  /**
1123
1188
  * Gets the current balance of the wallet.
1124
1189
  * You can use the forceRefetch option to synchronize your wallet and claim any
@@ -1126,24 +1191,21 @@ export class SparkWallet extends EventEmitter {
1126
1191
  *
1127
1192
  * @returns {Promise<Object>} Object containing:
1128
1193
  * - balance: The wallet's current balance in satoshis
1129
- * - tokenBalances: Map of token public keys to token balances and token info
1194
+ * - tokenBalances: Map of the human readable token identifier to token balances and token info
1130
1195
  */
1131
1196
  public async getBalance(): Promise<{
1132
1197
  balance: bigint;
1133
- tokenBalances: Map<string, { balance: bigint; tokenInfo: TokenInfo }>;
1198
+ tokenBalances: TokenBalanceMap;
1134
1199
  }> {
1135
1200
  const leaves = await this.getLeaves(true);
1136
1201
  await this.syncTokenOutputs();
1137
1202
 
1138
- let tokenBalances: Map<string, { balance: bigint; tokenInfo: TokenInfo }>;
1203
+ let tokenBalances: TokenBalanceMap;
1139
1204
 
1140
1205
  if (this.tokenOutputs.size !== 0) {
1141
1206
  tokenBalances = await this.getTokenBalance();
1142
1207
  } else {
1143
- tokenBalances = new Map<
1144
- string,
1145
- { balance: bigint; tokenInfo: TokenInfo }
1146
- >();
1208
+ tokenBalances = new Map();
1147
1209
  }
1148
1210
 
1149
1211
  return {
@@ -1152,32 +1214,33 @@ export class SparkWallet extends EventEmitter {
1152
1214
  };
1153
1215
  }
1154
1216
 
1155
- private async getTokenBalance(): Promise<
1156
- Map<string, { balance: bigint; tokenInfo: TokenInfo }>
1157
- > {
1158
- const lrc20Client = await this.lrc20ConnectionManager.createLrc20Client();
1217
+ private async getTokenBalance(): Promise<TokenBalanceMap> {
1218
+ const sparkTokenClient =
1219
+ await this.connectionManager.createSparkTokenClient(
1220
+ this.config.getCoordinatorAddress(),
1221
+ );
1159
1222
 
1160
- // Get token info for all tokens
1161
- const tokenInfo = await lrc20Client.getTokenPubkeyInfo({
1162
- publicKeys: Array.from(this.tokenOutputs.keys()).map(hexToBytes),
1223
+ const tokenMetadata = await sparkTokenClient.query_token_metadata({
1224
+ issuerPublicKeys: Array.from(this.tokenOutputs.keys()).map(hexToBytes),
1163
1225
  });
1226
+ const result: TokenBalanceMap = new Map();
1164
1227
 
1165
- const result = new Map<string, { balance: bigint; tokenInfo: TokenInfo }>();
1166
-
1167
- for (const info of tokenInfo.tokenPubkeyInfos) {
1168
- const tokenPublicKey = bytesToHex(
1169
- info.announcement!.publicKey!.publicKey,
1170
- );
1228
+ for (const metadata of tokenMetadata.tokenMetadata) {
1229
+ const tokenPublicKey = bytesToHex(metadata.issuerPublicKey);
1171
1230
  const leaves = this.tokenOutputs.get(tokenPublicKey);
1172
-
1173
- result.set(tokenPublicKey, {
1231
+ const humanReadableTokenIdentifier = encodeHumanReadableTokenIdentifier({
1232
+ tokenIdentifier: metadata.tokenIdentifier,
1233
+ network: this.config.getNetworkType(),
1234
+ });
1235
+ result.set(humanReadableTokenIdentifier, {
1174
1236
  balance: leaves ? calculateAvailableTokenAmount(leaves) : BigInt(0),
1175
- tokenInfo: {
1237
+ tokenMetadata: {
1176
1238
  tokenPublicKey,
1177
- tokenName: info.announcement!.name,
1178
- tokenSymbol: info.announcement!.symbol,
1179
- tokenDecimals: Number(bytesToNumberBE(info.announcement!.decimal)),
1180
- maxSupply: bytesToNumberBE(info.announcement!.maxSupply),
1239
+ rawTokenIdentifier: metadata.tokenIdentifier,
1240
+ tokenName: metadata.tokenName,
1241
+ tokenTicker: metadata.tokenTicker,
1242
+ decimals: metadata.decimals,
1243
+ maxSupply: bytesToNumberBE(metadata.maxSupply),
1181
1244
  },
1182
1245
  });
1183
1246
  }
@@ -1750,16 +1813,60 @@ export class SparkWallet extends EventEmitter {
1750
1813
  * @returns {Promise<string[]>} The unused deposit addresses
1751
1814
  */
1752
1815
  public async getUnusedDepositAddresses(): Promise<string[]> {
1816
+ return (await this.queryAllUnusedDepositAddresses({})).map(
1817
+ (addr) => addr.depositAddress,
1818
+ );
1819
+ }
1820
+
1821
+ /**
1822
+ * Gets all unused deposit addresses for the wallet.
1823
+ *
1824
+ * @param {Object} params - Parameters for querying unused deposit addresses
1825
+ * @param {Uint8Array<ArrayBufferLike>} [params.identityPublicKey] - The identity public key
1826
+ * @param {NetworkProto} [params.network] - The network
1827
+ * @returns {Promise<DepositAddressQueryResult[]>} The unused deposit addresses
1828
+ */
1829
+ private async queryAllUnusedDepositAddresses({
1830
+ identityPublicKey,
1831
+ network,
1832
+ }: {
1833
+ identityPublicKey?: Uint8Array<ArrayBufferLike>;
1834
+ network?: NetworkProto | undefined;
1835
+ }): Promise<DepositAddressQueryResult[]> {
1753
1836
  const sparkClient = await this.connectionManager.createSparkClient(
1754
1837
  this.config.getCoordinatorAddress(),
1755
1838
  );
1756
- return (
1757
- await sparkClient.query_unused_deposit_addresses({
1758
- identityPublicKey: await this.config.signer.getIdentityPublicKey(),
1759
- network: NetworkToProto[this.config.getNetwork()],
1760
- })
1761
- ).depositAddresses.map((addr) => addr.depositAddress);
1839
+
1840
+ let limit = 100;
1841
+ let offset = 0;
1842
+ const pastOffsets = new Set<number>();
1843
+ const depositAddresses: DepositAddressQueryResult[] = [];
1844
+
1845
+ while (offset >= 0) {
1846
+ // Prevent infinite loop in case error with coordinator
1847
+ if (pastOffsets.has(offset)) {
1848
+ console.warn("Offset has already been seen, stopping");
1849
+ break;
1850
+ }
1851
+
1852
+ const response = await sparkClient.query_unused_deposit_addresses({
1853
+ identityPublicKey:
1854
+ identityPublicKey ??
1855
+ (await this.config.signer.getIdentityPublicKey()),
1856
+ network: network ?? NetworkToProto[this.config.getNetwork()],
1857
+ limit,
1858
+ offset,
1859
+ });
1860
+
1861
+ depositAddresses.push(...response.depositAddresses);
1862
+
1863
+ pastOffsets.add(offset);
1864
+ offset = response.offset;
1865
+ }
1866
+
1867
+ return depositAddresses;
1762
1868
  }
1869
+
1763
1870
  /**
1764
1871
  * Claims a deposit to the wallet.
1765
1872
  * Note that if you used advancedDeposit, you don't need to call this function.
@@ -1816,19 +1923,15 @@ export class SparkWallet extends EventEmitter {
1816
1923
  }
1817
1924
  const depositTx = getTxFromRawTxHex(txHex);
1818
1925
 
1819
- const sparkClient = await this.connectionManager.createSparkClient(
1820
- this.config.getCoordinatorAddress(),
1821
- );
1822
-
1823
1926
  const unusedDepositAddresses: Map<string, DepositAddressQueryResult> =
1824
1927
  new Map(
1825
1928
  (
1826
- await sparkClient.query_unused_deposit_addresses({
1929
+ await this.queryAllUnusedDepositAddresses({
1827
1930
  identityPublicKey:
1828
1931
  await this.config.signer.getIdentityPublicKey(),
1829
1932
  network: NetworkToProto[this.config.getNetwork()],
1830
1933
  })
1831
- ).depositAddresses.map((addr) => [addr.depositAddress, addr]),
1934
+ ).map((addr) => [addr.depositAddress, addr]),
1832
1935
  );
1833
1936
  let depositAddress: DepositAddressQueryResult | undefined;
1834
1937
  let vout = 0;
@@ -1888,17 +1991,15 @@ export class SparkWallet extends EventEmitter {
1888
1991
  */
1889
1992
  public async advancedDeposit(txHex: string) {
1890
1993
  const depositTx = getTxFromRawTxHex(txHex);
1891
- const sparkClient = await this.connectionManager.createSparkClient(
1892
- this.config.getCoordinatorAddress(),
1893
- );
1994
+
1894
1995
  const unusedDepositAddresses: Map<string, DepositAddressQueryResult> =
1895
1996
  new Map(
1896
1997
  (
1897
- await sparkClient.query_unused_deposit_addresses({
1998
+ await this.queryAllUnusedDepositAddresses({
1898
1999
  identityPublicKey: await this.config.signer.getIdentityPublicKey(),
1899
2000
  network: NetworkToProto[this.config.getNetwork()],
1900
2001
  })
1901
- ).depositAddresses.map((addr) => [addr.depositAddress, addr]),
2002
+ ).map((addr) => [addr.depositAddress, addr]),
1902
2003
  );
1903
2004
 
1904
2005
  let vout = 0;
@@ -2023,7 +2124,9 @@ export class SparkWallet extends EventEmitter {
2023
2124
  );
2024
2125
 
2025
2126
  return await this.withLeaves(async () => {
2026
- let leavesToSend = await this.selectLeaves(amountSats);
2127
+ let leavesToSend = (await this.selectLeaves([amountSats])).get(
2128
+ amountSats,
2129
+ )!;
2027
2130
 
2028
2131
  leavesToSend = await this.checkRefreshTimelockNodes(leavesToSend);
2029
2132
  leavesToSend = await this.checkExtendTimeLockNodes(leavesToSend);
@@ -2325,7 +2428,7 @@ export class SparkWallet extends EventEmitter {
2325
2428
  transfer.status !==
2326
2429
  TransferStatus.TRANSFER_STATUS_RECEIVER_KEY_TWEAKED &&
2327
2430
  transfer.status !==
2328
- TransferStatus.TRANSFER_STATUSR_RECEIVER_REFUND_SIGNED &&
2431
+ TransferStatus.TRANSFER_STATUS_RECEIVER_REFUND_SIGNED &&
2329
2432
  transfer.status !==
2330
2433
  TransferStatus.TRANSFER_STATUS_RECEIVER_KEY_TWEAK_APPLIED
2331
2434
  ) {
@@ -2482,27 +2585,9 @@ export class SparkWallet extends EventEmitter {
2482
2585
  invoice.invoice.encodedInvoice,
2483
2586
  ).fallbackAddress;
2484
2587
 
2485
- if (
2486
- sparkFallbackAddress &&
2487
- isValidSparkFallback(hexToBytes(sparkFallbackAddress))
2488
- ) {
2489
- const invoiceIdentityPubkey = sparkFallbackAddress.slice(6); // remove the 3 byte header
2490
- const expectedIdentityPubkey =
2491
- receiverIdentityPubkey ?? (await this.getIdentityPublicKey());
2492
-
2493
- if (invoiceIdentityPubkey !== expectedIdentityPubkey) {
2494
- throw new ValidationError(
2495
- "Mismatch between spark identity embedded in lightning invoice and designated recipient spark identity",
2496
- {
2497
- field: "sparkFallbackAddress",
2498
- value: invoiceIdentityPubkey,
2499
- expected: expectedIdentityPubkey,
2500
- },
2501
- );
2502
- }
2503
- } else {
2588
+ if (!sparkFallbackAddress) {
2504
2589
  throw new ValidationError(
2505
- "No valid spark fallback address found in lightning invoice",
2590
+ "No spark fallback address found in lightning invoice",
2506
2591
  {
2507
2592
  field: "sparkFallbackAddress",
2508
2593
  value: sparkFallbackAddress,
@@ -2510,6 +2595,20 @@ export class SparkWallet extends EventEmitter {
2510
2595
  },
2511
2596
  );
2512
2597
  }
2598
+
2599
+ const expectedIdentityPubkey =
2600
+ receiverIdentityPubkey ?? (await this.getIdentityPublicKey());
2601
+
2602
+ if (sparkFallbackAddress !== expectedIdentityPubkey) {
2603
+ throw new ValidationError(
2604
+ "Mismatch between spark identity embedded in lightning invoice and designated recipient spark identity",
2605
+ {
2606
+ field: "sparkFallbackAddress",
2607
+ value: sparkFallbackAddress,
2608
+ expected: expectedIdentityPubkey,
2609
+ },
2610
+ );
2611
+ }
2513
2612
  }
2514
2613
 
2515
2614
  return invoice;
@@ -2607,9 +2706,8 @@ export class SparkWallet extends EventEmitter {
2607
2706
  "No valid spark address found in invoice. Defaulting to lightning.",
2608
2707
  );
2609
2708
  } else {
2610
- const identityPublicKey = sparkFallbackAddress.slice(6); // remove the 3 byte header
2611
2709
  const receiverSparkAddress = encodeSparkAddress({
2612
- identityPublicKey,
2710
+ identityPublicKey: sparkFallbackAddress,
2613
2711
  network: Network[invoiceNetwork] as NetworkType,
2614
2712
  });
2615
2713
  return await this.transfer({
@@ -2648,7 +2746,7 @@ export class SparkWallet extends EventEmitter {
2648
2746
  });
2649
2747
  }
2650
2748
 
2651
- let leaves = await this.selectLeaves(totalAmount);
2749
+ let leaves = (await this.selectLeaves([totalAmount])).get(totalAmount)!;
2652
2750
 
2653
2751
  leaves = await this.checkRefreshTimelockNodes(leaves);
2654
2752
  leaves = await this.checkExtendTimeLockNodes(leaves);
@@ -2786,17 +2884,24 @@ export class SparkWallet extends EventEmitter {
2786
2884
  *
2787
2885
  * @param {Object} params - Parameters for the withdrawal
2788
2886
  * @param {string} params.onchainAddress - The Bitcoin address where the funds should be sent
2789
- * @param {number} [params.amountSats] - The amount in satoshis to withdraw. If not specified, attempts to withdraw all available funds
2887
+ * @param {CoopExitFeeQuote} params.feeQuote - The fee quote for the withdrawal
2888
+ * @param {ExitSpeed} params.exitSpeed - The exit speed chosen for the withdrawal
2889
+ * @param {number} [params.amountSats] - The amount in satoshis to withdraw. If not specified, attempts to withdraw all available funds and deductFeeFromWithdrawalAmount is set to true.
2890
+ * @param {boolean} [params.deductFeeFromWithdrawalAmount] - Controls how the withdrawal fee is handled. If true, the fee is deducted from the withdrawal amount (amountSats), meaning the recipient will receive amountSats minus the fee. If false, the fee is paid separately from the wallet balance, meaning the recipient will receive the full amountSats.
2790
2891
  * @returns {Promise<CoopExitRequest | null | undefined>} The withdrawal request details, or null/undefined if the request cannot be completed
2791
2892
  */
2792
2893
  public async withdraw({
2793
2894
  onchainAddress,
2794
2895
  exitSpeed,
2896
+ feeQuote,
2795
2897
  amountSats,
2898
+ deductFeeFromWithdrawalAmount = true,
2796
2899
  }: {
2797
2900
  onchainAddress: string;
2798
2901
  exitSpeed: ExitSpeed;
2902
+ feeQuote: CoopExitFeeQuote;
2799
2903
  amountSats?: number;
2904
+ deductFeeFromWithdrawalAmount?: boolean;
2800
2905
  }) {
2801
2906
  if (!Number.isSafeInteger(amountSats)) {
2802
2907
  throw new ValidationError("Sats amount must be less than 2^53", {
@@ -2806,7 +2911,13 @@ export class SparkWallet extends EventEmitter {
2806
2911
  });
2807
2912
  }
2808
2913
  return await this.withLeaves(async () => {
2809
- return await this.coopExit(onchainAddress, exitSpeed, amountSats);
2914
+ return await this.coopExit(
2915
+ onchainAddress,
2916
+ feeQuote,
2917
+ exitSpeed,
2918
+ deductFeeFromWithdrawalAmount,
2919
+ amountSats,
2920
+ );
2810
2921
  });
2811
2922
  }
2812
2923
 
@@ -2820,7 +2931,9 @@ export class SparkWallet extends EventEmitter {
2820
2931
  */
2821
2932
  private async coopExit(
2822
2933
  onchainAddress: string,
2934
+ feeEstimate: CoopExitFeeQuote,
2823
2935
  exitSpeed: ExitSpeed,
2936
+ deductFeeFromWithdrawalAmount: boolean,
2824
2937
  targetAmountSats?: number,
2825
2938
  ) {
2826
2939
  if (!Number.isSafeInteger(targetAmountSats)) {
@@ -2831,53 +2944,46 @@ export class SparkWallet extends EventEmitter {
2831
2944
  });
2832
2945
  }
2833
2946
 
2834
- let leavesToSend: TreeNode[] = [];
2835
- if (targetAmountSats) {
2836
- leavesToSend = await this.selectLeaves(targetAmountSats);
2837
- } else {
2838
- leavesToSend = this.leaves.map((leaf) => ({
2839
- ...leaf,
2840
- }));
2947
+ if (!targetAmountSats) {
2948
+ deductFeeFromWithdrawalAmount = true;
2841
2949
  }
2842
2950
 
2843
- const sspClient = this.getSspClient();
2844
- const feeEstimate = await sspClient.getCoopExitFeeEstimate({
2845
- leafExternalIds: leavesToSend.map((leaf) => leaf.id),
2846
- withdrawalAddress: onchainAddress,
2847
- });
2951
+ let fee: number | undefined;
2952
+ switch (exitSpeed) {
2953
+ case ExitSpeed.FAST:
2954
+ fee =
2955
+ (feeEstimate.l1BroadcastFeeFast?.originalValue || 0) +
2956
+ (feeEstimate.userFeeFast?.originalValue || 0);
2957
+ break;
2958
+ case ExitSpeed.MEDIUM:
2959
+ fee =
2960
+ (feeEstimate.l1BroadcastFeeMedium?.originalValue || 0) +
2961
+ (feeEstimate.userFeeMedium?.originalValue || 0);
2962
+ break;
2963
+ case ExitSpeed.SLOW:
2964
+ fee =
2965
+ (feeEstimate.l1BroadcastFeeSlow?.originalValue || 0) +
2966
+ (feeEstimate.userFeeSlow?.originalValue || 0);
2967
+ break;
2968
+ default:
2969
+ throw new ValidationError("Invalid exit speed", {
2970
+ field: "exitSpeed",
2971
+ value: exitSpeed,
2972
+ expected: "FAST, MEDIUM, or SLOW",
2973
+ });
2974
+ }
2848
2975
 
2849
- if (feeEstimate) {
2850
- let fee: number | undefined;
2851
- switch (exitSpeed) {
2852
- case ExitSpeed.FAST:
2853
- fee =
2854
- (feeEstimate.speedFast?.l1BroadcastFee.originalValue || 0) +
2855
- (feeEstimate.speedFast?.userFee.originalValue || 0);
2856
- break;
2857
- case ExitSpeed.MEDIUM:
2858
- fee =
2859
- (feeEstimate.speedMedium?.l1BroadcastFee.originalValue || 0) +
2860
- (feeEstimate.speedMedium?.userFee.originalValue || 0);
2861
- break;
2862
- case ExitSpeed.SLOW:
2863
- fee =
2864
- (feeEstimate.speedSlow?.l1BroadcastFee.originalValue || 0) +
2865
- (feeEstimate.speedSlow?.userFee.originalValue || 0);
2866
- break;
2867
- default:
2868
- throw new ValidationError("Invalid exit speed", {
2869
- field: "exitSpeed",
2870
- value: exitSpeed,
2871
- expected: "FAST, MEDIUM, or SLOW",
2872
- });
2873
- }
2976
+ let leavesToSendToSsp: TreeNode[] = [];
2977
+ let leavesToSendToSE: TreeNode[] = [];
2874
2978
 
2875
- if (
2876
- fee !== undefined &&
2877
- fee > leavesToSend.reduce((acc, leaf) => acc + leaf.value, 0)
2878
- ) {
2979
+ if (deductFeeFromWithdrawalAmount) {
2980
+ leavesToSendToSsp = targetAmountSats
2981
+ ? (await this.selectLeaves([targetAmountSats])).get(targetAmountSats)!
2982
+ : this.leaves;
2983
+
2984
+ if (fee > leavesToSendToSsp.reduce((acc, leaf) => acc + leaf.value, 0)) {
2879
2985
  throw new ValidationError(
2880
- "The fee for the withdrawal is greater than the target amount",
2986
+ "The fee for the withdrawal is greater than the target withdrawal amount",
2881
2987
  {
2882
2988
  field: "fee",
2883
2989
  value: fee,
@@ -2885,12 +2991,38 @@ export class SparkWallet extends EventEmitter {
2885
2991
  },
2886
2992
  );
2887
2993
  }
2994
+ } else {
2995
+ if (!targetAmountSats) {
2996
+ throw new ValidationError(
2997
+ "targetAmountSats is required when deductFeeFromWithdrawalAmount is false",
2998
+ {
2999
+ field: "targetAmountSats",
3000
+ value: targetAmountSats,
3001
+ expected: "defined when deductFeeFromWithdrawalAmount is false",
3002
+ },
3003
+ );
3004
+ }
3005
+
3006
+ const leaves = await this.selectLeaves([targetAmountSats, fee]);
3007
+
3008
+ const leavesForTargetAmount = leaves.get(targetAmountSats);
3009
+ const leavesForFee = leaves.get(fee);
3010
+
3011
+ if (!leavesForTargetAmount || !leavesForFee) {
3012
+ throw new Error("Failed to select leaves for target amount and fee");
3013
+ }
3014
+
3015
+ leavesToSendToSsp = leavesForTargetAmount;
3016
+ leavesToSendToSE = leavesForFee;
2888
3017
  }
2889
- leavesToSend = await this.checkRefreshTimelockNodes(leavesToSend);
2890
- leavesToSend = await this.checkExtendTimeLockNodes(leavesToSend);
3018
+
3019
+ leavesToSendToSsp = await this.checkRefreshTimelockNodes(leavesToSendToSsp);
3020
+ leavesToSendToSsp = await this.checkExtendTimeLockNodes(leavesToSendToSsp);
3021
+ leavesToSendToSE = await this.checkRefreshTimelockNodes(leavesToSendToSE);
3022
+ leavesToSendToSE = await this.checkExtendTimeLockNodes(leavesToSendToSE);
2891
3023
 
2892
3024
  const leafKeyTweaks = await Promise.all(
2893
- leavesToSend.map(async (leaf) => ({
3025
+ [...leavesToSendToSE, ...leavesToSendToSsp].map(async (leaf) => ({
2894
3026
  leaf,
2895
3027
  signingPubKey: await this.config.signer.generatePublicKey(
2896
3028
  sha256(leaf.id),
@@ -2899,13 +3031,26 @@ export class SparkWallet extends EventEmitter {
2899
3031
  })),
2900
3032
  );
2901
3033
 
2902
- const coopExitRequest = await sspClient.requestCoopExit({
2903
- leafExternalIds: leavesToSend.map((leaf) => leaf.id),
3034
+ const requestCoopExitParams: RequestCoopExitInput = {
3035
+ leafExternalIds: leavesToSendToSsp.map((leaf) => leaf.id),
2904
3036
  withdrawalAddress: onchainAddress,
2905
3037
  idempotencyKey: uuidv7(),
2906
3038
  exitSpeed,
2907
- withdrawAll: true,
2908
- });
3039
+ withdrawAll: deductFeeFromWithdrawalAmount,
3040
+ };
3041
+
3042
+ if (!deductFeeFromWithdrawalAmount) {
3043
+ requestCoopExitParams.feeQuoteId = feeEstimate.id;
3044
+ requestCoopExitParams.feeLeafExternalIds = leavesToSendToSE.map(
3045
+ (leaf) => leaf.id,
3046
+ );
3047
+ }
3048
+
3049
+ const sspClient = this.getSspClient();
3050
+
3051
+ const coopExitRequest = await sspClient.requestCoopExit(
3052
+ requestCoopExitParams,
3053
+ );
2909
3054
 
2910
3055
  if (!coopExitRequest?.rawConnectorTransaction) {
2911
3056
  throw new Error("Failed to request coop exit");
@@ -2952,15 +3097,15 @@ export class SparkWallet extends EventEmitter {
2952
3097
  * @param {Object} params - Input parameters for fee estimation
2953
3098
  * @param {number} params.amountSats - The amount in satoshis to withdraw
2954
3099
  * @param {string} params.withdrawalAddress - The Bitcoin address where the funds should be sent
2955
- * @returns {Promise<CoopExitFeeEstimatesOutput | null>} Fee estimate for the withdrawal
3100
+ * @returns {Promise<CoopExitFeeQuote | null>} Fee estimate for the withdrawal
2956
3101
  */
2957
- public async getWithdrawalFeeEstimate({
3102
+ public async getWithdrawalFeeQuote({
2958
3103
  amountSats,
2959
3104
  withdrawalAddress,
2960
3105
  }: {
2961
3106
  amountSats: number;
2962
3107
  withdrawalAddress: string;
2963
- }): Promise<CoopExitFeeEstimatesOutput | null> {
3108
+ }): Promise<CoopExitFeeQuote | null> {
2964
3109
  const sspClient = this.getSspClient();
2965
3110
 
2966
3111
  if (!Number.isSafeInteger(amountSats)) {
@@ -2971,12 +3116,12 @@ export class SparkWallet extends EventEmitter {
2971
3116
  });
2972
3117
  }
2973
3118
 
2974
- let leaves = await this.selectLeaves(amountSats);
3119
+ let leaves = (await this.selectLeaves([amountSats])).get(amountSats)!;
2975
3120
 
2976
3121
  leaves = await this.checkRefreshTimelockNodes(leaves);
2977
3122
  leaves = await this.checkExtendTimeLockNodes(leaves);
2978
3123
 
2979
- const feeEstimate = await sspClient.getCoopExitFeeEstimate({
3124
+ const feeEstimate = await sspClient.getCoopExitFeeQuote({
2980
3125
  leafExternalIds: leaves.map((leaf) => leaf.id),
2981
3126
  withdrawalAddress,
2982
3127
  });
@@ -3005,8 +3150,15 @@ export class SparkWallet extends EventEmitter {
3005
3150
  * @param {string} id - The ID of the transfer
3006
3151
  * @returns {Promise<Transfer | undefined>} The transfer
3007
3152
  */
3008
- public async getTransfer(id: string): Promise<Transfer | undefined> {
3009
- return await this.transferService.queryTransfer(id);
3153
+ public async getTransfer(id: string): Promise<WalletTransfer | undefined> {
3154
+ const transfer = await this.transferService.queryTransfer(id);
3155
+ if (!transfer) {
3156
+ return undefined;
3157
+ }
3158
+ return mapTransferToWalletTransfer(
3159
+ transfer,
3160
+ bytesToHex(await this.config.signer.getIdentityPublicKey()),
3161
+ );
3010
3162
  }
3011
3163
 
3012
3164
  // ***** Token Flow *****
@@ -3021,10 +3173,9 @@ export class SparkWallet extends EventEmitter {
3021
3173
  this.tokenOutputs.clear();
3022
3174
 
3023
3175
  const unsortedTokenOutputs =
3024
- await this.tokenTransactionService.fetchOwnedTokenOutputs(
3025
- [await this.config.signer.getIdentityPublicKey()],
3026
- [],
3027
- );
3176
+ await this.tokenTransactionService.fetchOwnedTokenOutputs({
3177
+ ownerPublicKeys: [await this.config.signer.getIdentityPublicKey()],
3178
+ });
3028
3179
 
3029
3180
  const filteredTokenOutputs = unsortedTokenOutputs.filter(
3030
3181
  (output) =>
@@ -3155,34 +3306,27 @@ export class SparkWallet extends EventEmitter {
3155
3306
  * Retrieves token transaction history for specified tokens owned by the wallet.
3156
3307
  * Can optionally filter by specific transaction hashes.
3157
3308
  *
3158
- * @param tokenPublicKeys - Array of token public keys to query transactions for
3309
+ * @param ownerPublicKeys - Optional array of owner public keys to query transactions for
3310
+ * @param issuerPublicKeys - Optional array of issuer public keys to query transactions for
3159
3311
  * @param tokenTransactionHashes - Optional array of specific transaction hashes to filter by
3312
+ * @param tokenIdentifiers - Optional array of token identifiers to filter by
3313
+ * @param outputIds - Optional array of output IDs to filter by
3160
3314
  * @returns Promise resolving to array of token transactions with their current status
3161
3315
  */
3162
3316
  public async queryTokenTransactions(
3163
- tokenPublicKeys: string[],
3317
+ ownerPublicKeys?: string[],
3318
+ issuerPublicKeys?: string[],
3164
3319
  tokenTransactionHashes?: string[],
3320
+ tokenIdentifiers?: string[],
3321
+ outputIds?: string[],
3165
3322
  ): Promise<TokenTransactionWithStatus[]> {
3166
- const sparkClient = await this.connectionManager.createSparkClient(
3167
- this.config.getCoordinatorAddress(),
3168
- );
3169
-
3170
- let queryParams;
3171
- if (tokenTransactionHashes?.length) {
3172
- queryParams = {
3173
- tokenPublicKeys: tokenPublicKeys?.map(hexToBytes)!,
3174
- ownerPublicKeys: [hexToBytes(await this.getIdentityPublicKey())],
3175
- tokenTransactionHashes: tokenTransactionHashes.map(hexToBytes),
3176
- };
3177
- } else {
3178
- queryParams = {
3179
- tokenPublicKeys: tokenPublicKeys?.map(hexToBytes)!,
3180
- ownerPublicKeys: [hexToBytes(await this.getIdentityPublicKey())],
3181
- };
3182
- }
3183
-
3184
- const response = await sparkClient.query_token_transactions(queryParams);
3185
- return response.tokenTransactionsWithStatus;
3323
+ return this.tokenTransactionService.queryTokenTransactions({
3324
+ ownerPublicKeys,
3325
+ issuerPublicKeys,
3326
+ tokenTransactionHashes,
3327
+ tokenIdentifiers,
3328
+ outputIds,
3329
+ }) as Promise<TokenTransactionWithStatus[]>;
3186
3330
  }
3187
3331
 
3188
3332
  public async getTokenL1Address(): Promise<string> {