diff --git a/apps/browser-extension-wallet/src/features/dapp/components/ConfirmTransaction.tsx b/apps/browser-extension-wallet/src/features/dapp/components/ConfirmTransaction.tsx index 7696cfb23..006298b5b 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/ConfirmTransaction.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/ConfirmTransaction.tsx @@ -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; @@ -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(); @@ -99,11 +101,12 @@ export const ConfirmTransaction = withAddressBookContext((): React.ReactElement const [isConfirmingTx, setIsConfirmingTx] = useState(); const [assetsInfo, setAssetsInfo] = useState(); const [dappInfo, setDappInfo] = useState(); + 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(); // Merge all assets (TokenMaps) from the tx outputs and mint const assetMaps = tx.body?.outputs?.map((output) => output.value.assets) ?? []; @@ -118,7 +121,7 @@ export const ConfirmTransaction = withAddressBookContext((): React.ReactElement } } return [...uniqueAssetIds.values()]; - }, [req]); + }, [tx]); useEffect(() => { if (assetIds?.length > 0) { @@ -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: @@ -203,7 +206,7 @@ export const ConfirmTransaction = withAddressBookContext((): React.ReactElement }; }); }, - [assets, assetsInfo, req] + [assets, assetsInfo, tx] ); const createAssetList = useCallback( @@ -231,7 +234,7 @@ export const ConfirmTransaction = withAddressBookContext((): React.ReactElement const [txSummary, setTxSummary] = useState(); useEffect(() => { - if (!req) { + if (!tx) { setTxSummary(void 0); return; } @@ -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; @@ -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, { diff --git a/apps/browser-extension-wallet/src/hooks/useComputeTxCollateral.ts b/apps/browser-extension-wallet/src/hooks/useComputeTxCollateral.ts new file mode 100644 index 000000000..38000c03c --- /dev/null +++ b/apps/browser-extension-wallet/src/hooks/useComputeTxCollateral.ts @@ -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(); + + 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; +}; diff --git a/apps/browser-extension-wallet/src/lib/translations/en.json b/apps/browser-extension-wallet/src/lib/translations/en.json index 56f43e42e..f239404b5 100644 --- a/apps/browser-extension-wallet/src/lib/translations/en.json +++ b/apps/browser-extension-wallet/src/lib/translations/en.json @@ -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", diff --git a/packages/cardano/src/wallet/types.ts b/packages/cardano/src/wallet/types.ts index 5d1cf1519..946b0e338 100644 --- a/packages/cardano/src/wallet/types.ts +++ b/packages/cardano/src/wallet/types.ts @@ -38,6 +38,7 @@ export type Cip30SignTxSummary = { type: 'Send' | 'Mint'; mintedAssets?: Cip30SignTxAssetItem[]; burnedAssets?: Cip30SignTxAssetItem[]; + collateral?: string; }; export type Cip30SignTxAssetItem = { diff --git a/packages/core/src/ui/components/ActivityDetail/Collateral.tsx b/packages/core/src/ui/components/ActivityDetail/Collateral.tsx new file mode 100644 index 000000000..4b3159a2b --- /dev/null +++ b/packages/core/src/ui/components/ActivityDetail/Collateral.tsx @@ -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 ( + + ); +}; diff --git a/packages/core/src/ui/components/ActivityDetail/TransactionDetails.tsx b/packages/core/src/ui/components/ActivityDetail/TransactionDetails.tsx index bc40229b6..1fa563df5 100644 --- a/packages/core/src/ui/components/ActivityDetail/TransactionDetails.tsx +++ b/packages/core/src/ui/components/ActivityDetail/TransactionDetails.tsx @@ -265,7 +265,7 @@ export const TransactionDetails = ({ {fee && fee !== '-' && ( - + )} diff --git a/packages/core/src/ui/components/ActivityDetail/TransactionFee.module.scss b/packages/core/src/ui/components/ActivityDetail/TransactionFee.module.scss deleted file mode 100644 index 7ba475863..000000000 --- a/packages/core/src/ui/components/ActivityDetail/TransactionFee.module.scss +++ /dev/null @@ -1,109 +0,0 @@ -@import '../../styles/theme.scss'; -@import '../../../../../common/src/ui/styles/abstracts/typography'; - -.txFeeContainer { - display: flex; - align-items: center; - justify-content: center; - gap: size_unit(1); -} - -.txfee { - color: var(--text-color-primary); - font-size: var(--body, 16px); - font-weight: 600; - line-height: size_unit(3); -} - -.details { - color: var(--text-color-primary, #ffffff); - display: flex; - justify-content: space-between; - align-items: flex-start; - width: 100%; - - .title { - display: flex; - flex: 0 0 50%; - align-self: baseline; - color: var(--text-color-primary, #ffffff); - @include text-body-semi-bold; - } - - .detail { - align-items: end; - display: flex; - flex-direction: column; - gap: size_unit(2); - color: var(--text-color-primary, #ffffff); - text-align: right; - word-break: break-all; - @include text-body-medium; - - @media (max-width: $breakpoint-popup) { - flex-direction: column; - } - - &.hash { - @include text-address; - font-weight: 500; - text-align: right; - cursor: pointer; - } - &.txLink { - color: var(--text-color-blue, #3489f7); - line-height: 17px; - @media (max-width: $breakpoint-popup) { - flex: 60%; - } - } - &.poolId { - color: var(--text-color-secondary); - font-size: var(--bodySmall); - font-weight: 500; - line-height: 17px; - } - } - - .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; - line-height: size_unit(2); - text-align: right; - margin-bottom: size_unit(5); - @media (max-width: $breakpoint-popup) { - margin-bottom: size_unit(6); - } - } - - .metadataLabel { - display: flex; - flex: 0 0 50%; - align-self: baseline; - @include text-bodyLarge-bold; - color: var(--text-color-primary); - } -} diff --git a/packages/core/src/ui/components/ActivityDetail/TransactionFee.tsx b/packages/core/src/ui/components/ActivityDetail/TransactionFee.tsx index ba4f3fd62..26312ad81 100644 --- a/packages/core/src/ui/components/ActivityDetail/TransactionFee.tsx +++ b/packages/core/src/ui/components/ActivityDetail/TransactionFee.tsx @@ -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; @@ -15,28 +11,12 @@ export const TransactionFee = ({ fee, amountTransformer, coinSymbol }: Transacti const { t } = useTranslate(); return ( -
-
-
- {t('package.core.activityDetails.transactionFee')} -
- - {Info ? ( - - ) : ( - - )} - -
- -
-
- {`${fee} ${coinSymbol}`} - - {amountTransformer(fee)} - -
-
-
+ ); }; diff --git a/packages/core/src/ui/components/ActivityDetail/index.ts b/packages/core/src/ui/components/ActivityDetail/index.ts index 7ba95eea8..c2ee72854 100644 --- a/packages/core/src/ui/components/ActivityDetail/index.ts +++ b/packages/core/src/ui/components/ActivityDetail/index.ts @@ -4,3 +4,4 @@ export * from './ActivityTypeIcon'; export * from './TransactionDetailAsset'; export * from './TransactionInputOutput'; export * from './TransactionFee'; +export * from './Collateral'; diff --git a/packages/core/src/ui/components/DappTransaction/DappTransaction.tsx b/packages/core/src/ui/components/DappTransaction/DappTransaction.tsx index 9f5717baa..1bde1147a 100644 --- a/packages/core/src/ui/components/DappTransaction/DappTransaction.tsx +++ b/packages/core/src/ui/components/DappTransaction/DappTransaction.tsx @@ -8,7 +8,7 @@ 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; @@ -16,8 +16,12 @@ type TransactionDetails = { 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; @@ -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, @@ -77,12 +81,23 @@ export const DappTransaction = ({ ))} )} + {collateral && ( + + )} {fee && fee !== '-' && ( - `${Wallet.util.convertAdaToFiat({ ada, fiat: fiatCurrencyPrice })} ${fiatCurrencyCode}` - } + amountTransformer={amountTransformer({ + price: fiatCurrencyPrice, + code: fiatCurrencyCode + })} coinSymbol={coinSymbol} /> )} diff --git a/packages/e2e-tests/src/elements/dappConnector/confirmTransactionPage.ts b/packages/e2e-tests/src/elements/dappConnector/confirmTransactionPage.ts index 1472ceed9..772693953 100644 --- a/packages/e2e-tests/src/elements/dappConnector/confirmTransactionPage.ts +++ b/packages/e2e-tests/src/elements/dappConnector/confirmTransactionPage.ts @@ -7,10 +7,10 @@ class ConfirmTransactionPage extends CommonDappPageElements { private TRANSACTION_TYPE = '[data-testid="dapp-transaction-type"]'; private TRANSACTION_AMOUNT_TITLE = '[data-testid="dapp-transaction-amount-title"]'; private TRANSACTION_AMOUNT_VALUE = '[data-testid="dapp-transaction-amount-value"]'; - private TRANSACTION_AMOUNT_FEE_TITLE = '[data-testid="tx-fee-title"]'; - private TRANSACTION_AMOUNT_FEE_TITLE_TOOLTIP_ICON = '[data-testid="tx-fee-tooltip-icon"]'; - private TRANSACTION_AMOUNT_FEE_VALUE_ADA = '[data-testid="tx-fee-ada"]'; - private TRANSACTION_AMOUNT_FEE_VALUE_FIAT = '[data-testid="tx-fee-fiat"]'; + private TRANSACTION_AMOUNT_FEE_TITLE = '[data-testid="tx-amount-fee-label"]'; + private TRANSACTION_AMOUNT_FEE_TITLE_TOOLTIP_ICON = '[data-testid="tx-amount-fee-tooltip-icon"]'; + private TRANSACTION_AMOUNT_FEE_VALUE_ADA = '[data-testid="tx-amount-fee-amount"]'; + private TRANSACTION_AMOUNT_FEE_VALUE_FIAT = '[data-testid="tx-amount-fee-fiat"]'; private TRANSACTION_AMOUNT_ASSET = '[data-testid="dapp-transaction-asset"]'; private TRANSACTION_RECIPIENT_TITLE = '[data-testid="dapp-transaction-recipient-title"]'; private TRANSACTION_RECIPIENT_ADDRESS_TITLE = '[data-testid="dapp-transaction-recipient-address-title"]'; diff --git a/packages/e2e-tests/src/elements/transactionDetails.ts b/packages/e2e-tests/src/elements/transactionDetails.ts index eb3fd6cfb..61a541b8d 100644 --- a/packages/e2e-tests/src/elements/transactionDetails.ts +++ b/packages/e2e-tests/src/elements/transactionDetails.ts @@ -18,8 +18,8 @@ class ActivityDetailsPage extends CommonDrawerElements { private TRANSACTION_DETAILS_TO_ADDRESS = '[data-testid="tx-to-detail"]'; private TRANSACTION_DETAILS_STATUS = '[data-testid="tx-status"]'; private TRANSACTION_DETAILS_TIMESTAMP = '[data-testid="tx-timestamp"]'; - private TRANSACTION_DETAILS_FEE_ADA = '[data-testid="tx-fee-ada"]'; - private TRANSACTION_DETAILS_FEE_FIAT = '[data-testid="tx-fee-fiat"]'; + private TRANSACTION_DETAILS_FEE_ADA = '[data-testid="tx-amount-fee-amount"]'; + private TRANSACTION_DETAILS_FEE_FIAT = '[data-testid="tx-amount-fee-fiat"]'; private TRANSACTION_DETAILS_INPUTS_SECTION = '[data-testid="tx-inputs"]'; private TRANSACTION_DETAILS_OUTPUTS_SECTION = '[data-testid="tx-outputs"]'; private TRANSACTION_DETAILS_DROPDOWN = '[data-testid="tx-addr-list_toggle"]'; diff --git a/packages/ui/src/design-system/table/table-header.component.tsx b/packages/ui/src/design-system/table/table-header.component.tsx index e693a48c6..e150cbb11 100644 --- a/packages/ui/src/design-system/table/table-header.component.tsx +++ b/packages/ui/src/design-system/table/table-header.component.tsx @@ -1,9 +1,10 @@ import React from 'react'; -import { IconButton } from '@lace/ui'; import { Tooltip } from 'antd'; import cn from 'classnames'; +import * as IconButton from '../icon-buttons'; + import * as cx from './table.css'; export interface Headers { diff --git a/packages/ui/src/design-system/table/table-row.component.tsx b/packages/ui/src/design-system/table/table-row.component.tsx index b1c060d8a..3db0bce39 100644 --- a/packages/ui/src/design-system/table/table-row.component.tsx +++ b/packages/ui/src/design-system/table/table-row.component.tsx @@ -1,9 +1,11 @@ /* eslint-disable react/no-multi-comp */ import React from 'react'; -import { Checkbox, Tooltip } from '@lace/ui'; import cn from 'classnames'; +import { Checkbox } from '../checkbox'; +import { Tooltip } from '../tooltip'; + import * as cx from './table.css'; export interface RowProps< diff --git a/packages/ui/src/design-system/transaction-summary/transaction-summary-amount.component.tsx b/packages/ui/src/design-system/transaction-summary/transaction-summary-amount.component.tsx index 7baef80ac..d6bc56c3d 100644 --- a/packages/ui/src/design-system/transaction-summary/transaction-summary-amount.component.tsx +++ b/packages/ui/src/design-system/transaction-summary/transaction-summary-amount.component.tsx @@ -1,7 +1,11 @@ import React from 'react'; +import { ReactComponent as InfoIcon } from '@lace/icons/dist/InfoComponent'; + +import { Box } from '../box'; import { Flex } from '../flex'; import { Grid, Cell } from '../grid'; +import { Tooltip } from '../tooltip'; import * as Typography from '../typography'; import * as cx from './transaction-summary.css'; @@ -10,29 +14,61 @@ import type { OmitClassName } from '../../types'; type Props = OmitClassName<'div'> & { label?: string; + tooltip?: string; amount: string; fiatPrice: string; + 'data-testid'?: string; +}; + +const makeTestId = (namespace = '', path = ''): string | undefined => { + return namespace === '' ? undefined : `tx-amount-${namespace}-${path}`; }; export const Amount = ({ label, amount, fiatPrice, + tooltip, ...props }: Readonly): JSX.Element => { + const testId = props['data-testid']; + return ( - + - - {label} - + + + {label} + + {tooltip !== undefined && ( + + +
+ +
+
+
+ )} +
- + {amount} - + {fiatPrice} diff --git a/packages/ui/src/design-system/transaction-summary/transaction-summary.css.ts b/packages/ui/src/design-system/transaction-summary/transaction-summary.css.ts index 502aa1294..995421544 100644 --- a/packages/ui/src/design-system/transaction-summary/transaction-summary.css.ts +++ b/packages/ui/src/design-system/transaction-summary/transaction-summary.css.ts @@ -24,3 +24,18 @@ export const secondaryText = style([ wordBreak: 'break-all', }, ]); + +export const tooltip = style([ + sx({ + color: '$transaction_summary_secondary_label_color', + width: '$24', + height: '$24', + fontSize: '$25', + }), +]); + +export const tooltipText = style([ + sx({ + display: 'flex', + }), +]); diff --git a/packages/ui/src/design-system/transaction-summary/transaction-summary.stories.tsx b/packages/ui/src/design-system/transaction-summary/transaction-summary.stories.tsx index 25d82548d..46b4c49d8 100644 --- a/packages/ui/src/design-system/transaction-summary/transaction-summary.stories.tsx +++ b/packages/ui/src/design-system/transaction-summary/transaction-summary.stories.tsx @@ -58,6 +58,15 @@ const Example = (): JSX.Element => ( + + +