Skip to content

Commit

Permalink
feat(ui): Flag addresses as own or foreign [LW-10061] (#998)
Browse files Browse the repository at this point in the history
* feat(ui): add address tag ui component LW-10061
* feat(extension): flag own, address book and foreign addresses in dapp txs LW-10061
* feat(extension): flag own, address book and foreign addresses in activity details LW-10061
  • Loading branch information
DominikGuzei authored and wklos-iohk committed Apr 22, 2024
1 parent f849f97 commit 1229413
Show file tree
Hide file tree
Showing 26 changed files with 409 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Flex } from '@lace/ui';
import { useViewsFlowContext } from '@providers/ViewFlowProvider';

import { Wallet } from '@lace/cardano';
import { withAddressBookContext } from '@src/features/address-book/context';
import { useAddressBookContext, withAddressBookContext } from '@src/features/address-book/context';
import { useWalletStore } from '@stores';
import { useFetchCoinPrice, useChainHistoryProvider } from '@hooks';
import {
Expand All @@ -23,6 +23,7 @@ import { useCurrencyStore, useAppSettingsContext } from '@providers';
import { logger } 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';

interface DappTransactionContainerProps {
errorMessage?: string;
Expand All @@ -43,6 +44,10 @@ export const DappTransactionContainer = withAddressBookContext(
walletState
} = useWalletStore();

const ownAddresses = useObservable(inMemoryWallet.addresses$)?.map((a) => a.address);
const { list: addressBook } = useAddressBookContext() as useDbStateValue<AddressBookSchema>;
const addressToNameMap = new Map(addressBook?.map((entry) => [entry.address as string, entry.name]));

const { fiatCurrency } = useCurrencyStore();
const { priceResult } = useFetchCoinPrice();

Expand Down Expand Up @@ -146,6 +151,8 @@ export const DappTransactionContainer = withAddressBookContext(
errorMessage={errorMessage}
toAddress={toAddressTokens}
collateral={txCollateral}
ownAddresses={ownAddresses}
addressToNameMap={addressToNameMap}
/>
) : (
<Skeleton loading />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ import { DappTransactionContainer } from '../DappTransactionContainer';
import '@testing-library/jest-dom';
import { BehaviorSubject } from 'rxjs';
import { act } from 'react-dom/test-utils';
import { buildMockTx } from '@src/utils/mocks/tx';
import { buildMockTx, sendingAddress } from '@src/utils/mocks/tx';
import { Wallet } from '@lace/cardano';
import { SignTxData } from '../types';
import { getWrapper } from '../testing.utils';
import { TransactionWitnessRequest } from '@cardano-sdk/web-extension';
import { cardanoCoin } from '@src/utils/constants';
import { AddressBookSchema } from '@lib/storage';

const { Cardano, Crypto } = Wallet;

Expand All @@ -51,6 +52,7 @@ const mockedAssetsInfo = new Map([['id', 'data']]);
const assetInfo$ = new BehaviorSubject(mockedAssetsInfo);
const available$ = new BehaviorSubject([]);
const signed$ = new BehaviorSubject([]);
const addresses$ = new BehaviorSubject([sendingAddress]);
const rewardAccounts$ = new BehaviorSubject([
{
// eslint-disable-next-line unicorn/consistent-destructuring
Expand All @@ -65,6 +67,7 @@ const protocolParameters$ = new BehaviorSubject({
});

const inMemoryWallet = {
addresses$,
assetInfo$,
balance: {
utxo: {
Expand Down Expand Up @@ -133,12 +136,12 @@ jest.mock('react-i18next', () => {
};
});

const addressList = ['addressList'];
const addressBook: AddressBookSchema[] = [];
jest.mock('@src/features/address-book/context', () => ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...jest.requireActual<any>('@src/features/address-book/context'),
withAddressBookContext: mockWithAddressBookContext,
useAddressBookContext: () => ({ list: addressList })
useAddressBookContext: () => ({ list: addressBook })
}));

jest.mock('antd', () => {
Expand Down Expand Up @@ -332,7 +335,9 @@ describe('Testing DappTransactionContainer component', () => {
errorMessage,
coinSymbol: 'ADA',
collateral: BigInt(1_000_000),
txInspectionDetails
txInspectionDetails,
ownAddresses: [sendingAddress.address],
addressToNameMap: new Map()
},
{}
);
Expand Down
2 changes: 2 additions & 0 deletions apps/browser-extension-wallet/src/lib/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1368,6 +1368,8 @@
"core.receive.usedAddresses.copy": "Copy Address",
"core.receive.usedAddresses.addressCopied": "Address copied",
"core.receive.showUsedAddresses": "Show used addresses",
"core.addressTags.own": "own",
"core.addressTags.foreign": "foreign",
"addressesDiscovery.overlay.title": "Your wallet is syncing, this might take a few minutes",
"addressesDiscovery.toast.errorText": "Wallet failed to sync",
"addressesDiscovery.toast.successText": "Wallet synced successfully",
Expand Down
14 changes: 8 additions & 6 deletions apps/browser-extension-wallet/src/utils/mocks/tx.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
/* eslint-disable no-magic-numbers */
import { Wallet } from '@lace/cardano';

const sendingAddress = Wallet.Cardano.PaymentAddress(
'addr_test1qq585l3hyxgj3nas2v3xymd23vvartfhceme6gv98aaeg9muzcjqw982pcftgx53fu5527z2cj2tkx2h8ux2vxsg475q2g7k3g'
);
export const sendingAddress = {
address: Wallet.Cardano.PaymentAddress(
'addr_test1qq585l3hyxgj3nas2v3xymd23vvartfhceme6gv98aaeg9muzcjqw982pcftgx53fu5527z2cj2tkx2h8ux2vxsg475q2g7k3g'
)
} as Wallet.KeyManagement.GroupedAddress;

const receivingAddress = Wallet.Cardano.PaymentAddress(
export const receivingAddress = Wallet.Cardano.PaymentAddress(
'addr_test1qpfhhfy2qgls50r9u4yh0l7z67xpg0a5rrhkmvzcuqrd0znuzcjqw982pcftgx53fu5527z2cj2tkx2h8ux2vxsg475q9gw0lz'
);

Expand Down Expand Up @@ -34,7 +36,7 @@ export const buildMockTx = (
]),
inputs: args.inputs ?? [
{
address: sendingAddress,
address: sendingAddress.address,
index: 0,
txId: Wallet.Cardano.TransactionId('bb217abaca60fc0ca68c1555eca6a96d2478547818ae76ce6836133f3cc546e0')
}
Expand Down Expand Up @@ -64,7 +66,7 @@ export const buildMockTx = (
}
},
{
address: sendingAddress,
address: sendingAddress.address,
value: {
assets: new Map([
[Wallet.Cardano.AssetId('659f2917fb63f12b33667463ee575eeac1845bbc736b9c0bbc40ba8254534c41'), BigInt(1)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { TxDirections } from '@src/types';
import { APP_MODE_POPUP } from '@src/utils/constants';
import { config } from '@src/config';
import { PostHogAction } from '@providers/AnalyticsProvider/analyticsTracker';
import { useObservable } from '@lace/common';

type TransactionDetailsProxyProps = {
name: string;
Expand All @@ -21,13 +22,23 @@ export const TransactionDetailsProxy = withAddressBookContext(
({ name, activityInfo, direction, status, amountTransformer }: TransactionDetailsProxyProps): ReactElement => {
const analytics = useAnalyticsContext();
const {
inMemoryWallet,
walletInfo,
environmentName,
walletUI: { cardanoCoin, appMode }
} = useWalletStore();
const isPopupView = appMode === APP_MODE_POPUP;
const openExternalLink = useExternalLinkOpener();

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

// Prepare address book data as Map<address, name>
const { list: addressList } = useAddressBookContext();
const addressToNameMap = useMemo(
() => new Map<string, string>(addressList?.map((item: AddressListType) => [item.address, item.name])),
[addressList]
);

const { CEXPLORER_BASE_URL, CEXPLORER_URL_PATHS } = config();
const explorerBaseUrl = useMemo(
Expand Down Expand Up @@ -72,11 +83,6 @@ export const TransactionDetailsProxy = withAddressBookContext(
externalLink && status === ActivityStatus.SUCCESS && openExternalLink(externalLink);
};

const addressToNameMap = useMemo(
() => new Map<string, string>(addressList?.map((item: AddressListType) => [item.address, item.name])),
[addressList]
);

return (
// eslint-disable-next-line react/jsx-pascal-case
<TransactionDetails
Expand All @@ -95,6 +101,7 @@ export const TransactionDetailsProxy = withAddressBookContext(
amountTransformer={amountTransformer}
headerDescription={getHeaderDescription() || cardanoCoin.symbol}
txSummary={txSummary}
ownAddresses={walletAddresses}
addressToNameMap={addressToNameMap}
coinSymbol={cardanoCoin.symbol}
isPopupView={isPopupView}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,31 +102,16 @@ $border-bottom: 1px solid var(--light-mode-light-grey-plus, var(--dark-mode-mid-
font-weight: 500;
line-height: 17px;
}

&.addressTag {
gap: size_unit(1);
}
}

.timestamp {
flex: 0 0 35%;
}

.amount {
display: flex;
flex-direction: column;
width: 100%;
align-items: flex-end;

.ada {
color: var(--text-color-primary, #ffffff);
}

.fiat {
color: var(--text-color-secondary, #878e9e);
}

.addrName {
margin-bottom: size_unit(1);
}
}

.addressDetail {
font-size: var(--bodySmall, 14px);
font-weight: 400;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
/* eslint-disable no-magic-numbers */
import React from 'react';
import cn from 'classnames';
import { TransactionDetailAsset, TransactionMetadataProps, TxOutputInput, TxSummary } from './TransactionDetailAsset';

import { Ellipsis, toast } from '@lace/common';
import { Box } from '@lace/ui';
import { useTranslate } from '@src/ui/hooks';
import { useTranslate } from '@ui/hooks';
import { getAddressTagTranslations, renderAddressTag } from '@ui/utils';

import { TransactionDetailAsset, TransactionMetadataProps, TxOutputInput, TxSummary } from './TransactionDetailAsset';
import CopyToClipboard from 'react-copy-to-clipboard';
import { ActivityStatus } from '../Activity';
import styles from './TransactionDetails.module.scss';
Expand Down Expand Up @@ -76,6 +79,7 @@ export interface TransactionDetailsProps {
txSummary?: TxSummary[];
coinSymbol: string;
tooltipContent?: string;
ownAddresses: string[];
addressToNameMap: Map<string, string>;
isPopupView?: boolean;
openExternalLink?: (url: string) => void;
Expand Down Expand Up @@ -122,6 +126,7 @@ export const TransactionDetails = ({
txSummary = [],
coinSymbol,
pools,
ownAddresses,
addressToNameMap,
isPopupView,
openExternalLink,
Expand Down Expand Up @@ -353,22 +358,19 @@ export const TransactionDetails = ({
</div>
)}
{(summary.addr as string[]).map((addr) => {
const addrName = addressToNameMap?.get(addr);
const address = isPopupView ? (
<Ellipsis className={cn(styles.addr, styles.fiat)} text={addr} ellipsisInTheMiddle />
) : (
<span className={cn(styles.addr, styles.fiat)}>{addr}</span>
);
return (
<div key={addr} data-testid="tx-to-detail" className={cn(styles.addr, styles.detail)}>
{addrName ? (
<div className={styles.amount}>
<span className={cn(styles.ada, styles.addrName)}>{addrName}</span>
{address}
</div>
) : (
address
)}
<div
key={addr}
data-testid="tx-to-detail"
className={cn([styles.detail, styles.addr, styles.addressTag])}
>
{address}
{renderAddressTag(addr, getAddressTagTranslations(t), ownAddresses, addressToNameMap)}
</div>
);
})}
Expand Down Expand Up @@ -590,6 +592,8 @@ export const TransactionDetails = ({
coinSymbol={coinSymbol}
withSeparatorLine
sendAnalytics={sendAnalyticsInputs}
ownAddresses={ownAddresses}
addressToNameMap={addressToNameMap}
/>
)}
{addrOutputs?.length > 0 && (
Expand All @@ -604,6 +608,8 @@ export const TransactionDetails = ({
}}
coinSymbol={coinSymbol}
sendAnalytics={sendAnalyticsOutputs}
ownAddresses={ownAddresses}
addressToNameMap={addressToNameMap}
/>
)}
{metadata?.length > 0 && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
import React, { useState } from 'react';
import { Tooltip } from 'antd';
import cn from 'classnames';
import { addEllipsis, Button } from '@lace/common';

import { InfoCircleOutlined, DownOutlined } from '@ant-design/icons';
import { addEllipsis, Button } from '@lace/common';

import { TxOutputInput } from './TransactionDetailAsset';
import { TranslationsFor } from '../../utils/types';

import { ReactComponent as BracketDown } from '../../assets/icons/bracket-down.component.svg';
import styles from './TransactionInputOutput.module.scss';
import { Flex } from '@lace/ui';
import { getAddressTagTranslations, renderAddressTag } from '@ui/utils';
import { useTranslate } from '@ui/hooks';

const rotateOpen: React.CSSProperties = {
transform: 'rotate(180deg)',
Expand All @@ -30,6 +34,8 @@ export interface TransactionInputOutputProps {
translations: TranslationsFor<'address' | 'sent'>;
coinSymbol: string;
withSeparatorLine?: boolean;
ownAddresses: string[];
addressToNameMap: Map<string, string>;
sendAnalytics?: () => void;
}

Expand All @@ -42,9 +48,12 @@ export const TransactionInputOutput = ({
translations,
coinSymbol,
withSeparatorLine,
ownAddresses,
addressToNameMap,
sendAnalytics
}: TransactionInputOutputProps): React.ReactElement => {
const [isVisible, setIsVisible] = useState<boolean>();
const { t } = useTranslate();

const animation = isVisible ? rotateOpen : rotateClose;
const Icon = BracketDown ? <BracketDown className={styles.bracket} style={{ ...animation }} /> : <DownOutlined />;
Expand Down Expand Up @@ -79,9 +88,12 @@ export const TransactionInputOutput = ({
<div className={styles.addressContainer} key={`${inputAddress}-${idx}`}>
<div className={styles.row}>
<div className={styles.label}>{translations.address}</div>
<div data-testid="tx-address" className={cn(styles.addressDetail, styles.content)}>
<Tooltip title={inputAddress}>{addEllipsis(inputAddress, 8, 8)}</Tooltip>
</div>
<Flex flexDirection="column" alignItems="flex-end" gap="$8">
<div data-testid="tx-address" className={cn(styles.addressDetail, styles.content)}>
<Tooltip title={inputAddress}>{addEllipsis(inputAddress, 8, 8)}</Tooltip>
</div>
{renderAddressTag(inputAddress, getAddressTagTranslations(t), ownAddresses, addressToNameMap)}
</Flex>
</div>

<div className={styles.row}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ describe('Testing ActivityDetailsBrowser component', () => {
],
amountTransformer: (amount) => `${amount} $`,
coinSymbol: 'ADA',
ownAddresses: [],
addressToNameMap: new Map()
};

Expand Down Expand Up @@ -83,4 +84,22 @@ describe('Testing ActivityDetailsBrowser component', () => {
const { queryByTestId: query } = render(<TransactionDetails {...addrListProps} />);
expect(query('tx-metadata')).not.toBeInTheDocument();
});

test('should show address tag for inputs', async () => {
// use empty addrOutputs (so we get only one toggle button for inputs)
const { findByTestId } = render(<TransactionDetails {...addrListProps} addrOutputs={[]} />);
const inputsSectionToggle = await findByTestId('tx-addr-list_toggle');
fireEvent.click(inputsSectionToggle);

expect(await findByTestId('address-tag')).toBeVisible();
});

test('should show address tag for outputs', async () => {
// use empty addrOutputs (so we get only one toggle button for outputs)
const { findByTestId } = render(<TransactionDetails {...addrListProps} addrOutputs={[]} />);
const outputsSectionToggle = await findByTestId('tx-addr-list_toggle');
fireEvent.click(outputsSectionToggle);

expect(await findByTestId('address-tag')).toBeVisible();
});
});
Loading

0 comments on commit 1229413

Please sign in to comment.