@buildonspark/spark-sdk 0.1.45 → 0.1.47

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 (146) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/{chunk-I54FARY2.js → chunk-EAP3U3CW.js} +14 -14
  3. package/dist/chunk-GWFQ7EBA.js +3773 -0
  4. package/dist/{chunk-J2IE4Z7Y.js → chunk-NNX4OK44.js} +3487 -934
  5. package/dist/{RequestLightningSendInput-Du0z7Om7.d.cts → client-CvpTRpcw.d.cts} +422 -212
  6. package/dist/{RequestLightningSendInput-DEPd_fPO.d.ts → client-D7KgLN44.d.ts} +422 -212
  7. package/dist/graphql/objects/index.d.cts +5 -9
  8. package/dist/graphql/objects/index.d.ts +5 -9
  9. package/dist/graphql/objects/index.js +1 -1
  10. package/dist/index.cjs +20461 -23377
  11. package/dist/index.d.cts +15 -769
  12. package/dist/index.d.ts +15 -769
  13. package/dist/index.js +81 -71
  14. package/dist/index.node.cjs +21994 -25018
  15. package/dist/index.node.d.cts +312 -34
  16. package/dist/index.node.d.ts +312 -34
  17. package/dist/index.node.js +82 -176
  18. package/dist/native/index.cjs +22847 -25841
  19. package/dist/native/index.d.cts +974 -1138
  20. package/dist/native/index.d.ts +974 -1138
  21. package/dist/native/index.js +10604 -13592
  22. package/dist/proto/lrc20.d.cts +2 -2
  23. package/dist/proto/lrc20.d.ts +2 -2
  24. package/dist/proto/lrc20.js +3098 -46
  25. package/dist/proto/spark.d.cts +1 -1
  26. package/dist/proto/spark.d.ts +1 -1
  27. package/dist/proto/spark_token.d.cts +1 -1
  28. package/dist/proto/spark_token.d.ts +1 -1
  29. package/dist/{sdk-types-Cc4l4kb1.d.ts → sdk-types-BGCeea0G.d.ts} +1 -1
  30. package/dist/{sdk-types-B0SwjolI.d.cts → sdk-types-XUeQMLFP.d.cts} +1 -1
  31. package/dist/{spark-dM7EYXYQ.d.cts → spark-BbUrbvZz.d.cts} +1 -1
  32. package/dist/{spark-dM7EYXYQ.d.ts → spark-BbUrbvZz.d.ts} +1 -1
  33. package/dist/spark-wallet-BAFPpPtY.d.cts +923 -0
  34. package/dist/spark-wallet-CJkQW8pK.d.ts +923 -0
  35. package/dist/spark_bindings/native/index.d.cts +1 -1
  36. package/dist/spark_bindings/native/index.d.ts +1 -1
  37. package/dist/spark_bindings/wasm/index.d.cts +1 -1
  38. package/dist/spark_bindings/wasm/index.d.ts +1 -1
  39. package/dist/{services/index.cjs → tests/test-utils.cjs} +2512 -4380
  40. package/dist/tests/test-utils.d.cts +79 -0
  41. package/dist/tests/test-utils.d.ts +79 -0
  42. package/dist/tests/test-utils.js +85 -0
  43. package/dist/types/index.d.cts +5 -9
  44. package/dist/types/index.d.ts +5 -9
  45. package/dist/types/index.js +5 -5
  46. package/dist/{types-C-Rp0Oo7.d.cts → types-BADxR3bm.d.cts} +1 -1
  47. package/dist/{types-C-Rp0Oo7.d.ts → types-BADxR3bm.d.ts} +1 -1
  48. package/package.json +7 -35
  49. package/src/graphql/client.ts +59 -20
  50. package/src/index.node.ts +28 -2
  51. package/src/index.ts +31 -1
  52. package/src/native/index.ts +16 -2
  53. package/src/services/config.ts +4 -6
  54. package/src/services/connection.ts +131 -64
  55. package/src/services/lightning.ts +1 -2
  56. package/src/services/token-transactions.ts +7 -7
  57. package/src/services/transfer.ts +1 -1
  58. package/src/services/tree-creation.ts +1 -1
  59. package/src/services/wallet-config.ts +18 -10
  60. package/src/signer/signer.react-native.ts +2 -5
  61. package/src/signer/signer.ts +138 -64
  62. package/src/signer/types.ts +52 -0
  63. package/src/spark-wallet/spark-wallet.ts +79 -36
  64. package/src/spark-wallet/types.ts +4 -4
  65. package/src/tests/integration/coop-exit.test.ts +2 -1
  66. package/src/tests/integration/lightning.test.ts +2 -2
  67. package/src/tests/integration/swap.test.ts +1 -1
  68. package/src/tests/integration/transfer.test.ts +5 -5
  69. package/src/tests/integration/tree-creation.test.ts +1 -1
  70. package/src/tests/integration/wallet.test.ts +1 -0
  71. package/src/tests/isHermeticTest.ts +3 -24
  72. package/src/tests/{test-util.ts → test-utils.ts} +3 -7
  73. package/src/tests/wrapWithOtelSpan.test.ts +1 -1
  74. package/src/{address → utils}/address.ts +1 -1
  75. package/src/utils/crypto.ts +19 -9
  76. package/src/utils/index.ts +2 -0
  77. package/src/utils/network.ts +17 -0
  78. package/src/utils/secret-sharing.ts +1 -2
  79. package/src/utils/signing.ts +1 -1
  80. package/src/utils/token-transactions.ts +3 -3
  81. package/src/utils/unilateral-exit.ts +32 -0
  82. package/src/utils/xchain-address.ts +1 -1
  83. package/dist/BitcoinNetwork-TnABML0T.d.cts +0 -18
  84. package/dist/BitcoinNetwork-TnABML0T.d.ts +0 -18
  85. package/dist/LightningSendFeeEstimateInput-BgOhEAI-.d.cts +0 -10
  86. package/dist/LightningSendFeeEstimateInput-BgOhEAI-.d.ts +0 -10
  87. package/dist/address/index.cjs +0 -458
  88. package/dist/address/index.d.cts +0 -32
  89. package/dist/address/index.d.ts +0 -32
  90. package/dist/address/index.js +0 -17
  91. package/dist/chunk-5FUB65LX.js +0 -838
  92. package/dist/chunk-6264CGDM.js +0 -113
  93. package/dist/chunk-7V6N75CC.js +0 -24
  94. package/dist/chunk-C2S227QR.js +0 -2336
  95. package/dist/chunk-GSI4OLXZ.js +0 -117
  96. package/dist/chunk-GZ5IPPJ2.js +0 -170
  97. package/dist/chunk-HWJWKEIU.js +0 -75
  98. package/dist/chunk-KMUMFYFX.js +0 -137
  99. package/dist/chunk-L3EHBOUX.js +0 -0
  100. package/dist/chunk-NSJF5F5O.js +0 -325
  101. package/dist/chunk-NTFKFRQ2.js +0 -3146
  102. package/dist/chunk-PQN3C2MF.js +0 -1122
  103. package/dist/chunk-QNNSEJ4P.js +0 -232
  104. package/dist/chunk-R5PXJZQS.js +0 -277
  105. package/dist/chunk-VTUGIIWI.js +0 -0
  106. package/dist/chunk-YUPMXTCJ.js +0 -622
  107. package/dist/chunk-Z5HIAYFT.js +0 -84
  108. package/dist/index-B2AwKW5J.d.cts +0 -214
  109. package/dist/index-CJDi1HWc.d.ts +0 -214
  110. package/dist/network-BTJl-Sul.d.ts +0 -46
  111. package/dist/network-CqgsdUF2.d.cts +0 -46
  112. package/dist/services/config.cjs +0 -2354
  113. package/dist/services/config.d.cts +0 -42
  114. package/dist/services/config.d.ts +0 -42
  115. package/dist/services/config.js +0 -17
  116. package/dist/services/connection.cjs +0 -17691
  117. package/dist/services/connection.d.cts +0 -95
  118. package/dist/services/connection.d.ts +0 -95
  119. package/dist/services/connection.js +0 -11
  120. package/dist/services/index.d.cts +0 -21
  121. package/dist/services/index.d.ts +0 -21
  122. package/dist/services/index.js +0 -58
  123. package/dist/services/lrc-connection.cjs +0 -4713
  124. package/dist/services/lrc-connection.d.cts +0 -34
  125. package/dist/services/lrc-connection.d.ts +0 -34
  126. package/dist/services/lrc-connection.js +0 -11
  127. package/dist/services/token-transactions.cjs +0 -2877
  128. package/dist/services/token-transactions.d.cts +0 -75
  129. package/dist/services/token-transactions.d.ts +0 -75
  130. package/dist/services/token-transactions.js +0 -15
  131. package/dist/services/wallet-config.cjs +0 -340
  132. package/dist/services/wallet-config.d.cts +0 -56
  133. package/dist/services/wallet-config.d.ts +0 -56
  134. package/dist/services/wallet-config.js +0 -33
  135. package/dist/signer/signer.cjs +0 -2004
  136. package/dist/signer/signer.d.cts +0 -10
  137. package/dist/signer/signer.d.ts +0 -10
  138. package/dist/signer/signer.js +0 -24
  139. package/dist/signer-BocS_J6B.d.ts +0 -187
  140. package/dist/signer-DKS0AJkw.d.cts +0 -187
  141. package/dist/utils/index.cjs +0 -2947
  142. package/dist/utils/index.d.cts +0 -18
  143. package/dist/utils/index.d.ts +0 -18
  144. package/dist/utils/index.js +0 -157
  145. package/src/address/index.ts +0 -1
  146. package/src/services/lrc-connection.ts +0 -215
@@ -1,4 +1,4 @@
1
- import { isNode } from "@lightsparkdev/core";
1
+ import { isError, isNode } from "@lightsparkdev/core";
2
2
  import { sha256 } from "@noble/hashes/sha2";
3
3
  import type { Channel, ClientFactory } from "nice-grpc";
4
4
  import { retryMiddleware } from "nice-grpc-client-middleware-retry";
@@ -23,6 +23,10 @@ import {
23
23
  SparkTokenServiceDefinition,
24
24
  } from "../proto/spark_token.js";
25
25
 
26
+ type SparkAuthnServiceClientWithClose = SparkAuthnServiceClient & {
27
+ close?: () => void;
28
+ };
29
+
26
30
  export class ConnectionManager {
27
31
  private config: WalletConfigService;
28
32
  private clients: Map<
@@ -52,6 +56,9 @@ export class ConnectionManager {
52
56
  }
53
57
  > = new Map();
54
58
 
59
+ // Tracks in-flight authenticate() promises so concurrent callers share one
60
+ private authPromises: Map<string, Promise<string>> = new Map();
61
+
55
62
  constructor(config: WalletConfigService) {
56
63
  this.config = config;
57
64
  }
@@ -231,57 +238,105 @@ export class ConnectionManager {
231
238
  }
232
239
 
233
240
  private async authenticate(address: string, certPath?: string) {
234
- try {
235
- const identityPublicKey = await this.config.signer.getIdentityPublicKey();
236
- const sparkAuthnClient = await this.createSparkAuthnGrpcConnection(
237
- address,
238
- certPath,
239
- );
241
+ const existing = this.authPromises.get(address);
242
+ if (existing) {
243
+ return existing;
244
+ }
245
+
246
+ const authPromise = (async () => {
247
+ const MAX_ATTEMPTS = 3;
248
+ let lastError: Error | undefined;
249
+
250
+ /* React Native can cause some outgoing requests to be paused which can result
251
+ in challenges expiring, so we'll retry any authentication failures: */
252
+ for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
253
+ let sparkAuthnClient: SparkAuthnServiceClientWithClose | undefined;
254
+ try {
255
+ const identityPublicKey =
256
+ await this.config.signer.getIdentityPublicKey();
257
+ sparkAuthnClient = await this.createSparkAuthnGrpcConnection(
258
+ address,
259
+ certPath,
260
+ );
240
261
 
241
- const challengeResp = await sparkAuthnClient.get_challenge({
242
- publicKey: identityPublicKey,
243
- });
262
+ const challengeResp = await sparkAuthnClient.get_challenge({
263
+ publicKey: identityPublicKey,
264
+ });
244
265
 
245
- if (!challengeResp.protectedChallenge?.challenge) {
246
- throw new AuthenticationError("Invalid challenge response", {
247
- endpoint: "get_challenge",
248
- reason: "Missing challenge in response",
249
- });
250
- }
266
+ if (!challengeResp.protectedChallenge?.challenge) {
267
+ throw new AuthenticationError("Invalid challenge response", {
268
+ endpoint: "get_challenge",
269
+ reason: "Missing challenge in response",
270
+ });
271
+ }
251
272
 
252
- const challengeBytes = Challenge.encode(
253
- challengeResp.protectedChallenge.challenge,
254
- ).finish();
255
- const hash = sha256(challengeBytes);
273
+ const challengeBytes = Challenge.encode(
274
+ challengeResp.protectedChallenge.challenge,
275
+ ).finish();
276
+ const hash = sha256(challengeBytes);
256
277
 
257
- const derSignatureBytes =
258
- await this.config.signer.signMessageWithIdentityKey(hash);
278
+ const derSignatureBytes =
279
+ await this.config.signer.signMessageWithIdentityKey(hash);
259
280
 
260
- const verifyResp = await sparkAuthnClient.verify_challenge({
261
- protectedChallenge: challengeResp.protectedChallenge,
262
- signature: derSignatureBytes,
263
- publicKey: identityPublicKey,
264
- });
281
+ const verifyResp = await sparkAuthnClient.verify_challenge({
282
+ protectedChallenge: challengeResp.protectedChallenge,
283
+ signature: derSignatureBytes,
284
+ publicKey: identityPublicKey,
285
+ });
286
+
287
+ sparkAuthnClient.close?.();
288
+ return verifyResp.sessionToken;
289
+ } catch (error: unknown) {
290
+ if (isError(error)) {
291
+ sparkAuthnClient?.close?.();
292
+
293
+ if (error.message.includes("challenge expired")) {
294
+ console.warn(
295
+ `Authentication attempt ${attempt + 1} failed due to expired challenge, retrying...`,
296
+ );
297
+ lastError = error;
298
+ continue;
299
+ }
300
+
301
+ throw new AuthenticationError(
302
+ "Authentication failed",
303
+ {
304
+ endpoint: "authenticate",
305
+ reason: error.message,
306
+ },
307
+ error,
308
+ );
309
+ } else {
310
+ lastError = new Error(
311
+ `Unknown error during authentication: ${String(error)}`,
312
+ );
313
+ }
314
+ }
315
+ }
265
316
 
266
- sparkAuthnClient.close?.();
267
- return verifyResp.sessionToken;
268
- } catch (error: any) {
269
- console.error("Authentication error:", error);
270
317
  throw new AuthenticationError(
271
- "Authentication failed",
318
+ "Authentication failed after retrying expired challenges",
272
319
  {
273
320
  endpoint: "authenticate",
274
- reason: error.message,
321
+ reason: lastError?.message ?? "Unknown error",
275
322
  },
276
- error,
323
+ lastError,
277
324
  );
325
+ })();
326
+
327
+ this.authPromises.set(address, authPromise);
328
+
329
+ try {
330
+ return await authPromise;
331
+ } finally {
332
+ this.authPromises.delete(address);
278
333
  }
279
334
  }
280
335
 
281
336
  private async createSparkAuthnGrpcConnection(
282
337
  address: string,
283
338
  certPath?: string,
284
- ): Promise<SparkAuthnServiceClient & { close?: () => void }> {
339
+ ): Promise<SparkAuthnServiceClientWithClose> {
285
340
  const channel = await this.createChannelWithTLS(address, certPath);
286
341
  const authnMiddleware = this.createAuthnMiddleware();
287
342
  return this.createGrpcClient<SparkAuthnServiceClient>(
@@ -335,6 +390,32 @@ export class ConnectionManager {
335
390
  }
336
391
  }
337
392
 
393
+ private async *handleMiddlewareError(
394
+ error: unknown,
395
+ address: string,
396
+ call: ClientMiddlewareCall<any, any>,
397
+ metadata: Metadata,
398
+ options: SparkCallOptions,
399
+ ) {
400
+ if (isError(error)) {
401
+ if (error.message.includes("token has expired")) {
402
+ const newAuthToken = await this.authenticate(address);
403
+ const clientData = this.clients.get(address);
404
+ if (!clientData) {
405
+ throw new Error(`No client found for address: ${address}`);
406
+ }
407
+ clientData.authToken = newAuthToken;
408
+
409
+ return yield* call.next(call.request, {
410
+ ...options,
411
+ metadata: metadata.set("Authorization", `Bearer ${newAuthToken}`),
412
+ });
413
+ }
414
+ }
415
+
416
+ throw error;
417
+ }
418
+
338
419
  private createNodeMiddleware(address: string, initialAuthToken: string) {
339
420
  return async function* (
340
421
  this: ConnectionManager,
@@ -353,21 +434,14 @@ export class ConnectionManager {
353
434
  `Bearer ${this.clients.get(address)?.authToken || initialAuthToken}`,
354
435
  ),
355
436
  });
356
- } catch (error: any) {
357
- if (error.message?.includes("token has expired")) {
358
- const newAuthToken = await this.authenticate(address);
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;
364
-
365
- return yield* call.next(call.request, {
366
- ...options,
367
- metadata: metadata.set("Authorization", `Bearer ${newAuthToken}`),
368
- });
369
- }
370
- throw error;
437
+ } catch (error: unknown) {
438
+ return yield* this.handleMiddlewareError(
439
+ error,
440
+ address,
441
+ call,
442
+ metadata,
443
+ options,
444
+ );
371
445
  }
372
446
  }.bind(this);
373
447
  }
@@ -393,20 +467,13 @@ export class ConnectionManager {
393
467
  ),
394
468
  });
395
469
  } catch (error: any) {
396
- if (error.message?.includes("token has expired")) {
397
- const newAuthToken = await this.authenticate(address);
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;
403
-
404
- return yield* call.next(call.request, {
405
- ...options,
406
- metadata: metadata.set("Authorization", `Bearer ${newAuthToken}`),
407
- });
408
- }
409
- throw error;
470
+ return yield* this.handleMiddlewareError(
471
+ error,
472
+ address,
473
+ call,
474
+ metadata,
475
+ options,
476
+ );
410
477
  }
411
478
  }.bind(this);
412
479
  }
@@ -25,8 +25,6 @@ import { SigningService } from "./signing.js";
25
25
  import type { LeafKeyTweak } from "./transfer.js";
26
26
  import { decodeInvoice } from "./bolt11-spark.js";
27
27
 
28
- const crypto = getCrypto();
29
-
30
28
  export type CreateLightningInvoiceParams = {
31
29
  invoiceCreator: (
32
30
  amountSats: number,
@@ -76,6 +74,7 @@ export class LightningService {
76
74
  receiverIdentityPubkey,
77
75
  descriptionHash,
78
76
  }: CreateLightningInvoiceParams): Promise<LightningReceiveRequest> {
77
+ const crypto = getCrypto();
79
78
  const randBytes = crypto.getRandomValues(new Uint8Array(32));
80
79
  const preimage = numberToBytesBE(
81
80
  bytesToNumberBE(randBytes) % secp256k1.CURVE.n,
@@ -20,7 +20,7 @@ import {
20
20
  hashTokenTransaction,
21
21
  } from "../utils/token-hashing.js";
22
22
  import {
23
- calculateAvailableTokenAmount,
23
+ sumAvailableTokens,
24
24
  checkIfSelectedOutputsAreAvailable,
25
25
  } from "../utils/token-transactions.js";
26
26
  import {
@@ -36,7 +36,7 @@ import {
36
36
  } from "../errors/types.js";
37
37
  import { SigningOperator } from "./wallet-config.js";
38
38
  import { hexToBytes } from "@noble/hashes/utils";
39
- import { decodeSparkAddress } from "../address/index.js";
39
+ import { decodeSparkAddress } from "../utils/address.js";
40
40
  import {
41
41
  TokenTransaction,
42
42
  SignatureWithIndex,
@@ -142,7 +142,7 @@ export class TokenTransactionService {
142
142
 
143
143
  // Take only the first MAX_TOKEN_OUTPUTS and calculate their total
144
144
  const maxOutputsToUse = sortedOutputs.slice(0, MAX_TOKEN_OUTPUTS);
145
- const maxAmount = calculateAvailableTokenAmount(maxOutputsToUse);
145
+ const maxAmount = sumAvailableTokens(maxOutputsToUse);
146
146
 
147
147
  throw new ValidationError(
148
148
  `Cannot transfer more than ${MAX_TOKEN_OUTPUTS} TTXOs in a single transaction (${outputsToUse.length} selected). Maximum transferable amount is: ${maxAmount}`,
@@ -204,7 +204,7 @@ export class TokenTransactionService {
204
204
  (a, b) => a.previousTransactionVout - b.previousTransactionVout,
205
205
  );
206
206
 
207
- const availableTokenAmount = calculateAvailableTokenAmount(selectedOutputs);
207
+ const availableTokenAmount = sumAvailableTokens(selectedOutputs);
208
208
  const totalRequestedAmount = tokenOutputData.reduce(
209
209
  (sum, output) => sum + output.tokenAmount,
210
210
  0n,
@@ -255,7 +255,7 @@ export class TokenTransactionService {
255
255
  (a, b) => a.previousTransactionVout - b.previousTransactionVout,
256
256
  );
257
257
 
258
- const availableTokenAmount = calculateAvailableTokenAmount(selectedOutputs);
258
+ const availableTokenAmount = sumAvailableTokens(selectedOutputs);
259
259
  const totalRequestedAmount = tokenOutputData.reduce(
260
260
  (sum, output) => sum + output.tokenAmount,
261
261
  0n,
@@ -1106,10 +1106,10 @@ export class TokenTransactionService {
1106
1106
  tokenAmount: bigint,
1107
1107
  strategy: "SMALL_FIRST" | "LARGE_FIRST",
1108
1108
  ): OutputWithPreviousTransactionData[] {
1109
- if (calculateAvailableTokenAmount(tokenOutputs) < tokenAmount) {
1109
+ if (sumAvailableTokens(tokenOutputs) < tokenAmount) {
1110
1110
  throw new ValidationError("Insufficient token amount", {
1111
1111
  field: "tokenAmount",
1112
- value: calculateAvailableTokenAmount(tokenOutputs),
1112
+ value: sumAvailableTokens(tokenOutputs),
1113
1113
  expected: tokenAmount,
1114
1114
  });
1115
1115
  }
@@ -36,7 +36,7 @@ import {
36
36
  TransferType,
37
37
  TreeNode,
38
38
  } from "../proto/spark.js";
39
- import { SigningCommitment } from "../signer/signer.js";
39
+ import type { SigningCommitment } from "../signer/types.js";
40
40
  import {
41
41
  getSigHashFromTx,
42
42
  getTxFromRawTxBytes,
@@ -16,7 +16,7 @@ import {
16
16
  SigningJob,
17
17
  TreeNode,
18
18
  } from "../proto/spark.js";
19
- import { SigningCommitment } from "../signer/signer.js";
19
+ import { SigningCommitment } from "../signer/types.js";
20
20
  import {
21
21
  getP2TRAddressFromPublicKey,
22
22
  getSigHashFromTx,
@@ -52,7 +52,7 @@ export const ELECTRS_CREDENTIALS = {
52
52
  export function getElectrsUrl(network: NetworkType): string {
53
53
  switch (network) {
54
54
  case "LOCAL":
55
- return isHermeticTest()
55
+ return isHermeticTest
56
56
  ? "http://mempool.minikube.local/api"
57
57
  : URL_CONFIG.LOCAL.ELECTRS;
58
58
  case "REGTEST":
@@ -106,7 +106,7 @@ export function getSspIdentityPublicKey(network: NetworkType): string {
106
106
  export function getSspUrl(network: NetworkType): string {
107
107
  switch (network) {
108
108
  case "LOCAL":
109
- return isHermeticTest()
109
+ return isHermeticTest
110
110
  ? "http://app.minikube.local"
111
111
  : URL_CONFIG.LOCAL.SSP;
112
112
  case "REGTEST":
@@ -163,10 +163,10 @@ const PROD_PUBKEYS = [
163
163
  ];
164
164
 
165
165
  function getLocalFrostSignerAddress(): string {
166
- return isHermeticTest() ? "localhost:9999" : "unix:///tmp/frost_0.sock";
166
+ return isHermeticTest ? "localhost:9999" : "unix:///tmp/frost_0.sock";
167
167
  }
168
168
 
169
- export const BASE_CONFIG: Required<ConfigOptions> = {
169
+ const BASE_CONFIG: Required<ConfigOptions> = {
170
170
  network: "LOCAL",
171
171
  lrc20Address: getLrc20Url("LOCAL"),
172
172
  coodinatorIdentifier:
@@ -175,7 +175,7 @@ export const BASE_CONFIG: Required<ConfigOptions> = {
175
175
  threshold: 2,
176
176
  signingOperators: getLocalSigningOperators(),
177
177
  tokenSignatures: "SCHNORR",
178
- tokenTransactionVersion: "V0",
178
+ tokenTransactionVersion: "V1",
179
179
  tokenValidityDurationSeconds: 180,
180
180
  electrsUrl: getElectrsUrl("LOCAL"),
181
181
  expectedWithdrawBondSats: 10000,
@@ -192,23 +192,23 @@ export const BASE_CONFIG: Required<ConfigOptions> = {
192
192
  },
193
193
  };
194
194
 
195
- export const LOCAL_WALLET_CONFIG: Required<ConfigOptions> = {
195
+ const LOCAL_WALLET_CONFIG: Required<ConfigOptions> = {
196
196
  ...BASE_CONFIG,
197
197
  threshold: 3,
198
198
  };
199
199
 
200
- export const LOCAL_WALLET_CONFIG_SCHNORR: Required<ConfigOptions> = {
200
+ const LOCAL_WALLET_CONFIG_SCHNORR: Required<ConfigOptions> = {
201
201
  ...LOCAL_WALLET_CONFIG,
202
202
  threshold: 3, // 3 for issuance tests.
203
203
  };
204
204
 
205
- export const LOCAL_WALLET_CONFIG_ECDSA: Required<ConfigOptions> = {
205
+ const LOCAL_WALLET_CONFIG_ECDSA: Required<ConfigOptions> = {
206
206
  ...LOCAL_WALLET_CONFIG,
207
207
  tokenSignatures: "ECDSA",
208
208
  threshold: 3, // 3 for issuance tests.
209
209
  };
210
210
 
211
- export const REGTEST_WALLET_CONFIG: Required<ConfigOptions> = {
211
+ const REGTEST_WALLET_CONFIG: Required<ConfigOptions> = {
212
212
  ...BASE_CONFIG,
213
213
  network: "REGTEST",
214
214
  lrc20Address: getLrc20Url("REGTEST"),
@@ -227,7 +227,7 @@ export const REGTEST_WALLET_CONFIG: Required<ConfigOptions> = {
227
227
  },
228
228
  };
229
229
 
230
- export const MAINNET_WALLET_CONFIG: Required<ConfigOptions> = {
230
+ const MAINNET_WALLET_CONFIG: Required<ConfigOptions> = {
231
231
  ...BASE_CONFIG,
232
232
  network: "MAINNET",
233
233
  lrc20Address: getLrc20Url("MAINNET"),
@@ -245,6 +245,14 @@ export const MAINNET_WALLET_CONFIG: Required<ConfigOptions> = {
245
245
  },
246
246
  };
247
247
 
248
+ export const WalletConfig = {
249
+ LOCAL: LOCAL_WALLET_CONFIG,
250
+ LOCAL_SCHNORR: LOCAL_WALLET_CONFIG_SCHNORR,
251
+ LOCAL_ECDSA: LOCAL_WALLET_CONFIG_ECDSA,
252
+ REGTEST: REGTEST_WALLET_CONFIG,
253
+ MAINNET: MAINNET_WALLET_CONFIG,
254
+ };
255
+
248
256
  function getSigningOperators(): Record<string, SigningOperator> {
249
257
  return {
250
258
  "0000000000000000000000000000000000000000000000000000000000000001": {
@@ -2,11 +2,8 @@ import { bytesToHex, hexToBytes } from "@noble/hashes/utils";
2
2
  import { ValidationError } from "../errors/index.js";
3
3
  import { NativeSparkFrost } from "../spark_bindings/native/index.js";
4
4
  import { IKeyPackage } from "../spark_bindings/types.js";
5
- import {
6
- AggregateFrostParams,
7
- DefaultSparkSigner,
8
- SignFrostParams,
9
- } from "./signer.js";
5
+ import { DefaultSparkSigner } from "./signer.js";
6
+ import type { AggregateFrostParams, SignFrostParams } from "./types.js";
10
7
 
11
8
  export class ReactNativeSparkSigner extends DefaultSparkSigner {
12
9
  async signFrost({
@@ -12,7 +12,7 @@ import * as ecies from "eciesjs";
12
12
  import { isReactNative } from "../constants.js";
13
13
  import { ConfigurationError, ValidationError } from "../errors/types.js";
14
14
  import { TreeNode } from "../proto/spark.js";
15
- import { IKeyPackage, ISigningCommitment } from "../spark_bindings/types.js";
15
+ import { IKeyPackage } from "../spark_bindings/types.js";
16
16
  import { generateAdaptorFromSignature } from "../utils/adaptor-signature.js";
17
17
  import { subtractPrivateKeys } from "../utils/keys.js";
18
18
  import {
@@ -23,19 +23,6 @@ import {
23
23
  getRandomSigningNonce,
24
24
  getSigningCommitmentFromNonce,
25
25
  } from "../utils/signing.js";
26
-
27
- let sparkFrostModule: any = undefined;
28
- const getSparkFrostModule = async () => {
29
- if (isReactNative) {
30
- return undefined;
31
- }
32
- if (!sparkFrostModule) {
33
- // Use dynamic import
34
- sparkFrostModule = await import("../spark_bindings/wasm/index.js");
35
- }
36
- return sparkFrostModule;
37
- };
38
-
39
26
  import { privateAdd, privateNegate } from "@bitcoinerlab/secp256k1";
40
27
  import {
41
28
  fromPrivateKey,
@@ -47,53 +34,26 @@ import { sha256 } from "@noble/hashes/sha2";
47
34
  import { Transaction } from "@scure/btc-signer";
48
35
  import { taprootTweakPrivKey } from "@scure/btc-signer/utils";
49
36
  import type { Psbt } from "bitcoinjs-lib";
37
+ import type {
38
+ AggregateFrostParams,
39
+ DerivedHDKey,
40
+ KeyPair,
41
+ SignFrostParams,
42
+ SigningCommitment,
43
+ SigningNonce,
44
+ SplitSecretWithProofsParams,
45
+ } from "./types.js";
50
46
 
51
- export { Receipt, PARITY, fromPrivateKey } from "@buildonspark/lrc20-sdk";
52
- export { MultisigReceiptInput } from "@buildonspark/lrc20-sdk/lrc/types";
53
-
54
- export type SigningNonce = {
55
- binding: Uint8Array;
56
- hiding: Uint8Array;
57
- };
58
-
59
- export type SigningCommitment = {
60
- binding: Uint8Array;
61
- hiding: Uint8Array;
62
- };
63
-
64
- export type SignFrostParams = {
65
- message: Uint8Array;
66
- privateAsPubKey: Uint8Array;
67
- publicKey: Uint8Array;
68
- verifyingKey: Uint8Array;
69
- selfCommitment: ISigningCommitment;
70
- statechainCommitments?: { [key: string]: ISigningCommitment } | undefined;
71
- adaptorPubKey?: Uint8Array | undefined;
72
- };
73
-
74
- export type AggregateFrostParams = Omit<SignFrostParams, "privateAsPubKey"> & {
75
- selfSignature: Uint8Array;
76
- statechainSignatures?: { [key: string]: Uint8Array } | undefined;
77
- statechainPublicKeys?: { [key: string]: Uint8Array } | undefined;
78
- };
79
-
80
- export type SplitSecretWithProofsParams = {
81
- secret: Uint8Array;
82
- curveOrder: bigint;
83
- threshold: number;
84
- numShares: number;
85
- isSecretPubkey?: boolean;
86
- };
87
-
88
- type DerivedHDKey = {
89
- hdKey: HDKey;
90
- privateKey: Uint8Array;
91
- publicKey: Uint8Array;
92
- };
93
-
94
- type KeyPair = {
95
- privateKey: Uint8Array;
96
- publicKey: Uint8Array;
47
+ let sparkFrostModule: any = undefined;
48
+ const getSparkFrostModule = async () => {
49
+ if (isReactNative) {
50
+ return undefined;
51
+ }
52
+ if (!sparkFrostModule) {
53
+ // Use dynamic import
54
+ sparkFrostModule = await import("../spark_bindings/wasm/index.js");
55
+ }
56
+ return sparkFrostModule;
97
57
  };
98
58
 
99
59
  interface SparkKeysGenerator {
@@ -178,6 +138,80 @@ class DefaultSparkKeysGenerator implements SparkKeysGenerator {
178
138
  }
179
139
  }
180
140
 
141
+ class DerivationPathKeysGenerator implements SparkKeysGenerator {
142
+ constructor(private readonly derivationPathTemplate: string) {}
143
+
144
+ async deriveKeysFromSeed(
145
+ seed: Uint8Array,
146
+ accountNumber: number,
147
+ ): Promise<{
148
+ masterPublicKey: Uint8Array;
149
+ identityKey: KeyPair;
150
+ signingHDKey: DerivedHDKey;
151
+ depositKey: KeyPair;
152
+ staticDepositHDKey: DerivedHDKey;
153
+ }> {
154
+ const hdkey = HDKey.fromMasterSeed(seed);
155
+
156
+ if (!hdkey.privateKey || !hdkey.publicKey) {
157
+ throw new ValidationError("Failed to derive keys from seed", {
158
+ field: "hdkey",
159
+ value: seed,
160
+ });
161
+ }
162
+
163
+ const derivationPath = this.derivationPathTemplate.replaceAll(
164
+ "?",
165
+ accountNumber.toString(),
166
+ );
167
+
168
+ const identityKey = hdkey.derive(derivationPath);
169
+ const signingKey = hdkey.derive(`${derivationPath}/1'`);
170
+ const depositKey = hdkey.derive(`${derivationPath}/2'`);
171
+ const staticDepositKey = hdkey.derive(`${derivationPath}/3'`);
172
+
173
+ if (
174
+ !identityKey.privateKey ||
175
+ !identityKey.publicKey ||
176
+ !signingKey.privateKey ||
177
+ !signingKey.publicKey ||
178
+ !depositKey.privateKey ||
179
+ !depositKey.publicKey ||
180
+ !staticDepositKey.privateKey ||
181
+ !staticDepositKey.publicKey
182
+ ) {
183
+ throw new ValidationError(
184
+ "Failed to derive all required keys from seed",
185
+ {
186
+ field: "derivedKeys",
187
+ },
188
+ );
189
+ }
190
+
191
+ return {
192
+ masterPublicKey: hdkey.publicKey,
193
+ identityKey: {
194
+ privateKey: identityKey.privateKey,
195
+ publicKey: identityKey.publicKey,
196
+ },
197
+ signingHDKey: {
198
+ hdKey: signingKey,
199
+ privateKey: signingKey.privateKey,
200
+ publicKey: signingKey.publicKey,
201
+ },
202
+ depositKey: {
203
+ privateKey: depositKey.privateKey,
204
+ publicKey: depositKey.publicKey,
205
+ },
206
+ staticDepositHDKey: {
207
+ hdKey: staticDepositKey,
208
+ privateKey: staticDepositKey.privateKey,
209
+ publicKey: staticDepositKey.publicKey,
210
+ },
211
+ };
212
+ }
213
+ }
214
+
181
215
  class TaprootOutputKeysGenerator implements SparkKeysGenerator {
182
216
  constructor(private readonly useAddressIndex: boolean = false) {}
183
217
 
@@ -931,10 +965,50 @@ class DefaultSparkSigner implements SparkSigner {
931
965
  }
932
966
 
933
967
  class TaprootSparkSigner extends DefaultSparkSigner {
934
- constructor() {
935
- super({ sparkKeysGenerator: new TaprootOutputKeysGenerator() });
968
+ constructor(useAddressIndex = false) {
969
+ super({
970
+ sparkKeysGenerator: new TaprootOutputKeysGenerator(useAddressIndex),
971
+ });
972
+ }
973
+ }
974
+
975
+ class NativeSegwitSparkSigner extends DefaultSparkSigner {
976
+ constructor(useAddressIndex = false) {
977
+ super({
978
+ sparkKeysGenerator: new DerivationPathKeysGenerator(
979
+ useAddressIndex ? "m/84'/0'/0'/0/?" : "m/84'/0'/?'/0/0",
980
+ ),
981
+ });
982
+ }
983
+ }
984
+
985
+ class WrappedSegwitSparkSigner extends DefaultSparkSigner {
986
+ constructor(useAddressIndex = false) {
987
+ super({
988
+ sparkKeysGenerator: new DerivationPathKeysGenerator(
989
+ useAddressIndex ? "m/49'/0'/0'/0/?" : "m/49'/0'/?'/0/0",
990
+ ),
991
+ });
936
992
  }
937
993
  }
938
994
 
939
- export { DefaultSparkSigner, TaprootSparkSigner, TaprootOutputKeysGenerator };
940
- export type { SparkSigner, TokenSigner };
995
+ class LegacyBitcoinSparkSigner extends DefaultSparkSigner {
996
+ constructor(useAddressIndex = false) {
997
+ super({
998
+ sparkKeysGenerator: new DerivationPathKeysGenerator(
999
+ useAddressIndex ? "m/44'/0'/0'/0/?" : "m/44'/0'/?'/0/0",
1000
+ ),
1001
+ });
1002
+ }
1003
+ }
1004
+
1005
+ export {
1006
+ DefaultSparkSigner,
1007
+ TaprootSparkSigner,
1008
+ NativeSegwitSparkSigner,
1009
+ WrappedSegwitSparkSigner,
1010
+ LegacyBitcoinSparkSigner,
1011
+ TaprootOutputKeysGenerator,
1012
+ type SparkSigner,
1013
+ type TokenSigner,
1014
+ };