Skip to content

Commit

Permalink
feat(extension): LW-9669 show collateral on dapp dialogue (#864)
Browse files Browse the repository at this point in the history
* feat(extension): add utility function to calculate the tx collateral

* feat(core): add collateral component

* feat(core,cardano): show collateral on dapp dialogue

* refactor(extension): extract tx collateral computation to a hook

* refactor(extension): use the SDK get collateral implementation

* feat(ui): add tooltip property to amount tx summary

* refactor(core): use ui toolkit amount component

* refactor(core): remove eslint comment

* refactor(extension): remove max-statements comment

* refactor(extension): add return type annotation

* refactor(extension): cache tx to avoid creating new object references

* refactor(core): remove magic numbers comment

* refactor(core): extract amount transformer

* fix(core): add test id for tx fee

* refactor(extension): use wallet state

* fix(ui): use relative imports

* refactor(core,ui): add back tx fee test id

* refactor(core): add test id to transaction details
  • Loading branch information
renanvalentin authored Feb 19, 2024
1 parent d3fd071 commit 2d7b410
Show file tree
Hide file tree
Showing 17 changed files with 177 additions and 168 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { TX_CREATION_TYPE_KEY, TxCreationType } from '@providers/AnalyticsProvid
import { txSubmitted$ } from '@providers/AnalyticsProvider/onChain';
import { signingCoordinator } from '@lib/wallet-api-ui';
import { senderToDappInfo } from '@src/utils/senderToDappInfo';
import { useComputeTxCollateral } from '@hooks/useComputeTxCollateral';

const DAPP_TOAST_DURATION = 50;

Expand Down Expand Up @@ -86,7 +87,8 @@ export const ConfirmTransaction = withAddressBookContext((): React.ReactElement
walletType,
isHardwareWallet,
blockchainProvider: { assetProvider },
walletUI: { cardanoCoin }
walletUI: { cardanoCoin },
walletState
} = useWalletStore();
const { fiatCurrency } = useCurrencyStore();
const { list: addressList } = useAddressBookContext();
Expand All @@ -99,11 +101,12 @@ export const ConfirmTransaction = withAddressBookContext((): React.ReactElement
const [isConfirmingTx, setIsConfirmingTx] = useState<boolean>();
const [assetsInfo, setAssetsInfo] = useState<TokenInfo | null>();
const [dappInfo, setDappInfo] = useState<Wallet.DappInfo>();
const tx = useMemo(() => req?.transaction.toCore(), [req?.transaction]);
const txCollateral = useComputeTxCollateral(walletState, tx);

// All assets' ids in the transaction body. Used to fetch their info from cardano services
const assetIds = useMemo(() => {
if (!req) return [];
const tx = req.transaction.toCore();
if (!tx) return [];
const uniqueAssetIds = new Set<Wallet.Cardano.AssetId>();
// Merge all assets (TokenMaps) from the tx outputs and mint
const assetMaps = tx.body?.outputs?.map((output) => output.value.assets) ?? [];
Expand All @@ -118,7 +121,7 @@ export const ConfirmTransaction = withAddressBookContext((): React.ReactElement
}
}
return [...uniqueAssetIds.values()];
}, [req]);
}, [tx]);

useEffect(() => {
if (assetIds?.length > 0) {
Expand Down Expand Up @@ -190,7 +193,7 @@ export const ConfirmTransaction = withAddressBookContext((): React.ReactElement
const assetId = Wallet.Cardano.AssetId.fromParts(asset.policyId, asset.assetName);
const assetInfo = assets.get(assetId) || assetsInfo?.get(assetId);
// If it's a new asset or the name is being updated we should be getting it from the tx metadata
const metadataName = getAssetNameFromMintMetadata(asset, req.transaction.toCore()?.auxiliaryData?.blob);
const metadataName = getAssetNameFromMintMetadata(asset, tx?.auxiliaryData?.blob);
return {
name: assetInfo?.name.toString() || asset.fingerprint || assetId,
ticker:
Expand All @@ -203,7 +206,7 @@ export const ConfirmTransaction = withAddressBookContext((): React.ReactElement
};
});
},
[assets, assetsInfo, req]
[assets, assetsInfo, tx]
);

const createAssetList = useCallback(
Expand Down Expand Up @@ -231,7 +234,7 @@ export const ConfirmTransaction = withAddressBookContext((): React.ReactElement
const [txSummary, setTxSummary] = useState<Wallet.Cip30SignTxSummary | undefined>();

useEffect(() => {
if (!req) {
if (!tx) {
setTxSummary(void 0);
return;
}
Expand All @@ -241,7 +244,6 @@ export const ConfirmTransaction = withAddressBookContext((): React.ReactElement
burned: assetsBurnedInspector
});

const tx = req.transaction.toCore();
const { minted, burned } = await inspector(tx as Wallet.Cardano.HydratedTx);
const isMintTransaction = minted.length > 0 || burned.length > 0;

Expand Down Expand Up @@ -274,11 +276,12 @@ export const ConfirmTransaction = withAddressBookContext((): React.ReactElement
outputs: txSummaryOutputs,
type: txType,
mintedAssets: createMintedList(minted),
burnedAssets: createMintedList(burned)
burnedAssets: createMintedList(burned),
collateral: txCollateral ? Wallet.util.lovelacesToAdaString(txCollateral.toString()) : undefined
});
};
getTxSummary();
}, [req, walletInfo.addresses, createAssetList, createMintedList, addressToNameMap, setTxSummary]);
}, [tx, walletInfo.addresses, createAssetList, createMintedList, addressToNameMap, setTxSummary, txCollateral]);

const onConfirm = () => {
analytics.sendEventToPostHog(PostHogAction.SendTransactionSummaryConfirmClick, {
Expand Down
32 changes: 32 additions & 0 deletions apps/browser-extension-wallet/src/hooks/useComputeTxCollateral.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Wallet } from '@lace/cardano';
import { createHistoricalOwnInputResolver } from '@src/utils/own-input-resolver';
import { useState, useEffect } from 'react';
import { getCollateral } from '@cardano-sdk/core';
import { ObservableWalletState } from './useWalletState';

export const useComputeTxCollateral = (wallet: ObservableWalletState, tx?: Wallet.Cardano.Tx): bigint | undefined => {
const [txCollateral, setTxCollateral] = useState<bigint>();

useEffect(() => {
if (!tx) return;

const computeCollateral = async () => {
const inputResolver = createHistoricalOwnInputResolver({
addresses: wallet.addresses,
transactions: wallet.transactions
});

const collateral = await getCollateral(
tx,
inputResolver,
wallet.addresses.map((addr) => addr.address)
);

setTxCollateral(collateral);
};

computeCollateral();
}, [tx, wallet]);

return txCollateral;
};
4 changes: 3 additions & 1 deletion apps/browser-extension-wallet/src/lib/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@
"to": "To",
"multipleAddresses": "Multiple addresses",
"pools": "Pool(s)",
"epoch": "Epoch"
"epoch": "Epoch",
"collateral": "Collateral",
"collateralInfo": "Amount set as collateral to cover contract execution failure. In case of no failure collateral remains unspent."
},
"walletNameAndPasswordSetupStep": {
"title": "Let's set up your new wallet",
Expand Down
1 change: 1 addition & 0 deletions packages/cardano/src/wallet/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export type Cip30SignTxSummary = {
type: 'Send' | 'Mint';
mintedAssets?: Cip30SignTxAssetItem[];
burnedAssets?: Cip30SignTxAssetItem[];
collateral?: string;
};

export type Cip30SignTxAssetItem = {
Expand Down
21 changes: 21 additions & 0 deletions packages/core/src/ui/components/ActivityDetail/Collateral.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import { useTranslate } from '@src/ui/hooks';
import { TransactionSummary } from '@lace/ui';

export interface Props {
collateral: string;
amountTransformer: (amount: string) => string;
coinSymbol: string;
}
export const Collateral = ({ collateral, amountTransformer, coinSymbol }: Props): React.ReactElement => {
const { t } = useTranslate();

return (
<TransactionSummary.Amount
amount={`${collateral} ${coinSymbol}`}
fiatPrice={amountTransformer(collateral)}
label={t('package.core.activityDetails.collateral')}
tooltip={t('package.core.activityDetails.collateralInfo')}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ export const TransactionDetails = ({
</div>
</div>
{fee && fee !== '-' && (
<Box mb="$32">
<Box mb="$32" data-testid="tx-fee">
<TransactionFee fee={fee} amountTransformer={amountTransformer} coinSymbol={coinSymbol} />
</Box>
)}
Expand Down

This file was deleted.

36 changes: 8 additions & 28 deletions packages/core/src/ui/components/ActivityDetail/TransactionFee.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
/* eslint-disable no-magic-numbers */
import React from 'react';
import { InfoCircleOutlined } from '@ant-design/icons';
import { Tooltip } from 'antd';
import styles from './TransactionFee.module.scss';
import { ReactComponent as Info } from '../../assets/icons/info-icon.component.svg';
import { useTranslate } from '@src/ui/hooks';
import { TransactionSummary } from '@lace/ui';

export interface TransactionFeeProps {
fee: string;
Expand All @@ -15,28 +11,12 @@ export const TransactionFee = ({ fee, amountTransformer, coinSymbol }: Transacti
const { t } = useTranslate();

return (
<div className={styles.details}>
<div className={styles.txFeeContainer}>
<div className={styles.txfee} data-testid="tx-fee-title">
{t('package.core.activityDetails.transactionFee')}
</div>
<Tooltip title={t('package.core.activityDetails.transactionFeeInfo')}>
{Info ? (
<Info data-testid="tx-fee-tooltip-icon" style={{ fontSize: '18px', color: '#8f97a8', cursor: 'pointer' }} />
) : (
<InfoCircleOutlined />
)}
</Tooltip>
</div>

<div data-testid="tx-fee" className={styles.detail}>
<div className={styles.amount}>
<span data-testid="tx-fee-ada" className={styles.ada}>{`${fee} ${coinSymbol}`}</span>
<span data-testid="tx-fee-fiat" className={styles.fiat}>
{amountTransformer(fee)}
</span>
</div>
</div>
</div>
<TransactionSummary.Amount
amount={`${fee} ${coinSymbol}`}
fiatPrice={amountTransformer(fee)}
label={t('package.core.activityDetails.transactionFee')}
tooltip={t('package.core.activityDetails.transactionFeeInfo')}
data-testid="fee"
/>
);
};
1 change: 1 addition & 0 deletions packages/core/src/ui/components/ActivityDetail/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './ActivityTypeIcon';
export * from './TransactionDetailAsset';
export * from './TransactionInputOutput';
export * from './TransactionFee';
export * from './Collateral';
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,20 @@ import { DappTxAsset, DappTxAssetProps } from './DappTxAsset/DappTxAsset';
import { DappTxOutput, DappTxOutputProps } from './DappTxOutput/DappTxOutput';
import styles from './DappTransaction.module.scss';
import { useTranslate } from '@src/ui/hooks';
import { TransactionFee } from '@ui/components/ActivityDetail';
import { TransactionFee, Collateral } from '@ui/components/ActivityDetail';

type TransactionDetails = {
fee: string;
outputs: DappTxOutputProps[];
type: 'Send' | 'Mint';
mintedAssets?: DappTxAssetProps[];
burnedAssets?: DappTxAssetProps[];
collateral?: string;
};

const amountTransformer = (fiat: { price: number; code: string }) => (ada: string) =>
`${Wallet.util.convertAdaToFiat({ ada, fiat: fiat.price })} ${fiat.code}`;

export interface DappTransactionProps {
/** Transaction details such as type, amount, fee and address */
transaction: TransactionDetails;
Expand All @@ -31,7 +35,7 @@ export interface DappTransactionProps {
}

export const DappTransaction = ({
transaction: { type, outputs, fee, mintedAssets, burnedAssets },
transaction: { type, outputs, fee, mintedAssets, burnedAssets, collateral },
dappInfo,
errorMessage,
fiatCurrencyCode,
Expand Down Expand Up @@ -77,12 +81,23 @@ export const DappTransaction = ({
))}
</>
)}
{collateral && (
<Collateral
collateral={collateral}
amountTransformer={amountTransformer({
price: fiatCurrencyPrice,
code: fiatCurrencyCode
})}
coinSymbol={coinSymbol}
/>
)}
{fee && fee !== '-' && (
<TransactionFee
fee={fee}
amountTransformer={(ada: string) =>
`${Wallet.util.convertAdaToFiat({ ada, fiat: fiatCurrencyPrice })} ${fiatCurrencyCode}`
}
amountTransformer={amountTransformer({
price: fiatCurrencyPrice,
code: fiatCurrencyCode
})}
coinSymbol={coinSymbol}
/>
)}
Expand Down
Loading

0 comments on commit 2d7b410

Please sign in to comment.