@buildonspark/spark-sdk 0.3.2 → 0.3.4
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 +15 -0
- package/dist/bare/index.cjs +953 -1567
- package/dist/bare/index.d.cts +132 -29
- package/dist/bare/index.d.ts +132 -29
- package/dist/bare/index.js +948 -1565
- package/dist/{chunk-MGCUXELA.js → chunk-IC4IUEOS.js} +931 -125
- package/dist/{chunk-MH7BMOLL.js → chunk-J2P3KTQP.js} +1 -1
- package/dist/{chunk-JDQKNT7G.js → chunk-KIQTO4FX.js} +403 -1808
- package/dist/{chunk-73GJOG5R.js → chunk-XWLR6G5C.js} +1 -1
- package/dist/{chunk-4KGN75J4.js → chunk-YH7MDVTT.js} +1 -1
- package/dist/{client-DrjQwET9.d.ts → client-DBZ43pJT.d.ts} +1 -1
- package/dist/{client-DUFejFfn.d.cts → client-DWml6sjL.d.cts} +1 -1
- package/dist/debug.cjs +957 -1569
- package/dist/debug.d.cts +8 -5
- package/dist/debug.d.ts +8 -5
- package/dist/debug.js +4 -4
- package/dist/graphql/objects/index.d.cts +3 -3
- package/dist/graphql/objects/index.d.ts +3 -3
- package/dist/index.cjs +913 -1528
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +9 -5
- package/dist/index.node.cjs +913 -1528
- package/dist/index.node.d.cts +6 -6
- package/dist/index.node.d.ts +6 -6
- package/dist/index.node.js +8 -4
- package/dist/{logging-IsaT3wjW.d.ts → logging-BUpzk4Z6.d.cts} +5 -5
- package/dist/{logging-Bt2FNHFR.d.cts → logging-Dt2ooQiP.d.ts} +5 -5
- package/dist/native/index.cjs +913 -1528
- package/dist/native/index.d.cts +133 -44
- package/dist/native/index.d.ts +133 -44
- package/dist/native/index.js +912 -1529
- package/dist/proto/spark.cjs +931 -125
- package/dist/proto/spark.d.cts +1 -1
- package/dist/proto/spark.d.ts +1 -1
- package/dist/proto/spark.js +17 -1
- package/dist/proto/spark_token.d.cts +1 -1
- package/dist/proto/spark_token.d.ts +1 -1
- package/dist/proto/spark_token.js +2 -2
- package/dist/{spark-CLz4-Ln8.d.cts → spark-DasxuVfm.d.cts} +150 -5
- package/dist/{spark-CLz4-Ln8.d.ts → spark-DasxuVfm.d.ts} +150 -5
- package/dist/{spark-wallet-V03V4Tgm.d.cts → spark-wallet-BoMIOPWW.d.cts} +15 -26
- package/dist/{spark-wallet-KI68-_jO.d.ts → spark-wallet-jlC0XN5f.d.ts} +15 -26
- package/dist/{spark-wallet.node-Cuvw8Kvq.d.ts → spark-wallet.node-07PksUHH.d.cts} +1 -1
- package/dist/{spark-wallet.node-BtkqW5vn.d.cts → spark-wallet.node-CdWkKMSq.d.ts} +1 -1
- package/dist/tests/test-utils.cjs +951 -147
- package/dist/tests/test-utils.d.cts +4 -4
- package/dist/tests/test-utils.d.ts +4 -4
- package/dist/tests/test-utils.js +5 -5
- package/dist/{token-transactions-5XxBewhV.d.ts → token-transactions-BDzCrQSk.d.cts} +5 -19
- package/dist/{token-transactions-fTnCtBpp.d.cts → token-transactions-DscJaJOE.d.ts} +5 -19
- package/dist/types/index.cjs +923 -125
- package/dist/types/index.d.cts +2 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +2 -2
- package/package.json +1 -1
- package/src/proto/spark.ts +1167 -103
- package/src/services/config.ts +0 -4
- package/src/services/token-transactions.ts +11 -703
- package/src/services/transfer.ts +2 -213
- package/src/services/wallet-config.ts +0 -2
- package/src/spark-wallet/proto-descriptors.ts +1 -1
- package/src/spark-wallet/spark-wallet.ts +47 -215
- package/src/spark_descriptors.pb +0 -0
- package/src/tests/address.test.ts +141 -0
- package/src/tests/integration/address.test.ts +4 -0
- package/src/tests/integration/lightning.test.ts +14 -9
- package/src/tests/integration/token-output.test.ts +0 -1
- package/src/tests/integration/transfer.test.ts +108 -2
- package/src/tests/token-hashing.test.ts +0 -247
- package/src/utils/address.ts +58 -35
- package/src/utils/token-hashing.ts +1 -420
- package/src/utils/token-transaction-validation.ts +0 -330
- package/src/utils/transaction.ts +8 -8
- package/src/tests/integration/swap.test.ts +0 -780
|
@@ -1,24 +1,15 @@
|
|
|
1
|
-
import { secp256k1 } from "@noble/curves/secp256k1";
|
|
2
1
|
import {
|
|
3
2
|
bytesToHex,
|
|
4
3
|
bytesToNumberBE,
|
|
5
4
|
numberToBytesBE,
|
|
6
5
|
} from "@noble/curves/utils";
|
|
7
6
|
import { hexToBytes } from "@noble/hashes/utils";
|
|
8
|
-
import {
|
|
9
|
-
InternalValidationError,
|
|
10
|
-
NetworkError,
|
|
11
|
-
ValidationError,
|
|
12
|
-
} from "../errors/types.js";
|
|
7
|
+
import { NetworkError, ValidationError } from "../errors/types.js";
|
|
13
8
|
import {
|
|
14
9
|
Direction,
|
|
15
|
-
OperatorSpecificOwnerSignature,
|
|
16
10
|
OperatorSpecificTokenTransactionSignablePayload,
|
|
17
11
|
OutputWithPreviousTransactionData,
|
|
18
|
-
QueryTokenTransactionsRequest as QueryTokenTransactionsRequestV0,
|
|
19
12
|
RevocationSecretWithIndex,
|
|
20
|
-
SignTokenTransactionResponse,
|
|
21
|
-
TokenTransaction as TokenTransactionV0,
|
|
22
13
|
} from "../proto/spark.js";
|
|
23
14
|
import {
|
|
24
15
|
InputTtxoSignaturesPerOperator,
|
|
@@ -39,20 +30,12 @@ import { collectResponses } from "../utils/response-validation.js";
|
|
|
39
30
|
import {
|
|
40
31
|
hashOperatorSpecificTokenTransactionSignablePayload,
|
|
41
32
|
hashTokenTransaction,
|
|
42
|
-
hashTokenTransactionV0,
|
|
43
33
|
} from "../utils/token-hashing.js";
|
|
44
34
|
import {
|
|
45
35
|
Bech32mTokenIdentifier,
|
|
46
36
|
decodeBech32mTokenIdentifier,
|
|
47
37
|
} from "../utils/token-identifier.js";
|
|
48
|
-
import {
|
|
49
|
-
KeyshareWithOperatorIndex,
|
|
50
|
-
recoverRevocationSecretFromKeyshares,
|
|
51
|
-
} from "../utils/token-keyshares.js";
|
|
52
|
-
import {
|
|
53
|
-
validateTokenTransaction,
|
|
54
|
-
validateTokenTransactionV0,
|
|
55
|
-
} from "../utils/token-transaction-validation.js";
|
|
38
|
+
import { validateTokenTransaction } from "../utils/token-transaction-validation.js";
|
|
56
39
|
import {
|
|
57
40
|
checkIfSelectedOutputsAreAvailable,
|
|
58
41
|
sumAvailableTokens,
|
|
@@ -177,18 +160,6 @@ export class TokenTransactionService {
|
|
|
177
160
|
this.config.getNetworkType(),
|
|
178
161
|
).tokenIdentifier;
|
|
179
162
|
|
|
180
|
-
// remove for full v0 deprecation
|
|
181
|
-
let tokenPublicKey: Uint8Array;
|
|
182
|
-
if (this.config.getTokenTransactionVersion() === "V0") {
|
|
183
|
-
const tokenClient = await this.connectionManager.createSparkTokenClient(
|
|
184
|
-
this.config.getCoordinatorAddress(),
|
|
185
|
-
);
|
|
186
|
-
const tokenMetadata = await tokenClient.query_token_metadata({
|
|
187
|
-
tokenIdentifiers: [rawTokenIdentifier],
|
|
188
|
-
});
|
|
189
|
-
tokenPublicKey = tokenMetadata.tokenMetadata[0]!.issuerPublicKey;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
163
|
let sparkInvoices: SparkAddressFormat[] = [];
|
|
193
164
|
|
|
194
165
|
const tokenOutputData = receiverOutputs.map((transfer) => {
|
|
@@ -201,10 +172,7 @@ export class TokenTransactionService {
|
|
|
201
172
|
sparkInvoices.push(transfer.receiverSparkAddress as SparkAddressFormat);
|
|
202
173
|
}
|
|
203
174
|
|
|
204
|
-
if (
|
|
205
|
-
this.config.getTokenTransactionVersion() !== "V0" &&
|
|
206
|
-
receiverAddress.sparkInvoiceFields
|
|
207
|
-
) {
|
|
175
|
+
if (receiverAddress.sparkInvoiceFields) {
|
|
208
176
|
return {
|
|
209
177
|
receiverPublicKey: hexToBytes(receiverAddress.identityPublicKey),
|
|
210
178
|
rawTokenIdentifier,
|
|
@@ -216,30 +184,16 @@ export class TokenTransactionService {
|
|
|
216
184
|
return {
|
|
217
185
|
receiverPublicKey: hexToBytes(receiverAddress.identityPublicKey),
|
|
218
186
|
rawTokenIdentifier,
|
|
219
|
-
tokenPublicKey, // Remove for full v0 deprecation
|
|
220
187
|
tokenAmount: transfer.tokenAmount,
|
|
221
188
|
};
|
|
222
189
|
});
|
|
223
190
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
tokenOutputData as Array<{
|
|
231
|
-
receiverPublicKey: Uint8Array;
|
|
232
|
-
tokenPublicKey: Uint8Array;
|
|
233
|
-
tokenAmount: bigint;
|
|
234
|
-
}>,
|
|
235
|
-
);
|
|
236
|
-
} else {
|
|
237
|
-
tokenTransaction = await this.constructTransferTokenTransaction(
|
|
238
|
-
outputsToUse,
|
|
239
|
-
tokenOutputData,
|
|
240
|
-
sparkInvoices,
|
|
241
|
-
);
|
|
242
|
-
}
|
|
191
|
+
const tokenTransaction = await this.constructTransferTokenTransaction(
|
|
192
|
+
outputsToUse,
|
|
193
|
+
tokenOutputData,
|
|
194
|
+
sparkInvoices,
|
|
195
|
+
);
|
|
196
|
+
|
|
243
197
|
const txId = await this.broadcastTokenTransaction(
|
|
244
198
|
tokenTransaction,
|
|
245
199
|
outputsToUse.map((output) => output.output!.ownerPublicKey),
|
|
@@ -249,60 +203,6 @@ export class TokenTransactionService {
|
|
|
249
203
|
return txId;
|
|
250
204
|
}
|
|
251
205
|
|
|
252
|
-
public async constructTransferTokenTransactionV0(
|
|
253
|
-
selectedOutputs: OutputWithPreviousTransactionData[],
|
|
254
|
-
tokenOutputData: Array<{
|
|
255
|
-
receiverPublicKey: Uint8Array;
|
|
256
|
-
tokenPublicKey: Uint8Array;
|
|
257
|
-
tokenAmount: bigint;
|
|
258
|
-
}>,
|
|
259
|
-
): Promise<TokenTransactionV0> {
|
|
260
|
-
// Ensure outputsToSpend are ordered by vout ascending so that the input indices
|
|
261
|
-
// used for owner signatures match the order expected by the SO, which sorts
|
|
262
|
-
// inputs by "prevTokenTransactionVout" before validating signatures.
|
|
263
|
-
selectedOutputs.sort(
|
|
264
|
-
(a, b) => a.previousTransactionVout - b.previousTransactionVout,
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
const availableTokenAmount = sumAvailableTokens(selectedOutputs);
|
|
268
|
-
const totalRequestedAmount = tokenOutputData.reduce(
|
|
269
|
-
(sum, output) => sum + output.tokenAmount,
|
|
270
|
-
0n,
|
|
271
|
-
);
|
|
272
|
-
|
|
273
|
-
const tokenOutputs = tokenOutputData.map((output) => ({
|
|
274
|
-
ownerPublicKey: output.receiverPublicKey,
|
|
275
|
-
tokenPublicKey: output.tokenPublicKey,
|
|
276
|
-
tokenAmount: numberToBytesBE(output.tokenAmount, 16),
|
|
277
|
-
}));
|
|
278
|
-
|
|
279
|
-
if (availableTokenAmount > totalRequestedAmount) {
|
|
280
|
-
const changeAmount = availableTokenAmount - totalRequestedAmount;
|
|
281
|
-
const firstTokenPublicKey = tokenOutputData[0]!!.tokenPublicKey;
|
|
282
|
-
|
|
283
|
-
tokenOutputs.push({
|
|
284
|
-
ownerPublicKey: await this.config.signer.getIdentityPublicKey(),
|
|
285
|
-
tokenPublicKey: firstTokenPublicKey,
|
|
286
|
-
tokenAmount: numberToBytesBE(changeAmount, 16),
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return {
|
|
291
|
-
network: this.config.getNetworkProto(),
|
|
292
|
-
tokenInputs: {
|
|
293
|
-
$case: "transferInput",
|
|
294
|
-
transferInput: {
|
|
295
|
-
outputsToSpend: selectedOutputs.map((output) => ({
|
|
296
|
-
prevTokenTransactionHash: output.previousTransactionHash,
|
|
297
|
-
prevTokenTransactionVout: output.previousTransactionVout,
|
|
298
|
-
})),
|
|
299
|
-
},
|
|
300
|
-
},
|
|
301
|
-
tokenOutputs,
|
|
302
|
-
sparkOperatorIdentityPublicKeys: this.collectOperatorIdentityPublicKeys(),
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
|
|
306
206
|
public async constructTransferTokenTransaction(
|
|
307
207
|
selectedOutputs: OutputWithPreviousTransactionData[],
|
|
308
208
|
tokenOutputData: Array<{
|
|
@@ -375,156 +275,12 @@ export class TokenTransactionService {
|
|
|
375
275
|
}
|
|
376
276
|
|
|
377
277
|
public async broadcastTokenTransaction(
|
|
378
|
-
tokenTransaction:
|
|
278
|
+
tokenTransaction: TokenTransaction,
|
|
379
279
|
outputsToSpendSigningPublicKeys?: Uint8Array[],
|
|
380
280
|
outputsToSpendCommitments?: Uint8Array[],
|
|
381
281
|
): Promise<string> {
|
|
382
282
|
const signingOperators = this.config.getSigningOperators();
|
|
383
|
-
if (!isTokenTransaction(tokenTransaction)) {
|
|
384
|
-
return this.broadcastTokenTransactionV0(
|
|
385
|
-
tokenTransaction,
|
|
386
|
-
signingOperators,
|
|
387
|
-
outputsToSpendSigningPublicKeys,
|
|
388
|
-
outputsToSpendCommitments,
|
|
389
|
-
);
|
|
390
|
-
} else {
|
|
391
|
-
return this.broadcastTokenTransactionV1(
|
|
392
|
-
tokenTransaction as TokenTransaction,
|
|
393
|
-
signingOperators,
|
|
394
|
-
outputsToSpendSigningPublicKeys,
|
|
395
|
-
outputsToSpendCommitments,
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
283
|
|
|
400
|
-
private async broadcastTokenTransactionV0(
|
|
401
|
-
tokenTransaction: TokenTransactionV0,
|
|
402
|
-
signingOperators: Record<string, SigningOperator>,
|
|
403
|
-
outputsToSpendSigningPublicKeys?: Uint8Array[],
|
|
404
|
-
outputsToSpendCommitments?: Uint8Array[],
|
|
405
|
-
): Promise<string> {
|
|
406
|
-
const { finalTokenTransaction, finalTokenTransactionHash, threshold } =
|
|
407
|
-
await this.startTokenTransactionV0(
|
|
408
|
-
tokenTransaction,
|
|
409
|
-
signingOperators,
|
|
410
|
-
outputsToSpendSigningPublicKeys,
|
|
411
|
-
outputsToSpendCommitments,
|
|
412
|
-
);
|
|
413
|
-
|
|
414
|
-
const { successfulSignatures } = await this.signTokenTransactionV0(
|
|
415
|
-
finalTokenTransaction,
|
|
416
|
-
finalTokenTransactionHash,
|
|
417
|
-
signingOperators,
|
|
418
|
-
);
|
|
419
|
-
|
|
420
|
-
if (finalTokenTransaction.tokenInputs!.$case === "transferInput") {
|
|
421
|
-
const outputsToSpend =
|
|
422
|
-
finalTokenTransaction.tokenInputs!.transferInput.outputsToSpend;
|
|
423
|
-
|
|
424
|
-
const errors: ValidationError[] = [];
|
|
425
|
-
const revocationSecrets: RevocationSecretWithIndex[] = [];
|
|
426
|
-
|
|
427
|
-
for (
|
|
428
|
-
let outputIndex = 0;
|
|
429
|
-
outputIndex < outputsToSpend.length;
|
|
430
|
-
outputIndex++
|
|
431
|
-
) {
|
|
432
|
-
// For each output, collect keyshares from all SOs that responded successfully
|
|
433
|
-
const outputKeyshares: KeyshareWithOperatorIndex[] =
|
|
434
|
-
successfulSignatures.map(({ identifier, response }) => ({
|
|
435
|
-
operatorIndex: parseInt(identifier, 16),
|
|
436
|
-
keyshare: response.revocationKeyshares[outputIndex]!,
|
|
437
|
-
}));
|
|
438
|
-
|
|
439
|
-
if (outputKeyshares.length < threshold) {
|
|
440
|
-
errors.push(
|
|
441
|
-
new ValidationError("Insufficient keyshares", {
|
|
442
|
-
field: "outputKeyshares",
|
|
443
|
-
value: outputKeyshares.length,
|
|
444
|
-
expected: threshold,
|
|
445
|
-
index: outputIndex,
|
|
446
|
-
}),
|
|
447
|
-
);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// Check for duplicate operator indices
|
|
451
|
-
const seenIndices = new Set<number>();
|
|
452
|
-
for (const { operatorIndex } of outputKeyshares) {
|
|
453
|
-
if (seenIndices.has(operatorIndex)) {
|
|
454
|
-
errors.push(
|
|
455
|
-
new ValidationError("Duplicate operator index", {
|
|
456
|
-
field: "outputKeyshares",
|
|
457
|
-
value: operatorIndex,
|
|
458
|
-
expected: "Unique operator index",
|
|
459
|
-
index: outputIndex,
|
|
460
|
-
}),
|
|
461
|
-
);
|
|
462
|
-
}
|
|
463
|
-
seenIndices.add(operatorIndex);
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
const revocationSecret = recoverRevocationSecretFromKeyshares(
|
|
467
|
-
outputKeyshares as KeyshareWithOperatorIndex[],
|
|
468
|
-
threshold,
|
|
469
|
-
);
|
|
470
|
-
const derivedRevocationCommitment = secp256k1.getPublicKey(
|
|
471
|
-
revocationSecret,
|
|
472
|
-
true,
|
|
473
|
-
);
|
|
474
|
-
|
|
475
|
-
if (
|
|
476
|
-
!outputsToSpendCommitments ||
|
|
477
|
-
!outputsToSpendCommitments[outputIndex] ||
|
|
478
|
-
!derivedRevocationCommitment.every(
|
|
479
|
-
(byte, i) => byte === outputsToSpendCommitments[outputIndex]![i],
|
|
480
|
-
)
|
|
481
|
-
) {
|
|
482
|
-
errors.push(
|
|
483
|
-
new InternalValidationError(
|
|
484
|
-
"Revocation commitment verification failed",
|
|
485
|
-
{
|
|
486
|
-
field: "revocationCommitment",
|
|
487
|
-
value: derivedRevocationCommitment,
|
|
488
|
-
expected: bytesToHex(outputsToSpendCommitments![outputIndex]!),
|
|
489
|
-
outputIndex: outputIndex,
|
|
490
|
-
},
|
|
491
|
-
),
|
|
492
|
-
);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
revocationSecrets.push({
|
|
496
|
-
inputIndex: outputIndex,
|
|
497
|
-
revocationSecret,
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
if (errors.length > 0) {
|
|
502
|
-
throw new ValidationError(
|
|
503
|
-
"Multiple validation errors occurred across outputs",
|
|
504
|
-
{
|
|
505
|
-
field: "outputValidation",
|
|
506
|
-
value: errors,
|
|
507
|
-
},
|
|
508
|
-
);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// Finalize the token transaction with the keyshares
|
|
512
|
-
await this.finalizeTokenTransaction(
|
|
513
|
-
finalTokenTransaction,
|
|
514
|
-
revocationSecrets,
|
|
515
|
-
threshold,
|
|
516
|
-
);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
return bytesToHex(finalTokenTransactionHash);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
private async broadcastTokenTransactionV1(
|
|
523
|
-
tokenTransaction: TokenTransaction,
|
|
524
|
-
signingOperators: Record<string, SigningOperator>,
|
|
525
|
-
outputsToSpendSigningPublicKeys?: Uint8Array[],
|
|
526
|
-
outputsToSpendCommitments?: Uint8Array[],
|
|
527
|
-
): Promise<string> {
|
|
528
284
|
const { finalTokenTransaction, finalTokenTransactionHash, threshold } =
|
|
529
285
|
await this.startTokenTransaction(
|
|
530
286
|
tokenTransaction,
|
|
@@ -542,123 +298,6 @@ export class TokenTransactionService {
|
|
|
542
298
|
return bytesToHex(finalTokenTransactionHash);
|
|
543
299
|
}
|
|
544
300
|
|
|
545
|
-
private async startTokenTransactionV0(
|
|
546
|
-
tokenTransaction: TokenTransactionV0,
|
|
547
|
-
signingOperators: Record<string, SigningOperator>,
|
|
548
|
-
outputsToSpendSigningPublicKeys?: Uint8Array[],
|
|
549
|
-
outputsToSpendCommitments?: Uint8Array[],
|
|
550
|
-
): Promise<{
|
|
551
|
-
finalTokenTransaction: TokenTransactionV0;
|
|
552
|
-
finalTokenTransactionHash: Uint8Array;
|
|
553
|
-
threshold: number;
|
|
554
|
-
}> {
|
|
555
|
-
const sparkClient = await this.connectionManager.createSparkClient(
|
|
556
|
-
this.config.getCoordinatorAddress(),
|
|
557
|
-
);
|
|
558
|
-
|
|
559
|
-
const partialTokenTransactionHash = hashTokenTransactionV0(
|
|
560
|
-
tokenTransaction,
|
|
561
|
-
true,
|
|
562
|
-
);
|
|
563
|
-
|
|
564
|
-
const ownerSignaturesWithIndex: SignatureWithIndex[] = [];
|
|
565
|
-
if (tokenTransaction.tokenInputs!.$case === "mintInput") {
|
|
566
|
-
const issuerPublicKey =
|
|
567
|
-
tokenTransaction.tokenInputs!.mintInput.issuerPublicKey;
|
|
568
|
-
if (!issuerPublicKey) {
|
|
569
|
-
throw new ValidationError("Invalid mint input", {
|
|
570
|
-
field: "issuerPublicKey",
|
|
571
|
-
value: null,
|
|
572
|
-
expected: "Non-null issuer public key",
|
|
573
|
-
});
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
const ownerSignature = await this.signMessageWithKey(
|
|
577
|
-
partialTokenTransactionHash,
|
|
578
|
-
issuerPublicKey,
|
|
579
|
-
);
|
|
580
|
-
|
|
581
|
-
ownerSignaturesWithIndex.push({
|
|
582
|
-
signature: ownerSignature,
|
|
583
|
-
inputIndex: 0,
|
|
584
|
-
});
|
|
585
|
-
} else if (tokenTransaction.tokenInputs!.$case === "transferInput") {
|
|
586
|
-
if (!outputsToSpendSigningPublicKeys || !outputsToSpendCommitments) {
|
|
587
|
-
throw new ValidationError("Invalid transfer input", {
|
|
588
|
-
field: "outputsToSpend",
|
|
589
|
-
value: {
|
|
590
|
-
signingPublicKeys: outputsToSpendSigningPublicKeys,
|
|
591
|
-
revocationPublicKeys: outputsToSpendCommitments,
|
|
592
|
-
},
|
|
593
|
-
expected: "Non-null signing and revocation public keys",
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
for (const [i, key] of outputsToSpendSigningPublicKeys.entries()) {
|
|
598
|
-
if (!key) {
|
|
599
|
-
throw new ValidationError("Invalid signing key", {
|
|
600
|
-
field: "outputsToSpendSigningPublicKeys",
|
|
601
|
-
value: i,
|
|
602
|
-
expected: "Non-null signing key",
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
|
-
const ownerSignature = await this.signMessageWithKey(
|
|
606
|
-
partialTokenTransactionHash,
|
|
607
|
-
key,
|
|
608
|
-
);
|
|
609
|
-
|
|
610
|
-
ownerSignaturesWithIndex.push({
|
|
611
|
-
signature: ownerSignature,
|
|
612
|
-
inputIndex: i,
|
|
613
|
-
});
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
const startResponse = await sparkClient.start_token_transaction(
|
|
618
|
-
{
|
|
619
|
-
identityPublicKey: await this.config.signer.getIdentityPublicKey(),
|
|
620
|
-
partialTokenTransaction: tokenTransaction,
|
|
621
|
-
tokenTransactionSignatures: {
|
|
622
|
-
ownerSignatures: ownerSignaturesWithIndex,
|
|
623
|
-
},
|
|
624
|
-
},
|
|
625
|
-
{
|
|
626
|
-
retry: true,
|
|
627
|
-
retryableStatuses: ["UNKNOWN", "UNAVAILABLE", "CANCELLED", "INTERNAL"],
|
|
628
|
-
retryMaxAttempts: 3,
|
|
629
|
-
} as SparkCallOptions,
|
|
630
|
-
);
|
|
631
|
-
|
|
632
|
-
if (!startResponse.finalTokenTransaction) {
|
|
633
|
-
throw new Error("Final token transaction missing in start response");
|
|
634
|
-
}
|
|
635
|
-
if (!startResponse.keyshareInfo) {
|
|
636
|
-
throw new Error("Keyshare info missing in start response");
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
validateTokenTransactionV0(
|
|
640
|
-
startResponse.finalTokenTransaction,
|
|
641
|
-
tokenTransaction,
|
|
642
|
-
signingOperators,
|
|
643
|
-
startResponse.keyshareInfo,
|
|
644
|
-
this.config.getExpectedWithdrawBondSats(),
|
|
645
|
-
this.config.getExpectedWithdrawRelativeBlockLocktime(),
|
|
646
|
-
this.config.getThreshold(),
|
|
647
|
-
);
|
|
648
|
-
|
|
649
|
-
const finalTokenTransaction = startResponse.finalTokenTransaction;
|
|
650
|
-
const finalTokenTransactionHash = hashTokenTransactionV0(
|
|
651
|
-
finalTokenTransaction,
|
|
652
|
-
false,
|
|
653
|
-
);
|
|
654
|
-
|
|
655
|
-
return {
|
|
656
|
-
finalTokenTransaction,
|
|
657
|
-
finalTokenTransactionHash,
|
|
658
|
-
threshold: startResponse.keyshareInfo!.threshold,
|
|
659
|
-
};
|
|
660
|
-
}
|
|
661
|
-
|
|
662
301
|
private async startTokenTransaction(
|
|
663
302
|
tokenTransaction: TokenTransaction,
|
|
664
303
|
signingOperators: Record<string, SigningOperator>,
|
|
@@ -804,134 +443,6 @@ export class TokenTransactionService {
|
|
|
804
443
|
};
|
|
805
444
|
}
|
|
806
445
|
|
|
807
|
-
private async signTokenTransactionV0(
|
|
808
|
-
finalTokenTransaction: TokenTransactionV0,
|
|
809
|
-
finalTokenTransactionHash: Uint8Array,
|
|
810
|
-
signingOperators: Record<string, SigningOperator>,
|
|
811
|
-
): Promise<{
|
|
812
|
-
successfulSignatures: {
|
|
813
|
-
index: number;
|
|
814
|
-
identifier: string;
|
|
815
|
-
response: SignTokenTransactionResponse;
|
|
816
|
-
}[];
|
|
817
|
-
}> {
|
|
818
|
-
// Submit sign_token_transaction to all SOs in parallel and track their indices
|
|
819
|
-
const soSignatures = await Promise.allSettled(
|
|
820
|
-
Object.entries(signingOperators).map(
|
|
821
|
-
async ([identifier, operator], index) => {
|
|
822
|
-
const internalSparkClient =
|
|
823
|
-
await this.connectionManager.createSparkClient(operator.address);
|
|
824
|
-
const identityPublicKey =
|
|
825
|
-
await this.config.signer.getIdentityPublicKey();
|
|
826
|
-
|
|
827
|
-
// Create operator-specific payload with operator's identity public key
|
|
828
|
-
const payload: OperatorSpecificTokenTransactionSignablePayload = {
|
|
829
|
-
finalTokenTransactionHash: finalTokenTransactionHash,
|
|
830
|
-
operatorIdentityPublicKey: hexToBytes(operator.identityPublicKey),
|
|
831
|
-
};
|
|
832
|
-
|
|
833
|
-
const payloadHash =
|
|
834
|
-
await hashOperatorSpecificTokenTransactionSignablePayload(payload);
|
|
835
|
-
|
|
836
|
-
let operatorSpecificSignatures: OperatorSpecificOwnerSignature[] = [];
|
|
837
|
-
if (finalTokenTransaction.tokenInputs!.$case === "mintInput") {
|
|
838
|
-
const issuerPublicKey =
|
|
839
|
-
finalTokenTransaction.tokenInputs!.mintInput.issuerPublicKey;
|
|
840
|
-
if (!issuerPublicKey) {
|
|
841
|
-
throw new ValidationError("Invalid mint input", {
|
|
842
|
-
field: "issuerPublicKey",
|
|
843
|
-
value: null,
|
|
844
|
-
expected: "Non-null issuer public key",
|
|
845
|
-
});
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
const ownerSignature = await this.signMessageWithKey(
|
|
849
|
-
payloadHash,
|
|
850
|
-
issuerPublicKey,
|
|
851
|
-
);
|
|
852
|
-
|
|
853
|
-
operatorSpecificSignatures.push({
|
|
854
|
-
ownerSignature: {
|
|
855
|
-
signature: ownerSignature,
|
|
856
|
-
inputIndex: 0,
|
|
857
|
-
},
|
|
858
|
-
payload: payload,
|
|
859
|
-
});
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
if (finalTokenTransaction.tokenInputs!.$case === "transferInput") {
|
|
863
|
-
const transferInput =
|
|
864
|
-
finalTokenTransaction.tokenInputs!.transferInput;
|
|
865
|
-
for (let i = 0; i < transferInput.outputsToSpend.length; i++) {
|
|
866
|
-
let ownerSignature: Uint8Array;
|
|
867
|
-
if (this.config.getTokenSignatures() === "SCHNORR") {
|
|
868
|
-
ownerSignature =
|
|
869
|
-
await this.config.signer.signSchnorrWithIdentityKey(
|
|
870
|
-
payloadHash,
|
|
871
|
-
);
|
|
872
|
-
} else {
|
|
873
|
-
ownerSignature =
|
|
874
|
-
await this.config.signer.signMessageWithIdentityKey(
|
|
875
|
-
payloadHash,
|
|
876
|
-
);
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
operatorSpecificSignatures.push({
|
|
880
|
-
ownerSignature: {
|
|
881
|
-
signature: ownerSignature,
|
|
882
|
-
inputIndex: i,
|
|
883
|
-
},
|
|
884
|
-
payload,
|
|
885
|
-
});
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
try {
|
|
890
|
-
const response = await internalSparkClient.sign_token_transaction(
|
|
891
|
-
{
|
|
892
|
-
finalTokenTransaction,
|
|
893
|
-
operatorSpecificSignatures,
|
|
894
|
-
identityPublicKey,
|
|
895
|
-
},
|
|
896
|
-
{
|
|
897
|
-
retry: true,
|
|
898
|
-
retryableStatuses: [
|
|
899
|
-
"UNKNOWN",
|
|
900
|
-
"UNAVAILABLE",
|
|
901
|
-
"CANCELLED",
|
|
902
|
-
"INTERNAL",
|
|
903
|
-
],
|
|
904
|
-
retryMaxAttempts: 3,
|
|
905
|
-
} as SparkCallOptions,
|
|
906
|
-
);
|
|
907
|
-
|
|
908
|
-
return {
|
|
909
|
-
index,
|
|
910
|
-
identifier,
|
|
911
|
-
response,
|
|
912
|
-
};
|
|
913
|
-
} catch (error) {
|
|
914
|
-
throw new NetworkError(
|
|
915
|
-
"Failed to sign token transaction",
|
|
916
|
-
{
|
|
917
|
-
operation: "sign_token_transaction",
|
|
918
|
-
errorCount: 1,
|
|
919
|
-
errors: error instanceof Error ? error.message : String(error),
|
|
920
|
-
},
|
|
921
|
-
error as Error,
|
|
922
|
-
);
|
|
923
|
-
}
|
|
924
|
-
},
|
|
925
|
-
),
|
|
926
|
-
);
|
|
927
|
-
|
|
928
|
-
const successfulSignatures = collectResponses(soSignatures);
|
|
929
|
-
|
|
930
|
-
return {
|
|
931
|
-
successfulSignatures,
|
|
932
|
-
};
|
|
933
|
-
}
|
|
934
|
-
|
|
935
446
|
private async signTokenTransaction(
|
|
936
447
|
finalTokenTransaction: TokenTransaction,
|
|
937
448
|
finalTokenTransactionHash: Uint8Array,
|
|
@@ -1017,67 +528,6 @@ export class TokenTransactionService {
|
|
|
1017
528
|
}
|
|
1018
529
|
}
|
|
1019
530
|
|
|
1020
|
-
if (this.config.getTokenTransactionVersion() === "V0") {
|
|
1021
|
-
return this.fetchOwnedTokenOutputsV0(params);
|
|
1022
|
-
} else {
|
|
1023
|
-
return this.fetchOwnedTokenOutputsV1(params);
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
public async queryTokenTransactions(
|
|
1028
|
-
params: QueryTokenTransactionsParams,
|
|
1029
|
-
): Promise<QueryTokenTransactionsResponse> {
|
|
1030
|
-
if (this.config.getTokenTransactionVersion() === "V0") {
|
|
1031
|
-
return this.queryTokenTransactionsV0(params);
|
|
1032
|
-
} else {
|
|
1033
|
-
return this.queryTokenTransactionsV1(params);
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
private async fetchOwnedTokenOutputsV0(
|
|
1038
|
-
params: FetchOwnedTokenOutputsParams,
|
|
1039
|
-
): Promise<OutputWithPreviousTransactionData[]> {
|
|
1040
|
-
const {
|
|
1041
|
-
ownerPublicKeys,
|
|
1042
|
-
issuerPublicKeys: tokenPublicKeys = [],
|
|
1043
|
-
tokenIdentifiers = [],
|
|
1044
|
-
} = params;
|
|
1045
|
-
|
|
1046
|
-
const sparkClient = await this.connectionManager.createSparkClient(
|
|
1047
|
-
this.config.getCoordinatorAddress(),
|
|
1048
|
-
);
|
|
1049
|
-
|
|
1050
|
-
try {
|
|
1051
|
-
const result = await sparkClient.query_token_outputs({
|
|
1052
|
-
ownerPublicKeys,
|
|
1053
|
-
tokenPublicKeys,
|
|
1054
|
-
tokenIdentifiers,
|
|
1055
|
-
network: this.config.getNetworkProto(),
|
|
1056
|
-
});
|
|
1057
|
-
|
|
1058
|
-
return result.outputsWithPreviousTransactionData;
|
|
1059
|
-
} catch (error) {
|
|
1060
|
-
throw new NetworkError(
|
|
1061
|
-
"Failed to fetch owned token outputs",
|
|
1062
|
-
{
|
|
1063
|
-
operation: "spark.query_token_outputs",
|
|
1064
|
-
errorCount: 1,
|
|
1065
|
-
errors: error instanceof Error ? error.message : String(error),
|
|
1066
|
-
},
|
|
1067
|
-
error as Error,
|
|
1068
|
-
);
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
private async fetchOwnedTokenOutputsV1(
|
|
1073
|
-
params: FetchOwnedTokenOutputsParams,
|
|
1074
|
-
): Promise<OutputWithPreviousTransactionData[]> {
|
|
1075
|
-
const {
|
|
1076
|
-
ownerPublicKeys,
|
|
1077
|
-
issuerPublicKeys = [],
|
|
1078
|
-
tokenIdentifiers = [],
|
|
1079
|
-
} = params;
|
|
1080
|
-
|
|
1081
531
|
const tokenClient = await this.connectionManager.createSparkTokenClient(
|
|
1082
532
|
this.config.getCoordinatorAddress(),
|
|
1083
533
|
);
|
|
@@ -1124,87 +574,7 @@ export class TokenTransactionService {
|
|
|
1124
574
|
}
|
|
1125
575
|
}
|
|
1126
576
|
|
|
1127
|
-
|
|
1128
|
-
params: QueryTokenTransactionsParams,
|
|
1129
|
-
): Promise<QueryTokenTransactionsResponse> {
|
|
1130
|
-
const {
|
|
1131
|
-
ownerPublicKeys,
|
|
1132
|
-
issuerPublicKeys,
|
|
1133
|
-
tokenTransactionHashes,
|
|
1134
|
-
tokenIdentifiers,
|
|
1135
|
-
outputIds,
|
|
1136
|
-
pageSize,
|
|
1137
|
-
offset,
|
|
1138
|
-
} = params;
|
|
1139
|
-
|
|
1140
|
-
const sparkClient = await this.connectionManager.createSparkClient(
|
|
1141
|
-
this.config.getCoordinatorAddress(),
|
|
1142
|
-
);
|
|
1143
|
-
|
|
1144
|
-
let queryParams: QueryTokenTransactionsRequestV0 = {
|
|
1145
|
-
tokenPublicKeys: issuerPublicKeys?.map(hexToBytes)!,
|
|
1146
|
-
ownerPublicKeys: ownerPublicKeys?.map(hexToBytes)!,
|
|
1147
|
-
tokenIdentifiers: tokenIdentifiers?.map((identifier) => {
|
|
1148
|
-
const { tokenIdentifier } = decodeBech32mTokenIdentifier(
|
|
1149
|
-
identifier as Bech32mTokenIdentifier,
|
|
1150
|
-
this.config.getNetworkType(),
|
|
1151
|
-
);
|
|
1152
|
-
return tokenIdentifier;
|
|
1153
|
-
})!,
|
|
1154
|
-
tokenTransactionHashes: tokenTransactionHashes?.map(hexToBytes)!,
|
|
1155
|
-
outputIds: outputIds || [],
|
|
1156
|
-
limit: pageSize!,
|
|
1157
|
-
offset: offset!,
|
|
1158
|
-
};
|
|
1159
|
-
|
|
1160
|
-
try {
|
|
1161
|
-
const response = await sparkClient.query_token_transactions(queryParams);
|
|
1162
|
-
return {
|
|
1163
|
-
tokenTransactionsWithStatus: response.tokenTransactionsWithStatus.map(
|
|
1164
|
-
(tx) => {
|
|
1165
|
-
// Convert V0 structure to V1 structure
|
|
1166
|
-
const v1TokenTransaction: TokenTransaction = {
|
|
1167
|
-
version: 1,
|
|
1168
|
-
network: tx.tokenTransaction!.network,
|
|
1169
|
-
tokenInputs: tx.tokenTransaction!.tokenInputs,
|
|
1170
|
-
tokenOutputs: tx.tokenTransaction!.tokenOutputs!,
|
|
1171
|
-
sparkOperatorIdentityPublicKeys:
|
|
1172
|
-
tx.tokenTransaction!.sparkOperatorIdentityPublicKeys!,
|
|
1173
|
-
expiryTime: undefined, // V0 doesn't have expiry time
|
|
1174
|
-
clientCreatedTimestamp:
|
|
1175
|
-
tx.tokenTransaction?.tokenInputs?.$case === "mintInput"
|
|
1176
|
-
? new Date(
|
|
1177
|
-
tx.tokenTransaction.tokenInputs.mintInput
|
|
1178
|
-
.issuerProvidedTimestamp * 1000,
|
|
1179
|
-
)
|
|
1180
|
-
: new Date(),
|
|
1181
|
-
invoiceAttachments: [],
|
|
1182
|
-
};
|
|
1183
|
-
|
|
1184
|
-
return {
|
|
1185
|
-
tokenTransaction: v1TokenTransaction,
|
|
1186
|
-
status: tx.status,
|
|
1187
|
-
confirmationMetadata: tx.confirmationMetadata,
|
|
1188
|
-
tokenTransactionHash: tx.tokenTransactionHash,
|
|
1189
|
-
};
|
|
1190
|
-
},
|
|
1191
|
-
),
|
|
1192
|
-
offset: response.offset,
|
|
1193
|
-
};
|
|
1194
|
-
} catch (error) {
|
|
1195
|
-
throw new NetworkError(
|
|
1196
|
-
"Failed to query token transactions",
|
|
1197
|
-
{
|
|
1198
|
-
operation: "spark.query_token_transactions",
|
|
1199
|
-
errorCount: 1,
|
|
1200
|
-
errors: error instanceof Error ? error.message : String(error),
|
|
1201
|
-
},
|
|
1202
|
-
error as Error,
|
|
1203
|
-
);
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
private async queryTokenTransactionsV1(
|
|
577
|
+
public async queryTokenTransactions(
|
|
1208
578
|
params: QueryTokenTransactionsParams,
|
|
1209
579
|
): Promise<QueryTokenTransactionsResponse> {
|
|
1210
580
|
const {
|
|
@@ -1354,62 +724,6 @@ export class TokenTransactionService {
|
|
|
1354
724
|
}
|
|
1355
725
|
}
|
|
1356
726
|
|
|
1357
|
-
private async finalizeTokenTransaction(
|
|
1358
|
-
finalTokenTransaction: TokenTransactionV0,
|
|
1359
|
-
revocationSecrets: RevocationSecretWithIndex[],
|
|
1360
|
-
threshold: number,
|
|
1361
|
-
): Promise<TokenTransactionV0> {
|
|
1362
|
-
const signingOperators = this.config.getSigningOperators();
|
|
1363
|
-
// Submit finalize_token_transaction to all SOs in parallel
|
|
1364
|
-
const soResponses = await Promise.allSettled(
|
|
1365
|
-
Object.entries(signingOperators).map(async ([identifier, operator]) => {
|
|
1366
|
-
const internalSparkClient =
|
|
1367
|
-
await this.connectionManager.createSparkClient(operator.address);
|
|
1368
|
-
const identityPublicKey =
|
|
1369
|
-
await this.config.signer.getIdentityPublicKey();
|
|
1370
|
-
|
|
1371
|
-
try {
|
|
1372
|
-
const response = await internalSparkClient.finalize_token_transaction(
|
|
1373
|
-
{
|
|
1374
|
-
finalTokenTransaction,
|
|
1375
|
-
revocationSecrets,
|
|
1376
|
-
identityPublicKey,
|
|
1377
|
-
},
|
|
1378
|
-
{
|
|
1379
|
-
retry: true,
|
|
1380
|
-
retryableStatuses: [
|
|
1381
|
-
"UNKNOWN",
|
|
1382
|
-
"UNAVAILABLE",
|
|
1383
|
-
"CANCELLED",
|
|
1384
|
-
"INTERNAL",
|
|
1385
|
-
],
|
|
1386
|
-
retryMaxAttempts: 3,
|
|
1387
|
-
} as SparkCallOptions,
|
|
1388
|
-
);
|
|
1389
|
-
|
|
1390
|
-
return {
|
|
1391
|
-
identifier,
|
|
1392
|
-
response,
|
|
1393
|
-
};
|
|
1394
|
-
} catch (error) {
|
|
1395
|
-
throw new NetworkError(
|
|
1396
|
-
"Failed to finalize token transaction",
|
|
1397
|
-
{
|
|
1398
|
-
operation: "finalize_token_transaction",
|
|
1399
|
-
errorCount: 1,
|
|
1400
|
-
errors: error instanceof Error ? error.message : String(error),
|
|
1401
|
-
},
|
|
1402
|
-
error as Error,
|
|
1403
|
-
);
|
|
1404
|
-
}
|
|
1405
|
-
}),
|
|
1406
|
-
);
|
|
1407
|
-
|
|
1408
|
-
collectResponses(soResponses);
|
|
1409
|
-
|
|
1410
|
-
return finalTokenTransaction;
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
727
|
private async createSignaturesForOperators(
|
|
1414
728
|
finalTokenTransaction: TokenTransaction,
|
|
1415
729
|
finalTokenTransactionHash: Uint8Array,
|
|
@@ -1514,9 +828,3 @@ export class TokenTransactionService {
|
|
|
1514
828
|
return inputTtxoSignaturesPerOperator;
|
|
1515
829
|
}
|
|
1516
830
|
}
|
|
1517
|
-
|
|
1518
|
-
function isTokenTransaction(
|
|
1519
|
-
tokenTransaction: TokenTransactionV0 | TokenTransaction,
|
|
1520
|
-
): tokenTransaction is TokenTransaction {
|
|
1521
|
-
return "version" in tokenTransaction && "expiryTime" in tokenTransaction;
|
|
1522
|
-
}
|