@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.
- package/CHANGELOG.md +22 -0
- package/dist/{chunk-I54FARY2.js → chunk-EAP3U3CW.js} +14 -14
- package/dist/chunk-GWFQ7EBA.js +3773 -0
- package/dist/{chunk-J2IE4Z7Y.js → chunk-NNX4OK44.js} +3487 -934
- package/dist/{RequestLightningSendInput-Du0z7Om7.d.cts → client-CvpTRpcw.d.cts} +422 -212
- package/dist/{RequestLightningSendInput-DEPd_fPO.d.ts → client-D7KgLN44.d.ts} +422 -212
- package/dist/graphql/objects/index.d.cts +5 -9
- package/dist/graphql/objects/index.d.ts +5 -9
- package/dist/graphql/objects/index.js +1 -1
- package/dist/index.cjs +20461 -23377
- package/dist/index.d.cts +15 -769
- package/dist/index.d.ts +15 -769
- package/dist/index.js +81 -71
- package/dist/index.node.cjs +21994 -25018
- package/dist/index.node.d.cts +312 -34
- package/dist/index.node.d.ts +312 -34
- package/dist/index.node.js +82 -176
- package/dist/native/index.cjs +22847 -25841
- package/dist/native/index.d.cts +974 -1138
- package/dist/native/index.d.ts +974 -1138
- package/dist/native/index.js +10604 -13592
- package/dist/proto/lrc20.d.cts +2 -2
- package/dist/proto/lrc20.d.ts +2 -2
- package/dist/proto/lrc20.js +3098 -46
- package/dist/proto/spark.d.cts +1 -1
- package/dist/proto/spark.d.ts +1 -1
- package/dist/proto/spark_token.d.cts +1 -1
- package/dist/proto/spark_token.d.ts +1 -1
- package/dist/{sdk-types-Cc4l4kb1.d.ts → sdk-types-BGCeea0G.d.ts} +1 -1
- package/dist/{sdk-types-B0SwjolI.d.cts → sdk-types-XUeQMLFP.d.cts} +1 -1
- package/dist/{spark-dM7EYXYQ.d.cts → spark-BbUrbvZz.d.cts} +1 -1
- package/dist/{spark-dM7EYXYQ.d.ts → spark-BbUrbvZz.d.ts} +1 -1
- package/dist/spark-wallet-BAFPpPtY.d.cts +923 -0
- package/dist/spark-wallet-CJkQW8pK.d.ts +923 -0
- package/dist/spark_bindings/native/index.d.cts +1 -1
- package/dist/spark_bindings/native/index.d.ts +1 -1
- package/dist/spark_bindings/wasm/index.d.cts +1 -1
- package/dist/spark_bindings/wasm/index.d.ts +1 -1
- package/dist/{services/index.cjs → tests/test-utils.cjs} +2512 -4380
- package/dist/tests/test-utils.d.cts +79 -0
- package/dist/tests/test-utils.d.ts +79 -0
- package/dist/tests/test-utils.js +85 -0
- package/dist/types/index.d.cts +5 -9
- package/dist/types/index.d.ts +5 -9
- package/dist/types/index.js +5 -5
- package/dist/{types-C-Rp0Oo7.d.cts → types-BADxR3bm.d.cts} +1 -1
- package/dist/{types-C-Rp0Oo7.d.ts → types-BADxR3bm.d.ts} +1 -1
- package/package.json +7 -35
- package/src/graphql/client.ts +59 -20
- package/src/index.node.ts +28 -2
- package/src/index.ts +31 -1
- package/src/native/index.ts +16 -2
- package/src/services/config.ts +4 -6
- package/src/services/connection.ts +131 -64
- package/src/services/lightning.ts +1 -2
- package/src/services/token-transactions.ts +7 -7
- package/src/services/transfer.ts +1 -1
- package/src/services/tree-creation.ts +1 -1
- package/src/services/wallet-config.ts +18 -10
- package/src/signer/signer.react-native.ts +2 -5
- package/src/signer/signer.ts +138 -64
- package/src/signer/types.ts +52 -0
- package/src/spark-wallet/spark-wallet.ts +79 -36
- package/src/spark-wallet/types.ts +4 -4
- package/src/tests/integration/coop-exit.test.ts +2 -1
- package/src/tests/integration/lightning.test.ts +2 -2
- package/src/tests/integration/swap.test.ts +1 -1
- package/src/tests/integration/transfer.test.ts +5 -5
- package/src/tests/integration/tree-creation.test.ts +1 -1
- package/src/tests/integration/wallet.test.ts +1 -0
- package/src/tests/isHermeticTest.ts +3 -24
- package/src/tests/{test-util.ts → test-utils.ts} +3 -7
- package/src/tests/wrapWithOtelSpan.test.ts +1 -1
- package/src/{address → utils}/address.ts +1 -1
- package/src/utils/crypto.ts +19 -9
- package/src/utils/index.ts +2 -0
- package/src/utils/network.ts +17 -0
- package/src/utils/secret-sharing.ts +1 -2
- package/src/utils/signing.ts +1 -1
- package/src/utils/token-transactions.ts +3 -3
- package/src/utils/unilateral-exit.ts +32 -0
- package/src/utils/xchain-address.ts +1 -1
- package/dist/BitcoinNetwork-TnABML0T.d.cts +0 -18
- package/dist/BitcoinNetwork-TnABML0T.d.ts +0 -18
- package/dist/LightningSendFeeEstimateInput-BgOhEAI-.d.cts +0 -10
- package/dist/LightningSendFeeEstimateInput-BgOhEAI-.d.ts +0 -10
- package/dist/address/index.cjs +0 -458
- package/dist/address/index.d.cts +0 -32
- package/dist/address/index.d.ts +0 -32
- package/dist/address/index.js +0 -17
- package/dist/chunk-5FUB65LX.js +0 -838
- package/dist/chunk-6264CGDM.js +0 -113
- package/dist/chunk-7V6N75CC.js +0 -24
- package/dist/chunk-C2S227QR.js +0 -2336
- package/dist/chunk-GSI4OLXZ.js +0 -117
- package/dist/chunk-GZ5IPPJ2.js +0 -170
- package/dist/chunk-HWJWKEIU.js +0 -75
- package/dist/chunk-KMUMFYFX.js +0 -137
- package/dist/chunk-L3EHBOUX.js +0 -0
- package/dist/chunk-NSJF5F5O.js +0 -325
- package/dist/chunk-NTFKFRQ2.js +0 -3146
- package/dist/chunk-PQN3C2MF.js +0 -1122
- package/dist/chunk-QNNSEJ4P.js +0 -232
- package/dist/chunk-R5PXJZQS.js +0 -277
- package/dist/chunk-VTUGIIWI.js +0 -0
- package/dist/chunk-YUPMXTCJ.js +0 -622
- package/dist/chunk-Z5HIAYFT.js +0 -84
- package/dist/index-B2AwKW5J.d.cts +0 -214
- package/dist/index-CJDi1HWc.d.ts +0 -214
- package/dist/network-BTJl-Sul.d.ts +0 -46
- package/dist/network-CqgsdUF2.d.cts +0 -46
- package/dist/services/config.cjs +0 -2354
- package/dist/services/config.d.cts +0 -42
- package/dist/services/config.d.ts +0 -42
- package/dist/services/config.js +0 -17
- package/dist/services/connection.cjs +0 -17691
- package/dist/services/connection.d.cts +0 -95
- package/dist/services/connection.d.ts +0 -95
- package/dist/services/connection.js +0 -11
- package/dist/services/index.d.cts +0 -21
- package/dist/services/index.d.ts +0 -21
- package/dist/services/index.js +0 -58
- package/dist/services/lrc-connection.cjs +0 -4713
- package/dist/services/lrc-connection.d.cts +0 -34
- package/dist/services/lrc-connection.d.ts +0 -34
- package/dist/services/lrc-connection.js +0 -11
- package/dist/services/token-transactions.cjs +0 -2877
- package/dist/services/token-transactions.d.cts +0 -75
- package/dist/services/token-transactions.d.ts +0 -75
- package/dist/services/token-transactions.js +0 -15
- package/dist/services/wallet-config.cjs +0 -340
- package/dist/services/wallet-config.d.cts +0 -56
- package/dist/services/wallet-config.d.ts +0 -56
- package/dist/services/wallet-config.js +0 -33
- package/dist/signer/signer.cjs +0 -2004
- package/dist/signer/signer.d.cts +0 -10
- package/dist/signer/signer.d.ts +0 -10
- package/dist/signer/signer.js +0 -24
- package/dist/signer-BocS_J6B.d.ts +0 -187
- package/dist/signer-DKS0AJkw.d.cts +0 -187
- package/dist/utils/index.cjs +0 -2947
- package/dist/utils/index.d.cts +0 -18
- package/dist/utils/index.d.ts +0 -18
- package/dist/utils/index.js +0 -157
- package/src/address/index.ts +0 -1
- 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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
262
|
+
const challengeResp = await sparkAuthnClient.get_challenge({
|
|
263
|
+
publicKey: identityPublicKey,
|
|
264
|
+
});
|
|
244
265
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
273
|
+
const challengeBytes = Challenge.encode(
|
|
274
|
+
challengeResp.protectedChallenge.challenge,
|
|
275
|
+
).finish();
|
|
276
|
+
const hash = sha256(challengeBytes);
|
|
256
277
|
|
|
257
|
-
|
|
258
|
-
|
|
278
|
+
const derSignatureBytes =
|
|
279
|
+
await this.config.signer.signMessageWithIdentityKey(hash);
|
|
259
280
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
|
321
|
+
reason: lastError?.message ?? "Unknown error",
|
|
275
322
|
},
|
|
276
|
-
|
|
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<
|
|
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:
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 (
|
|
1109
|
+
if (sumAvailableTokens(tokenOutputs) < tokenAmount) {
|
|
1110
1110
|
throw new ValidationError("Insufficient token amount", {
|
|
1111
1111
|
field: "tokenAmount",
|
|
1112
|
-
value:
|
|
1112
|
+
value: sumAvailableTokens(tokenOutputs),
|
|
1113
1113
|
expected: tokenAmount,
|
|
1114
1114
|
});
|
|
1115
1115
|
}
|
package/src/services/transfer.ts
CHANGED
|
@@ -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
|
|
166
|
+
return isHermeticTest ? "localhost:9999" : "unix:///tmp/frost_0.sock";
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
|
|
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: "
|
|
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
|
-
|
|
195
|
+
const LOCAL_WALLET_CONFIG: Required<ConfigOptions> = {
|
|
196
196
|
...BASE_CONFIG,
|
|
197
197
|
threshold: 3,
|
|
198
198
|
};
|
|
199
199
|
|
|
200
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
package/src/signer/signer.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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({
|
|
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
|
-
|
|
940
|
-
|
|
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
|
+
};
|