@buildonspark/spark-sdk 0.0.20 → 0.0.22

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 (47) hide show
  1. package/dist/graphql/client.d.ts +4 -0
  2. package/dist/graphql/client.js +57 -0
  3. package/dist/graphql/client.js.map +1 -1
  4. package/dist/graphql/queries/InvoiceRequest.d.ts +1 -0
  5. package/dist/graphql/queries/InvoiceRequest.js +2 -0
  6. package/dist/graphql/queries/InvoiceRequest.js.map +1 -0
  7. package/dist/graphql/queries/UserRequest.d.ts +1 -0
  8. package/dist/graphql/queries/UserRequest.js +10 -0
  9. package/dist/graphql/queries/UserRequest.js.map +1 -0
  10. package/dist/services/lightning.d.ts +4 -3
  11. package/dist/services/lightning.js +1 -1
  12. package/dist/services/lightning.js.map +1 -1
  13. package/dist/services/transfer.d.ts +1 -1
  14. package/dist/services/transfer.js.map +1 -1
  15. package/dist/spark-sdk.d.ts +57 -11
  16. package/dist/spark-sdk.js +154 -81
  17. package/dist/spark-sdk.js.map +1 -1
  18. package/dist/tests/adaptor-signature.test.js +1 -1
  19. package/dist/tests/adaptor-signature.test.js.map +1 -1
  20. package/dist/tests/coop-exit.test.js +2 -2
  21. package/dist/tests/coop-exit.test.js.map +1 -1
  22. package/dist/tests/deposit.test.js +2 -2
  23. package/dist/tests/deposit.test.js.map +1 -1
  24. package/dist/tests/lightning.test.js +33 -4
  25. package/dist/tests/lightning.test.js.map +1 -1
  26. package/dist/tests/swap.test.js +2 -2
  27. package/dist/tests/swap.test.js.map +1 -1
  28. package/dist/tests/transfer.test.js +7 -7
  29. package/dist/tests/transfer.test.js.map +1 -1
  30. package/dist/tests/utils/spark-testing-wallet.d.ts +1 -1
  31. package/dist/tests/utils/spark-testing-wallet.js +1 -1
  32. package/dist/tests/utils/spark-testing-wallet.js.map +1 -1
  33. package/package.json +2 -2
  34. package/src/examples/example.ts +99 -19
  35. package/src/graphql/client.ts +69 -0
  36. package/src/graphql/queries/InvoiceRequest.ts +0 -0
  37. package/src/graphql/queries/UserRequest.ts +10 -0
  38. package/src/services/lightning.ts +5 -4
  39. package/src/services/transfer.ts +1 -1
  40. package/src/spark-sdk.ts +223 -98
  41. package/src/tests/adaptor-signature.test.ts +1 -1
  42. package/src/tests/coop-exit.test.ts +2 -2
  43. package/src/tests/deposit.test.ts +2 -2
  44. package/src/tests/lightning.test.ts +41 -5
  45. package/src/tests/swap.test.ts +2 -2
  46. package/src/tests/transfer.test.ts +7 -7
  47. package/src/tests/utils/spark-testing-wallet.ts +1 -1
@@ -0,0 +1,10 @@
1
+ import { FRAGMENT as UserRequestFragment } from "../objects/UserRequest.js";
2
+
3
+ export const UserRequest = `
4
+ query UserRequest($request_id: ID!) {
5
+ user_request(request_id: $request_id) {
6
+ ...UserRequestFragment
7
+ }
8
+ }
9
+ ${UserRequestFragment}
10
+ `;
@@ -7,6 +7,7 @@ import { secp256k1 } from "@noble/curves/secp256k1";
7
7
  import { TransactionInput } from "@scure/btc-signer/psbt";
8
8
  import { sha256 } from "@scure/btc-signer/utils";
9
9
  import { decode } from "light-bolt11-decoder";
10
+ import LightningReceiveRequest from "../graphql/objects/LightningReceiveRequest.js";
10
11
  import {
11
12
  GetSigningCommitmentsResponse,
12
13
  InitiatePreimageSwapRequest_Reason,
@@ -38,7 +39,7 @@ export type CreateLightningInvoiceParams = {
38
39
  amountSats: number,
39
40
  paymentHash: Uint8Array,
40
41
  memo?: string,
41
- ) => Promise<string | undefined>;
42
+ ) => Promise<LightningReceiveRequest | null>;
42
43
  amountSats: number;
43
44
  memo?: string;
44
45
  };
@@ -71,7 +72,7 @@ export class LightningService {
71
72
  invoiceCreator,
72
73
  amountSats,
73
74
  memo,
74
- }: CreateLightningInvoiceParams): Promise<string> {
75
+ }: CreateLightningInvoiceParams): Promise<LightningReceiveRequest> {
75
76
  const randBytes = crypto.getRandomValues(new Uint8Array(32));
76
77
  const preimage = numberToBytesBE(
77
78
  bytesToNumberBE(randBytes) % secp256k1.CURVE.n,
@@ -90,7 +91,7 @@ export class LightningService {
90
91
  amountSats,
91
92
  memo,
92
93
  preimage,
93
- }: CreateLightningInvoiceWithPreimageParams): Promise<string> {
94
+ }: CreateLightningInvoiceWithPreimageParams): Promise<LightningReceiveRequest> {
94
95
  const paymentHash = sha256(preimage);
95
96
  const invoice = await invoiceCreator(amountSats, paymentHash, memo);
96
97
  if (!invoice) {
@@ -124,7 +125,7 @@ export class LightningService {
124
125
  proofs: share.proofs,
125
126
  },
126
127
  threshold: this.config.getThreshold(),
127
- invoiceString: invoice,
128
+ invoiceString: invoice.invoice.encodedEnvoice,
128
129
  userIdentityPublicKey:
129
130
  await this.config.signer.getIdentityPublicKey(),
130
131
  });
@@ -1056,7 +1056,7 @@ export class TransferService extends BaseTransferService {
1056
1056
  });
1057
1057
  }
1058
1058
 
1059
- private async extendTimelock(node: TreeNode, signingPubKey: Uint8Array) {
1059
+ async extendTimelock(node: TreeNode, signingPubKey: Uint8Array) {
1060
1060
  const nodeTx = getTxFromRawTxBytes(node.nodeTx);
1061
1061
  const refundTx = getTxFromRawTxBytes(node.refundTx);
1062
1062
 
package/src/spark-sdk.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  bytesToHex,
3
- hexToBytes,
4
3
  bytesToNumberBE,
4
+ hexToBytes,
5
5
  } from "@noble/curves/abstract/utils";
6
6
  import { secp256k1 } from "@noble/curves/secp256k1";
7
7
  import { Address, OutScript, Transaction } from "@scure/btc-signer";
@@ -11,14 +11,16 @@ import { decode } from "light-bolt11-decoder";
11
11
  import SspClient from "./graphql/client.js";
12
12
  import {
13
13
  BitcoinNetwork,
14
- CoopExitFeeEstimateInput,
15
14
  CoopExitFeeEstimateOutput,
15
+ CoopExitRequest,
16
16
  LeavesSwapFeeEstimateOutput,
17
17
  LeavesSwapRequest,
18
18
  LightningReceiveFeeEstimateInput,
19
19
  LightningReceiveFeeEstimateOutput,
20
+ LightningReceiveRequest,
20
21
  LightningSendFeeEstimateInput,
21
22
  LightningSendFeeEstimateOutput,
23
+ LightningSendRequest,
22
24
  UserLeafInput,
23
25
  } from "./graphql/objects/index.js";
24
26
  import {
@@ -38,11 +40,14 @@ import { LightningService } from "./services/lightning.js";
38
40
  import { TokenTransactionService } from "./services/token-transactions.js";
39
41
  import { LeafKeyTweak, TransferService } from "./services/transfer.js";
40
42
  import { ConfigOptions } from "./services/wallet-config.js";
41
-
43
+ import {
44
+ createLrc20ConnectionManager,
45
+ ILrc20ConnectionManager,
46
+ Lrc20SparkClient,
47
+ } from "@buildonspark/lrc20-sdk/grpc";
42
48
  import { validateMnemonic } from "@scure/bip39";
43
49
  import { wordlist } from "@scure/bip39/wordlists/english";
44
50
  import { Mutex } from "async-mutex";
45
- import bitcoin from "bitcoinjs-lib";
46
51
  import {
47
52
  DepositAddressTree,
48
53
  TreeCreationService,
@@ -103,6 +108,14 @@ type DepositParams = {
103
108
  vout: number;
104
109
  };
105
110
 
111
+ export type TokenInfo = {
112
+ tokenPublicKey: string;
113
+ tokenName: string;
114
+ tokenSymbol: string;
115
+ tokenDecimals: number;
116
+ tokenSupply: bigint;
117
+ }
118
+
106
119
  export type InitWalletResponse = {
107
120
  mnemonic?: string | undefined;
108
121
  };
@@ -122,6 +135,7 @@ export class SparkWallet {
122
135
  protected config: WalletConfigService;
123
136
 
124
137
  protected connectionManager: ConnectionManager;
138
+ protected lrc20ConnectionManager: ILrc20ConnectionManager;
125
139
  protected lrc20Wallet: LRCWallet | undefined;
126
140
 
127
141
  private depositService: DepositService;
@@ -137,6 +151,8 @@ export class SparkWallet {
137
151
  private sspClient: SspClient | null = null;
138
152
  private wasmModule: InitOutput | null = null;
139
153
 
154
+ private mutexes: Map<string, Mutex> = new Map();
155
+
140
156
  private pendingWithdrawnLeafIds: string[] = [];
141
157
 
142
158
  protected leaves: TreeNode[] = [];
@@ -146,6 +162,7 @@ export class SparkWallet {
146
162
  protected constructor(options?: ConfigOptions, signer?: SparkSigner) {
147
163
  this.config = new WalletConfigService(options, signer);
148
164
  this.connectionManager = new ConnectionManager(this.config);
165
+ this.lrc20ConnectionManager = createLrc20ConnectionManager(this.config.getLrc20Address());
149
166
  this.depositService = new DepositService(
150
167
  this.config,
151
168
  this.connectionManager,
@@ -172,7 +189,7 @@ export class SparkWallet {
172
189
  );
173
190
  }
174
191
 
175
- public static async intialize({
192
+ public static async initialize({
176
193
  mnemonicOrSeed,
177
194
  signer,
178
195
  options,
@@ -326,7 +343,7 @@ export class SparkWallet {
326
343
  this.leaves = await this.getLeaves();
327
344
  await this.config.signer.restoreSigningKeysFromLeafs(this.leaves);
328
345
  await this.refreshTimelockNodes();
329
-
346
+ await this.extendTimeLockNodes();
330
347
  this.optimizeLeaves().catch((e) => {
331
348
  console.error("Failed to optimize leaves", e);
332
349
  });
@@ -670,6 +687,25 @@ export class SparkWallet {
670
687
  return await this.transferService.queryAllTransfers(limit, offset);
671
688
  }
672
689
 
690
+ public async getTokenInfo(): Promise<TokenInfo[]> {
691
+ await this.syncTokenLeaves();
692
+
693
+ const lrc20Client = await this.lrc20ConnectionManager.createLrc20Client();
694
+ const { balance, tokenBalances } = await this.getBalance();
695
+
696
+ const tokenInfo = await lrc20Client.getTokenPubkeyInfo({
697
+ publicKeys: Array.from(tokenBalances.keys()).map(hexToBytes)
698
+ });
699
+
700
+ return tokenInfo.tokenPubkeyInfos.map((info) => ({
701
+ tokenPublicKey: bytesToHex(info.announcement!.publicKey!.publicKey),
702
+ tokenName: info.announcement!.name,
703
+ tokenSymbol: info.announcement!.symbol,
704
+ tokenDecimals: Number(bytesToNumberBE(info.announcement!.decimal)),
705
+ tokenSupply: bytesToNumberBE(info.totalSupply),
706
+ }));
707
+ }
708
+
673
709
  /**
674
710
  * Gets the current balance of the wallet.
675
711
  * You can use the forceRefetch option to synchronize your wallet and claim any
@@ -776,71 +812,84 @@ export class SparkWallet {
776
812
  * @returns {Promise<TreeNode[] | undefined>} The nodes resulting from the deposit
777
813
  */
778
814
  public async claimDeposit(txid: string) {
779
- const baseUrl =
780
- this.config.getNetwork() === Network.REGTEST
781
- ? "https://regtest-mempool.dev.dev.sparkinfra.net/api"
782
- : "https://mempool.space/api";
783
- const auth = btoa("spark-sdk:mCMk1JqlBNtetUNy");
784
-
785
- const headers: Record<string, string> = {
786
- "Content-Type": "application/json",
787
- };
788
-
789
- if (this.config.getNetwork() === Network.REGTEST) {
790
- headers["Authorization"] = `Basic ${auth}`;
815
+ let mutex = this.mutexes.get(txid);
816
+ if (!mutex) {
817
+ mutex = new Mutex();
818
+ this.mutexes.set(txid, mutex);
791
819
  }
792
820
 
793
- const response = await fetch(`${baseUrl}/tx/${txid}/hex`, {
794
- headers,
795
- });
821
+ const nodes = await mutex.runExclusive(async () => {
822
+ const baseUrl =
823
+ this.config.getNetwork() === Network.REGTEST
824
+ ? "https://regtest-mempool.dev.dev.sparkinfra.net/api"
825
+ : "https://mempool.space/api";
826
+ const auth = btoa("spark-sdk:mCMk1JqlBNtetUNy");
796
827
 
797
- const txHex = await response.text();
798
- if (!/^[0-9A-Fa-f]+$/.test(txHex)) {
799
- throw new Error("Transaction not found");
800
- }
801
- const depositTx = getTxFromRawTxHex(txHex);
828
+ const headers: Record<string, string> = {
829
+ "Content-Type": "application/json",
830
+ };
802
831
 
803
- const sparkClient = await this.connectionManager.createSparkClient(
804
- this.config.getCoordinatorAddress(),
805
- );
832
+ if (this.config.getNetwork() === Network.REGTEST) {
833
+ headers["Authorization"] = `Basic ${auth}`;
834
+ }
806
835
 
807
- const unusedDepositAddresses: Map<string, DepositAddressQueryResult> =
808
- new Map(
809
- (
810
- await sparkClient.query_unused_deposit_addresses({
811
- identityPublicKey: await this.config.signer.getIdentityPublicKey(),
812
- })
813
- ).depositAddresses.map((addr) => [addr.depositAddress, addr]),
814
- );
836
+ const response = await fetch(`${baseUrl}/tx/${txid}/hex`, {
837
+ headers,
838
+ });
815
839
 
816
- let depositAddress: DepositAddressQueryResult | undefined;
817
- let vout = 0;
818
- for (let i = 0; i < depositTx.outputsLength; i++) {
819
- const output = depositTx.getOutput(i);
820
- if (!output) {
821
- continue;
840
+ const txHex = await response.text();
841
+ if (!/^[0-9A-Fa-f]+$/.test(txHex)) {
842
+ throw new Error("Transaction not found");
822
843
  }
823
- const parsedScript = OutScript.decode(output.script!);
824
- const address = Address(getNetwork(this.config.getNetwork())).encode(
825
- parsedScript,
844
+ const depositTx = getTxFromRawTxHex(txHex);
845
+
846
+ const sparkClient = await this.connectionManager.createSparkClient(
847
+ this.config.getCoordinatorAddress(),
826
848
  );
827
- if (unusedDepositAddresses.has(address)) {
828
- vout = i;
829
- depositAddress = unusedDepositAddresses.get(address);
830
- break;
849
+
850
+ const unusedDepositAddresses: Map<string, DepositAddressQueryResult> =
851
+ new Map(
852
+ (
853
+ await sparkClient.query_unused_deposit_addresses({
854
+ identityPublicKey:
855
+ await this.config.signer.getIdentityPublicKey(),
856
+ })
857
+ ).depositAddresses.map((addr) => [addr.depositAddress, addr]),
858
+ );
859
+
860
+ let depositAddress: DepositAddressQueryResult | undefined;
861
+ let vout = 0;
862
+ for (let i = 0; i < depositTx.outputsLength; i++) {
863
+ const output = depositTx.getOutput(i);
864
+ if (!output) {
865
+ continue;
866
+ }
867
+ const parsedScript = OutScript.decode(output.script!);
868
+ const address = Address(getNetwork(this.config.getNetwork())).encode(
869
+ parsedScript,
870
+ );
871
+ if (unusedDepositAddresses.has(address)) {
872
+ vout = i;
873
+ depositAddress = unusedDepositAddresses.get(address);
874
+ break;
875
+ }
876
+ }
877
+ if (!depositAddress) {
878
+ return [];
831
879
  }
832
- }
833
- if (!depositAddress) {
834
- throw new Error("Deposit address not found");
835
- }
836
880
 
837
- const nodes = await this.finalizeDeposit({
838
- signingPubKey: depositAddress.userSigningPublicKey,
839
- verifyingKey: depositAddress.verifyingPublicKey,
840
- depositTx,
841
- vout,
881
+ const nodes = await this.finalizeDeposit({
882
+ signingPubKey: depositAddress.userSigningPublicKey,
883
+ verifyingKey: depositAddress.verifyingPublicKey,
884
+ depositTx,
885
+ vout,
886
+ });
887
+
888
+ return nodes;
842
889
  });
843
890
 
891
+ this.mutexes.delete(txid);
892
+
844
893
  return nodes;
845
894
  }
846
895
 
@@ -864,18 +913,12 @@ export class SparkWallet {
864
913
  })),
865
914
  );
866
915
 
867
- await this.transferService.sendTransfer(
916
+ const transfer = await this.transferService.sendTransfer(
868
917
  leafKeyTweaks,
869
918
  await this.config.signer.getIdentityPublicKey(),
870
919
  );
871
920
 
872
- const pendingTransfers = await this.transferService.queryPendingTransfers();
873
- if (pendingTransfers.transfers.length > 0) {
874
- // @ts-ignore - We check the length, so the first element is guaranteed to exist
875
- return (await this.claimTransfer(pendingTransfers.transfers[0])).nodes;
876
- }
877
-
878
- return;
921
+ return await this.claimTransfer(transfer);
879
922
  }
880
923
  // ***** Transfer Flow *****
881
924
 
@@ -892,6 +935,7 @@ export class SparkWallet {
892
935
  const leavesToSend = await this.selectLeaves(amountSats);
893
936
 
894
937
  await this.refreshTimelockNodes();
938
+ await this.extendTimeLockNodes();
895
939
 
896
940
  const leafKeyTweaks = await Promise.all(
897
941
  leavesToSend.map(async (leaf) => ({
@@ -915,6 +959,29 @@ export class SparkWallet {
915
959
  });
916
960
  }
917
961
 
962
+ private async extendTimeLockNodes() {
963
+ const nodesToExtend: TreeNode[] = [];
964
+ const nodeIds: string[] = [];
965
+
966
+ for (const node of this.leaves) {
967
+ const nodeTx = getTxFromRawTxBytes(node.nodeTx);
968
+ const { nextSequence } = getNextTransactionSequence(
969
+ nodeTx.getInput(0).sequence,
970
+ );
971
+ if (nextSequence <= 0) {
972
+ nodesToExtend.push(node);
973
+ nodeIds.push(node.id);
974
+ }
975
+ }
976
+
977
+ for (const node of nodesToExtend) {
978
+ await this.transferService.extendTimelock(
979
+ node,
980
+ await this.config.signer.generatePublicKey(sha256(node.id)),
981
+ );
982
+ }
983
+ }
984
+
918
985
  /**
919
986
  * Internal method to refresh timelock nodes.
920
987
  *
@@ -1048,6 +1115,7 @@ export class SparkWallet {
1048
1115
 
1049
1116
  this.leaves.push(...response.nodes);
1050
1117
  await this.refreshTimelockNodes();
1118
+ await this.extendTimeLockNodes();
1051
1119
 
1052
1120
  return response.nodes;
1053
1121
  });
@@ -1112,13 +1180,13 @@ export class SparkWallet {
1112
1180
  * @param {number} params.amountSats - Amount in satoshis
1113
1181
  * @param {string} params.memo - Description for the invoice
1114
1182
  * @param {number} [params.expirySeconds] - Optional expiry time in seconds
1115
- * @returns {Promise<string>} BOLT11 encoded invoice
1183
+ * @returns {Promise<LightningReceiveRequest>} BOLT11 encoded invoice
1116
1184
  */
1117
1185
  public async createLightningInvoice({
1118
1186
  amountSats,
1119
1187
  memo,
1120
1188
  expirySeconds = 60 * 60 * 24 * 30,
1121
- }: CreateLightningInvoiceParams) {
1189
+ }: CreateLightningInvoiceParams): Promise<LightningReceiveRequest> {
1122
1190
  if (!this.sspClient) {
1123
1191
  throw new Error("SSP client not initialized");
1124
1192
  }
@@ -1144,14 +1212,16 @@ export class SparkWallet {
1144
1212
  memo,
1145
1213
  });
1146
1214
 
1147
- return invoice?.invoice.encodedEnvoice;
1215
+ return invoice;
1148
1216
  };
1149
1217
 
1150
- return this.lightningService!.createLightningInvoice({
1218
+ const invoice = await this.lightningService!.createLightningInvoice({
1151
1219
  amountSats,
1152
1220
  memo,
1153
1221
  invoiceCreator: requestLightningInvoice,
1154
1222
  });
1223
+
1224
+ return invoice;
1155
1225
  }
1156
1226
 
1157
1227
  /**
@@ -1191,6 +1261,8 @@ export class SparkWallet {
1191
1261
  const leaves = await this.selectLeaves(amountSats);
1192
1262
 
1193
1263
  await this.refreshTimelockNodes();
1264
+ await this.extendTimeLockNodes();
1265
+
1194
1266
  const leavesToSend = await Promise.all(
1195
1267
  leaves.map(async (leaf) => ({
1196
1268
  leaf,
@@ -1346,21 +1418,21 @@ export class SparkWallet {
1346
1418
  *
1347
1419
  * @param {Object} params - Parameters for the withdrawal
1348
1420
  * @param {string} params.onchainAddress - The Bitcoin address where the funds should be sent
1349
- * @param {number} [params.targetAmountSats] - The amount in satoshis to withdraw. If not specified, attempts to withdraw all available funds
1421
+ * @param {number} [params.amountSats] - The amount in satoshis to withdraw. If not specified, attempts to withdraw all available funds
1350
1422
  * @returns {Promise<CoopExitRequest | null | undefined>} The withdrawal request details, or null/undefined if the request cannot be completed
1351
1423
  */
1352
1424
  public async withdraw({
1353
1425
  onchainAddress,
1354
- targetAmountSats,
1426
+ amountSats,
1355
1427
  }: {
1356
1428
  onchainAddress: string;
1357
- targetAmountSats?: number;
1429
+ amountSats?: number;
1358
1430
  }) {
1359
- if (targetAmountSats && targetAmountSats < 10000) {
1431
+ if (amountSats && amountSats < 10000) {
1360
1432
  throw new Error("The minimum amount for a withdrawal is 10000 sats");
1361
1433
  }
1362
1434
  return await this.withLeaves(async () => {
1363
- return await this.coopExit(onchainAddress, targetAmountSats);
1435
+ return await this.coopExit(onchainAddress, amountSats);
1364
1436
  });
1365
1437
  }
1366
1438
 
@@ -1446,19 +1518,30 @@ export class SparkWallet {
1446
1518
  /**
1447
1519
  * Gets fee estimate for cooperative exit (on-chain withdrawal).
1448
1520
  *
1449
- * @param {CoopExitFeeEstimateInput} params - Input parameters for fee estimation
1521
+ * @param {Object} params - Input parameters for fee estimation
1522
+ * @param {number} params.amountSats - The amount in satoshis to withdraw
1523
+ * @param {string} params.withdrawalAddress - The Bitcoin address where the funds should be sent
1450
1524
  * @returns {Promise<CoopExitFeeEstimateOutput | null>} Fee estimate for the withdrawal
1451
1525
  */
1452
1526
  public async getCoopExitFeeEstimate({
1453
- leafExternalIds,
1527
+ amountSats,
1454
1528
  withdrawalAddress,
1455
- }: CoopExitFeeEstimateInput): Promise<CoopExitFeeEstimateOutput | null> {
1529
+ }: {
1530
+ amountSats: number;
1531
+ withdrawalAddress: string;
1532
+ }): Promise<CoopExitFeeEstimateOutput | null> {
1456
1533
  if (!this.sspClient) {
1457
1534
  throw new Error("SSP client not initialized");
1458
1535
  }
1459
1536
 
1537
+ if (amountSats < 10000) {
1538
+ throw new Error("The minimum amount for a withdrawal is 10000 sats");
1539
+ }
1540
+
1541
+ const leaves = await this.selectLeaves(amountSats);
1542
+
1460
1543
  return await this.sspClient.getCoopExitFeeEstimate({
1461
- leafExternalIds,
1544
+ leafExternalIds: leaves.map((leaf) => leaf.id),
1462
1545
  withdrawalAddress,
1463
1546
  });
1464
1547
  }
@@ -1757,25 +1840,67 @@ export class SparkWallet {
1757
1840
  return;
1758
1841
  }
1759
1842
  }
1760
- }
1761
1843
 
1762
- /**
1763
- * Utility function to determine the network from a Bitcoin address.
1764
- *
1765
- * @param {string} address - The Bitcoin address
1766
- * @returns {BitcoinNetwork | null} The detected network or null if not detected
1767
- */
1768
- function getNetworkFromAddress(address: string) {
1769
- try {
1770
- const decoded = bitcoin.address.fromBech32(address);
1771
- // HRP (human-readable part) determines the network
1772
- if (decoded.prefix === "bc") {
1773
- return BitcoinNetwork.MAINNET;
1774
- } else if (decoded.prefix === "bcrt") {
1775
- return BitcoinNetwork.REGTEST;
1844
+ /**
1845
+ * Signs a message with the identity key.
1846
+ *
1847
+ * @param {string} message - Unhashed message to sign
1848
+ * @param {boolean} [compact] - Whether to use compact encoding. If false, the message will be encoded as DER.
1849
+ * @returns {Promise<string>} The signed message
1850
+ */
1851
+ public async signMessage(
1852
+ message: string,
1853
+ compact?: boolean,
1854
+ ): Promise<string> {
1855
+ const hash = sha256(message);
1856
+ return bytesToHex(
1857
+ await this.config.signer.signMessageWithIdentityKey(hash, compact),
1858
+ );
1859
+ }
1860
+
1861
+ /**
1862
+ * Get a Lightning receive request by ID.
1863
+ *
1864
+ * @param {string} id - The ID of the Lightning receive request
1865
+ * @returns {Promise<LightningReceiveRequest | null>} The Lightning receive request
1866
+ */
1867
+ public async getLightningReceiveRequest(
1868
+ id: string,
1869
+ ): Promise<LightningReceiveRequest | null> {
1870
+ if (!this.sspClient) {
1871
+ throw new Error("SSP client not initialized");
1776
1872
  }
1777
- } catch (err) {
1778
- throw new Error("Invalid Bitcoin address");
1873
+
1874
+ return await this.sspClient.getLightningReceiveRequest(id);
1875
+ }
1876
+
1877
+ /**
1878
+ * Get a Lightning send request by ID.
1879
+ *
1880
+ * @param {string} id - The ID of the Lightning send request
1881
+ * @returns {Promise<LightningSendRequest | null>} The Lightning send request
1882
+ */
1883
+ public async getLightningSendRequest(
1884
+ id: string,
1885
+ ): Promise<LightningSendRequest | null> {
1886
+ if (!this.sspClient) {
1887
+ throw new Error("SSP client not initialized");
1888
+ }
1889
+
1890
+ return await this.sspClient.getLightningSendRequest(id);
1891
+ }
1892
+
1893
+ /**
1894
+ * Get a coop exit request by ID.
1895
+ *
1896
+ * @param {string} id - The ID of the coop exit request
1897
+ * @returns {Promise<CoopExitRequest | null>} The coop exit request
1898
+ */
1899
+ public async getCoopExitRequest(id: string): Promise<CoopExitRequest | null> {
1900
+ if (!this.sspClient) {
1901
+ throw new Error("SSP client not initialized");
1902
+ }
1903
+
1904
+ return await this.sspClient.getCoopExitRequest(id);
1779
1905
  }
1780
- return null;
1781
1906
  }
@@ -16,7 +16,7 @@ describe("adaptor signature", () => {
16
16
  async () => {
17
17
  let failures = 0;
18
18
 
19
- const { wallet } = await SparkWalletTesting.intialize({
19
+ const { wallet } = await SparkWalletTesting.initialize({
20
20
  options: {
21
21
  network: "LOCAL",
22
22
  },
@@ -41,7 +41,7 @@ describe("coop exit", () => {
41
41
  };
42
42
 
43
43
  // Setup user with leaves
44
- const { wallet: userWallet } = await SparkWalletTesting.intialize({
44
+ const { wallet: userWallet } = await SparkWalletTesting.initialize({
45
45
  options,
46
46
  });
47
47
 
@@ -66,7 +66,7 @@ describe("coop exit", () => {
66
66
  );
67
67
 
68
68
  // Setup ssp
69
- const { wallet: sspWallet } = await SparkWalletTesting.intialize({
69
+ const { wallet: sspWallet } = await SparkWalletTesting.initialize({
70
70
  options,
71
71
  });
72
72
  const sspPubkey = await sspWallet.getIdentityPublicKey();
@@ -15,7 +15,7 @@ describe("deposit", () => {
15
15
  async () => {
16
16
  const mnemonic =
17
17
  "raise benefit echo client clutch short pyramid grass fall core slogan boil device plastic drastic discover decide penalty middle appear medal elbow original income";
18
- const { wallet: sdk } = await SparkWalletTesting.intialize({
18
+ const { wallet: sdk } = await SparkWalletTesting.initialize({
19
19
  options: {
20
20
  network: "LOCAL",
21
21
  },
@@ -39,7 +39,7 @@ describe("deposit", () => {
39
39
 
40
40
  const coin = await faucet.fund();
41
41
 
42
- const { wallet: sdk } = await SparkWalletTesting.intialize({
42
+ const { wallet: sdk } = await SparkWalletTesting.initialize({
43
43
  options: {
44
44
  network: "LOCAL",
45
45
  },