Skip to content

Commit

Permalink
feat: cache wallets addresses in metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
lucas-barros committed Apr 19, 2024
1 parent 1f939b9 commit e28823f
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 6 deletions.
Expand Up @@ -20,10 +20,11 @@ import { createWalletAssetProvider } from '@cardano-sdk/wallet';
import { Skeleton } from 'antd';

import { useCurrencyStore, useAppSettingsContext } from '@providers';
import { logger } from '@lib/wallet-api-ui';
import { logger, walletRepository } from '@lib/wallet-api-ui';
import { useComputeTxCollateral } from '@hooks/useComputeTxCollateral';
import { utxoAndBackendChainHistoryResolver } from '@src/utils/utxo-chain-history-resolver';
import { AddressBookSchema, useDbStateValue } from '@lib/storage';
import { getAllWalletsAddresses } from '@src/utils/get-all-wallets-addresses';

interface DappTransactionContainerProps {
errorMessage?: string;
Expand Down Expand Up @@ -82,6 +83,7 @@ export const DappTransactionContainer = withAddressBookContext(
const userRewardAccounts = useObservable(inMemoryWallet.delegation.rewardAccounts$);
const rewardAccountsAddresses = useMemo(() => userRewardAccounts?.map((key) => key.address), [userRewardAccounts]);
const protocolParameters = useObservable(inMemoryWallet?.protocolParameters$);
const allWalletsAddresses = getAllWalletsAddresses(useObservable(walletRepository.wallets$));

useEffect(() => {
if (!req || !protocolParameters) {
Expand Down Expand Up @@ -151,7 +153,7 @@ export const DappTransactionContainer = withAddressBookContext(
errorMessage={errorMessage}
toAddress={toAddressTokens}
collateral={txCollateral}
ownAddresses={ownAddresses}
ownAddresses={allWalletsAddresses.length > 0 ? allWalletsAddresses : ownAddresses}
addressToNameMap={addressToNameMap}
/>
) : (
Expand Down
@@ -0,0 +1,107 @@
/* eslint-disable no-magic-numbers */
/* eslint-disable unicorn/no-useless-undefined */
import { WalletManager, WalletRepository } from '@cardano-sdk/web-extension';
import { cacheActivatedWalletAddressSubscription } from '../cache-wallets-address';
import { Wallet } from '@lace/cardano';
import { BehaviorSubject, of } from 'rxjs';

describe('cacheActivatedWalletAddressSubscription', () => {
afterEach(() => {
jest.clearAllMocks();
});

it('should not trigger subscription for no active wallet', () => {
const mockWalletManager = {
activeWallet$: of(undefined),
activeWalletId$: of(undefined)
} as unknown as WalletManager<Wallet.WalletMetadata, Wallet.AccountMetadata>;

const mockWalletRepository = {
wallets$: of([]),
updateWalletMetadata: jest.fn()
} as unknown as WalletRepository<Wallet.WalletMetadata, Wallet.AccountMetadata>;

cacheActivatedWalletAddressSubscription(mockWalletManager, mockWalletRepository);

expect(mockWalletRepository.updateWalletMetadata).not.toHaveBeenCalled();
});

it('should subscribe and update metadata', () => {
const mockWalletManager = {
activeWallet$: of({ addresses$: of([{ address: 'address1' }]) }),
activeWalletId$: of({ walletId: 'walletId' })
} as unknown as WalletManager<Wallet.WalletMetadata, Wallet.AccountMetadata>;

const mockWalletRepository = {
wallets$: of([
{
walletId: 'walletId',
metadata: {}
}
]),
updateWalletMetadata: jest.fn()
} as unknown as WalletRepository<Wallet.WalletMetadata, Wallet.AccountMetadata>;

cacheActivatedWalletAddressSubscription(mockWalletManager, mockWalletRepository);

expect(mockWalletRepository.updateWalletMetadata).toHaveBeenCalledWith({
walletId: 'walletId',
metadata: {
walletAddresses: ['address1']
}
});
});

it('should subscribe and update metadata when a new wallet is added and activated', () => {
const activeWallet$ = new BehaviorSubject({
addresses$: of([{ address: 'address1' }])
});
const activeWalletId$ = new BehaviorSubject({
walletId: 'walletId1'
});
const wallets$ = new BehaviorSubject<{ walletId: string; metadata: { walletAddresses?: string[] } }[]>([
{
walletId: 'walletId1',
metadata: { walletAddresses: ['address1'] }
}
]);
const mockWalletManager = {
activeWallet$,
activeWalletId$
} as unknown as WalletManager<Wallet.WalletMetadata, Wallet.AccountMetadata>;

const mockWalletRepository = {
wallets$,
updateWalletMetadata: jest.fn()
} as unknown as WalletRepository<Wallet.WalletMetadata, Wallet.AccountMetadata>;

cacheActivatedWalletAddressSubscription(mockWalletManager, mockWalletRepository);

wallets$.next([
{
walletId: 'walletId1',
metadata: { walletAddresses: ['address1'] }
},
{
walletId: 'walletId2',
metadata: {}
}
]);
activeWalletId$.next({ walletId: 'walletId2' });
activeWallet$.next({ addresses$: of([{ address: 'address2' }, { address: 'address3' }]) });

expect(mockWalletRepository.updateWalletMetadata).toHaveBeenNthCalledWith(1, {
walletId: 'walletId1',
metadata: {
walletAddresses: ['address1']
}
});

expect(mockWalletRepository.updateWalletMetadata).toHaveBeenNthCalledWith(2, {
walletId: 'walletId2',
metadata: {
walletAddresses: ['address2', 'address3']
}
});
});
});
@@ -0,0 +1,31 @@
import { WalletManager, WalletRepository } from '@cardano-sdk/web-extension';
import { Wallet } from '@lace/cardano';
import { filter, switchMap, withLatestFrom, zip } from 'rxjs';

export const cacheActivatedWalletAddressSubscription = (
walletManager: WalletManager<Wallet.WalletMetadata, Wallet.AccountMetadata>,
walletRepository: WalletRepository<Wallet.WalletMetadata, Wallet.AccountMetadata>
): void => {
zip([
walletManager.activeWalletId$.pipe(filter((activeWalletId) => Boolean(activeWalletId))),
walletManager.activeWallet$.pipe(
filter((wallet) => Boolean(wallet)),
switchMap((wallet) => wallet.addresses$)
)
])
.pipe(withLatestFrom(walletRepository.wallets$))
.subscribe(([[activeWallet, walletAddresses], wallets]) => {
const wallet = wallets.find(({ walletId }) => walletId === activeWallet.walletId);
const uniqueAddresses = [
...new Set([...(wallet.metadata.walletAddresses || []), ...walletAddresses.map(({ address }) => address)])
];

walletRepository.updateWalletMetadata({
walletId: activeWallet.walletId,
metadata: {
...wallet.metadata,
walletAddresses: uniqueAddresses
}
});
});
};
Expand Up @@ -23,6 +23,7 @@ import {
import { Wallet } from '@lace/cardano';
import { ADA_HANDLE_POLICY_ID, HANDLE_SERVER_URLS } from '@src/features/ada-handle/config';
import { Cardano, NotImplementedError } from '@cardano-sdk/core';
import { cacheActivatedWalletAddressSubscription } from './cache-wallets-address';

const logger = console;

Expand Down Expand Up @@ -198,4 +199,6 @@ walletManager
logger.error('Failed to initialize wallet manager', error);
});

cacheActivatedWalletAddressSubscription(walletManager, walletRepository);

export const wallet$ = walletManager.activeWallet$;
@@ -0,0 +1,36 @@
import { AnyWallet } from '@cardano-sdk/web-extension';
import { getAllWalletsAddresses } from '../get-all-wallets-addresses';
import { Wallet } from '@lace/cardano';

describe('getAllWalletsAddresses', () => {
it('should return an empty array if undefined is provided', () => {
const addresses = getAllWalletsAddresses();

expect(addresses).toEqual([]);
});

it('should return an empty array if no wallets are provided', () => {
const addresses = getAllWalletsAddresses([]);

expect(addresses).toEqual([]);
});

it('should return an array of payment addresses', () => {
const mockWallets = [
{
metadata: {
walletAddresses: ['addr1', 'addr2']
}
},
{
metadata: {
walletAddresses: ['addr2', 'addr3']
}
}
] as AnyWallet<Wallet.WalletMetadata, Wallet.AccountMetadata>[];

const addresses = getAllWalletsAddresses(mockWallets);

expect(addresses).toEqual(['addr1', 'addr2', 'addr3']);
});
});
@@ -0,0 +1,9 @@
import { AnyWallet } from '@cardano-sdk/web-extension';
import { Wallet } from '@lace/cardano';
import flatMap from 'lodash/flatMap';

export const getAllWalletsAddresses = (
wallets: AnyWallet<Wallet.WalletMetadata, Wallet.AccountMetadata>[] = []
): Wallet.Cardano.PaymentAddress[] => [
...new Set(flatMap(wallets.map(({ metadata: { walletAddresses = [] } }) => walletAddresses)))
];
Expand Up @@ -10,6 +10,8 @@ import { APP_MODE_POPUP } from '@src/utils/constants';
import { config } from '@src/config';
import { PostHogAction } from '@providers/AnalyticsProvider/analyticsTracker';
import { useObservable } from '@lace/common';
import { getAllWalletsAddresses } from '@src/utils/get-all-wallets-addresses';
import { walletRepository } from '@lib/wallet-api-ui';

type TransactionDetailsProxyProps = {
name: string;
Expand All @@ -31,6 +33,7 @@ export const TransactionDetailsProxy = withAddressBookContext(
const openExternalLink = useExternalLinkOpener();

// Prepare own addresses of active account
const allWalletsAddresses = getAllWalletsAddresses(useObservable(walletRepository.wallets$));
const walletAddresses = useObservable(inMemoryWallet.addresses$)?.map((a) => a.address);

// Prepare address book data as Map<address, name>
Expand Down Expand Up @@ -101,7 +104,7 @@ export const TransactionDetailsProxy = withAddressBookContext(
amountTransformer={amountTransformer}
headerDescription={getHeaderDescription() || cardanoCoin.symbol}
txSummary={txSummary}
ownAddresses={walletAddresses}
ownAddresses={allWalletsAddresses.length > 0 ? allWalletsAddresses : walletAddresses}
addressToNameMap={addressToNameMap}
coinSymbol={cardanoCoin.symbol}
isPopupView={isPopupView}
Expand Down
1 change: 1 addition & 0 deletions packages/cardano/src/wallet/lib/cardano-wallet.ts
Expand Up @@ -27,6 +27,7 @@ export interface WalletMetadata {
name: string;
lockValue?: HexBlob;
lastActiveAccountIndex?: number;
walletAddresses?: Cardano.PaymentAddress[];
}

export interface AccountMetadata {
Expand Down
Expand Up @@ -56,7 +56,8 @@ const data: ComponentProps<typeof TransactionDetails> = {
openExternalLink: (url) => window.open(url, '_blank', 'noopener,noreferrer'),
handleOpenExternalHashLink: () => {
console.log('handle on hash click', '639a43144dc2c0ead16f2fb753360f4b4f536502dbdb8aa5e424b00abb7534ff');
}
},
ownAddresses: []
};

const stakeVoteDelegationCertificate = [
Expand Down
@@ -1,8 +1,8 @@
/* eslint-disable sonarjs/no-identical-functions */
import React from 'react';
import { addEllipsis, truncate } from '@lace/common';
import { truncate, addEllipsis } from '@lace/common';
import { Wallet } from '@lace/cardano';
import { AssetInfoWithAmount, Cardano } from '@cardano-sdk/core';
import { Cardano, AssetInfoWithAmount } from '@cardano-sdk/core';
import { Typography } from 'antd';

import styles from './DappAddressSections.module.scss';
Expand Down

0 comments on commit e28823f

Please sign in to comment.