@buildonspark/spark-sdk 0.1.41 → 0.1.43

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 (156) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/android/src/main/jniLibs/arm64-v8a/libuniffi_spark_frost.so +0 -0
  3. package/android/src/main/jniLibs/armeabi-v7a/libuniffi_spark_frost.so +0 -0
  4. package/android/src/main/jniLibs/x86/libuniffi_spark_frost.so +0 -0
  5. package/android/src/main/jniLibs/x86_64/libuniffi_spark_frost.so +0 -0
  6. package/dist/LightningSendFeeEstimateInput-BgOhEAI-.d.cts +10 -0
  7. package/dist/LightningSendFeeEstimateInput-BgOhEAI-.d.ts +10 -0
  8. package/dist/{RequestLightningSendInput-mXUWn_cp.d.ts → RequestLightningSendInput-D7fZdT4A.d.ts} +35 -16
  9. package/dist/{RequestLightningSendInput-DXcLoiCe.d.cts → RequestLightningSendInput-Na1mHdWg.d.cts} +35 -16
  10. package/dist/address/index.cjs +38 -7
  11. package/dist/address/index.d.cts +2 -2
  12. package/dist/address/index.d.ts +2 -2
  13. package/dist/address/index.js +3 -3
  14. package/dist/{chunk-ATEHMLKP.js → chunk-6AFUC5M2.js} +1 -1
  15. package/dist/{chunk-ZXDE2XMU.js → chunk-BUTZWYBW.js} +9 -6
  16. package/dist/{chunk-7EFSUADA.js → chunk-DOA6QXYQ.js} +1 -0
  17. package/dist/{chunk-J5W5Q2ZP.js → chunk-DQYKQJRZ.js} +291 -7
  18. package/dist/{chunk-TWF35O6M.js → chunk-GSI4OLXZ.js} +32 -1
  19. package/dist/{chunk-2ZXXLPG2.js → chunk-GYQR4B4P.js} +5 -4
  20. package/dist/{chunk-ROKY5KS4.js → chunk-HRQRRDSS.js} +53 -15
  21. package/dist/{chunk-YEZDPUFY.js → chunk-IRW5TWMH.js} +8 -8
  22. package/dist/{chunk-7VMYMQLF.js → chunk-NSJF5F5O.js} +1 -1
  23. package/dist/{chunk-TM4TOEOX.js → chunk-O4RYNJNB.js} +3 -3
  24. package/dist/{chunk-MGPRLH6Q.js → chunk-QNNSEJ4P.js} +1 -1
  25. package/dist/{chunk-UKT6OFLO.js → chunk-TIUBYNN5.js} +13 -7
  26. package/dist/{chunk-6YVPOQ2A.js → chunk-TOSP3INR.js} +235 -143
  27. package/dist/chunk-VFJQNBFX.js +21 -0
  28. package/dist/{chunk-KKSU7OZO.js → chunk-WWOTVNPP.js} +195 -67
  29. package/dist/{chunk-HK6LPV6Z.js → chunk-Z5HIAYFT.js} +1 -1
  30. package/dist/graphql/objects/index.cjs +229 -135
  31. package/dist/graphql/objects/index.d.cts +54 -9
  32. package/dist/graphql/objects/index.d.ts +54 -9
  33. package/dist/graphql/objects/index.js +3 -3
  34. package/dist/{index-OSDtPMmC.d.ts → index-7RYRH5wc.d.ts} +10 -8
  35. package/dist/{index-CFh4uWzi.d.cts → index-BJOc8Ur-.d.cts} +10 -8
  36. package/dist/index.cjs +790 -315
  37. package/dist/index.d.cts +8 -7
  38. package/dist/index.d.ts +8 -7
  39. package/dist/index.js +26 -26
  40. package/dist/index.node.cjs +790 -315
  41. package/dist/index.node.d.cts +9 -8
  42. package/dist/index.node.d.ts +9 -8
  43. package/dist/index.node.js +26 -26
  44. package/dist/native/index.cjs +812 -332
  45. package/dist/native/index.d.cts +53 -18
  46. package/dist/native/index.d.ts +53 -18
  47. package/dist/native/index.js +659 -181
  48. package/dist/{network-BiwBmoOg.d.cts → network-D5lKssVl.d.cts} +1 -1
  49. package/dist/{network-BF2GYPye.d.ts → network-xkBSpaTn.d.ts} +1 -1
  50. package/dist/proto/lrc20.d.cts +1 -1
  51. package/dist/proto/lrc20.d.ts +1 -1
  52. package/dist/proto/spark.d.cts +1 -1
  53. package/dist/proto/spark.d.ts +1 -1
  54. package/dist/proto/spark_token.d.cts +1 -1
  55. package/dist/proto/spark_token.d.ts +1 -1
  56. package/dist/{sdk-types-CfhdFnsA.d.cts → sdk-types-B-q9py_P.d.cts} +1 -1
  57. package/dist/{sdk-types-MnQrHolg.d.ts → sdk-types-BPoPgzda.d.ts} +1 -1
  58. package/dist/services/config.cjs +118 -69
  59. package/dist/services/config.d.cts +6 -4
  60. package/dist/services/config.d.ts +6 -4
  61. package/dist/services/config.js +9 -9
  62. package/dist/services/connection.cjs +95 -15
  63. package/dist/services/connection.d.cts +6 -4
  64. package/dist/services/connection.d.ts +6 -4
  65. package/dist/services/connection.js +3 -3
  66. package/dist/services/index.cjs +487 -117
  67. package/dist/services/index.d.cts +5 -4
  68. package/dist/services/index.d.ts +5 -4
  69. package/dist/services/index.js +19 -19
  70. package/dist/services/lrc-connection.cjs +50 -7
  71. package/dist/services/lrc-connection.d.cts +5 -4
  72. package/dist/services/lrc-connection.d.ts +5 -4
  73. package/dist/services/lrc-connection.js +3 -3
  74. package/dist/services/token-transactions.cjs +351 -36
  75. package/dist/services/token-transactions.d.cts +5 -4
  76. package/dist/services/token-transactions.d.ts +5 -4
  77. package/dist/services/token-transactions.js +6 -6
  78. package/dist/services/wallet-config.cjs +1 -0
  79. package/dist/services/wallet-config.d.cts +6 -4
  80. package/dist/services/wallet-config.d.ts +6 -4
  81. package/dist/services/wallet-config.js +1 -1
  82. package/dist/signer/signer.cjs +117 -64
  83. package/dist/signer/signer.d.cts +4 -3
  84. package/dist/signer/signer.d.ts +4 -3
  85. package/dist/signer/signer.js +15 -7
  86. package/dist/{signer-CylxIujU.d.ts → signer-IO3oMRNj.d.cts} +2 -1
  87. package/dist/{signer-BhLS7SYR.d.cts → signer-wqesWifN.d.ts} +2 -1
  88. package/dist/{spark-DjR1b3TC.d.cts → spark-CDm4gqS6.d.cts} +1 -1
  89. package/dist/{spark-DjR1b3TC.d.ts → spark-CDm4gqS6.d.ts} +1 -1
  90. package/dist/types/index.cjs +282 -188
  91. package/dist/types/index.d.cts +7 -6
  92. package/dist/types/index.d.ts +7 -6
  93. package/dist/types/index.js +3 -3
  94. package/dist/utils/index.cjs +90 -58
  95. package/dist/utils/index.d.cts +6 -5
  96. package/dist/utils/index.d.ts +6 -5
  97. package/dist/utils/index.js +16 -16
  98. package/ios/spark_frostFFI.xcframework/ios-arm64/SparkFrost +0 -0
  99. package/ios/spark_frostFFI.xcframework/ios-arm64/spark_frostFFI.framework/spark_frostFFI +0 -0
  100. package/ios/spark_frostFFI.xcframework/ios-arm64_x86_64-simulator/SparkFrost +0 -0
  101. package/ios/spark_frostFFI.xcframework/ios-arm64_x86_64-simulator/spark_frostFFI.framework/spark_frostFFI +0 -0
  102. package/ios/spark_frostFFI.xcframework/macos-arm64_x86_64/spark_frostFFI.framework/spark_frostFFI +0 -0
  103. package/package.json +4 -4
  104. package/src/constants.ts +21 -0
  105. package/src/errors/base.ts +43 -1
  106. package/src/graphql/client.ts +4 -0
  107. package/src/graphql/mutations/RequestLightningSend.ts +2 -0
  108. package/src/graphql/objects/ClaimStaticDepositInput.ts +1 -1
  109. package/src/graphql/objects/ClaimStaticDepositStatus.ts +4 -2
  110. package/src/graphql/objects/Connection.ts +7 -7
  111. package/src/graphql/objects/CoopExitFeeEstimate.ts +1 -1
  112. package/src/graphql/objects/CoopExitFeeQuote.ts +202 -0
  113. package/src/graphql/objects/CoopExitFeeQuoteInput.ts +41 -0
  114. package/src/graphql/objects/CoopExitFeeQuoteOutput.ts +45 -0
  115. package/src/graphql/objects/CoopExitRequest.ts +21 -0
  116. package/src/graphql/objects/CurrencyUnit.ts +26 -28
  117. package/src/graphql/objects/Entity.ts +84 -0
  118. package/src/graphql/objects/Invoice.ts +2 -2
  119. package/src/graphql/objects/Leaf.ts +1 -1
  120. package/src/graphql/objects/LeavesSwapFeeEstimateOutput.ts +1 -1
  121. package/src/graphql/objects/LeavesSwapRequest.ts +6 -0
  122. package/src/graphql/objects/LightningReceiveRequest.ts +11 -0
  123. package/src/graphql/objects/LightningSendFeeEstimateInput.ts +8 -0
  124. package/src/graphql/objects/LightningSendFeeEstimateOutput.ts +1 -1
  125. package/src/graphql/objects/LightningSendRequest.ts +3 -0
  126. package/src/graphql/objects/RequestCoopExitInput.ts +8 -0
  127. package/src/graphql/objects/RequestLeavesSwapInput.ts +5 -1
  128. package/src/graphql/objects/RequestLightningReceiveInput.ts +9 -2
  129. package/src/graphql/objects/RequestLightningSendInput.ts +8 -0
  130. package/src/graphql/objects/SparkCoopExitRequestStatus.ts +2 -0
  131. package/src/graphql/objects/SparkUserRequestType.ts +2 -0
  132. package/src/graphql/objects/SparkWalletUser.ts +20 -0
  133. package/src/graphql/objects/UserRequest.ts +32 -0
  134. package/src/graphql/objects/VerifyChallengeInput.ts +1 -1
  135. package/src/graphql/objects/index.ts +12 -3
  136. package/src/graphql/queries/LightningSendFeeEstimate.ts +2 -0
  137. package/src/logger.ts +3 -0
  138. package/src/native/index.ts +1 -0
  139. package/src/services/config.ts +4 -0
  140. package/src/services/connection.ts +68 -29
  141. package/src/services/coop-exit.ts +1 -1
  142. package/src/services/lightning.ts +25 -1
  143. package/src/services/lrc-connection.ts +3 -3
  144. package/src/services/token-transactions.ts +6 -2
  145. package/src/services/wallet-config.ts +2 -0
  146. package/src/signer/signer.ts +4 -1
  147. package/src/spark-wallet/spark-wallet.ts +51 -15
  148. package/src/spark-wallet/types.ts +1 -0
  149. package/src/tests/errors.test.ts +58 -0
  150. package/src/tests/integration/lightning.test.ts +184 -0
  151. package/src/tests/integration/ssp/static_deposit.test.ts +1 -2
  152. package/src/tests/tokens.test.ts +52 -3
  153. package/src/utils/token-hashing.ts +335 -1
  154. package/dist/LightningSendFeeEstimateInput-CJvPnCSB.d.cts +0 -5
  155. package/dist/LightningSendFeeEstimateInput-CJvPnCSB.d.ts +0 -5
  156. package/dist/chunk-HKAKEKCE.js +0 -8
@@ -4,4 +4,5 @@ export { ReactNativeSparkSigner } from "../signer/signer.react-native.js";
4
4
  export { ReactNativeSparkSigner as DefaultSparkSigner } from "../signer/signer.react-native.js";
5
5
  export { SparkWallet } from "../spark-wallet/spark-wallet.js";
6
6
  export { getLatestDepositTxId } from "../utils/mempool.js";
7
+ export { createDummyTx } from "../spark_bindings/native/index.js";
7
8
  export * from "../utils/index.js";
@@ -118,6 +118,10 @@ export class WalletConfigService
118
118
  return this.config.tokenTransactionVersion;
119
119
  }
120
120
 
121
+ public getTokenValidityDurationSeconds(): number {
122
+ return this.config.tokenValidityDurationSeconds;
123
+ }
124
+
121
125
  public getElectrsUrl(): string {
122
126
  return this.config.electrsUrl;
123
127
  }
@@ -7,7 +7,7 @@ import type {
7
7
  Channel as ChannelWeb,
8
8
  ClientFactory as ClientFactoryWeb,
9
9
  } from "nice-grpc-web";
10
- import { isBun, isReactNative } from "../constants.js";
10
+ import { isBun, isReactNative, clientEnv } from "../constants.js";
11
11
  import { AuthenticationError, NetworkError } from "../errors/types.js";
12
12
  import { MockServiceClient, MockServiceDefinition } from "../proto/mock.js";
13
13
  import { SparkServiceClient, SparkServiceDefinition } from "../proto/spark.js";
@@ -283,13 +283,50 @@ export class ConnectionManager {
283
283
  certPath?: string,
284
284
  ): Promise<SparkAuthnServiceClient & { close?: () => void }> {
285
285
  const channel = await this.createChannelWithTLS(address, certPath);
286
+ const authnMiddleware = this.createAuthnMiddleware();
286
287
  return this.createGrpcClient<SparkAuthnServiceClient>(
287
288
  SparkAuthnServiceDefinition,
288
289
  channel,
289
290
  false,
291
+ authnMiddleware,
290
292
  );
291
293
  }
292
294
 
295
+ private createAuthnMiddleware() {
296
+ if (isNode) {
297
+ return async function* (
298
+ this: ConnectionManager,
299
+ call: ClientMiddlewareCall<any, any>,
300
+ options: SparkCallOptions,
301
+ ) {
302
+ const metadata = Metadata(options.metadata).set(
303
+ "X-Client-Env",
304
+ clientEnv,
305
+ );
306
+ return yield* call.next(call.request, {
307
+ ...options,
308
+ metadata,
309
+ });
310
+ }.bind(this);
311
+ } else {
312
+ return async function* (
313
+ this: ConnectionManager,
314
+ call: ClientMiddlewareCall<any, any>,
315
+ options: SparkCallOptions,
316
+ ) {
317
+ const metadata = Metadata(options.metadata)
318
+ .set("X-Requested-With", "XMLHttpRequest")
319
+ .set("X-Grpc-Web", "1")
320
+ .set("X-Client-Env", clientEnv)
321
+ .set("Content-Type", "application/grpc-web+proto");
322
+ return yield* call.next(call.request, {
323
+ ...options,
324
+ metadata,
325
+ });
326
+ }.bind(this);
327
+ }
328
+ }
329
+
293
330
  private createMiddleware(address: string, authToken: string) {
294
331
  if (isNode) {
295
332
  return this.createNodeMiddleware(address, authToken);
@@ -304,27 +341,30 @@ export class ConnectionManager {
304
341
  call: ClientMiddlewareCall<any, any>,
305
342
  options: SparkCallOptions,
306
343
  ) {
344
+ const metadata = Metadata(options.metadata).set(
345
+ "X-Client-Env",
346
+ clientEnv,
347
+ );
307
348
  try {
308
349
  return yield* call.next(call.request, {
309
350
  ...options,
310
- metadata: Metadata(options.metadata)
311
- .set(
312
- "Authorization",
313
- `Bearer ${this.clients.get(address)?.authToken || initialAuthToken}`,
314
- )
315
- .set("User-Agent", "spark-js-sdk"),
351
+ metadata: metadata.set(
352
+ "Authorization",
353
+ `Bearer ${this.clients.get(address)?.authToken || initialAuthToken}`,
354
+ ),
316
355
  });
317
356
  } catch (error: any) {
318
357
  if (error.message?.includes("token has expired")) {
319
358
  const newAuthToken = await this.authenticate(address);
320
- // @ts-ignore - We can only get here if the client exists
321
- this.clients.get(address).authToken = newAuthToken;
359
+ const clientData = this.clients.get(address);
360
+ if (!clientData) {
361
+ throw new Error(`No client found for address: ${address}`);
362
+ }
363
+ clientData.authToken = newAuthToken;
322
364
 
323
365
  return yield* call.next(call.request, {
324
366
  ...options,
325
- metadata: Metadata(options.metadata)
326
- .set("Authorization", `Bearer ${newAuthToken}`)
327
- .set("User-Agent", "spark-js-sdk"),
367
+ metadata: metadata.set("Authorization", `Bearer ${newAuthToken}`),
328
368
  });
329
369
  }
330
370
  throw error;
@@ -338,33 +378,32 @@ export class ConnectionManager {
338
378
  call: ClientMiddlewareCall<any, any>,
339
379
  options: SparkCallOptions,
340
380
  ) {
381
+ const metadata = Metadata(options.metadata)
382
+ .set("X-Requested-With", "XMLHttpRequest")
383
+ .set("X-Grpc-Web", "1")
384
+ .set("X-Client-Env", clientEnv)
385
+ .set("Content-Type", "application/grpc-web+proto");
386
+
341
387
  try {
342
388
  return yield* call.next(call.request, {
343
389
  ...options,
344
- metadata: Metadata(options.metadata)
345
- .set(
346
- "Authorization",
347
- `Bearer ${this.clients.get(address)?.authToken || initialAuthToken}`,
348
- )
349
- .set("X-Requested-With", "XMLHttpRequest")
350
- .set("X-Grpc-Web", "1")
351
- .set("Content-Type", "application/grpc-web+proto")
352
- .set("User-Agent", "spark-js-sdk"),
390
+ metadata: metadata.set(
391
+ "Authorization",
392
+ `Bearer ${this.clients.get(address)?.authToken || initialAuthToken}`,
393
+ ),
353
394
  });
354
395
  } catch (error: any) {
355
396
  if (error.message?.includes("token has expired")) {
356
397
  const newAuthToken = await this.authenticate(address);
357
- // @ts-ignore - We can only get here if the client exists
358
- this.clients.get(address).authToken = newAuthToken;
398
+ const clientData = this.clients.get(address);
399
+ if (!clientData) {
400
+ throw new Error(`No client found for address: ${address}`);
401
+ }
402
+ clientData.authToken = newAuthToken;
359
403
 
360
404
  return yield* call.next(call.request, {
361
405
  ...options,
362
- metadata: Metadata(options.metadata)
363
- .set("Authorization", `Bearer ${newAuthToken}`)
364
- .set("X-Requested-With", "XMLHttpRequest")
365
- .set("X-Grpc-Web", "1")
366
- .set("Content-Type", "application/grpc-web+proto")
367
- .set("User-Agent", "spark-js-sdk"),
406
+ metadata: metadata.set("Authorization", `Bearer ${newAuthToken}`),
368
407
  });
369
408
  }
370
409
  throw error;
@@ -50,7 +50,7 @@ export class CoopExitService extends BaseTransferService {
50
50
  connectorOutputs,
51
51
  receiverPubKey,
52
52
  );
53
- const transferTweak = await this.sendTransferTweakKey(
53
+ const transferTweak = await this.deliverTransferPackage(
54
54
  transfer,
55
55
  leaves,
56
56
  signaturesMap,
@@ -52,6 +52,7 @@ export type SwapNodesForPreimageParams = {
52
52
  invoiceString?: string;
53
53
  isInboundPayment: boolean;
54
54
  feeSats?: number;
55
+ amountSatsToSend?: number;
55
56
  };
56
57
 
57
58
  export class LightningService {
@@ -182,6 +183,7 @@ export class LightningService {
182
183
  invoiceString,
183
184
  isInboundPayment,
184
185
  feeSats = 0,
186
+ amountSatsToSend,
185
187
  }: SwapNodesForPreimageParams): Promise<InitiatePreimageSwapResponse> {
186
188
  const sparkClient = await this.connectionManager.createSparkClient(
187
189
  this.config.getCoordinatorAddress(),
@@ -222,7 +224,29 @@ export class LightningService {
222
224
  console.error("Error decoding invoice", error);
223
225
  }
224
226
 
225
- amountSats = amountMsats / 1000;
227
+ const isZeroAmountInvoice = !amountMsats;
228
+
229
+ if (isZeroAmountInvoice && amountSatsToSend === undefined) {
230
+ throw new ValidationError(
231
+ "Invalid amount. User must specify amountSatsToSend for 0 amount lightning invoice",
232
+ {
233
+ field: "amountSatsToSend",
234
+ value: amountSatsToSend,
235
+ expected: "positive number",
236
+ },
237
+ );
238
+ }
239
+
240
+ amountSats = isZeroAmountInvoice ? amountSatsToSend! : amountMsats / 1000;
241
+
242
+ if (isNaN(amountSats) || amountSats <= 0) {
243
+ throw new ValidationError("Invalid amount", {
244
+ field: "amountSats",
245
+ value: amountSats,
246
+ expected: "greater than 0",
247
+ });
248
+ }
249
+
226
250
  bolt11String = invoiceString;
227
251
  }
228
252
 
@@ -6,7 +6,7 @@ import type {
6
6
  Channel as ChannelWeb,
7
7
  ClientFactory as ClientFactoryWeb,
8
8
  } from "nice-grpc-web";
9
- import { isBun, isReactNative } from "../constants.js";
9
+ import { isBun, isReactNative, clientEnv } from "../constants.js";
10
10
  import { NetworkError } from "../errors/types.js";
11
11
  import { SparkServiceClient, SparkServiceDefinition } from "../proto/lrc20.js";
12
12
  import { RetryOptions, SparkCallOptions } from "../types/grpc.js";
@@ -126,7 +126,7 @@ export class Lrc20ConnectionManager {
126
126
  ) {
127
127
  return yield* call.next(call.request, {
128
128
  ...options,
129
- metadata: Metadata(options.metadata).set("User-Agent", "spark-js-sdk"),
129
+ metadata: Metadata(options.metadata).set("X-Client-Env", clientEnv),
130
130
  });
131
131
  }.bind(this);
132
132
  }
@@ -143,7 +143,7 @@ export class Lrc20ConnectionManager {
143
143
  .set("X-Requested-With", "XMLHttpRequest")
144
144
  .set("X-Grpc-Web", "1")
145
145
  .set("Content-Type", "application/grpc-web+proto")
146
- .set("User-Agent", "spark-js-sdk"),
146
+ .set("X-Client-Env", clientEnv),
147
147
  });
148
148
  }.bind(this);
149
149
  }
@@ -14,6 +14,7 @@ import { secp256k1 } from "@noble/curves/secp256k1";
14
14
  import { SparkCallOptions } from "../types/grpc.js";
15
15
  import {
16
16
  hashOperatorSpecificTokenTransactionSignablePayload,
17
+ hashTokenTransactionV0,
17
18
  hashTokenTransaction,
18
19
  } from "../utils/token-hashing.js";
19
20
  import {
@@ -456,7 +457,7 @@ export class TokenTransactionService {
456
457
  this.config.getCoordinatorAddress(),
457
458
  );
458
459
 
459
- const partialTokenTransactionHash = hashTokenTransaction(
460
+ const partialTokenTransactionHash = hashTokenTransactionV0(
460
461
  tokenTransaction,
461
462
  true,
462
463
  );
@@ -547,7 +548,7 @@ export class TokenTransactionService {
547
548
  );
548
549
 
549
550
  const finalTokenTransaction = startResponse.finalTokenTransaction;
550
- const finalTokenTransactionHash = hashTokenTransaction(
551
+ const finalTokenTransactionHash = hashTokenTransactionV0(
551
552
  finalTokenTransaction,
552
553
  false,
553
554
  );
@@ -635,6 +636,9 @@ export class TokenTransactionService {
635
636
  {
636
637
  identityPublicKey: await this.config.signer.getIdentityPublicKey(),
637
638
  partialTokenTransaction: tokenTransaction,
639
+ validityDurationSeconds:
640
+ await this.config.getTokenValidityDurationSeconds(),
641
+ partialTokenTransactionOwnerSignatures: ownerSignaturesWithIndex,
638
642
  },
639
643
  {
640
644
  retry: true,
@@ -141,6 +141,7 @@ export type ConfigOptions = MayHaveLrc20WalletApiConfig &
141
141
  readonly lrc20Address?: string;
142
142
  readonly threshold?: number;
143
143
  readonly tokenSignatures?: "ECDSA" | "SCHNORR";
144
+ readonly tokenValidityDurationSeconds?: number;
144
145
  readonly tokenTransactionVersion?: "V0" | "V1";
145
146
  readonly electrsUrl?: string;
146
147
  readonly lrc20ApiConfig?: LRC20WalletApiConfig;
@@ -175,6 +176,7 @@ const BASE_CONFIG: Required<ConfigOptions> = {
175
176
  signingOperators: getLocalSigningOperators(),
176
177
  tokenSignatures: "SCHNORR",
177
178
  tokenTransactionVersion: "V0",
179
+ tokenValidityDurationSeconds: 180,
178
180
  electrsUrl: getElectrsUrl("LOCAL"),
179
181
  expectedWithdrawBondSats: 10000,
180
182
  expectedWithdrawRelativeBlockLocktime: 1000,
@@ -48,6 +48,9 @@ import { Transaction } from "@scure/btc-signer";
48
48
  import { taprootTweakPrivKey } from "@scure/btc-signer/utils";
49
49
  import type { Psbt } from "bitcoinjs-lib";
50
50
 
51
+ export { Receipt, PARITY, fromPrivateKey } from "@buildonspark/lrc20-sdk";
52
+ export { MultisigReceiptInput } from "@buildonspark/lrc20-sdk/lrc/types";
53
+
51
54
  export type SigningNonce = {
52
55
  binding: Uint8Array;
53
56
  hiding: Uint8Array;
@@ -934,4 +937,4 @@ class TaprootSparkSigner extends DefaultSparkSigner {
934
937
  }
935
938
 
936
939
  export { DefaultSparkSigner, TaprootSparkSigner, TaprootOutputKeysGenerator };
937
- export type { SparkSigner };
940
+ export type { SparkSigner, TokenSigner };
@@ -2532,12 +2532,14 @@ export class SparkWallet extends EventEmitter {
2532
2532
  * @param {Object} params - Parameters for paying the invoice
2533
2533
  * @param {string} params.invoice - The BOLT11-encoded Lightning invoice to pay
2534
2534
  * @param {boolean} [params.preferSpark] - Whether to prefer a spark transfer over lightning for the payment
2535
+ * @param {number} [params.amountSatsToSend] - The amount in sats to send. This is only valid for 0 amount lightning invoices.
2535
2536
  * @returns {Promise<LightningSendRequest>} The Lightning payment request details
2536
2537
  */
2537
2538
  public async payLightningInvoice({
2538
2539
  invoice,
2539
2540
  maxFeeSats,
2540
2541
  preferSpark = false,
2542
+ amountSatsToSend,
2541
2543
  }: PayLightningInvoiceParams) {
2542
2544
  const invoiceNetwork = getNetworkFromInvoice(invoice);
2543
2545
  const walletNetwork = this.config.getNetwork();
@@ -2553,10 +2555,45 @@ export class SparkWallet extends EventEmitter {
2553
2555
  }
2554
2556
 
2555
2557
  const decodedInvoice = decodeInvoice(invoice);
2556
- const amountSats =
2557
- decodedInvoice.amountMSats !== null
2558
- ? Number(decodedInvoice.amountMSats / 1000n)
2559
- : 0;
2558
+ const amountMSats = decodedInvoice.amountMSats;
2559
+ const isZeroAmountInvoice = !amountMSats;
2560
+
2561
+ // Check if user is trying to send amountSatsToSend for non 0 amount lightning invoice
2562
+ if (!isZeroAmountInvoice && amountSatsToSend !== undefined) {
2563
+ throw new ValidationError(
2564
+ "Invalid amount. User can only specify amountSatsToSend for 0 amount lightning invoice",
2565
+ {
2566
+ field: "amountMSats",
2567
+ value: Number(amountMSats),
2568
+ expected: "0",
2569
+ },
2570
+ );
2571
+ }
2572
+
2573
+ // If 0 amount lightning invoice, check that user has specified amountSatsToSend
2574
+ if (isZeroAmountInvoice && amountSatsToSend === undefined) {
2575
+ throw new ValidationError(
2576
+ "Invalid amount. User must specify amountSatsToSend for 0 amount lightning invoice",
2577
+ {
2578
+ field: "amountMSats",
2579
+ value: Number(amountMSats),
2580
+ expected: "0",
2581
+ },
2582
+ );
2583
+ }
2584
+
2585
+ const amountSats = isZeroAmountInvoice
2586
+ ? amountSatsToSend!
2587
+ : Number(amountMSats / 1000n);
2588
+
2589
+ if (isNaN(amountSats) || amountSats <= 0) {
2590
+ throw new ValidationError("Invalid amount", {
2591
+ field: "amountSats",
2592
+ value: amountSats,
2593
+ expected: "greater than 0",
2594
+ });
2595
+ }
2596
+
2560
2597
  const sparkFallbackAddress = decodedInvoice.fallbackAddress;
2561
2598
  const paymentHash = decodedInvoice.paymentHash;
2562
2599
 
@@ -2586,16 +2623,10 @@ export class SparkWallet extends EventEmitter {
2586
2623
  return await this.withLeaves(async () => {
2587
2624
  const sspClient = this.getSspClient();
2588
2625
 
2589
- if (isNaN(amountSats) || amountSats <= 0) {
2590
- throw new ValidationError("Invalid amount", {
2591
- field: "amountSats",
2592
- value: amountSats,
2593
- expected: "positive number",
2594
- });
2595
- }
2596
-
2626
+ // If 0 amount lightning invoice, use amountSatsToSend for fee estimate
2597
2627
  const feeEstimate = await this.getLightningSendFeeEstimate({
2598
2628
  encodedInvoice: invoice,
2629
+ amountSats: isZeroAmountInvoice ? amountSatsToSend! : undefined,
2599
2630
  });
2600
2631
 
2601
2632
  if (maxFeeSats < feeEstimate) {
@@ -2641,13 +2672,14 @@ export class SparkWallet extends EventEmitter {
2641
2672
  isInboundPayment: false,
2642
2673
  invoiceString: invoice,
2643
2674
  feeSats: feeEstimate,
2675
+ amountSatsToSend: amountSatsToSend,
2644
2676
  });
2645
2677
 
2646
2678
  if (!swapResponse.transfer) {
2647
2679
  throw new Error("Failed to swap nodes for preimage");
2648
2680
  }
2649
2681
 
2650
- const transfer = await this.transferService.sendTransferTweakKey(
2682
+ await this.transferService.deliverTransferPackage(
2651
2683
  swapResponse.transfer,
2652
2684
  leavesToSend,
2653
2685
  new Map(),
@@ -2656,6 +2688,7 @@ export class SparkWallet extends EventEmitter {
2656
2688
  const sspResponse = await sspClient.requestLightningSend({
2657
2689
  encodedInvoice: invoice,
2658
2690
  idempotencyKey: paymentHash,
2691
+ amountSats: isZeroAmountInvoice ? amountSatsToSend! : undefined,
2659
2692
  });
2660
2693
 
2661
2694
  if (!sspResponse) {
@@ -2677,11 +2710,14 @@ export class SparkWallet extends EventEmitter {
2677
2710
  */
2678
2711
  public async getLightningSendFeeEstimate({
2679
2712
  encodedInvoice,
2713
+ amountSats,
2680
2714
  }: LightningSendFeeEstimateInput): Promise<number> {
2681
2715
  const sspClient = this.getSspClient();
2682
2716
 
2683
- const feeEstimate =
2684
- await sspClient.getLightningSendFeeEstimate(encodedInvoice);
2717
+ const feeEstimate = await sspClient.getLightningSendFeeEstimate(
2718
+ encodedInvoice,
2719
+ amountSats,
2720
+ );
2685
2721
 
2686
2722
  if (!feeEstimate) {
2687
2723
  throw new Error("Failed to get lightning send fee estimate");
@@ -15,6 +15,7 @@ export type PayLightningInvoiceParams = {
15
15
  invoice: string;
16
16
  maxFeeSats: number;
17
17
  preferSpark?: boolean;
18
+ amountSatsToSend?: number;
18
19
  };
19
20
 
20
21
  export type TransferParams = {
@@ -0,0 +1,58 @@
1
+ import { SparkSDKError } from "../errors/base.js";
2
+
3
+ describe("SparkSDKError", () => {
4
+ it("should not throw and should stringify BigInt values in context", () => {
5
+ const bigNumber = 123n;
6
+
7
+ const err = new SparkSDKError("Test BigInt", { big: bigNumber });
8
+
9
+ expect(err.message).toContain("Context:");
10
+ // BigInt should be converted to a string representation
11
+ expect(err.message).toContain('big: "123"');
12
+ });
13
+
14
+ it("should stringify regular primitives correctly", () => {
15
+ const err = new SparkSDKError("Test primitives", {
16
+ num: 1,
17
+ str: "abc",
18
+ bool: true,
19
+ });
20
+
21
+ expect(err.message).toContain("num: 1");
22
+ expect(err.message).toContain('str: "abc"');
23
+ expect(err.message).toContain("bool: true");
24
+ });
25
+
26
+ it("should include original error message when provided", () => {
27
+ const original = new Error("something broke");
28
+ const err = new SparkSDKError("Wrapper error", {}, original);
29
+
30
+ expect(err.message).toContain("Original Error: something broke");
31
+ });
32
+
33
+ it("should stringify Uint8Array values correctly", () => {
34
+ const bytes = new Uint8Array([1, 2, 3]);
35
+ const err = new SparkSDKError("Uint8Array test", { bytes });
36
+
37
+ expect(err.message).toContain("bytes:");
38
+ expect(err.message).toMatch(/Uint8Array\(0x010203\)/);
39
+ });
40
+
41
+ it("should generate the correct full error message", () => {
42
+ const original = new Error("root cause");
43
+ const context = {
44
+ big: 123n,
45
+ bytes: new Uint8Array([1, 2, 3]),
46
+ num: 42,
47
+ } as const;
48
+
49
+ const err = new SparkSDKError("Something went wrong", context, original);
50
+
51
+ const expectedMessage =
52
+ "SparkSDKError: Something went wrong\n" +
53
+ 'Context: big: "123", bytes: "Uint8Array(0x010203)", num: 42\n' +
54
+ "Original Error: root cause";
55
+
56
+ expect(err.message).toBe(expectedMessage);
57
+ });
58
+ });