|
1 | 1 | /* eslint-disable unicorn/consistent-destructuring, sonarjs/no-duplicate-string, @typescript-eslint/no-floating-promises, promise/no-nesting */
|
2 | 2 | import * as Crypto from '@cardano-sdk/crypto';
|
3 |
| -import { AddressDiscovery, BaseWallet, TxInFlight, createPersonalWallet } from '../../src'; |
| 3 | +import { AddressDiscovery, BaseWallet, TxInFlight, createPersonalWallet, createSharedWallet } from '../../src'; |
4 | 4 | import { AddressType, Bip32Account, GroupedAddress, Witnesser, util } from '@cardano-sdk/key-management';
|
5 | 5 | import { AssetId, createStubStakePoolProvider, mockProviders as mocks } from '@cardano-sdk/util-dev';
|
6 | 6 | import { BehaviorSubject, Subscription, firstValueFrom, skip } from 'rxjs';
|
@@ -76,6 +76,9 @@ describe('BaseWallet methods', () => {
|
76 | 76 | stakeKeyDerivationPath,
|
77 | 77 | type: AddressType.External
|
78 | 78 | };
|
| 79 | + |
| 80 | + const groupedAddress2: GroupedAddress = { ...groupedAddress, address: '1' as Cardano.PaymentAddress, index: 1 }; |
| 81 | + const groupedAddress3: GroupedAddress = { ...groupedAddress, address: '2' as Cardano.PaymentAddress, index: 2 }; |
79 | 82 | let txSubmitProvider: mocks.TxSubmitProviderStub;
|
80 | 83 | let networkInfoProvider: mocks.NetworkInfoProviderStub;
|
81 | 84 | let assetProvider: mocks.MockAssetProvider;
|
@@ -531,4 +534,260 @@ describe('BaseWallet methods', () => {
|
531 | 534 | await expect(firstValueFrom(wallet.addresses$)).resolves.toEqual(newAddresses);
|
532 | 535 | });
|
533 | 536 | });
|
| 537 | + |
| 538 | + // eslint-disable-next-line sonarjs/cognitive-complexity |
| 539 | + describe('getNextUnusedAddress', () => { |
| 540 | + const script: Cardano.NativeScript = { |
| 541 | + __type: Cardano.ScriptType.Native, |
| 542 | + kind: Cardano.NativeScriptKind.RequireAllOf, |
| 543 | + scripts: [ |
| 544 | + { |
| 545 | + __type: Cardano.ScriptType.Native, |
| 546 | + keyHash: Crypto.Ed25519KeyHashHex('b275b08c999097247f7c17e77007c7010cd19f20cc086ad99d398538'), |
| 547 | + kind: Cardano.NativeScriptKind.RequireSignature |
| 548 | + } |
| 549 | + ] |
| 550 | + }; |
| 551 | + |
| 552 | + // Computed from script above |
| 553 | + const scriptAddress = { |
| 554 | + accountIndex: 0, |
| 555 | + address: |
| 556 | + 'addr_test1xrrujjsfy60k96pm8ymadcxhx82p54z3aswlwxa8r9pggy78e99qjf5lvt5rkwfh6msdwvw5rf29rmqa7ud6wx2zssfs0l4tsq' as Cardano.PaymentAddress, |
| 557 | + index: 0, |
| 558 | + networkId: Cardano.NetworkId.Testnet, |
| 559 | + rewardAccount: 'stake_test17rrujjsfy60k96pm8ymadcxhx82p54z3aswlwxa8r9pggycfg4jxy' as Cardano.RewardAccount, |
| 560 | + type: AddressType.External |
| 561 | + }; |
| 562 | + |
| 563 | + beforeEach(() => { |
| 564 | + wallet.shutdown(); |
| 565 | + |
| 566 | + bip32Account.deriveAddress = jest.fn((args) => { |
| 567 | + if (args.index === 0) { |
| 568 | + return Promise.resolve(groupedAddress); |
| 569 | + } |
| 570 | + |
| 571 | + if (args.index === 1) { |
| 572 | + return Promise.resolve(groupedAddress2); |
| 573 | + } |
| 574 | + |
| 575 | + return Promise.resolve(groupedAddress3); |
| 576 | + }); |
| 577 | + }); |
| 578 | + |
| 579 | + it('returns the latest known empty address if any', async () => { |
| 580 | + chainHistoryProvider = { |
| 581 | + blocksByHashes: jest.fn().mockResolvedValue([{ epoch: Cardano.EpochNo(3) }]), |
| 582 | + healthCheck: jest.fn().mockResolvedValue({ ok: true }), |
| 583 | + transactionsByAddresses: jest.fn().mockResolvedValue({ |
| 584 | + pageResults: [], |
| 585 | + totalResultCount: 0 |
| 586 | + }), |
| 587 | + transactionsByHashes: jest.fn().mockResolvedValue([]) |
| 588 | + }; |
| 589 | + |
| 590 | + wallet = createPersonalWallet( |
| 591 | + { name: 'Test Wallet' }, |
| 592 | + { |
| 593 | + addressDiscovery, |
| 594 | + assetProvider, |
| 595 | + bip32Account, |
| 596 | + chainHistoryProvider, |
| 597 | + handleProvider, |
| 598 | + logger, |
| 599 | + networkInfoProvider, |
| 600 | + rewardsProvider, |
| 601 | + stakePoolProvider, |
| 602 | + txSubmitProvider, |
| 603 | + utxoProvider, |
| 604 | + witnesser |
| 605 | + } |
| 606 | + ); |
| 607 | + |
| 608 | + await waitForWalletStateSettle(wallet); |
| 609 | + |
| 610 | + // Only one address being tracked. |
| 611 | + await expect(firstValueFrom(wallet.addresses$)).resolves.toEqual([groupedAddress]); |
| 612 | + await expect(wallet.getNextUnusedAddress()).resolves.toEqual([groupedAddress]); |
| 613 | + }); |
| 614 | + |
| 615 | + it('returns a fresh empty address if all known addresses are used', async () => { |
| 616 | + chainHistoryProvider = { |
| 617 | + blocksByHashes: jest.fn().mockResolvedValue([{ epoch: Cardano.EpochNo(3) }]), |
| 618 | + healthCheck: jest.fn().mockResolvedValue({ ok: true }), |
| 619 | + transactionsByAddresses: jest.fn((args) => { |
| 620 | + if (args.addresses[0] === '1') { |
| 621 | + return Promise.resolve({ |
| 622 | + pageResults: [], |
| 623 | + totalResultCount: 0 |
| 624 | + }); |
| 625 | + } |
| 626 | + |
| 627 | + return Promise.resolve({ |
| 628 | + pageResults: mocks.queryTransactionsResult.pageResults, |
| 629 | + totalResultCount: mocks.queryTransactionsResult.totalResultCount |
| 630 | + }); |
| 631 | + }), |
| 632 | + transactionsByHashes: jest.fn().mockResolvedValue([]) |
| 633 | + }; |
| 634 | + |
| 635 | + wallet = createPersonalWallet( |
| 636 | + { name: 'Test Wallet' }, |
| 637 | + { |
| 638 | + addressDiscovery, |
| 639 | + assetProvider, |
| 640 | + bip32Account, |
| 641 | + chainHistoryProvider, |
| 642 | + handleProvider, |
| 643 | + logger, |
| 644 | + networkInfoProvider, |
| 645 | + rewardsProvider, |
| 646 | + stakePoolProvider, |
| 647 | + txSubmitProvider, |
| 648 | + utxoProvider, |
| 649 | + witnesser |
| 650 | + } |
| 651 | + ); |
| 652 | + |
| 653 | + await waitForWalletStateSettle(wallet); |
| 654 | + |
| 655 | + // Only one address being tracked. |
| 656 | + await expect(firstValueFrom(wallet.addresses$)).resolves.toEqual([groupedAddress]); |
| 657 | + await expect(wallet.getNextUnusedAddress()).resolves.toEqual([groupedAddress2]); |
| 658 | + |
| 659 | + // New empty address is now being tracked. |
| 660 | + await expect(firstValueFrom(wallet.addresses$)).resolves.toEqual([groupedAddress, groupedAddress2]); |
| 661 | + |
| 662 | + // No new addresses are generated until the new empty address is used up. |
| 663 | + await expect(wallet.getNextUnusedAddress()).resolves.toEqual([groupedAddress2]); |
| 664 | + }); |
| 665 | + |
| 666 | + it('returns a fresh empty address if all known addresses are used, and the new created address was also used', async () => { |
| 667 | + chainHistoryProvider = { |
| 668 | + blocksByHashes: jest.fn().mockResolvedValue([{ epoch: Cardano.EpochNo(3) }]), |
| 669 | + healthCheck: jest.fn().mockResolvedValue({ ok: true }), |
| 670 | + transactionsByAddresses: jest.fn((args) => { |
| 671 | + if (args.addresses[0] === '2') { |
| 672 | + return Promise.resolve({ |
| 673 | + pageResults: [], |
| 674 | + totalResultCount: 0 |
| 675 | + }); |
| 676 | + } |
| 677 | + |
| 678 | + return Promise.resolve({ |
| 679 | + pageResults: mocks.queryTransactionsResult.pageResults, |
| 680 | + totalResultCount: mocks.queryTransactionsResult.totalResultCount |
| 681 | + }); |
| 682 | + }), |
| 683 | + transactionsByHashes: jest.fn().mockResolvedValue([]) |
| 684 | + }; |
| 685 | + |
| 686 | + wallet = createPersonalWallet( |
| 687 | + { name: 'Test Wallet' }, |
| 688 | + { |
| 689 | + addressDiscovery, |
| 690 | + assetProvider, |
| 691 | + bip32Account, |
| 692 | + chainHistoryProvider, |
| 693 | + handleProvider, |
| 694 | + logger, |
| 695 | + networkInfoProvider, |
| 696 | + rewardsProvider, |
| 697 | + stakePoolProvider, |
| 698 | + txSubmitProvider, |
| 699 | + utxoProvider, |
| 700 | + witnesser |
| 701 | + } |
| 702 | + ); |
| 703 | + |
| 704 | + await waitForWalletStateSettle(wallet); |
| 705 | + |
| 706 | + // Only one address being tracked. |
| 707 | + await expect(firstValueFrom(wallet.addresses$)).resolves.toEqual([groupedAddress]); |
| 708 | + await expect(wallet.getNextUnusedAddress()).resolves.toEqual([groupedAddress3]); |
| 709 | + |
| 710 | + // Discovered used address, plus new empty address is now being tracked. |
| 711 | + await expect(firstValueFrom(wallet.addresses$)).resolves.toEqual([ |
| 712 | + groupedAddress, |
| 713 | + groupedAddress2, |
| 714 | + groupedAddress3 |
| 715 | + ]); |
| 716 | + |
| 717 | + // No new addresses are generated until the new empty address is used up. |
| 718 | + await expect(wallet.getNextUnusedAddress()).resolves.toEqual([groupedAddress3]); |
| 719 | + }); |
| 720 | + |
| 721 | + it('returns script address if unused', async () => { |
| 722 | + chainHistoryProvider = { |
| 723 | + blocksByHashes: jest.fn().mockResolvedValue([{ epoch: Cardano.EpochNo(3) }]), |
| 724 | + healthCheck: jest.fn().mockResolvedValue({ ok: true }), |
| 725 | + transactionsByAddresses: jest.fn().mockResolvedValue({ |
| 726 | + pageResults: [], |
| 727 | + totalResultCount: 0 |
| 728 | + }), |
| 729 | + transactionsByHashes: jest.fn().mockResolvedValue([]) |
| 730 | + }; |
| 731 | + |
| 732 | + wallet = createSharedWallet( |
| 733 | + { name: 'Test Wallet' }, |
| 734 | + { |
| 735 | + assetProvider, |
| 736 | + chainHistoryProvider, |
| 737 | + handleProvider, |
| 738 | + logger, |
| 739 | + networkInfoProvider, |
| 740 | + paymentScript: script, |
| 741 | + rewardsProvider, |
| 742 | + stakePoolProvider, |
| 743 | + stakingScript: script, |
| 744 | + txSubmitProvider, |
| 745 | + utxoProvider, |
| 746 | + witnesser |
| 747 | + } |
| 748 | + ); |
| 749 | + |
| 750 | + await waitForWalletStateSettle(wallet); |
| 751 | + |
| 752 | + await expect(wallet.getNextUnusedAddress()).resolves.toEqual([scriptAddress]); |
| 753 | + // Only one address being tracked. |
| 754 | + await expect(firstValueFrom(wallet.addresses$)).resolves.toEqual([scriptAddress]); |
| 755 | + }); |
| 756 | + |
| 757 | + it('returns an empty array if script address is already used', async () => { |
| 758 | + chainHistoryProvider = { |
| 759 | + blocksByHashes: jest.fn().mockResolvedValue([{ epoch: Cardano.EpochNo(3) }]), |
| 760 | + healthCheck: jest.fn().mockResolvedValue({ ok: true }), |
| 761 | + transactionsByAddresses: jest.fn().mockResolvedValue({ |
| 762 | + pageResults: [], |
| 763 | + totalResultCount: 1 |
| 764 | + }), |
| 765 | + transactionsByHashes: jest.fn().mockResolvedValue([]) |
| 766 | + }; |
| 767 | + |
| 768 | + wallet = createSharedWallet( |
| 769 | + { name: 'Test Wallet' }, |
| 770 | + { |
| 771 | + assetProvider, |
| 772 | + chainHistoryProvider, |
| 773 | + handleProvider, |
| 774 | + logger, |
| 775 | + networkInfoProvider, |
| 776 | + paymentScript: script, |
| 777 | + rewardsProvider, |
| 778 | + stakePoolProvider, |
| 779 | + stakingScript: script, |
| 780 | + txSubmitProvider, |
| 781 | + utxoProvider, |
| 782 | + witnesser |
| 783 | + } |
| 784 | + ); |
| 785 | + |
| 786 | + await waitForWalletStateSettle(wallet); |
| 787 | + |
| 788 | + await expect(wallet.getNextUnusedAddress()).resolves.toEqual([]); |
| 789 | + // Only one address being tracked. |
| 790 | + await expect(firstValueFrom(wallet.addresses$)).resolves.toEqual([scriptAddress]); |
| 791 | + }); |
| 792 | + }); |
534 | 793 | });
|
0 commit comments