Skip to content

Commit 2499b5c

Browse files
authored
Merge pull request #1014 from input-output-hk/feat/add-observable-wallet-address-discovery-method
feat: add ObservableWallet.discoverAddreses
2 parents 468cfd5 + efc4e50 commit 2499b5c

File tree

4 files changed

+53
-11
lines changed

4 files changed

+53
-11
lines changed

packages/wallet/src/PersonalWallet/PersonalWallet.ts

+12
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ export class PersonalWallet implements ObservableWallet {
205205
#failedFromReemitter$: Subject<FailedTx>;
206206
#trackedTxSubmitProvider: TrackedTxSubmitProvider;
207207
#addressTracker: AddressTracker;
208+
#addressDiscovery: AddressDiscovery;
208209
#submittingPromises: Partial<Record<Cardano.TransactionId, Promise<Cardano.TransactionId>>> = {};
209210

210211
readonly witnesser: Witnesser;
@@ -306,6 +307,7 @@ export class PersonalWallet implements ObservableWallet {
306307
filter((status) => status === ConnectionStatus.down)
307308
);
308309

310+
this.#addressDiscovery = addressDiscovery;
309311
this.#addressTracker = createAddressTracker({
310312
addressDiscovery$: coldObservableProvider({
311313
cancel$,
@@ -735,4 +737,14 @@ export class PersonalWallet implements ObservableWallet {
735737
}
736738
return Promise.resolve(this.drepPubKey);
737739
}
740+
741+
async discoverAddresses(): Promise<GroupedAddress[]> {
742+
const addresses = await this.#addressDiscovery.discover(this.bip32Account);
743+
const knownAddresses = await firstValueFrom(this.addresses$);
744+
const newAddresses = addresses.filter(
745+
({ address }) => !knownAddresses.some((knownAddr) => knownAddr.address === address)
746+
);
747+
await firstValueFrom(this.#addressTracker.addAddresses(newAddresses));
748+
return firstValueFrom(this.addresses$);
749+
}
738750
}

packages/wallet/src/types.ts

+8
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,14 @@ export interface ObservableWallet {
9696
/** Create a TxBuilder from this wallet */
9797
createTxBuilder(): TxBuilder;
9898

99+
/**
100+
* Discover addresses that might have been created by other applications.
101+
* This is run automatically when the wallet is first created.
102+
*
103+
* @returns Promise that resolves when discovery is complete, with an updated array of wallet addresses
104+
*/
105+
discoverAddresses(): Promise<GroupedAddress[]>;
106+
99107
shutdown(): void;
100108
}
101109

packages/wallet/test/PersonalWallet/methods.test.ts

+32-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-disable unicorn/consistent-destructuring, sonarjs/no-duplicate-string, @typescript-eslint/no-floating-promises, promise/no-nesting, promise/always-return */
22
import * as Crypto from '@cardano-sdk/crypto';
3+
import { AddressDiscovery, PersonalWallet, TxInFlight } from '../../src';
34
import { AddressType, Bip32Account, GroupedAddress, Witnesser, util } from '@cardano-sdk/key-management';
45
import { AssetId, createStubStakePoolProvider, mockProviders as mocks } from '@cardano-sdk/util-dev';
56
import { BehaviorSubject, Subscription, firstValueFrom, skip } from 'rxjs';
@@ -18,7 +19,6 @@ import {
1819
} from '@cardano-sdk/core';
1920
import { HexBlob } from '@cardano-sdk/util';
2021
import { InitializeTxProps } from '@cardano-sdk/tx-construction';
21-
import { PersonalWallet, TxInFlight } from '../../src';
2222
import { buildDRepIDFromDRepKey, toOutgoingTx, waitForWalletStateSettle } from '../util';
2323
import { getPassphrase, stakeKeyDerivationPath, testAsyncKeyAgent } from '../../../key-management/test/mocks';
2424
import { dummyLogger as logger } from 'ts-log';
@@ -60,6 +60,15 @@ const promiseTimeout = (p: Promise<unknown>, timeout = 10): Promise<unknown> =>
6060

6161
describe('PersonalWallet methods', () => {
6262
const address = mocks.utxo[0][0].address!;
63+
const groupedAddress: GroupedAddress = {
64+
accountIndex: 0,
65+
address,
66+
index: 0,
67+
networkId: Cardano.NetworkId.Testnet,
68+
rewardAccount: mocks.rewardAccount,
69+
stakeKeyDerivationPath,
70+
type: AddressType.External
71+
};
6372
let txSubmitProvider: mocks.TxSubmitProviderStub;
6473
let networkInfoProvider: mocks.NetworkInfoProviderStub;
6574
let assetProvider: mocks.MockAssetProvider;
@@ -71,33 +80,27 @@ describe('PersonalWallet methods', () => {
7180
let utxoProvider: mocks.UtxoProviderStub;
7281
let witnesser: Witnesser;
7382
let bip32Account: Bip32Account;
83+
let addressDiscovery: jest.Mocked<AddressDiscovery>;
7484

7585
beforeEach(async () => {
7686
txSubmitProvider = mocks.mockTxSubmitProvider();
7787
networkInfoProvider = mocks.mockNetworkInfoProvider();
7888
utxoProvider = mocks.mockUtxoProvider();
79-
8089
assetProvider = mocks.mockAssetProvider();
8190
stakePoolProvider = createStubStakePoolProvider();
8291
rewardsProvider = mockRewardsProvider();
8392
chainHistoryProvider = mockChainHistoryProvider();
8493
handleProvider = mocks.mockHandleProvider();
85-
const groupedAddress: GroupedAddress = {
86-
accountIndex: 0,
87-
address,
88-
index: 0,
89-
networkId: Cardano.NetworkId.Testnet,
90-
rewardAccount: mocks.rewardAccount,
91-
stakeKeyDerivationPath,
92-
type: AddressType.External
93-
};
94+
addressDiscovery = { discover: jest.fn().mockImplementation(async () => [groupedAddress]) };
95+
9496
const asyncKeyAgent = await testAsyncKeyAgent();
9597
bip32Account = await Bip32Account.fromAsyncKeyAgent(asyncKeyAgent);
9698
bip32Account.deriveAddress = jest.fn().mockResolvedValue(groupedAddress);
9799
witnesser = util.createBip32Ed25519Witnesser(asyncKeyAgent);
98100
wallet = new PersonalWallet(
99101
{ name: 'Test Wallet' },
100102
{
103+
addressDiscovery,
101104
assetProvider,
102105
bip32Account,
103106
chainHistoryProvider,
@@ -465,4 +468,22 @@ describe('PersonalWallet methods', () => {
465468
expect(response).toBe('string');
466469
expect(bip32Account.derivePublicKey).toHaveBeenCalledTimes(3);
467470
});
471+
472+
describe('discoverAddresses', () => {
473+
it('discovers new addreses and emits them from addresses$', async () => {
474+
const newAddresses: GroupedAddress[] = [
475+
groupedAddress,
476+
{
477+
...groupedAddress,
478+
address: Cardano.PaymentAddress(
479+
'addr_test1qzs0umu0s2ammmpw0hea0w2crtcymdjvvlqngpgqy76gpfnuzcjqw982pcftgx53fu5527z2cj2tkx2h8ux2vxsg475qp3y3vz'
480+
),
481+
index: groupedAddress.index + 1
482+
}
483+
];
484+
addressDiscovery.discover.mockResolvedValueOnce(newAddresses);
485+
await expect(wallet.discoverAddresses()).resolves.toEqual(newAddresses);
486+
await expect(firstValueFrom(wallet.addresses$)).resolves.toEqual(newAddresses);
487+
});
488+
});
468489
});

packages/web-extension/src/observableWallet/util.ts

+1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ export const observableWalletProperties: RemoteApiProperties<ObservableWallet> =
101101
rewardAccounts$: RemoteApiPropertyType.HotObservable,
102102
rewardsHistory$: RemoteApiPropertyType.HotObservable
103103
},
104+
discoverAddresses: RemoteApiPropertyType.MethodReturningPromise,
104105
eraSummaries$: RemoteApiPropertyType.HotObservable,
105106
fatalError$: RemoteApiPropertyType.HotObservable,
106107
finalizeTx: RemoteApiPropertyType.MethodReturningPromise,

0 commit comments

Comments
 (0)