@buildonspark/spark-sdk 0.0.21 → 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.
@@ -1,6 +1,7 @@
1
1
  import readline from "readline";
2
2
  import { ConfigOptions } from "../../dist/services/wallet-config.js";
3
3
  import { SparkWallet } from "../../dist/spark-sdk";
4
+ import { BitcoinNetwork } from "../../dist/types/index.js";
4
5
  import { getLatestDepositTxId } from "../../dist/utils/mempool.js";
5
6
 
6
7
  // Initialize Spark Wallet
@@ -18,23 +19,29 @@ async function runCLI() {
18
19
  });
19
20
  const helpMessage = `
20
21
  Available commands:
21
- initwallet [mnemonic | seed] - Create a new wallet from a mnemonic or seed. If no mnemonic or seed is provided, a new mnemonic will be generated.
22
- getbalance - Get the wallet's balance
23
- getdepositaddress - Get an address to deposit funds from L1 to Spark
24
- getsparkaddress - Get the wallet's spark address
25
- getlatesttx <address> - Get the latest deposit transaction id for an address
26
- claimdeposit <txid> - Claim any pending deposits to the wallet
27
- claimtransfers - Claim any pending transfers to the wallet
28
- createinvoice <amount> <memo> - Create a new lightning invoice
29
- payinvoice <invoice> - Pay a lightning invoice
30
- sendtransfer <amount> <receiverSparkAddress> - Send a spark transfer
31
- withdraw <onchainAddress> <amount> - Withdraw funds to an L1 address
32
- sendtokentransfer <tokenPubKey> <amount> <receiverSparkAddress> - Transfer tokens
33
- help - Show this help message
22
+ initwallet [mnemonic | seed] - Create a new wallet from a mnemonic or seed. If no mnemonic or seed is provided, a new mnemonic will be generated.
23
+ getbalance - Get the wallet's balance
24
+ getdepositaddress - Get an address to deposit funds from L1 to Spark
25
+ getsparkaddress - Get the wallet's spark address
26
+ getlatesttx <address> - Get the latest deposit transaction id for an address
27
+ claimdeposit <txid> - Claim any pending deposits to the wallet
28
+ claimtransfers - Claim any pending transfers to the wallet
29
+ createinvoice <amount> <memo> - Create a new lightning invoice
30
+ payinvoice <invoice> - Pay a lightning invoice
31
+ sendtransfer <amount> <receiverSparkAddress> - Send a spark transfer
32
+ withdraw <amount> <onchainAddress> - Withdraw funds to an L1 address
33
+ coopfee <amount> <withdrawalAddress> - Get a fee estimate for a cooperative exit
34
+ lightningsendfee <invoice> - Get a fee estimate for a lightning send
35
+ lightningreceivefee <amount> <REGTEST | MAINNET | TESTNET | SIGNET> - Get a fee estimate for a lightning receive
36
+ getlightningsendrequest <requestId> - Get a lightning send request by ID
37
+ getlightningreceiverequest <requestId> - Get a lightning receive request by ID
38
+ getcoopexitrequest <requestId> - Get a coop exit request by ID
39
+ sendtokentransfer <tokenPubKey> <amount> <receiverSparkAddress> - Transfer tokens
40
+ help - Show this help message
34
41
  exit/quit
35
42
 
36
43
  L1 commands:
37
- tokenwithdraw <tokenPublicKey> [receiverPublicKey] - Unilaterally withdraw tokens to L1- Exit the program
44
+ tokenwithdraw <tokenPublicKey> [tokenAmount] - Unilaterally withdraw tokens to L1- Exit the program
38
45
  `;
39
46
  console.log(helpMessage);
40
47
 
@@ -67,6 +74,42 @@ async function runCLI() {
67
74
  const depositResult = await wallet.claimDeposit(args[0]);
68
75
  console.log(depositResult);
69
76
  break;
77
+ case "pendingtransfers":
78
+ if (!wallet) {
79
+ console.log("Please initialize a wallet first");
80
+ break;
81
+ }
82
+ const pendingTransfers = await wallet.getPendingTransfers();
83
+ console.log(pendingTransfers);
84
+ break;
85
+ case "getlightningsendrequest":
86
+ if (!wallet) {
87
+ console.log("Please initialize a wallet first");
88
+ break;
89
+ }
90
+ const lightningSendRequest = await wallet.getLightningSendRequest(
91
+ args[0],
92
+ );
93
+ console.log(lightningSendRequest);
94
+ break;
95
+ case "getlightningreceiverequest":
96
+ if (!wallet) {
97
+ console.log("Please initialize a wallet first");
98
+ break;
99
+ }
100
+ const lightningReceiveRequest = await wallet.getLightningReceiveRequest(
101
+ args[0],
102
+ );
103
+ console.log(lightningReceiveRequest);
104
+ break;
105
+ case "getcoopexitrequest":
106
+ if (!wallet) {
107
+ console.log("Please initialize a wallet first");
108
+ break;
109
+ }
110
+ const coopExitRequest = await wallet.getCoopExitRequest(args[0]);
111
+ console.log(coopExitRequest);
112
+ break;
70
113
  case "claimtransfers":
71
114
  if (!wallet) {
72
115
  console.log("Please initialize a wallet first");
@@ -187,8 +230,8 @@ async function runCLI() {
187
230
  break;
188
231
  }
189
232
  const withdrawal = await wallet.withdraw({
190
- onchainAddress: args[0],
191
- targetAmountSats: parseInt(args[1]),
233
+ amountSats: parseInt(args[0]),
234
+ onchainAddress: args[1],
192
235
  });
193
236
  console.log(withdrawal);
194
237
  break;
@@ -198,17 +241,54 @@ async function runCLI() {
198
241
  break;
199
242
  }
200
243
  const tokenPublicKey = args[0];
201
- const receiverPublicKey = args[1];
244
+ const amount = BigInt(parseInt(args[1]));
202
245
 
203
246
  let withdrawResult = await wallet.withdrawTokens(
204
247
  tokenPublicKey,
205
- receiverPublicKey,
248
+ amount,
206
249
  );
207
250
  if (withdrawResult) {
208
251
  console.log("Withdrawal L1 Transaction ID:", withdrawResult.txid);
209
252
  }
210
253
  break;
211
254
  }
255
+ case "coopfee": {
256
+ if (!wallet) {
257
+ console.log("Please initialize a wallet first");
258
+ break;
259
+ }
260
+ const fee = await wallet.getCoopExitFeeEstimate({
261
+ amountSats: parseInt(args[0]),
262
+ withdrawalAddress: args[1],
263
+ });
264
+
265
+ console.log(fee);
266
+ break;
267
+ }
268
+ case "lightningsendfee": {
269
+ if (!wallet) {
270
+ console.log("Please initialize a wallet first");
271
+ break;
272
+ }
273
+ const fee = await wallet.getLightningSendFeeEstimate({
274
+ encodedInvoice: args[0],
275
+ });
276
+ console.log(fee);
277
+ break;
278
+ }
279
+ case "lightningreceivefee": {
280
+ if (!wallet) {
281
+ console.log("Please initialize a wallet first");
282
+ break;
283
+ }
284
+ const network = args[1] as BitcoinNetwork;
285
+ const fee = await wallet.getLightningReceiveFeeEstimate({
286
+ amountSats: parseInt(args[0]),
287
+ network,
288
+ });
289
+ console.log(fee);
290
+ break;
291
+ }
212
292
  }
213
293
  }
214
294
  }
@@ -47,6 +47,7 @@ import { CoopExitFeeEstimate } from "./queries/CoopExitFeeEstimate.js";
47
47
  import { LeavesSwapFeeEstimate } from "./queries/LeavesSwapFeeEstimate.js";
48
48
  import { LightningReceiveFeeEstimate } from "./queries/LightningReceiveFeeEstimate.js";
49
49
  import { LightningSendFeeEstimate } from "./queries/LightningSendFeeEstimate.js";
50
+ import { UserRequest } from "./queries/UserRequest.js";
50
51
 
51
52
  export default class SspClient {
52
53
  private readonly requester: Requester;
@@ -267,6 +268,74 @@ export default class SspClient {
267
268
  },
268
269
  });
269
270
  }
271
+
272
+ async getLightningReceiveRequest(
273
+ id: string,
274
+ ): Promise<LightningReceiveRequest | null> {
275
+ return await this.executeRawQuery({
276
+ queryPayload: UserRequest,
277
+ variables: {
278
+ request_id: id,
279
+ },
280
+ constructObject: (response: { user_request: any }) => {
281
+ if (!response.user_request) {
282
+ return null;
283
+ }
284
+
285
+ return LightningReceiveRequestFromJson(response.user_request);
286
+ },
287
+ });
288
+ }
289
+
290
+ async getLightningSendRequest(
291
+ id: string,
292
+ ): Promise<LightningSendRequest | null> {
293
+ return await this.executeRawQuery({
294
+ queryPayload: UserRequest,
295
+ variables: {
296
+ request_id: id,
297
+ },
298
+ constructObject: (response: { user_request: any }) => {
299
+ if (!response.user_request) {
300
+ return null;
301
+ }
302
+
303
+ return LightningSendRequestFromJson(response.user_request);
304
+ },
305
+ });
306
+ }
307
+
308
+ async getLeaveSwapRequest(id: string): Promise<LeavesSwapRequest | null> {
309
+ return await this.executeRawQuery({
310
+ queryPayload: UserRequest,
311
+ variables: {
312
+ request_id: id,
313
+ },
314
+ constructObject: (response: { user_request: any }) => {
315
+ if (!response.user_request) {
316
+ return null;
317
+ }
318
+
319
+ return LeavesSwapRequestFromJson(response.user_request);
320
+ },
321
+ });
322
+ }
323
+
324
+ async getCoopExitRequest(id: string): Promise<CoopExitRequest | null> {
325
+ return await this.executeRawQuery({
326
+ queryPayload: UserRequest,
327
+ variables: {
328
+ request_id: id,
329
+ },
330
+ constructObject: (response: { user_request: any }) => {
331
+ if (!response.user_request) {
332
+ return null;
333
+ }
334
+
335
+ return CoopExitRequestFromJson(response.user_request);
336
+ },
337
+ });
338
+ }
270
339
  }
271
340
 
272
341
  class SparkAuthProvider implements AuthProvider {
File without changes
@@ -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
@@ -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,7 +40,11 @@ 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";
@@ -102,6 +108,14 @@ type DepositParams = {
102
108
  vout: number;
103
109
  };
104
110
 
111
+ export type TokenInfo = {
112
+ tokenPublicKey: string;
113
+ tokenName: string;
114
+ tokenSymbol: string;
115
+ tokenDecimals: number;
116
+ tokenSupply: bigint;
117
+ }
118
+
105
119
  export type InitWalletResponse = {
106
120
  mnemonic?: string | undefined;
107
121
  };
@@ -121,6 +135,7 @@ export class SparkWallet {
121
135
  protected config: WalletConfigService;
122
136
 
123
137
  protected connectionManager: ConnectionManager;
138
+ protected lrc20ConnectionManager: ILrc20ConnectionManager;
124
139
  protected lrc20Wallet: LRCWallet | undefined;
125
140
 
126
141
  private depositService: DepositService;
@@ -147,6 +162,7 @@ export class SparkWallet {
147
162
  protected constructor(options?: ConfigOptions, signer?: SparkSigner) {
148
163
  this.config = new WalletConfigService(options, signer);
149
164
  this.connectionManager = new ConnectionManager(this.config);
165
+ this.lrc20ConnectionManager = createLrc20ConnectionManager(this.config.getLrc20Address());
150
166
  this.depositService = new DepositService(
151
167
  this.config,
152
168
  this.connectionManager,
@@ -327,7 +343,7 @@ export class SparkWallet {
327
343
  this.leaves = await this.getLeaves();
328
344
  await this.config.signer.restoreSigningKeysFromLeafs(this.leaves);
329
345
  await this.refreshTimelockNodes();
330
-
346
+ await this.extendTimeLockNodes();
331
347
  this.optimizeLeaves().catch((e) => {
332
348
  console.error("Failed to optimize leaves", e);
333
349
  });
@@ -671,6 +687,25 @@ export class SparkWallet {
671
687
  return await this.transferService.queryAllTransfers(limit, offset);
672
688
  }
673
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
+
674
709
  /**
675
710
  * Gets the current balance of the wallet.
676
711
  * You can use the forceRefetch option to synchronize your wallet and claim any
@@ -900,6 +935,7 @@ export class SparkWallet {
900
935
  const leavesToSend = await this.selectLeaves(amountSats);
901
936
 
902
937
  await this.refreshTimelockNodes();
938
+ await this.extendTimeLockNodes();
903
939
 
904
940
  const leafKeyTweaks = await Promise.all(
905
941
  leavesToSend.map(async (leaf) => ({
@@ -923,6 +959,29 @@ export class SparkWallet {
923
959
  });
924
960
  }
925
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
+
926
985
  /**
927
986
  * Internal method to refresh timelock nodes.
928
987
  *
@@ -1056,6 +1115,7 @@ export class SparkWallet {
1056
1115
 
1057
1116
  this.leaves.push(...response.nodes);
1058
1117
  await this.refreshTimelockNodes();
1118
+ await this.extendTimeLockNodes();
1059
1119
 
1060
1120
  return response.nodes;
1061
1121
  });
@@ -1120,13 +1180,13 @@ export class SparkWallet {
1120
1180
  * @param {number} params.amountSats - Amount in satoshis
1121
1181
  * @param {string} params.memo - Description for the invoice
1122
1182
  * @param {number} [params.expirySeconds] - Optional expiry time in seconds
1123
- * @returns {Promise<string>} BOLT11 encoded invoice
1183
+ * @returns {Promise<LightningReceiveRequest>} BOLT11 encoded invoice
1124
1184
  */
1125
1185
  public async createLightningInvoice({
1126
1186
  amountSats,
1127
1187
  memo,
1128
1188
  expirySeconds = 60 * 60 * 24 * 30,
1129
- }: CreateLightningInvoiceParams) {
1189
+ }: CreateLightningInvoiceParams): Promise<LightningReceiveRequest> {
1130
1190
  if (!this.sspClient) {
1131
1191
  throw new Error("SSP client not initialized");
1132
1192
  }
@@ -1152,14 +1212,16 @@ export class SparkWallet {
1152
1212
  memo,
1153
1213
  });
1154
1214
 
1155
- return invoice?.invoice.encodedEnvoice;
1215
+ return invoice;
1156
1216
  };
1157
1217
 
1158
- return this.lightningService!.createLightningInvoice({
1218
+ const invoice = await this.lightningService!.createLightningInvoice({
1159
1219
  amountSats,
1160
1220
  memo,
1161
1221
  invoiceCreator: requestLightningInvoice,
1162
1222
  });
1223
+
1224
+ return invoice;
1163
1225
  }
1164
1226
 
1165
1227
  /**
@@ -1199,6 +1261,8 @@ export class SparkWallet {
1199
1261
  const leaves = await this.selectLeaves(amountSats);
1200
1262
 
1201
1263
  await this.refreshTimelockNodes();
1264
+ await this.extendTimeLockNodes();
1265
+
1202
1266
  const leavesToSend = await Promise.all(
1203
1267
  leaves.map(async (leaf) => ({
1204
1268
  leaf,
@@ -1354,21 +1418,21 @@ export class SparkWallet {
1354
1418
  *
1355
1419
  * @param {Object} params - Parameters for the withdrawal
1356
1420
  * @param {string} params.onchainAddress - The Bitcoin address where the funds should be sent
1357
- * @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
1358
1422
  * @returns {Promise<CoopExitRequest | null | undefined>} The withdrawal request details, or null/undefined if the request cannot be completed
1359
1423
  */
1360
1424
  public async withdraw({
1361
1425
  onchainAddress,
1362
- targetAmountSats,
1426
+ amountSats,
1363
1427
  }: {
1364
1428
  onchainAddress: string;
1365
- targetAmountSats?: number;
1429
+ amountSats?: number;
1366
1430
  }) {
1367
- if (targetAmountSats && targetAmountSats < 10000) {
1431
+ if (amountSats && amountSats < 10000) {
1368
1432
  throw new Error("The minimum amount for a withdrawal is 10000 sats");
1369
1433
  }
1370
1434
  return await this.withLeaves(async () => {
1371
- return await this.coopExit(onchainAddress, targetAmountSats);
1435
+ return await this.coopExit(onchainAddress, amountSats);
1372
1436
  });
1373
1437
  }
1374
1438
 
@@ -1454,19 +1518,30 @@ export class SparkWallet {
1454
1518
  /**
1455
1519
  * Gets fee estimate for cooperative exit (on-chain withdrawal).
1456
1520
  *
1457
- * @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
1458
1524
  * @returns {Promise<CoopExitFeeEstimateOutput | null>} Fee estimate for the withdrawal
1459
1525
  */
1460
1526
  public async getCoopExitFeeEstimate({
1461
- leafExternalIds,
1527
+ amountSats,
1462
1528
  withdrawalAddress,
1463
- }: CoopExitFeeEstimateInput): Promise<CoopExitFeeEstimateOutput | null> {
1529
+ }: {
1530
+ amountSats: number;
1531
+ withdrawalAddress: string;
1532
+ }): Promise<CoopExitFeeEstimateOutput | null> {
1464
1533
  if (!this.sspClient) {
1465
1534
  throw new Error("SSP client not initialized");
1466
1535
  }
1467
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
+
1468
1543
  return await this.sspClient.getCoopExitFeeEstimate({
1469
- leafExternalIds,
1544
+ leafExternalIds: leaves.map((leaf) => leaf.id),
1470
1545
  withdrawalAddress,
1471
1546
  });
1472
1547
  }
@@ -1782,4 +1857,50 @@ export class SparkWallet {
1782
1857
  await this.config.signer.signMessageWithIdentityKey(hash, compact),
1783
1858
  );
1784
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");
1872
+ }
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);
1905
+ }
1785
1906
  }
@@ -1,11 +1,17 @@
1
1
  import { afterEach, beforeAll, describe, expect, it } from "@jest/globals";
2
2
  import { hexToBytes } from "@noble/curves/abstract/utils";
3
3
  import { equalBytes, sha256 } from "@scure/btc-signer/utils";
4
+ import LightningReceiveRequest from "../graphql/objects/LightningReceiveRequest.js";
4
5
  import { TransferStatus } from "../proto/spark.js";
5
6
  import { WalletConfigService } from "../services/config.js";
6
7
  import { ConnectionManager } from "../services/connection.js";
7
8
  import { LightningService } from "../services/lightning.js";
8
9
  import { LeafKeyTweak, TransferService } from "../services/transfer.js";
10
+ import {
11
+ BitcoinNetwork,
12
+ CurrencyUnit,
13
+ LightningReceiveRequestStatus,
14
+ } from "../types/index.js";
9
15
  import { createNewTree, getTestWalletConfig } from "./test-util.js";
10
16
  import { SparkWalletTesting } from "./utils/spark-testing-wallet.js";
11
17
  import { BitcoinFaucet } from "./utils/test-faucet.js";
@@ -27,8 +33,38 @@ async function cleanUp() {
27
33
  }
28
34
  }
29
35
 
30
- const fakeInvoiceCreator = async () => {
31
- return "lnbcrt123450n1pnj6uf4pp5l26hsdxssmr52vd4xmn5xran7puzx34hpr6uevaq7ta0ayzrp8esdqqcqzpgxqyz5vqrzjqtr2vd60g57hu63rdqk87u3clac6jlfhej4kldrrjvfcw3mphcw8sqqqqzp3jlj6zyqqqqqqqqqqqqqq9qsp5w22fd8aqn7sdum7hxdf59ptgk322fkv589ejxjltngvgehlcqcyq9qxpqysgqvykwsxdx64qrj0s5pgcgygmrpj8w25jsjgltwn09yp24l9nvghe3dl3y0ycy70ksrlqmcn42hxn24e0ucuy3g9fjltudvhv4lrhhamgq3stqgp";
36
+ const fakeInvoiceCreator = async (): Promise<LightningReceiveRequest> => {
37
+ return {
38
+ id: "123",
39
+ createdAt: new Date().toISOString(),
40
+ updatedAt: new Date().toISOString(),
41
+ network: BitcoinNetwork.REGTEST,
42
+ fee: {
43
+ originalValue: 1000,
44
+ originalUnit: CurrencyUnit.SATOSHI,
45
+ preferredCurrencyUnit: CurrencyUnit.SATOSHI,
46
+ preferredCurrencyValueRounded: 1000,
47
+ preferredCurrencyValueApprox: 1000,
48
+ },
49
+ status: LightningReceiveRequestStatus.INVOICE_CREATED,
50
+ typename: "LightningReceiveRequest",
51
+ invoice: {
52
+ encodedEnvoice:
53
+ "lnbcrt123450n1pnj6uf4pp5l26hsdxssmr52vd4xmn5xran7puzx34hpr6uevaq7ta0ayzrp8esdqqcqzpgxqyz5vqrzjqtr2vd60g57hu63rdqk87u3clac6jlfhej4kldrrjvfcw3mphcw8sqqqqzp3jlj6zyqqqqqqqqqqqqqq9qsp5w22fd8aqn7sdum7hxdf59ptgk322fkv589ejxjltngvgehlcqcyq9qxpqysgqvykwsxdx64qrj0s5pgcgygmrpj8w25jsjgltwn09yp24l9nvghe3dl3y0ycy70ksrlqmcn42hxn24e0ucuy3g9fjltudvhv4lrhhamgq3stqgp",
54
+ bitcoinNetwork: BitcoinNetwork.REGTEST,
55
+ paymentHash:
56
+ "2d059c3ede82a107aa1452c0bea47759be3c5c6e5342be6a310f6c3a907d9f4c",
57
+ amount: {
58
+ originalValue: 10000,
59
+ originalUnit: CurrencyUnit.SATOSHI,
60
+ preferredCurrencyUnit: CurrencyUnit.SATOSHI,
61
+ preferredCurrencyValueRounded: 10000,
62
+ preferredCurrencyValueApprox: 10000,
63
+ },
64
+ createdAt: new Date().toISOString(),
65
+ expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(),
66
+ },
67
+ };
32
68
  };
33
69
 
34
70
  describe("LightningService", () => {
@@ -244,7 +280,7 @@ describe("LightningService", () => {
244
280
  receiverIdentityPubkey: await sspConfig.signer.getIdentityPublicKey(),
245
281
  paymentHash,
246
282
  isInboundPayment: false,
247
- invoiceString: await fakeInvoiceCreator(),
283
+ invoiceString: (await fakeInvoiceCreator()).invoice.encodedEnvoice,
248
284
  });
249
285
 
250
286
  expect(response.transfer).toBeDefined();