@buildonspark/spark-sdk 0.2.7 → 0.2.9
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 +17 -0
- package/dist/bare/index.cjs +1683 -1901
- package/dist/bare/index.d.cts +242 -292
- package/dist/bare/index.d.ts +242 -292
- package/dist/bare/index.js +1604 -1829
- package/dist/{chunk-7LY7PJQL.js → chunk-23BBEC25.js} +14 -5
- package/dist/{chunk-R5VUHUJR.js → chunk-5Y7YILMA.js} +4153 -3728
- package/dist/{chunk-GIDAHHDB.js → chunk-6CMNEDBK.js} +217 -9
- package/dist/{chunk-J24LM4RO.js → chunk-76SYPHOC.js} +1 -1
- package/dist/{chunk-2HD3USKS.js → chunk-A5M55UR3.js} +0 -24
- package/dist/{client-BmnZ1xDg.d.cts → client-B9CAWKWz.d.cts} +1 -1
- package/dist/{client-DmjOifnt.d.ts → client-Dd3QnxQu.d.ts} +1 -1
- package/dist/debug.cjs +1680 -1957
- package/dist/debug.d.cts +13 -8
- package/dist/debug.d.ts +13 -8
- package/dist/debug.js +6 -8
- package/dist/graphql/objects/index.d.cts +3 -3
- package/dist/graphql/objects/index.d.ts +3 -3
- package/dist/index.cjs +1729 -1948
- package/dist/index.d.cts +18 -6
- package/dist/index.d.ts +18 -6
- package/dist/index.js +17 -8
- package/dist/index.node.cjs +1729 -1948
- package/dist/index.node.d.cts +7 -6
- package/dist/index.node.d.ts +7 -6
- package/dist/index.node.js +22 -6
- package/dist/native/index.cjs +1723 -1949
- package/dist/native/index.d.cts +80 -125
- package/dist/native/index.d.ts +80 -125
- package/dist/native/index.js +1652 -1884
- package/dist/proto/spark.cjs +0 -24
- package/dist/proto/spark.d.cts +1 -1
- package/dist/proto/spark.d.ts +1 -1
- package/dist/proto/spark.js +1 -1
- package/dist/proto/spark_token.cjs +221 -8
- package/dist/proto/spark_token.d.cts +25 -2
- package/dist/proto/spark_token.d.ts +25 -2
- package/dist/proto/spark_token.js +12 -2
- package/dist/{spark-B305mDNB.d.cts → spark-CtGJPkx4.d.cts} +3 -31
- package/dist/{spark-B305mDNB.d.ts → spark-CtGJPkx4.d.ts} +3 -31
- package/dist/{spark-wallet-BdwARy70.d.cts → spark-wallet-Cp3yv6cK.d.ts} +40 -31
- package/dist/{spark-wallet-enp968Uc.d.ts → spark-wallet-yc2KhsVY.d.cts} +40 -31
- package/dist/{spark-wallet.node-CtpJlYBs.d.cts → spark-wallet.node-D0Qw5Wb4.d.cts} +1 -1
- package/dist/{spark-wallet.node-DqWcsNb6.d.ts → spark-wallet.node-D4IovOHu.d.ts} +1 -1
- package/dist/tests/test-utils.cjs +483 -1120
- package/dist/tests/test-utils.d.cts +9 -5
- package/dist/tests/test-utils.d.ts +9 -5
- package/dist/tests/test-utils.js +5 -6
- package/dist/{token-transactions-3-pVToE0.d.cts → token-transactions-0nmR9mQO.d.ts} +17 -12
- package/dist/{token-transactions-84Hp0hGz.d.ts → token-transactions-CwhlOgIP.d.cts} +17 -12
- package/dist/types/index.cjs +0 -24
- package/dist/types/index.d.cts +2 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +2 -2
- package/dist/{xchain-address-BtuJEbzG.d.cts → xchain-address-BPwpnmuY.d.ts} +9 -3
- package/dist/{xchain-address-Q1BrcwID.d.ts → xchain-address-CNQEwLjR.d.cts} +9 -3
- package/package.json +1 -1
- package/src/constants.ts +7 -1
- package/src/debug.ts +1 -1
- package/src/proto/spark.ts +2 -48
- package/src/proto/spark_token.ts +255 -7
- package/src/services/token-transactions.ts +92 -44
- package/src/services/transfer.ts +20 -17
- package/src/services/wallet-config.ts +2 -0
- package/src/signer/signer.react-native.ts +0 -2
- package/src/spark-wallet/spark-wallet.browser.ts +9 -8
- package/src/spark-wallet/spark-wallet.node.ts +8 -4
- package/src/spark-wallet/spark-wallet.ts +427 -229
- package/src/tests/address.test.ts +87 -1
- package/src/tests/integration/retry.test.ts +78 -0
- package/src/tests/integration/ssp/static-deposit-validation.test.ts +1 -1
- package/src/tests/integration/transfer.test.ts +285 -1
- package/src/tests/integration/wallet.test.ts +160 -0
- package/src/tests/{tokens.test.ts → token-hashing.test.ts} +150 -162
- package/src/tests/token-outputs.test.ts +194 -0
- package/src/tests/utils/spark-testing-wallet.ts +16 -8
- package/src/utils/address.ts +152 -11
- package/src/utils/invoice-hashing.test.ts +235 -0
- package/src/utils/invoice-hashing.ts +227 -0
- package/src/utils/mempool.ts +6 -0
- package/src/utils/retry.ts +116 -0
- package/src/utils/token-hashing.ts +566 -0
- package/src/utils/token-transactions.ts +9 -5
- package/dist/chunk-7N6R7G3E.js +0 -7
- package/dist/spark-wallet.browser-BYlprQpX.d.ts +0 -12
- package/dist/spark-wallet.browser-CVI2Ss3u.d.cts +0 -12
- package/src/services/tree-creation.ts +0 -893
- package/src/tests/integration/tree-creation.test.ts +0 -46
|
@@ -2,6 +2,7 @@ import { numberToBytesBE } from "@noble/curves/abstract/utils";
|
|
|
2
2
|
import {
|
|
3
3
|
hashTokenTransactionV0,
|
|
4
4
|
hashTokenTransactionV1,
|
|
5
|
+
hashTokenTransactionV2,
|
|
5
6
|
} from "../utils/token-hashing.js";
|
|
6
7
|
import { Network, OutputWithPreviousTransactionData } from "../proto/spark.js";
|
|
7
8
|
import { TokenTransactionService } from "../services/token-transactions.js";
|
|
@@ -30,6 +31,17 @@ const TEST_OPERATOR_PUB_KEY = new Uint8Array([
|
|
|
30
31
|
63, 99, 54, 137, 226, 7, 224, 163, 122, 93, 248, 42, 159, 173, 46,
|
|
31
32
|
]);
|
|
32
33
|
|
|
34
|
+
const TEST_INVOICE_ATTACHMENTS = [
|
|
35
|
+
{
|
|
36
|
+
sparkInvoice:
|
|
37
|
+
"sprt1pgssypkrjhrpzt2hw0ggrmndanmm035ley75nxu3gejaju4wx9nq86lwzfjqsqgjzqqe3zul2fm8a24y576t0ne2ehup5fg2yz4r6hxlhatyu9kpw09s2fk36ta5j0k85qascf6snpuy4sp0rp4ezyspvs4qgmt9d4hnyggzqmpet3s394th85ypaek7eaahc60uj02fnwg5vewew2hrzesra0hqflc0vn",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
sparkInvoice:
|
|
41
|
+
"sprt1pgssypkrjhrpzt2hw0ggrmndanmm035ley75nxu3gejaju4wx9nq86lwzf5ssqgjzqqe3zulcs6h42v0kqkdsv9utxyp5fs2yz4r6hxlhatyu9kpw09s2fk36ta5j0k85qascf6snpuy4sp0rp4ezyszq86z5zryd9nxvmt9d4hnyggzqmpet3s394th85ypaek7eaahc60uj02fnwg5vewew2hrzesra0hql7r5ne",
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
|
|
33
45
|
const TEST_LEAF_ID = "db1a4e48-0fc5-4f6c-8a80-d9d6c561a436";
|
|
34
46
|
const TEST_BOND_SATS = 10000;
|
|
35
47
|
const TEST_LOCKTIME = 100;
|
|
@@ -217,6 +229,7 @@ describe("hash token transaction", () => {
|
|
|
217
229
|
network: Network.REGTEST,
|
|
218
230
|
expiryTime: new Date(TEST_EXPIRY_TIME),
|
|
219
231
|
clientCreatedTimestamp: new Date(TEST_CLIENT_TIMESTAMP),
|
|
232
|
+
invoiceAttachments: [],
|
|
220
233
|
};
|
|
221
234
|
|
|
222
235
|
const hash = hashTokenTransactionV1(tokenTransaction, false);
|
|
@@ -246,6 +259,7 @@ describe("hash token transaction", () => {
|
|
|
246
259
|
network: Network.REGTEST,
|
|
247
260
|
expiryTime: new Date(TEST_EXPIRY_TIME),
|
|
248
261
|
clientCreatedTimestamp: new Date(TEST_CLIENT_TIMESTAMP),
|
|
262
|
+
invoiceAttachments: [],
|
|
249
263
|
};
|
|
250
264
|
|
|
251
265
|
const hash = hashTokenTransactionV1(tokenTransaction, false);
|
|
@@ -285,6 +299,7 @@ describe("hash token transaction", () => {
|
|
|
285
299
|
network: Network.REGTEST,
|
|
286
300
|
expiryTime: new Date(TEST_EXPIRY_TIME),
|
|
287
301
|
clientCreatedTimestamp: new Date(TEST_CLIENT_TIMESTAMP),
|
|
302
|
+
invoiceAttachments: [],
|
|
288
303
|
};
|
|
289
304
|
|
|
290
305
|
const hash = hashTokenTransactionV1(tokenTransaction, false);
|
|
@@ -295,177 +310,150 @@ describe("hash token transaction", () => {
|
|
|
295
310
|
35,
|
|
296
311
|
]);
|
|
297
312
|
});
|
|
298
|
-
});
|
|
299
313
|
|
|
300
|
-
|
|
301
|
-
|
|
314
|
+
it("should produce the exact same hash for mint v2", () => {
|
|
315
|
+
const tokenTransaction = {
|
|
316
|
+
version: 2,
|
|
317
|
+
tokenInputs: {
|
|
318
|
+
$case: "mintInput" as const,
|
|
319
|
+
mintInput: {
|
|
320
|
+
issuerPublicKey: TEST_TOKEN_PUBLIC_KEY,
|
|
321
|
+
tokenIdentifier: TEST_TOKEN_IDENTIFIER,
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
tokenOutputs: [
|
|
325
|
+
{
|
|
326
|
+
id: TEST_LEAF_ID,
|
|
327
|
+
ownerPublicKey: TEST_IDENTITY_PUB_KEY,
|
|
328
|
+
withdrawBondSats: TEST_WITHDRAW_BOND_SATS,
|
|
329
|
+
withdrawRelativeBlockLocktime: TEST_WITHDRAW_RELATIVE_BLOCK_LOCKTIME,
|
|
330
|
+
tokenPublicKey: TEST_TOKEN_PUBLIC_KEY,
|
|
331
|
+
tokenAmount: numberToBytesBE(TEST_TOKEN_AMOUNT, 16),
|
|
332
|
+
revocationCommitment: TEST_REVOCATION_PUB_KEY,
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
sparkOperatorIdentityPublicKeys: [TEST_OPERATOR_PUB_KEY],
|
|
336
|
+
network: Network.REGTEST,
|
|
337
|
+
expiryTime: new Date(TEST_EXPIRY_TIME),
|
|
338
|
+
clientCreatedTimestamp: new Date(TEST_CLIENT_TIMESTAMP),
|
|
339
|
+
invoiceAttachments: [],
|
|
340
|
+
};
|
|
302
341
|
|
|
303
|
-
|
|
304
|
-
const mockConfig = {} as WalletConfigService;
|
|
305
|
-
const mockConnectionManager = {} as ConnectionManager;
|
|
306
|
-
tokenTransactionService = new TokenTransactionService(
|
|
307
|
-
mockConfig,
|
|
308
|
-
mockConnectionManager,
|
|
309
|
-
);
|
|
310
|
-
});
|
|
342
|
+
const hash = hashTokenTransactionV1(tokenTransaction, false);
|
|
311
343
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
ownerPublicKey: Uint8Array = new Uint8Array(32).fill(2),
|
|
317
|
-
): OutputWithPreviousTransactionData => ({
|
|
318
|
-
output: {
|
|
319
|
-
id,
|
|
320
|
-
ownerPublicKey,
|
|
321
|
-
tokenPublicKey,
|
|
322
|
-
tokenAmount: numberToBytesBE(tokenAmount, 16),
|
|
323
|
-
revocationCommitment: new Uint8Array(32).fill(3),
|
|
324
|
-
},
|
|
325
|
-
previousTransactionHash: new Uint8Array(32).fill(4),
|
|
326
|
-
previousTransactionVout: 0,
|
|
344
|
+
expect(Array.from(hash)).toEqual([
|
|
345
|
+
2, 4, 36, 141, 246, 170, 160, 204, 181, 102, 122, 220, 56, 182, 138, 153,
|
|
346
|
+
199, 216, 80, 3, 35, 2, 146, 139, 209, 31, 195, 129, 121, 120, 236, 126,
|
|
347
|
+
]);
|
|
327
348
|
});
|
|
328
349
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
350
|
+
it("should produce the exact same hash for create v2", () => {
|
|
351
|
+
const tokenTransaction = {
|
|
352
|
+
version: 2,
|
|
353
|
+
tokenInputs: {
|
|
354
|
+
$case: "createInput" as const,
|
|
355
|
+
createInput: {
|
|
356
|
+
issuerPublicKey: TEST_TOKEN_PUBLIC_KEY,
|
|
357
|
+
tokenName: TEST_TOKEN_NAME,
|
|
358
|
+
tokenTicker: TEST_TOKEN_TICKER,
|
|
359
|
+
decimals: TEST_DECIMALS,
|
|
360
|
+
maxSupply: TEST_MAX_SUPPLY,
|
|
361
|
+
isFreezable: false,
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
tokenOutputs: [],
|
|
365
|
+
sparkOperatorIdentityPublicKeys: [TEST_OPERATOR_PUB_KEY],
|
|
366
|
+
network: Network.REGTEST,
|
|
367
|
+
expiryTime: new Date(TEST_EXPIRY_TIME),
|
|
368
|
+
clientCreatedTimestamp: new Date(TEST_CLIENT_TIMESTAMP),
|
|
369
|
+
invoiceAttachments: [],
|
|
370
|
+
};
|
|
347
371
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
];
|
|
355
|
-
|
|
356
|
-
const result = tokenTransactionService.selectTokenOutputs(
|
|
357
|
-
tokenOutputs,
|
|
358
|
-
350n,
|
|
359
|
-
"SMALL_FIRST",
|
|
360
|
-
);
|
|
361
|
-
|
|
362
|
-
expect(result).toHaveLength(2);
|
|
363
|
-
expect(result[0]!.output!.id).toBe("output2"); // 100n
|
|
364
|
-
expect(result[1]!.output!.id).toBe("output3"); // 300n
|
|
365
|
-
// Total: 400n >= 350n
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
it("should select minimum number of outputs needed", () => {
|
|
369
|
-
const tokenOutputs = [
|
|
370
|
-
createMockTokenOutput("output1", 50n),
|
|
371
|
-
createMockTokenOutput("output2", 100n),
|
|
372
|
-
createMockTokenOutput("output3", 200n),
|
|
373
|
-
createMockTokenOutput("output4", 1000n),
|
|
374
|
-
];
|
|
375
|
-
|
|
376
|
-
const result = tokenTransactionService.selectTokenOutputs(
|
|
377
|
-
tokenOutputs,
|
|
378
|
-
300n,
|
|
379
|
-
"SMALL_FIRST",
|
|
380
|
-
);
|
|
381
|
-
|
|
382
|
-
expect(result).toHaveLength(3);
|
|
383
|
-
expect(result[0]!.output!.id).toBe("output1"); // 50n
|
|
384
|
-
expect(result[1]!.output!.id).toBe("output2"); // 100n
|
|
385
|
-
expect(result[2]!.output!.id).toBe("output3"); // 200n
|
|
386
|
-
// Total: 350n >= 300n
|
|
387
|
-
});
|
|
372
|
+
const hash = hashTokenTransactionV1(tokenTransaction, false);
|
|
373
|
+
|
|
374
|
+
expect(Array.from(hash)).toEqual([
|
|
375
|
+
92, 161, 134, 55, 164, 211, 69, 97, 149, 43, 29, 110, 94, 225, 55, 59,
|
|
376
|
+
178, 51, 203, 51, 189, 197, 203, 56, 6, 105, 55, 156, 106, 147, 155, 185,
|
|
377
|
+
]);
|
|
388
378
|
});
|
|
389
379
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
380
|
+
it("should produce the exact same hash for transfer v2", () => {
|
|
381
|
+
const tokenTransaction = {
|
|
382
|
+
version: 2,
|
|
383
|
+
tokenInputs: {
|
|
384
|
+
$case: "transferInput" as const,
|
|
385
|
+
transferInput: {
|
|
386
|
+
outputsToSpend: [
|
|
387
|
+
{
|
|
388
|
+
prevTokenTransactionHash: PREV_TX_HASH,
|
|
389
|
+
prevTokenTransactionVout: 0,
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
tokenOutputs: [
|
|
395
|
+
{
|
|
396
|
+
id: TEST_LEAF_ID,
|
|
397
|
+
ownerPublicKey: TEST_IDENTITY_PUB_KEY,
|
|
398
|
+
tokenPublicKey: TEST_TOKEN_PUBLIC_KEY,
|
|
399
|
+
tokenAmount: numberToBytesBE(TEST_TOKEN_AMOUNT, 16),
|
|
400
|
+
revocationCommitment: TEST_REVOCATION_PUB_KEY,
|
|
401
|
+
withdrawBondSats: TEST_BOND_SATS,
|
|
402
|
+
withdrawRelativeBlockLocktime: TEST_LOCKTIME,
|
|
403
|
+
},
|
|
404
|
+
],
|
|
405
|
+
sparkOperatorIdentityPublicKeys: [TEST_OPERATOR_PUB_KEY],
|
|
406
|
+
network: Network.REGTEST,
|
|
407
|
+
expiryTime: new Date(TEST_EXPIRY_TIME),
|
|
408
|
+
clientCreatedTimestamp: new Date(TEST_CLIENT_TIMESTAMP),
|
|
409
|
+
invoiceAttachments: [],
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const hash = hashTokenTransactionV2(tokenTransaction, false);
|
|
413
|
+
|
|
414
|
+
expect(Array.from(hash)).toEqual([
|
|
415
|
+
21, 226, 190, 223, 0, 62, 121, 223, 94, 193, 34, 62, 186, 68, 52, 197, 6,
|
|
416
|
+
189, 107, 37, 65, 141, 222, 109, 212, 128, 5, 40, 81, 247, 15, 249,
|
|
417
|
+
]);
|
|
426
418
|
});
|
|
427
419
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
expect(result).toHaveLength(3);
|
|
468
|
-
// Total: 600n >= 600n
|
|
469
|
-
});
|
|
420
|
+
it("should produce the exact same hash for transfer v2 with invoice attachments", () => {
|
|
421
|
+
const tokenTransaction = {
|
|
422
|
+
version: 2,
|
|
423
|
+
tokenInputs: {
|
|
424
|
+
$case: "transferInput" as const,
|
|
425
|
+
transferInput: {
|
|
426
|
+
outputsToSpend: [
|
|
427
|
+
{
|
|
428
|
+
prevTokenTransactionHash: PREV_TX_HASH,
|
|
429
|
+
prevTokenTransactionVout: 0,
|
|
430
|
+
},
|
|
431
|
+
],
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
tokenOutputs: [
|
|
435
|
+
{
|
|
436
|
+
id: TEST_LEAF_ID,
|
|
437
|
+
ownerPublicKey: TEST_IDENTITY_PUB_KEY,
|
|
438
|
+
tokenPublicKey: TEST_TOKEN_PUBLIC_KEY,
|
|
439
|
+
tokenAmount: numberToBytesBE(TEST_TOKEN_AMOUNT, 16),
|
|
440
|
+
revocationCommitment: TEST_REVOCATION_PUB_KEY,
|
|
441
|
+
withdrawBondSats: TEST_BOND_SATS,
|
|
442
|
+
withdrawRelativeBlockLocktime: TEST_LOCKTIME,
|
|
443
|
+
},
|
|
444
|
+
],
|
|
445
|
+
sparkOperatorIdentityPublicKeys: [TEST_OPERATOR_PUB_KEY],
|
|
446
|
+
network: Network.REGTEST,
|
|
447
|
+
expiryTime: new Date(TEST_EXPIRY_TIME),
|
|
448
|
+
clientCreatedTimestamp: new Date(TEST_CLIENT_TIMESTAMP),
|
|
449
|
+
invoiceAttachments: TEST_INVOICE_ATTACHMENTS,
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
const hash = hashTokenTransactionV2(tokenTransaction, false);
|
|
453
|
+
|
|
454
|
+
expect(Array.from(hash)).toEqual([
|
|
455
|
+
139, 4, 220, 112, 69, 32, 149, 81, 90, 67, 151, 101, 240, 182, 13, 123,
|
|
456
|
+
70, 4, 153, 159, 172, 225, 15, 120, 71, 219, 154, 27, 72, 167, 2, 149,
|
|
457
|
+
]);
|
|
470
458
|
});
|
|
471
459
|
});
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { numberToBytesBE } from "@noble/curves/abstract/utils";
|
|
2
|
+
import { OutputWithPreviousTransactionData } from "../proto/spark.js";
|
|
3
|
+
import { TokenTransactionService } from "../services/token-transactions.js";
|
|
4
|
+
import { WalletConfigService } from "../services/config.js";
|
|
5
|
+
import { ConnectionManager } from "../services/connection.js";
|
|
6
|
+
import { ValidationError } from "../errors/types.js";
|
|
7
|
+
|
|
8
|
+
describe("select token outputs", () => {
|
|
9
|
+
let tokenTransactionService: TokenTransactionService;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
const mockConfig = {} as WalletConfigService;
|
|
13
|
+
const mockConnectionManager = {} as ConnectionManager;
|
|
14
|
+
tokenTransactionService = new TokenTransactionService(
|
|
15
|
+
mockConfig,
|
|
16
|
+
mockConnectionManager,
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const createMockTokenOutput = (
|
|
21
|
+
id: string,
|
|
22
|
+
tokenAmount: bigint,
|
|
23
|
+
tokenPublicKey: Uint8Array = new Uint8Array(32).fill(1),
|
|
24
|
+
ownerPublicKey: Uint8Array = new Uint8Array(32).fill(2),
|
|
25
|
+
): OutputWithPreviousTransactionData => ({
|
|
26
|
+
output: {
|
|
27
|
+
id,
|
|
28
|
+
ownerPublicKey,
|
|
29
|
+
tokenPublicKey,
|
|
30
|
+
tokenAmount: numberToBytesBE(tokenAmount, 16),
|
|
31
|
+
revocationCommitment: new Uint8Array(32).fill(3),
|
|
32
|
+
},
|
|
33
|
+
previousTransactionHash: new Uint8Array(32).fill(4),
|
|
34
|
+
previousTransactionVout: 0,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("exact match scenarios", () => {
|
|
38
|
+
it("should return exact match when available", () => {
|
|
39
|
+
const tokenOutputs = [
|
|
40
|
+
createMockTokenOutput("output1", 100n),
|
|
41
|
+
createMockTokenOutput("output2", 500n),
|
|
42
|
+
createMockTokenOutput("output3", 1000n),
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const result = tokenTransactionService.selectTokenOutputs(
|
|
46
|
+
tokenOutputs,
|
|
47
|
+
500n,
|
|
48
|
+
"SMALL_FIRST",
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(result).toHaveLength(1);
|
|
52
|
+
expect(result[0]!.output!.id).toBe("output2");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("SMALL_FIRST strategy", () => {
|
|
57
|
+
it("should select smallest outputs first when no exact match", () => {
|
|
58
|
+
const tokenOutputs = [
|
|
59
|
+
createMockTokenOutput("output1", 1000n),
|
|
60
|
+
createMockTokenOutput("output2", 100n),
|
|
61
|
+
createMockTokenOutput("output3", 300n),
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
const result = tokenTransactionService.selectTokenOutputs(
|
|
65
|
+
tokenOutputs,
|
|
66
|
+
350n,
|
|
67
|
+
"SMALL_FIRST",
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
expect(result).toHaveLength(2);
|
|
71
|
+
expect(result[0]!.output!.id).toBe("output2"); // 100n
|
|
72
|
+
expect(result[1]!.output!.id).toBe("output3"); // 300n
|
|
73
|
+
// Total: 400n >= 350n
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should select minimum number of outputs needed", () => {
|
|
77
|
+
const tokenOutputs = [
|
|
78
|
+
createMockTokenOutput("output1", 50n),
|
|
79
|
+
createMockTokenOutput("output2", 100n),
|
|
80
|
+
createMockTokenOutput("output3", 200n),
|
|
81
|
+
createMockTokenOutput("output4", 1000n),
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
const result = tokenTransactionService.selectTokenOutputs(
|
|
85
|
+
tokenOutputs,
|
|
86
|
+
300n,
|
|
87
|
+
"SMALL_FIRST",
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
expect(result).toHaveLength(3);
|
|
91
|
+
expect(result[0]!.output!.id).toBe("output1"); // 50n
|
|
92
|
+
expect(result[1]!.output!.id).toBe("output2"); // 100n
|
|
93
|
+
expect(result[2]!.output!.id).toBe("output3"); // 200n
|
|
94
|
+
// Total: 350n >= 300n
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("LARGE_FIRST strategy", () => {
|
|
99
|
+
it("should select largest outputs first when no exact match", () => {
|
|
100
|
+
const tokenOutputs = [
|
|
101
|
+
createMockTokenOutput("output1", 100n),
|
|
102
|
+
createMockTokenOutput("output2", 1000n),
|
|
103
|
+
createMockTokenOutput("output3", 300n),
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
const result = tokenTransactionService.selectTokenOutputs(
|
|
107
|
+
tokenOutputs,
|
|
108
|
+
350n,
|
|
109
|
+
"LARGE_FIRST",
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
expect(result).toHaveLength(1);
|
|
113
|
+
expect(result[0]!.output!.id).toBe("output2"); // 1000n >= 350n
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("should select multiple outputs if largest is insufficient", () => {
|
|
117
|
+
const tokenOutputs = [
|
|
118
|
+
createMockTokenOutput("output1", 100n),
|
|
119
|
+
createMockTokenOutput("output2", 200n),
|
|
120
|
+
createMockTokenOutput("output3", 150n),
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
const result = tokenTransactionService.selectTokenOutputs(
|
|
124
|
+
tokenOutputs,
|
|
125
|
+
350n,
|
|
126
|
+
"LARGE_FIRST",
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
expect(result).toHaveLength(2);
|
|
130
|
+
expect(result[0]!.output!.id).toBe("output2"); // 200n
|
|
131
|
+
expect(result[1]!.output!.id).toBe("output3"); // 150n
|
|
132
|
+
// Total: 350n >= 350n
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("edge cases", () => {
|
|
137
|
+
it("should handle single output that exactly matches", () => {
|
|
138
|
+
const tokenOutputs = [createMockTokenOutput("output1", 500n)];
|
|
139
|
+
|
|
140
|
+
const result = tokenTransactionService.selectTokenOutputs(
|
|
141
|
+
tokenOutputs,
|
|
142
|
+
500n,
|
|
143
|
+
"SMALL_FIRST",
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
expect(result).toHaveLength(1);
|
|
147
|
+
expect(result[0]!.output!.id).toBe("output1");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("should throw ValidationError when tokenAmount is 0", () => {
|
|
151
|
+
const tokenOutputs = [createMockTokenOutput("output1", 100n)];
|
|
152
|
+
|
|
153
|
+
expect(() =>
|
|
154
|
+
tokenTransactionService.selectTokenOutputs(
|
|
155
|
+
tokenOutputs,
|
|
156
|
+
0n,
|
|
157
|
+
"SMALL_FIRST",
|
|
158
|
+
),
|
|
159
|
+
).toThrow(ValidationError);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("should throw ValidationError when available token amount is less than needed", () => {
|
|
163
|
+
const tokenOutputs = [
|
|
164
|
+
createMockTokenOutput("output1", 100n),
|
|
165
|
+
createMockTokenOutput("output2", 50n),
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
expect(() =>
|
|
169
|
+
tokenTransactionService.selectTokenOutputs(
|
|
170
|
+
tokenOutputs,
|
|
171
|
+
500n,
|
|
172
|
+
"SMALL_FIRST",
|
|
173
|
+
),
|
|
174
|
+
).toThrow(ValidationError);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("should select all outputs if needed", () => {
|
|
178
|
+
const tokenOutputs = [
|
|
179
|
+
createMockTokenOutput("output1", 100n),
|
|
180
|
+
createMockTokenOutput("output2", 200n),
|
|
181
|
+
createMockTokenOutput("output3", 300n),
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
const result = tokenTransactionService.selectTokenOutputs(
|
|
185
|
+
tokenOutputs,
|
|
186
|
+
600n,
|
|
187
|
+
"SMALL_FIRST",
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
expect(result).toHaveLength(3);
|
|
191
|
+
// Total: 600n >= 600n
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
});
|
|
@@ -35,14 +35,22 @@ export class SparkWalletTesting
|
|
|
35
35
|
disableEvents,
|
|
36
36
|
);
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
if (props.options && props.options.signerWithPreExistingKeys) {
|
|
39
|
+
await wallet.initWalletWithoutSeed();
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
wallet,
|
|
43
|
+
};
|
|
44
|
+
} else {
|
|
45
|
+
const initResponse = await wallet.initWallet(
|
|
46
|
+
props.mnemonicOrSeed,
|
|
47
|
+
props.accountNumber,
|
|
48
|
+
);
|
|
49
|
+
return {
|
|
50
|
+
wallet,
|
|
51
|
+
mnemonic: initResponse?.mnemonic,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
protected override async setupBackgroundStream() {
|