Skip to content

Commit

Permalink
feat(wallet): address tracker addresses$ now always emits addresses s…
Browse files Browse the repository at this point in the history
…orted by derivation index
  • Loading branch information
AngelCastilloB committed Sep 12, 2024
1 parent 143461f commit 4fa3bd5
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 33 deletions.
3 changes: 2 additions & 1 deletion packages/wallet/src/services/AddressTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from 'rxjs';
import { WalletStores } from '../persistence';
import { groupedAddressesEquals } from './util';
import { sortAddresses } from './util/sortAddresses';

export type AddressTrackerDependencies = {
store: WalletStores['addresses'];
Expand Down Expand Up @@ -86,7 +87,7 @@ export const createAddressTracker = ({ addressDiscovery$, store, logger }: Addre
take(1)
);
},
addresses$,
addresses$: addresses$.pipe(map(sortAddresses)),
shutdown: newAddresses$.complete.bind(newAddresses$)
};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { GroupedAddress } from '@cardano-sdk/key-management';
import { InvalidStateError } from '@cardano-sdk/util';
import { Logger } from 'ts-log';
import { Observable, firstValueFrom } from 'rxjs';
import { sortAddresses } from '../util/sortAddresses';
import isEqual from 'lodash/isEqual.js';
import uniq from 'lodash/uniq.js';

Expand Down Expand Up @@ -284,33 +285,6 @@ export const delegationMatchesPortfolio = (
/** Gets the current delegation portfolio. */
export type GetDelegationPortfolio = () => Promise<Cardano.Cip17DelegationPortfolio | null>;

/**
* Sorts an array of addresses by their primary index and, if available, by the
* index of their stakeKeyDerivationPath.
*
* @param addresses - The array of addresses to sort.
* @returns A new sorted array of addresses.
*/
const sortAddresses = (addresses: GroupedAddress[]): GroupedAddress[] =>
[...addresses].sort((a, b) => {
if (a.index !== b.index) {
return a.index - b.index;
}

if (a.stakeKeyDerivationPath && b.stakeKeyDerivationPath) {
return a.stakeKeyDerivationPath.index - b.stakeKeyDerivationPath.index;
}

if (a.stakeKeyDerivationPath && !b.stakeKeyDerivationPath) {
return -1;
}
if (!a.stakeKeyDerivationPath && b.stakeKeyDerivationPath) {
return 1;
}

return 0;
});

/** Resolves the address to be used for change. */
export class DynamicChangeAddressResolver implements ChangeAddressResolver {
readonly #getDelegationPortfolio: GetDelegationPortfolio;
Expand Down
29 changes: 29 additions & 0 deletions packages/wallet/src/services/util/sortAddresses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { GroupedAddress } from '@cardano-sdk/key-management';

/**
* Sorts an array of addresses by their primary index and, if available, by the
* index of their stakeKeyDerivationPath.
*
* @param addresses - The array of addresses to sort.
* @returns A new sorted array of addresses.
*/
export const sortAddresses = (addresses: GroupedAddress[]): GroupedAddress[] =>
[...addresses].sort((a, b) => {
if (a.index !== b.index) {
return a.index - b.index;
}

if (a.stakeKeyDerivationPath && b.stakeKeyDerivationPath) {
return a.stakeKeyDerivationPath.index - b.stakeKeyDerivationPath.index;
}

if (a.stakeKeyDerivationPath && !b.stakeKeyDerivationPath) {
return -1;
}

if (!a.stakeKeyDerivationPath && b.stakeKeyDerivationPath) {
return 1;
}

return 0;
});
42 changes: 37 additions & 5 deletions packages/wallet/test/services/AddressTracker.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,44 @@
import { AddressTracker, createAddressTracker } from '../../src';
import { AddressType, GroupedAddress, KeyRole } from '@cardano-sdk/key-management';
import { Cardano } from '@cardano-sdk/core';
import { EMPTY, firstValueFrom, of } from 'rxjs';
import { GroupedAddress } from '@cardano-sdk/key-management';
import { WalletStores } from '../../src/persistence';
import { address_0_0, address_0_5, address_5_0, rewardAccount_0, rewardAccount_5 } from './ChangeAddress/testData';
import { createTestScheduler, logger } from '@cardano-sdk/util-dev';
import { sortAddresses } from '../../src/services/util/sortAddresses';

describe('AddressTracker', () => {
let store: jest.Mocked<WalletStores['addresses']>;
let addressTracker: AddressTracker;
const discoveredAddresses = [{ address: 'addr1' as Cardano.PaymentAddress } as GroupedAddress];
const discoveredAddresses = [
{
accountIndex: 0,
address: address_5_0,
index: 5,
networkId: Cardano.NetworkId.Testnet,
rewardAccount: rewardAccount_0,
stakeKeyDerivationPath: { index: 0, role: KeyRole.Stake },
type: AddressType.External
},
{
accountIndex: 0,
address: address_0_5,
index: 0,
networkId: Cardano.NetworkId.Testnet,
rewardAccount: rewardAccount_5,
stakeKeyDerivationPath: { index: 5, role: KeyRole.Stake },
type: AddressType.External
},
{
accountIndex: 0,
address: address_0_0,
index: 0,
networkId: Cardano.NetworkId.Testnet,
rewardAccount: rewardAccount_0,
stakeKeyDerivationPath: { index: 0, role: KeyRole.Stake },
type: AddressType.External
}
];

beforeEach(() => {
store = {
Expand All @@ -19,6 +49,8 @@ describe('AddressTracker', () => {
};
});

const sortedDiscoveredAddresses = sortAddresses(discoveredAddresses);

afterEach(() => addressTracker.shutdown());

describe('load', () => {
Expand All @@ -31,7 +63,7 @@ describe('AddressTracker', () => {
logger,
store
});
expectObservable(addressTracker.addresses$).toBe('a', { a: discoveredAddresses });
expectObservable(addressTracker.addresses$).toBe('a', { a: sortedDiscoveredAddresses });
expectSubscriptions(addressDiscovery$.subscriptions).toBe('^');
flush();
expect(store.get).toBeCalledTimes(1);
Expand Down Expand Up @@ -70,12 +102,12 @@ describe('AddressTracker', () => {
logger,
store
});
await expect(firstValueFrom(addressTracker.addresses$)).resolves.toEqual(discoveredAddresses);
await expect(firstValueFrom(addressTracker.addresses$)).resolves.toEqual(sortedDiscoveredAddresses);
const newAddress = { address: 'addr2' as Cardano.PaymentAddress } as GroupedAddress;
const combinedAddresses = [...discoveredAddresses, newAddress];

await expect(firstValueFrom(addressTracker.addAddresses([newAddress]))).resolves.toEqual(combinedAddresses);
await expect(firstValueFrom(addressTracker.addresses$)).resolves.toEqual(combinedAddresses);
await expect(firstValueFrom(addressTracker.addresses$)).resolves.toEqual(sortAddresses(combinedAddresses));
expect(store.set).toBeCalledTimes(2);
expect(store.set).toBeCalledWith(combinedAddresses);
});
Expand Down

0 comments on commit 4fa3bd5

Please sign in to comment.