Skip to content

Commit 4fa3bd5

Browse files
feat(wallet): address tracker addresses$ now always emits addresses sorted by derivation index
1 parent 143461f commit 4fa3bd5

File tree

4 files changed

+69
-33
lines changed

4 files changed

+69
-33
lines changed

packages/wallet/src/services/AddressTracker.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
} from 'rxjs';
1818
import { WalletStores } from '../persistence';
1919
import { groupedAddressesEquals } from './util';
20+
import { sortAddresses } from './util/sortAddresses';
2021

2122
export type AddressTrackerDependencies = {
2223
store: WalletStores['addresses'];
@@ -86,7 +87,7 @@ export const createAddressTracker = ({ addressDiscovery$, store, logger }: Addre
8687
take(1)
8788
);
8889
},
89-
addresses$,
90+
addresses$: addresses$.pipe(map(sortAddresses)),
9091
shutdown: newAddresses$.complete.bind(newAddresses$)
9192
};
9293
};

packages/wallet/src/services/ChangeAddress/DynamicChangeAddressResolver.ts

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { GroupedAddress } from '@cardano-sdk/key-management';
77
import { InvalidStateError } from '@cardano-sdk/util';
88
import { Logger } from 'ts-log';
99
import { Observable, firstValueFrom } from 'rxjs';
10+
import { sortAddresses } from '../util/sortAddresses';
1011
import isEqual from 'lodash/isEqual.js';
1112
import uniq from 'lodash/uniq.js';
1213

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

287-
/**
288-
* Sorts an array of addresses by their primary index and, if available, by the
289-
* index of their stakeKeyDerivationPath.
290-
*
291-
* @param addresses - The array of addresses to sort.
292-
* @returns A new sorted array of addresses.
293-
*/
294-
const sortAddresses = (addresses: GroupedAddress[]): GroupedAddress[] =>
295-
[...addresses].sort((a, b) => {
296-
if (a.index !== b.index) {
297-
return a.index - b.index;
298-
}
299-
300-
if (a.stakeKeyDerivationPath && b.stakeKeyDerivationPath) {
301-
return a.stakeKeyDerivationPath.index - b.stakeKeyDerivationPath.index;
302-
}
303-
304-
if (a.stakeKeyDerivationPath && !b.stakeKeyDerivationPath) {
305-
return -1;
306-
}
307-
if (!a.stakeKeyDerivationPath && b.stakeKeyDerivationPath) {
308-
return 1;
309-
}
310-
311-
return 0;
312-
});
313-
314288
/** Resolves the address to be used for change. */
315289
export class DynamicChangeAddressResolver implements ChangeAddressResolver {
316290
readonly #getDelegationPortfolio: GetDelegationPortfolio;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { GroupedAddress } from '@cardano-sdk/key-management';
2+
3+
/**
4+
* Sorts an array of addresses by their primary index and, if available, by the
5+
* index of their stakeKeyDerivationPath.
6+
*
7+
* @param addresses - The array of addresses to sort.
8+
* @returns A new sorted array of addresses.
9+
*/
10+
export const sortAddresses = (addresses: GroupedAddress[]): GroupedAddress[] =>
11+
[...addresses].sort((a, b) => {
12+
if (a.index !== b.index) {
13+
return a.index - b.index;
14+
}
15+
16+
if (a.stakeKeyDerivationPath && b.stakeKeyDerivationPath) {
17+
return a.stakeKeyDerivationPath.index - b.stakeKeyDerivationPath.index;
18+
}
19+
20+
if (a.stakeKeyDerivationPath && !b.stakeKeyDerivationPath) {
21+
return -1;
22+
}
23+
24+
if (!a.stakeKeyDerivationPath && b.stakeKeyDerivationPath) {
25+
return 1;
26+
}
27+
28+
return 0;
29+
});

packages/wallet/test/services/AddressTracker.test.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,44 @@
11
import { AddressTracker, createAddressTracker } from '../../src';
2+
import { AddressType, GroupedAddress, KeyRole } from '@cardano-sdk/key-management';
23
import { Cardano } from '@cardano-sdk/core';
34
import { EMPTY, firstValueFrom, of } from 'rxjs';
4-
import { GroupedAddress } from '@cardano-sdk/key-management';
55
import { WalletStores } from '../../src/persistence';
6+
import { address_0_0, address_0_5, address_5_0, rewardAccount_0, rewardAccount_5 } from './ChangeAddress/testData';
67
import { createTestScheduler, logger } from '@cardano-sdk/util-dev';
8+
import { sortAddresses } from '../../src/services/util/sortAddresses';
79

810
describe('AddressTracker', () => {
911
let store: jest.Mocked<WalletStores['addresses']>;
1012
let addressTracker: AddressTracker;
11-
const discoveredAddresses = [{ address: 'addr1' as Cardano.PaymentAddress } as GroupedAddress];
13+
const discoveredAddresses = [
14+
{
15+
accountIndex: 0,
16+
address: address_5_0,
17+
index: 5,
18+
networkId: Cardano.NetworkId.Testnet,
19+
rewardAccount: rewardAccount_0,
20+
stakeKeyDerivationPath: { index: 0, role: KeyRole.Stake },
21+
type: AddressType.External
22+
},
23+
{
24+
accountIndex: 0,
25+
address: address_0_5,
26+
index: 0,
27+
networkId: Cardano.NetworkId.Testnet,
28+
rewardAccount: rewardAccount_5,
29+
stakeKeyDerivationPath: { index: 5, role: KeyRole.Stake },
30+
type: AddressType.External
31+
},
32+
{
33+
accountIndex: 0,
34+
address: address_0_0,
35+
index: 0,
36+
networkId: Cardano.NetworkId.Testnet,
37+
rewardAccount: rewardAccount_0,
38+
stakeKeyDerivationPath: { index: 0, role: KeyRole.Stake },
39+
type: AddressType.External
40+
}
41+
];
1242

1343
beforeEach(() => {
1444
store = {
@@ -19,6 +49,8 @@ describe('AddressTracker', () => {
1949
};
2050
});
2151

52+
const sortedDiscoveredAddresses = sortAddresses(discoveredAddresses);
53+
2254
afterEach(() => addressTracker.shutdown());
2355

2456
describe('load', () => {
@@ -31,7 +63,7 @@ describe('AddressTracker', () => {
3163
logger,
3264
store
3365
});
34-
expectObservable(addressTracker.addresses$).toBe('a', { a: discoveredAddresses });
66+
expectObservable(addressTracker.addresses$).toBe('a', { a: sortedDiscoveredAddresses });
3567
expectSubscriptions(addressDiscovery$.subscriptions).toBe('^');
3668
flush();
3769
expect(store.get).toBeCalledTimes(1);
@@ -70,12 +102,12 @@ describe('AddressTracker', () => {
70102
logger,
71103
store
72104
});
73-
await expect(firstValueFrom(addressTracker.addresses$)).resolves.toEqual(discoveredAddresses);
105+
await expect(firstValueFrom(addressTracker.addresses$)).resolves.toEqual(sortedDiscoveredAddresses);
74106
const newAddress = { address: 'addr2' as Cardano.PaymentAddress } as GroupedAddress;
75107
const combinedAddresses = [...discoveredAddresses, newAddress];
76108

77109
await expect(firstValueFrom(addressTracker.addAddresses([newAddress]))).resolves.toEqual(combinedAddresses);
78-
await expect(firstValueFrom(addressTracker.addresses$)).resolves.toEqual(combinedAddresses);
110+
await expect(firstValueFrom(addressTracker.addresses$)).resolves.toEqual(sortAddresses(combinedAddresses));
79111
expect(store.set).toBeCalledTimes(2);
80112
expect(store.set).toBeCalledWith(combinedAddresses);
81113
});

0 commit comments

Comments
 (0)