From 5472186c551830f17c9a39d9360d69feb6db77c7 Mon Sep 17 00:00:00 2001 From: Renan Ferreira Date: Wed, 7 Feb 2024 15:18:37 -0300 Subject: [PATCH 01/12] feat(ui): create info bar component --- packages/ui/src/design-system/index.ts | 1 + .../ui/src/design-system/info-bar/index.ts | 1 + .../info-bar/info-bar.component.tsx | 44 +++++++++ .../design-system/info-bar/info-bar.css.ts | 30 ++++++ .../info-bar/info-bar.stories.tsx | 95 +++++++++++++++++++ packages/ui/src/design-tokens/colors.data.ts | 4 + .../src/design-tokens/theme/dark-theme.css.ts | 4 + .../design-tokens/theme/light-theme.css.ts | 4 + 8 files changed, 183 insertions(+) create mode 100644 packages/ui/src/design-system/info-bar/index.ts create mode 100644 packages/ui/src/design-system/info-bar/info-bar.component.tsx create mode 100644 packages/ui/src/design-system/info-bar/info-bar.css.ts create mode 100644 packages/ui/src/design-system/info-bar/info-bar.stories.tsx diff --git a/packages/ui/src/design-system/index.ts b/packages/ui/src/design-system/index.ts index 6f05636a5..5498d4b91 100644 --- a/packages/ui/src/design-system/index.ts +++ b/packages/ui/src/design-system/index.ts @@ -48,3 +48,4 @@ export { ActionCard } from './action-card'; export { Loader } from './loader'; export * from './auto-suggest-box'; export * from './table'; +export { InfoBar } from './info-bar'; diff --git a/packages/ui/src/design-system/info-bar/index.ts b/packages/ui/src/design-system/info-bar/index.ts new file mode 100644 index 000000000..56542d46e --- /dev/null +++ b/packages/ui/src/design-system/info-bar/index.ts @@ -0,0 +1 @@ +export { InfoBar } from './info-bar.component'; diff --git a/packages/ui/src/design-system/info-bar/info-bar.component.tsx b/packages/ui/src/design-system/info-bar/info-bar.component.tsx new file mode 100644 index 000000000..e46795f0c --- /dev/null +++ b/packages/ui/src/design-system/info-bar/info-bar.component.tsx @@ -0,0 +1,44 @@ +import type { ReactNode } from 'react'; +import React from 'react'; + +import { Box } from '../box'; +import { CallToAction } from '../buttons'; +import { Flex } from '../flex'; +import * as Typography from '../typography'; + +import * as cx from './info-bar.css'; + +export interface Props { + message: string; + icon: ReactNode; + callToAction?: { + label?: string; + onClick?: () => void; + }; +} + +export const InfoBar = ({ + message, + icon, + callToAction, +}: Readonly): JSX.Element => { + return ( + + {icon} + + + {message} + + + + {callToAction && ( + + + + )} + + ); +}; diff --git a/packages/ui/src/design-system/info-bar/info-bar.css.ts b/packages/ui/src/design-system/info-bar/info-bar.css.ts new file mode 100644 index 000000000..659c3d435 --- /dev/null +++ b/packages/ui/src/design-system/info-bar/info-bar.css.ts @@ -0,0 +1,30 @@ +import { style } from '@vanilla-extract/css'; + +import { sx } from '../../design-tokens'; + +export const container = sx({ + px: '$24', + py: '$16', + backgroundColor: '$info_bar_container_bgColor', + borderRadius: '$medium', + alignItems: 'center', +}); + +export const icon = sx({ + width: '$24', + height: '$24', + fontSize: '$25', + mr: '$24', + color: '$info_bar_icon_color', +}); + +export const message = style([ + sx({ + color: '$info_bar_message_color', + mt: '$8', + }), + { + textAlign: 'center', + whiteSpace: 'pre-wrap', + }, +]); diff --git a/packages/ui/src/design-system/info-bar/info-bar.stories.tsx b/packages/ui/src/design-system/info-bar/info-bar.stories.tsx new file mode 100644 index 000000000..ea6ef71aa --- /dev/null +++ b/packages/ui/src/design-system/info-bar/info-bar.stories.tsx @@ -0,0 +1,95 @@ +import React from 'react'; + +import { ReactComponent as InfoIcon } from '@lace/icons/dist/InfoComponent'; + +import { LocalThemeProvider, ThemeColorScheme } from '../../design-tokens'; +import { page, Section, Variants } from '../decorators'; +import { Divider } from '../divider'; +import { Flex } from '../flex'; +import { Cell, Grid } from '../grid'; + +import { InfoBar } from './info-bar.component'; + +const subtitle = + 'The info bar control is for displaying app-wide status messages to users that are highly visible yet non-intrusive. There are built-in severity levels to easily indicate the type of message shown as well as the option to include your own call to action or hyperlink button.'; + +export default { + title: 'Status & info/Info Bar', + component: InfoBar, + decorators: [page({ title: 'Info bar', subtitle })], +}; + +const UsageSample = ({ + withCTA = false, +}: Readonly<{ withCTA?: boolean }>): JSX.Element => ( + } + message={`Lorem ipsum dolor sit amet, consectetuer adipiscing elit\nmaecenas porttitor congue massa. Fusce posuere, magna.`} + callToAction={ + withCTA + ? { + label: 'Label', + } + : undefined + } + /> +); + +const VariantsTable = (): JSX.Element => ( +
+ + + + + + + + + + +
+); + +const MainComponents = (): JSX.Element => ( + <> + + + + + + + + + + + +); + +export const Overview = (): JSX.Element => ( + + +
+ + + +
+ + + + + + + +
+ + + + + + + + +
+
+
+); diff --git a/packages/ui/src/design-tokens/colors.data.ts b/packages/ui/src/design-tokens/colors.data.ts index de7ca2fe9..54045cc9a 100644 --- a/packages/ui/src/design-tokens/colors.data.ts +++ b/packages/ui/src/design-tokens/colors.data.ts @@ -294,6 +294,10 @@ export const colors = { $stake_pool_header_text_color: '', $stake_pool_item_bg_hover: '', $stake_pool_item_text_color: '', + + $info_bar_container_bgColor: '', + $info_bar_message_color: '', + $info_bar_icon_color: '', }; export type Colors = typeof colors; diff --git a/packages/ui/src/design-tokens/theme/dark-theme.css.ts b/packages/ui/src/design-tokens/theme/dark-theme.css.ts index bb8aa7c03..e76a27daa 100644 --- a/packages/ui/src/design-tokens/theme/dark-theme.css.ts +++ b/packages/ui/src/design-tokens/theme/dark-theme.css.ts @@ -393,6 +393,10 @@ const colors: Colors = { $stake_pool_item_bg_hover: darkColorScheme.$primary_dark_grey_plus, $stake_pool_header_text_color: darkColorScheme.$primary_white, $stake_pool_item_text_color: darkColorScheme.$primary_light_grey, + + $info_bar_container_bgColor: darkColorScheme.$primary_dark_grey_plus, + $info_bar_icon_color: darkColorScheme.$secondary_data_pink, + $info_bar_message_color: darkColorScheme.$primary_light_grey, }; const elevation: Elevation = { diff --git a/packages/ui/src/design-tokens/theme/light-theme.css.ts b/packages/ui/src/design-tokens/theme/light-theme.css.ts index b026d6cb9..6685d847e 100644 --- a/packages/ui/src/design-tokens/theme/light-theme.css.ts +++ b/packages/ui/src/design-tokens/theme/light-theme.css.ts @@ -419,6 +419,10 @@ const colors: Colors = { $stake_pool_item_bg_hover: lightColorScheme.$primary_light_grey, $stake_pool_header_text_color: lightColorScheme.$primary_dark_grey, $stake_pool_item_text_color: lightColorScheme.$primary_black, + + $info_bar_container_bgColor: lightColorScheme.$secondary_cream, + $info_bar_icon_color: lightColorScheme.$secondary_data_pink, + $info_bar_message_color: lightColorScheme.$primary_black, }; export const elevation: Elevation = { From 7d914ad8a44a9078a777bb9b119f87d5b1430955 Mon Sep 17 00:00:00 2001 From: Renan Ferreira Date: Wed, 14 Feb 2024 13:52:45 -0300 Subject: [PATCH 02/12] fix(ui): add zIndex property to tooltip --- .../src/design-system/tooltip/tooltip-root.component.tsx | 3 ++- packages/ui/src/design-system/tooltip/tooltip-root.css.ts | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 packages/ui/src/design-system/tooltip/tooltip-root.css.ts diff --git a/packages/ui/src/design-system/tooltip/tooltip-root.component.tsx b/packages/ui/src/design-system/tooltip/tooltip-root.component.tsx index 3470baba5..f060d5b04 100644 --- a/packages/ui/src/design-system/tooltip/tooltip-root.component.tsx +++ b/packages/ui/src/design-system/tooltip/tooltip-root.component.tsx @@ -4,6 +4,7 @@ import React from 'react'; import * as Tooltip from '@radix-ui/react-tooltip'; import { TooltipContent } from './tooltip-content.component'; +import * as cx from './tooltip-root.css'; export type Props = PropsWithChildren< Pick & @@ -21,7 +22,7 @@ export const Root = ({ {children} - + diff --git a/packages/ui/src/design-system/tooltip/tooltip-root.css.ts b/packages/ui/src/design-system/tooltip/tooltip-root.css.ts new file mode 100644 index 000000000..2b1467d10 --- /dev/null +++ b/packages/ui/src/design-system/tooltip/tooltip-root.css.ts @@ -0,0 +1,7 @@ +import { style, sx } from '../../design-tokens'; + +export const root = style([ + { + zIndex: 9999, + }, +]); From 2e916959aea1d19aafdaa8040755a6cd81b5c9fc Mon Sep 17 00:00:00 2001 From: Renan Ferreira Date: Wed, 14 Feb 2024 13:57:17 -0300 Subject: [PATCH 03/12] feat(core): add collateral tooltip and info bar --- .../components/ActivityDetail/Collateral.tsx | 49 ++++++++++++++++--- .../ActivityDetail/TransactionDetails.tsx | 32 +++++++++++- packages/core/src/ui/lib/translations/en.json | 8 +++ 3 files changed, 80 insertions(+), 9 deletions(-) diff --git a/packages/core/src/ui/components/ActivityDetail/Collateral.tsx b/packages/core/src/ui/components/ActivityDetail/Collateral.tsx index 4b3159a2b..a939e5e61 100644 --- a/packages/core/src/ui/components/ActivityDetail/Collateral.tsx +++ b/packages/core/src/ui/components/ActivityDetail/Collateral.tsx @@ -1,21 +1,54 @@ import React from 'react'; import { useTranslate } from '@src/ui/hooks'; -import { TransactionSummary } from '@lace/ui'; +import { Box, TransactionSummary, InfoBar } from '@lace/ui'; +import { ReactComponent as InfoIcon } from '@lace/icons/dist/InfoComponent'; + +export enum CollateralStatus { + REVIEW = 'review', + SUCCESS = 'success', + ERROR = 'error' +} export interface Props { collateral: string; amountTransformer: (amount: string) => string; coinSymbol: string; + status?: CollateralStatus; } -export const Collateral = ({ collateral, amountTransformer, coinSymbol }: Props): React.ReactElement => { + +export const Collateral = ({ + collateral, + amountTransformer, + coinSymbol, + status = CollateralStatus.REVIEW +}: Props): React.ReactElement => { const { t } = useTranslate(); + const tooltip = (): string => { + switch (status) { + case 'review': + case 'error': + return t('package.core.activityDetails.collateral.tooltip.info'); + case 'success': + return t('package.core.activityDetails.collateral.tooltip.success'); + } + + return ''; + }; + return ( - + <> + + {status === CollateralStatus.ERROR && ( + + } message={t('package.core.activityDetails.collateral.error')} /> + + )} + ); }; diff --git a/packages/core/src/ui/components/ActivityDetail/TransactionDetails.tsx b/packages/core/src/ui/components/ActivityDetail/TransactionDetails.tsx index ecd094a3c..011883470 100644 --- a/packages/core/src/ui/components/ActivityDetail/TransactionDetails.tsx +++ b/packages/core/src/ui/components/ActivityDetail/TransactionDetails.tsx @@ -20,6 +20,7 @@ import { TxDetails, TxDetail } from './types'; +import { Collateral, CollateralStatus } from './Collateral'; // eslint-disable-next-line @typescript-eslint/no-explicit-any const displayMetadataMsg = (value: any[]): string => value?.find((val: any) => val.hasOwnProperty('msg'))?.msg || ''; @@ -49,6 +50,10 @@ export interface TransactionDetailsProps { * Transaction total output */ totalOutput?: string; + /** + * Transaction collateral + */ + collateral?: string; /** * Transaction fee */ @@ -125,7 +130,8 @@ export const TransactionDetails = ({ sendAnalyticsOutputs, proposalProcedures, votingProcedures, - certificates + certificates, + collateral }: TransactionDetailsProps): React.ReactElement => { const { t } = useTranslate(); @@ -219,6 +225,20 @@ export const TransactionDetails = ({ ) ); + const getCollateralStatus = (): CollateralStatus => { + switch (status) { + // TODO: HOW ABOUT ActivityStatus.SPENDABLE?? + case ActivityStatus.PENDING: + return CollateralStatus.REVIEW; + case ActivityStatus.SUCCESS: + return CollateralStatus.SUCCESS; + case ActivityStatus.ERROR: + return CollateralStatus.ERROR; + default: + return CollateralStatus.REVIEW; + } + }; + const renderDepositValueSection = ({ value, label }: { value: string; label: string }) => (
{label}
@@ -370,6 +390,16 @@ export const TransactionDetails = ({  {includedTime}
+ {collateral && ( + + + + )} {fee && fee !== '-' && ( diff --git a/packages/core/src/ui/lib/translations/en.json b/packages/core/src/ui/lib/translations/en.json index 5a8c48b60..73abab32d 100644 --- a/packages/core/src/ui/lib/translations/en.json +++ b/packages/core/src/ui/lib/translations/en.json @@ -115,6 +115,14 @@ "copiedToClipboard": "Copied to clipboard", "pools": "Pool(s)", "self": "Self Transaction", + "collateral": { + "label": "Collateral", + "tooltip": { + "info": "Amount set as collateral to cover contract execution failure. In case of no failure collateral remains unspent.", + "success": "Amount set as collateral. Since this Tx was successful collateral was not used" + }, + "error": "Since the transaction was unsuccessful, collateral has been used to cover the costs incurred from the contract execution failure." + }, "RegisterDelegateRepresentativeCertificate": "DRep Registration", "UnregisterDelegateRepresentativeCertificate": "DRep De-Registration", "UpdateDelegateRepresentativeCertificate": "DRep Update", From c7ee6fb7669cec728ae59d291bf37794bb2298e4 Mon Sep 17 00:00:00 2001 From: Renan Ferreira Date: Wed, 14 Feb 2024 13:58:35 -0300 Subject: [PATCH 04/12] feat(extension): display collateral info on activity details --- .../src/lib/translations/en.json | 10 +++++++-- .../stores/slices/activity-detail-slice.ts | 22 ++++++++++++++++++- .../src/types/activity-detail.ts | 1 + .../components/TransactionDetailsProxy.tsx | 4 +++- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/apps/browser-extension-wallet/src/lib/translations/en.json b/apps/browser-extension-wallet/src/lib/translations/en.json index a41de159a..cff2260f4 100644 --- a/apps/browser-extension-wallet/src/lib/translations/en.json +++ b/apps/browser-extension-wallet/src/lib/translations/en.json @@ -88,8 +88,14 @@ "multipleAddresses": "Multiple addresses", "pools": "Pool(s)", "epoch": "Epoch", - "collateral": "Collateral", - "collateralInfo": "Amount set as collateral to cover contract execution failure. In case of no failure collateral remains unspent." + "collateral": { + "label": "Collateral", + "tooltip": { + "info": "Amount set as collateral to cover contract execution failure. In case of no failure collateral remains unspent.", + "success": "Amount set as collateral. Since this Tx was successful collateral was not used" + }, + "error": "Since the transaction was unsuccessful, collateral has been used to cover the costs incurred from the contract execution failure." + } }, "walletNameAndPasswordSetupStep": { "title": "Let's set up your new wallet", diff --git a/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts b/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts index 386dd7210..928eaf97d 100644 --- a/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts +++ b/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts @@ -24,6 +24,8 @@ import { governanceProposalsTransformer, votingProceduresTransformer } from '@src/views/browser-view/features/activity/helpers/common-tx-transformer'; +import { createHistoricalOwnInputResolver } from '@src/utils/own-input-resolver'; +import { getCollateral } from '@cardano-sdk/core'; /** * validates if the transaction is confirmed @@ -89,6 +91,21 @@ const getPoolInfos = async (poolIds: Wallet.Cardano.PoolId[], stakePoolProvider: return pools; }; +const computeCollateral = async (wallet: Wallet.ObservableWallet, tx?: Wallet.Cardano.Tx): Promise => { + const addresses = await firstValueFrom(wallet.addresses$); + + const inputResolver = createHistoricalOwnInputResolver({ + addresses$: wallet.addresses$, + transactionsHistory$: wallet.transactions.history$ + }); + + return await getCollateral( + tx, + inputResolver, + addresses.map((addr) => addr.address) + ); +}; + /** * fetches asset information */ @@ -204,6 +221,8 @@ const buildGetActivityDetail = ? Wallet.util.lovelacesToAdaString(depositReclaimValue.toString()) : undefined; const feeInAda = Wallet.util.lovelacesToAdaString(tx.body.fee.toString()); + const collateral = await computeCollateral(wallet, tx); + const collateralInAda = Wallet.util.lovelacesToAdaString(collateral.toString()); // Delegation tx additional data (LW-3324) @@ -230,7 +249,8 @@ const buildGetActivityDetail = fiatCurrency, proposalProcedures: tx.body.proposalProcedures }), - certificates: certificateTransformer(cardanoCoin, coinPrices, fiatCurrency, tx.body.certificates) + certificates: certificateTransformer(cardanoCoin, coinPrices, fiatCurrency, tx.body.certificates), + collateral: collateral > 0 ? collateralInAda : undefined }; if (type === DelegationActivityType.delegation && delegationInfo) { diff --git a/apps/browser-extension-wallet/src/types/activity-detail.ts b/apps/browser-extension-wallet/src/types/activity-detail.ts index eb0339f6a..6927a1011 100644 --- a/apps/browser-extension-wallet/src/types/activity-detail.ts +++ b/apps/browser-extension-wallet/src/types/activity-detail.ts @@ -31,6 +31,7 @@ type TransactionActivity = { includedUtcTime?: string; totalOutput?: string; fee?: string; + collateral?: string; depositReclaim?: string; deposit?: string; addrInputs?: TxOutputInput[]; diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/TransactionDetailsProxy.tsx b/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/TransactionDetailsProxy.tsx index fb8919949..412cb9a52 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/TransactionDetailsProxy.tsx +++ b/apps/browser-extension-wallet/src/views/browser-view/features/activity/components/TransactionDetailsProxy.tsx @@ -52,7 +52,8 @@ export const TransactionDetailsProxy = withAddressBookContext( metadata, proposalProcedures, votingProcedures, - certificates + certificates, + collateral } = activityInfo.activity; const txSummary = useMemo( () => @@ -104,6 +105,7 @@ export const TransactionDetailsProxy = withAddressBookContext( certificates={certificates} handleOpenExternalHashLink={handleOpenExternalHashLink} openExternalLink={openExternalLink} + collateral={collateral} /> ); } From 8247c03d18b0d6a3269b8e19ff0567d1fc1624ae Mon Sep 17 00:00:00 2001 From: Renan Ferreira Date: Wed, 14 Feb 2024 15:32:19 -0300 Subject: [PATCH 05/12] refactor(extension): use wallet state --- .../src/stores/slices/activity-detail-slice.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts b/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts index 928eaf97d..dc59ab125 100644 --- a/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts +++ b/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts @@ -26,6 +26,7 @@ import { } from '@src/views/browser-view/features/activity/helpers/common-tx-transformer'; import { createHistoricalOwnInputResolver } from '@src/utils/own-input-resolver'; import { getCollateral } from '@cardano-sdk/core'; +import { ObservableWalletState } from '@hooks/useWalletState'; /** * validates if the transaction is confirmed @@ -91,18 +92,16 @@ const getPoolInfos = async (poolIds: Wallet.Cardano.PoolId[], stakePoolProvider: return pools; }; -const computeCollateral = async (wallet: Wallet.ObservableWallet, tx?: Wallet.Cardano.Tx): Promise => { - const addresses = await firstValueFrom(wallet.addresses$); - +const computeCollateral = async (wallet: ObservableWalletState, tx?: Wallet.Cardano.Tx): Promise => { const inputResolver = createHistoricalOwnInputResolver({ - addresses$: wallet.addresses$, - transactionsHistory$: wallet.transactions.history$ + addresses: wallet.addresses, + transactions: wallet.transactions }); return await getCollateral( tx, inputResolver, - addresses.map((addr) => addr.address) + wallet.addresses.map((addr) => addr.address) ); }; @@ -123,7 +122,8 @@ const buildGetActivityDetail = inMemoryWallet: wallet, walletUI: { cardanoCoin }, activityDetail, - walletInfo + walletInfo, + walletState } = get(); set({ fetchingActivityInfo: true }); @@ -221,7 +221,7 @@ const buildGetActivityDetail = ? Wallet.util.lovelacesToAdaString(depositReclaimValue.toString()) : undefined; const feeInAda = Wallet.util.lovelacesToAdaString(tx.body.fee.toString()); - const collateral = await computeCollateral(wallet, tx); + const collateral = await computeCollateral(walletState, tx); const collateralInAda = Wallet.util.lovelacesToAdaString(collateral.toString()); // Delegation tx additional data (LW-3324) From b4e55084a23d23844e350bf4867af1d9d155208d Mon Sep 17 00:00:00 2001 From: Renan Ferreira Date: Mon, 19 Feb 2024 09:34:42 -0300 Subject: [PATCH 06/12] refactor(extension): reduce argument scope --- .../src/stores/slices/activity-detail-slice.ts | 14 ++++++++------ .../src/utils/own-input-resolver.ts | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts b/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts index dc59ab125..783c4c447 100644 --- a/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts +++ b/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts @@ -24,9 +24,8 @@ import { governanceProposalsTransformer, votingProceduresTransformer } from '@src/views/browser-view/features/activity/helpers/common-tx-transformer'; -import { createHistoricalOwnInputResolver } from '@src/utils/own-input-resolver'; +import { createHistoricalOwnInputResolver, HistoricalOwnInputResolverArgs } from '@src/utils/own-input-resolver'; import { getCollateral } from '@cardano-sdk/core'; -import { ObservableWalletState } from '@hooks/useWalletState'; /** * validates if the transaction is confirmed @@ -92,16 +91,19 @@ const getPoolInfos = async (poolIds: Wallet.Cardano.PoolId[], stakePoolProvider: return pools; }; -const computeCollateral = async (wallet: ObservableWalletState, tx?: Wallet.Cardano.Tx): Promise => { +const computeCollateral = async ( + { addresses, transactions }: HistoricalOwnInputResolverArgs, + tx?: Wallet.Cardano.Tx +): Promise => { const inputResolver = createHistoricalOwnInputResolver({ - addresses: wallet.addresses, - transactions: wallet.transactions + addresses, + transactions }); return await getCollateral( tx, inputResolver, - wallet.addresses.map((addr) => addr.address) + addresses.map((addr) => addr.address) ); }; diff --git a/apps/browser-extension-wallet/src/utils/own-input-resolver.ts b/apps/browser-extension-wallet/src/utils/own-input-resolver.ts index d8b54fd46..0cf2b6e10 100644 --- a/apps/browser-extension-wallet/src/utils/own-input-resolver.ts +++ b/apps/browser-extension-wallet/src/utils/own-input-resolver.ts @@ -2,14 +2,14 @@ import { ObservableWalletState } from '@hooks/useWalletState'; import { Wallet } from '@lace/cardano'; -type Args = Pick & { +export type HistoricalOwnInputResolverArgs = Pick & { transactions: Pick; }; export const createHistoricalOwnInputResolver = ({ transactions: { history: txs }, addresses -}: Args): Wallet.Cardano.InputResolver => ({ +}: HistoricalOwnInputResolverArgs): Wallet.Cardano.InputResolver => ({ async resolveInput({ txId, index }: Wallet.Cardano.TxIn) { for (const tx of txs) { if (txId !== tx.id) { From 11e41b388e1bf8cbaae5dbef8b59aa0ad42393f3 Mon Sep 17 00:00:00 2001 From: Renan Ferreira Date: Mon, 19 Feb 2024 09:45:33 -0300 Subject: [PATCH 07/12] refactor(extension): move ternary operation to variable declaration --- .../src/stores/slices/activity-detail-slice.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts b/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts index 783c4c447..1e315e1b1 100644 --- a/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts +++ b/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts @@ -224,7 +224,7 @@ const buildGetActivityDetail = : undefined; const feeInAda = Wallet.util.lovelacesToAdaString(tx.body.fee.toString()); const collateral = await computeCollateral(walletState, tx); - const collateralInAda = Wallet.util.lovelacesToAdaString(collateral.toString()); + const collateralInAda = collateral > 0 ? Wallet.util.lovelacesToAdaString(collateral.toString()) : undefined; // Delegation tx additional data (LW-3324) @@ -252,7 +252,7 @@ const buildGetActivityDetail = proposalProcedures: tx.body.proposalProcedures }), certificates: certificateTransformer(cardanoCoin, coinPrices, fiatCurrency, tx.body.certificates), - collateral: collateral > 0 ? collateralInAda : undefined + collateral: collateralInAda }; if (type === DelegationActivityType.delegation && delegationInfo) { From ef4a35b8ab39abceb26bd14ae8d7d9da97a59fff Mon Sep 17 00:00:00 2001 From: Renan Ferreira Date: Mon, 19 Feb 2024 09:47:31 -0300 Subject: [PATCH 08/12] refactor(core): rename tooltip function --- packages/core/src/ui/components/ActivityDetail/Collateral.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/ui/components/ActivityDetail/Collateral.tsx b/packages/core/src/ui/components/ActivityDetail/Collateral.tsx index a939e5e61..f9a824830 100644 --- a/packages/core/src/ui/components/ActivityDetail/Collateral.tsx +++ b/packages/core/src/ui/components/ActivityDetail/Collateral.tsx @@ -24,7 +24,7 @@ export const Collateral = ({ }: Props): React.ReactElement => { const { t } = useTranslate(); - const tooltip = (): string => { + const getTooltipText = (): string => { switch (status) { case 'review': case 'error': @@ -42,7 +42,7 @@ export const Collateral = ({ amount={`${collateral} ${coinSymbol}`} fiatPrice={amountTransformer(collateral)} label={t('package.core.activityDetails.collateral.label')} - tooltip={tooltip()} + tooltip={getTooltipText()} /> {status === CollateralStatus.ERROR && ( From 48184ef87a7e154e0f257b731c5ef6f6ef55559d Mon Sep 17 00:00:00 2001 From: Renan Ferreira Date: Mon, 19 Feb 2024 09:50:14 -0300 Subject: [PATCH 09/12] refactor(ui): remove unused variable --- packages/ui/src/design-system/tooltip/tooltip-root.css.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/design-system/tooltip/tooltip-root.css.ts b/packages/ui/src/design-system/tooltip/tooltip-root.css.ts index 2b1467d10..6c4043a7a 100644 --- a/packages/ui/src/design-system/tooltip/tooltip-root.css.ts +++ b/packages/ui/src/design-system/tooltip/tooltip-root.css.ts @@ -1,4 +1,4 @@ -import { style, sx } from '../../design-tokens'; +import { style } from '../../design-tokens'; export const root = style([ { From b6dfeb487b67ce164ca81e42e96bbdea8dafc2e2 Mon Sep 17 00:00:00 2001 From: Renan Ferreira Date: Mon, 19 Feb 2024 10:46:49 -0300 Subject: [PATCH 10/12] refactor(core): ignore spendable activity status --- packages/core/src/ui/components/ActivityDetail/Collateral.tsx | 3 ++- .../src/ui/components/ActivityDetail/TransactionDetails.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/core/src/ui/components/ActivityDetail/Collateral.tsx b/packages/core/src/ui/components/ActivityDetail/Collateral.tsx index f9a824830..763f165d1 100644 --- a/packages/core/src/ui/components/ActivityDetail/Collateral.tsx +++ b/packages/core/src/ui/components/ActivityDetail/Collateral.tsx @@ -6,7 +6,8 @@ import { ReactComponent as InfoIcon } from '@lace/icons/dist/InfoComponent'; export enum CollateralStatus { REVIEW = 'review', SUCCESS = 'success', - ERROR = 'error' + ERROR = 'error', + NONE = 'none' } export interface Props { diff --git a/packages/core/src/ui/components/ActivityDetail/TransactionDetails.tsx b/packages/core/src/ui/components/ActivityDetail/TransactionDetails.tsx index 011883470..4cd059b35 100644 --- a/packages/core/src/ui/components/ActivityDetail/TransactionDetails.tsx +++ b/packages/core/src/ui/components/ActivityDetail/TransactionDetails.tsx @@ -227,7 +227,8 @@ export const TransactionDetails = ({ const getCollateralStatus = (): CollateralStatus => { switch (status) { - // TODO: HOW ABOUT ActivityStatus.SPENDABLE?? + case ActivityStatus.SPENDABLE: + return CollateralStatus.NONE; case ActivityStatus.PENDING: return CollateralStatus.REVIEW; case ActivityStatus.SUCCESS: From 1210a21b873ee85ed88a4946446a7a9ff100d68a Mon Sep 17 00:00:00 2001 From: Renan Ferreira Date: Tue, 12 Mar 2024 13:14:30 -0300 Subject: [PATCH 11/12] feat(extension): display collateral balance --- .../stores/slices/activity-detail-slice.ts | 15 +++- .../src/utils/tx-inspection.ts | 6 +- .../activity/helpers/common-tx-transformer.ts | 87 ++++++++++++++++--- 3 files changed, 90 insertions(+), 18 deletions(-) diff --git a/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts b/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts index 1e315e1b1..1a5f75e75 100644 --- a/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts +++ b/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts @@ -61,17 +61,26 @@ const transactionMetadataTransformer = ( ): TransactionActivityDetail['activity']['metadata'] => [...metadata.entries()].map(([key, value]) => ({ key: key.toString(), value: Wallet.cardanoMetadatumToObj(value) })); +const hasPhase2ValidationFailed = (tx: Wallet.Cardano.HydratedTx | Wallet.Cardano.Tx) => + 'inputSource' in tx && tx.inputSource === Wallet.Cardano.InputSource.collaterals; + const shouldIncludeFee = ( + tx: Wallet.Cardano.HydratedTx | Wallet.Cardano.Tx, type: ActivityType, delegationInfo: Wallet.Cardano.StakeDelegationCertificate[] | undefined -) => - !( +) => { + if (hasPhase2ValidationFailed(tx)) { + return false; + } + + return !( type === DelegationActivityType.delegationRegistration || // Existence of any (new) delegationInfo means that this "de-registration" // activity is accompanied by a "delegation" activity, which carries the fees. // However, fees should be shown if de-registration activity is standalone. (type === DelegationActivityType.delegationDeregistration && !!delegationInfo?.length) ); +}; const getPoolInfos = async (poolIds: Wallet.Cardano.PoolId[], stakePoolProvider: Wallet.StakePoolProvider) => { const filters: Wallet.QueryStakePoolsArgs = { @@ -235,7 +244,7 @@ const buildGetActivityDetail = let transaction: ActivityDetail['activity'] = { hash: tx.id.toString(), totalOutput: totalOutputInAda, - fee: shouldIncludeFee(type, delegationInfo) ? feeInAda : undefined, + fee: shouldIncludeFee(tx, type, delegationInfo) ? feeInAda : undefined, deposit, depositReclaim, addrInputs: inputs, diff --git a/apps/browser-extension-wallet/src/utils/tx-inspection.ts b/apps/browser-extension-wallet/src/utils/tx-inspection.ts index 3cdcfec00..f36a72a0b 100644 --- a/apps/browser-extension-wallet/src/utils/tx-inspection.ts +++ b/apps/browser-extension-wallet/src/utils/tx-inspection.ts @@ -19,7 +19,7 @@ import { } from '@lace/core'; import { TxDirection, TxDirections } from '@src/types'; -const { CertificateType, GovernanceActionType, Vote, VoterType } = Wallet.Cardano; +const { CertificateType, GovernanceActionType, Vote, VoterType, InputSource } = Wallet.Cardano; const hasWalletStakeAddress = ( withdrawals: Wallet.Cardano.HydratedTx['body']['withdrawals'], @@ -142,6 +142,10 @@ export const inspectTxType = async ({ tx: Wallet.Cardano.HydratedTx; inputResolver: Wallet.Cardano.InputResolver; }): Promise> => { + if (tx.inputSource === InputSource.collaterals) { + return TransactionActivityType.outgoing; + } + const { paymentAddresses, rewardAccounts } = getWalletAccounts(walletAddresses); const inspectionProperties = await createTxInspector({ diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/common-tx-transformer.ts b/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/common-tx-transformer.ts index fa3c62961..426d8d192 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/common-tx-transformer.ts +++ b/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/common-tx-transformer.ts @@ -25,7 +25,7 @@ import { PriceResult } from '@hooks'; import { formatPercentages } from '@lace/common'; import { depositPaidWithSymbol } from '@src/features/dapp/components/confirm-transaction/utils'; -const { util, GovernanceActionType, PlutusLanguageVersion, CertificateType } = Wallet.Cardano; +const { util, GovernanceActionType, PlutusLanguageVersion, CertificateType, InputSource } = Wallet.Cardano; export interface TxTransformerInput { tx: Wallet.TxInFlight | Wallet.Cardano.HydratedTx; @@ -98,7 +98,17 @@ const splitDelegationTx = (tx: TransformedActivity): TransformedTransactionActiv ]; }; -const transformTransactionStatus = (status: Wallet.TransactionStatus): ActivityStatus => { +const hasPhase2ValidationFailed = (tx: Wallet.TxInFlight | Wallet.Cardano.HydratedTx) => + 'inputSource' in tx && tx.inputSource === InputSource.collaterals; + +const transformTransactionStatus = ( + tx: Wallet.TxInFlight | Wallet.Cardano.HydratedTx, + status: Wallet.TransactionStatus +): ActivityStatus => { + if (hasPhase2ValidationFailed(tx)) { + return ActivityStatus.ERROR; + } + const statuses = { [Wallet.TransactionStatus.PENDING]: ActivityStatus.PENDING, [Wallet.TransactionStatus.ERROR]: ActivityStatus.ERROR, @@ -107,6 +117,53 @@ const transformTransactionStatus = (status: Wallet.TransactionStatus): ActivityS }; return statuses[status]; }; + +type GetTxFormattedAmount = ( + args: Pick< + TxTransformerInput, + 'walletAddresses' | 'tx' | 'direction' | 'resolveInput' | 'cardanoCoin' | 'fiatCurrency' | 'fiatPrice' + > +) => Promise<{ + amount: string; + fiatAmount: string; +}>; + +const getTxFormattedAmount: GetTxFormattedAmount = async ({ + resolveInput, + tx, + walletAddresses, + direction, + cardanoCoin, + fiatCurrency, + fiatPrice +}) => { + if (hasPhase2ValidationFailed(tx)) { + return { + amount: Wallet.util.getFormattedAmount({ amount: tx.body.totalCollateral.toString(), cardanoCoin }), + fiatAmount: getFormattedFiatAmount({ + amount: new BigNumber(tx.body.totalCollateral?.toString() ?? '0'), + fiatCurrency, + fiatPrice + }) + }; + } + + const outputAmount = await getTransactionTotalAmount({ + addresses: walletAddresses, + inputs: tx.body.inputs, + outputs: tx.body.outputs, + fee: tx.body.fee, + direction, + withdrawals: tx.body.withdrawals, + resolveInput + }); + + return { + amount: Wallet.util.getFormattedAmount({ amount: outputAmount.toString(), cardanoCoin }), + fiatAmount: getFormattedFiatAmount({ amount: outputAmount, fiatCurrency, fiatPrice }) + }; +}; + /** Simplifies the transaction object to be used in the activity list @@ -145,15 +202,7 @@ export const txTransformer = async ({ tx: tx as unknown as Wallet.Cardano.HydratedTx, direction }); - const outputAmount = await getTransactionTotalAmount({ - addresses: walletAddresses, - inputs: tx.body.inputs, - outputs: tx.body.outputs, - fee: tx.body.fee, - direction, - withdrawals: tx.body.withdrawals, - resolveInput - }); + const formattedDate = dayjs().isSame(date, 'day') ? 'Today' : formatDate({ date, format: 'DD MMMM YYYY', type: 'local' }); @@ -168,14 +217,24 @@ export const txTransformer = async ({ .sort((a, b) => Number(b.val) - Number(a.val)) : []; + const formattedAmount = await getTxFormattedAmount({ + cardanoCoin, + fiatCurrency, + resolveInput, + tx, + walletAddresses, + direction, + fiatPrice + }); + const baseTransformedActivity = { id: tx.id.toString(), deposit, depositReclaim, fee: Wallet.util.lovelacesToAdaString(tx.body.fee.toString()), - status: transformTransactionStatus(status), - amount: Wallet.util.getFormattedAmount({ amount: outputAmount.toString(), cardanoCoin }), - fiatAmount: getFormattedFiatAmount({ amount: outputAmount, fiatCurrency, fiatPrice }), + status: transformTransactionStatus(tx, status), + amount: formattedAmount.amount, + fiatAmount: formattedAmount.fiatAmount, assets: assetsEntries, assetsNumber: (assets?.size ?? 0) + 1, date, From 20f0b36e3525a5afc610f377a2b09fc6004c9d00 Mon Sep 17 00:00:00 2001 From: Renan Ferreira Date: Mon, 18 Mar 2024 09:40:06 -0300 Subject: [PATCH 12/12] refactor(extension): move phase 2 validation func to utils --- .../src/stores/slices/activity-detail-slice.ts | 4 +--- .../browser-extension-wallet/src/utils/phase2-validation.ts | 5 +++++ .../features/activity/helpers/common-tx-transformer.ts | 6 ++---- 3 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 apps/browser-extension-wallet/src/utils/phase2-validation.ts diff --git a/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts b/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts index 1a5f75e75..f23a0dc73 100644 --- a/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts +++ b/apps/browser-extension-wallet/src/stores/slices/activity-detail-slice.ts @@ -26,6 +26,7 @@ import { } from '@src/views/browser-view/features/activity/helpers/common-tx-transformer'; import { createHistoricalOwnInputResolver, HistoricalOwnInputResolverArgs } from '@src/utils/own-input-resolver'; import { getCollateral } from '@cardano-sdk/core'; +import { hasPhase2ValidationFailed } from '@src/utils/phase2-validation'; /** * validates if the transaction is confirmed @@ -61,9 +62,6 @@ const transactionMetadataTransformer = ( ): TransactionActivityDetail['activity']['metadata'] => [...metadata.entries()].map(([key, value]) => ({ key: key.toString(), value: Wallet.cardanoMetadatumToObj(value) })); -const hasPhase2ValidationFailed = (tx: Wallet.Cardano.HydratedTx | Wallet.Cardano.Tx) => - 'inputSource' in tx && tx.inputSource === Wallet.Cardano.InputSource.collaterals; - const shouldIncludeFee = ( tx: Wallet.Cardano.HydratedTx | Wallet.Cardano.Tx, type: ActivityType, diff --git a/apps/browser-extension-wallet/src/utils/phase2-validation.ts b/apps/browser-extension-wallet/src/utils/phase2-validation.ts new file mode 100644 index 000000000..eb1d3639f --- /dev/null +++ b/apps/browser-extension-wallet/src/utils/phase2-validation.ts @@ -0,0 +1,5 @@ +import { Wallet } from '@lace/cardano'; + +export const hasPhase2ValidationFailed = ( + tx: Wallet.TxInFlight | Wallet.Cardano.HydratedTx | Wallet.Cardano.Tx +): boolean => 'inputSource' in tx && tx.inputSource === Wallet.Cardano.InputSource.collaterals; diff --git a/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/common-tx-transformer.ts b/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/common-tx-transformer.ts index 426d8d192..33bcb6e79 100644 --- a/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/common-tx-transformer.ts +++ b/apps/browser-extension-wallet/src/views/browser-view/features/activity/helpers/common-tx-transformer.ts @@ -24,8 +24,9 @@ import isEmpty from 'lodash/isEmpty'; import { PriceResult } from '@hooks'; import { formatPercentages } from '@lace/common'; import { depositPaidWithSymbol } from '@src/features/dapp/components/confirm-transaction/utils'; +import { hasPhase2ValidationFailed } from '@src/utils/phase2-validation'; -const { util, GovernanceActionType, PlutusLanguageVersion, CertificateType, InputSource } = Wallet.Cardano; +const { util, GovernanceActionType, PlutusLanguageVersion, CertificateType } = Wallet.Cardano; export interface TxTransformerInput { tx: Wallet.TxInFlight | Wallet.Cardano.HydratedTx; @@ -98,9 +99,6 @@ const splitDelegationTx = (tx: TransformedActivity): TransformedTransactionActiv ]; }; -const hasPhase2ValidationFailed = (tx: Wallet.TxInFlight | Wallet.Cardano.HydratedTx) => - 'inputSource' in tx && tx.inputSource === InputSource.collaterals; - const transformTransactionStatus = ( tx: Wallet.TxInFlight | Wallet.Cardano.HydratedTx, status: Wallet.TransactionStatus