Skip to content

Commit

Permalink
feat: add transaction expiry date (#1008)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucas-barros committed May 8, 2024
1 parent 7d765d7 commit 0fd1666
Show file tree
Hide file tree
Showing 16 changed files with 233 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { useCurrencyStore, useAppSettingsContext } from '@providers';
import { logger, walletRepository } from '@lib/wallet-api-ui';
import { useComputeTxCollateral } from '@hooks/useComputeTxCollateral';
import { utxoAndBackendChainHistoryResolver } from '@src/utils/utxo-chain-history-resolver';
import { eraSlotDateTime } from '@src/utils/era-slot-datetime';
import { AddressBookSchema, useDbStateValue } from '@lib/storage';
import { getAllWalletsAddresses } from '@src/utils/get-all-wallets-addresses';

Expand Down Expand Up @@ -83,6 +84,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 eraSummaries = useObservable(inMemoryWallet?.eraSummaries$);
const allWalletsAddresses = getAllWalletsAddresses(useObservable(walletRepository.wallets$));

useEffect(() => {
Expand Down Expand Up @@ -153,6 +155,7 @@ export const DappTransactionContainer = withAddressBookContext(
errorMessage={errorMessage}
toAddress={toAddressTokens}
collateral={txCollateral}
expiresBy={eraSlotDateTime(eraSummaries, tx.body.validityInterval?.invalidHereafter)}
ownAddresses={allWalletsAddresses.length > 0 ? allWalletsAddresses : ownAddresses}
addressToNameMap={addressToNameMap}
/>
Expand Down
3 changes: 2 additions & 1 deletion apps/browser-extension-wallet/src/hooks/useInitializeTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ export const useInitializeTx = (
fee: inspection.inputSelection.fee,
hash: inspection.hash,
outputs: inspection.inputSelection.outputs,
handleResolutions: inspection.handleResolutions
handleResolutions: inspection.handleResolutions,
validityInterval: inspection.body.validityInterval
},
tx,
totalMinimumCoins,
Expand Down
4 changes: 4 additions & 0 deletions apps/browser-extension-wallet/src/lib/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,10 @@
"core.outputSummaryList.metaData": "Metadata",
"core.outputSummaryList.deposit": "Deposit",
"core.outputSummaryList.output": "Bundle",
"core.outputSummaryList.expiresBy": "Expires by",
"core.outputSummaryList.expiresByTooltip": "This transaction can be added to the blockchain until the specified time. After this, it will automatically expire and no longer be valid.",
"core.outputSummaryList.noLimit": "No limit",
"core.outputSummaryList.utc": "UTC",
"core.sendReceive.send": "Send",
"core.sendReceive.receive": "Receive",
"core.coinInputSelection.assetSelection": "Select tokens or NFTs",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* eslint-disable unicorn/no-useless-undefined */
/* eslint-disable no-magic-numbers */
import { Cardano, EraSummary } from '@cardano-sdk/core';
import { eraSlotDateTime } from '../era-slot-datetime';
import { fromSerializableObject } from '@cardano-sdk/util';

export const eraSummaries = fromSerializableObject<EraSummary[]>([
{
parameters: { epochLength: 4320, slotLength: 20_000 },
start: { slot: 0, time: { __type: 'Date', value: 1_666_656_000_000 } }
},
{
parameters: { epochLength: 86_400, slotLength: 1000 },
start: { slot: 0, time: { __type: 'Date', value: 1_666_656_000_000 } }
}
]);

const testSlot = Cardano.Slot(36_201_583);

describe('Testing eraSlotDateTime', () => {
test('should return undefined', async () => {
expect(eraSlotDateTime(undefined, testSlot)).toEqual(undefined);
});
test('should return undefined', async () => {
expect(eraSlotDateTime(eraSummaries, undefined)).toEqual(undefined);
});
test('should return formatted time', async () => {
expect(eraSlotDateTime(eraSummaries, testSlot)).toEqual({
utcDate: '12/17/2023',
utcTime: '23:59:43'
});
});
});
19 changes: 19 additions & 0 deletions apps/browser-extension-wallet/src/utils/era-slot-datetime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* eslint-disable consistent-return */
import { EraSummary } from '@cardano-sdk/core';
import { Wallet } from '@lace/cardano';
import { formatDate, formatTime } from './format-date';

export const eraSlotDateTime = (
eraSummaries: EraSummary[] | undefined,
slot: Wallet.Cardano.Slot | undefined
): { utcDate: string; utcTime: string } | undefined => {
if (!eraSummaries || !slot) {
return undefined;
}
const slotTimeCalc = Wallet.createSlotTimeCalc(eraSummaries);
const date = slotTimeCalc(slot);
return {
utcDate: formatDate({ date, format: 'MM/DD/YYYY', type: 'utc' }),
utcTime: formatTime({ date, type: 'utc' })
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { getTokenAmountInFiat, parseFiat } from '@src/utils/assets-transformers'
import { useObservable, Banner } from '@lace/common';
import ExclamationIcon from '../../../../../assets/icons/exclamation-triangle-red.component.svg';
import { WalletType } from '@cardano-sdk/web-extension';
import { eraSlotDateTime } from '@src/utils/era-slot-datetime';
import { getAllWalletsAddresses } from '@src/utils/get-all-wallets-addresses';
import { walletRepository } from '@lib/wallet-api-ui';

Expand Down Expand Up @@ -100,7 +101,8 @@ interface SendTransactionSummaryProps {
export const SendTransactionSummary = withAddressBookContext(
({ isPopupView = false }: SendTransactionSummaryProps): React.ReactElement => {
const { t } = useTranslation();
const { builtTxData: { uiTx: { fee, outputs, handleResolutions } = {} } = {} } = useBuiltTxState();
const { builtTxData: { uiTx: { fee, outputs, handleResolutions, validityInterval } = {} } = {} } =
useBuiltTxState();
const [metadata] = useMetadata();
const {
inMemoryWallet,
Expand All @@ -115,14 +117,19 @@ export const SendTransactionSummary = withAddressBookContext(
const { fiatCurrency } = useCurrencyStore();

const assetsInfo = useObservable(inMemoryWallet.assetInfo$);
const eraSummaries = useObservable(inMemoryWallet.eraSummaries$);

const outputSummaryListTranslation = {
recipientAddress: t('core.outputSummaryList.recipientAddress'),
sending: t('core.outputSummaryList.sending'),
output: t('core.outputSummaryList.output'),
metadata: t('core.outputSummaryList.metaData'),
deposit: t('core.outputSummaryList.deposit'),
txFee: t('core.outputSummaryList.txFee')
txFee: t('core.outputSummaryList.txFee'),
expiresBy: t('core.outputSummaryList.expiresBy'),
expiresByTooltip: t('core.outputSummaryList.expiresByTooltip'),
noLimit: t('core.outputSummaryList.noLimit'),
utc: t('core.outputSummaryList.utc')
};

const addressToNameMap = useMemo(
Expand All @@ -149,6 +156,7 @@ export const SendTransactionSummary = withAddressBookContext(
<>
<OutputSummaryList
rows={rows}
expiresBy={eraSlotDateTime(eraSummaries, validityInterval?.invalidHereafter)}
txFee={{
...getFee(fee?.toString(), priceResult?.cardano?.price, cardanoCoin, fiatCurrency),
tootipText: t('send.theAmountYoullBeChargedToProcessYourTransaction')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export interface BuiltTxData {
outputs: Set<Wallet.Cardano.TxOut & { handleResolution?: HandleResolution }>;
fee: Wallet.Cardano.Lovelace;
handleResolutions?: HandleResolution[];
validityInterval?: Wallet.Cardano.ValidityInterval;
};
error?: string;
reachedMaxAmountList?: (string | Wallet.Cardano.AssetId)[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,16 @@ import styles from './DappTransaction.module.scss';
import { useTranslate } from '@src/ui/hooks';
import { TransactionFee, Collateral } from '@ui/components/ActivityDetail';

import { TransactionType, DappTransactionSummary, TransactionAssets, Text, Box, Divider } from '@lace/ui';
import {
TransactionType,
DappTransactionSummary,
TransactionAssets,
DappTransactionTextField,
Flex,
Text,
Box,
Divider
} from '@lace/ui';
import { DappAddressSections } from '../DappAddressSections/DappAddressSections';

const amountTransformer = (fiat: { price: number; code: string }) => (ada: string) =>
Expand All @@ -26,6 +35,7 @@ export interface DappTransactionProps {
fiatCurrencyCode?: string;
fiatCurrencyPrice?: number;
coinSymbol?: string;
expiresBy?: { utcDate: string; utcTime: string };
/** tokens send to being sent to or from the user */
fromAddress: Map<Cardano.PaymentAddress, TokenTransferValue>;
toAddress: Map<Cardano.PaymentAddress, TokenTransferValue>;
Expand Down Expand Up @@ -113,6 +123,7 @@ export const DappTransaction = ({
fiatCurrencyPrice,
coinSymbol,
dappInfo,
expiresBy,
ownAddresses = [],
addressToNameMap = new Map()
}: DappTransactionProps): React.ReactElement => {
Expand All @@ -124,6 +135,17 @@ export const DappTransaction = ({
const isFromAddressesEnabled = groupedFromAddresses.size > 0;
const isToAddressesEnabled = groupedToAddresses.size > 0;

const expireByText = expiresBy ? (
<Flex flexDirection="column" alignItems="flex-end">
<span>{expiresBy.utcDate}</span>
<span>
{expiresBy.utcTime} {t('core.outputSummaryList.utc')}
</span>
</Flex>
) : (
t('core.outputSummaryList.noLimit')
);

return (
<div>
{errorMessage && <ErrorPane error={errorMessage} className={styles.error} />}
Expand Down Expand Up @@ -184,6 +206,14 @@ export const DappTransaction = ({
/>
)}

<div className={styles.depositContainer}>
<DappTransactionTextField
text={expireByText}
label={t('core.outputSummaryList.expiresBy')}
tooltip={t('core.outputSummaryList.expiresByTooltip')}
/>
</div>

{returnedDeposit !== BigInt(0) && (
<TransactionFee
fee={Wallet.util.lovelacesToAdaString(returnedDeposit.toString())}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@
color: var(--text-color-primary);
}

.validityIntervalExpiresBy {
@include text-body-medium;
display: flex;
flex-direction: column;
color: var(--text-color-primary);
overflow-wrap: break-word;
text-align: right;
}

.metadataRow {
display: flex;
justify-content: space-between;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { renderAmountInfo, renderLabel, RowContainer } from '../OutputSummary/Ou
import { Typography } from 'antd';
import styles from './OutputSummaryList.module.scss';
import { TranslationsFor } from '@ui/utils/types';
import { Flex } from '@lace/ui';

const { Text } = Typography;

Expand All @@ -19,9 +20,21 @@ export type Costs = {
export interface OutputSummaryListProps {
rows: OutputSummaryProps[];
txFee: Costs & TootipText;
expiresBy: { utcDate: string; utcTime: string };
deposit?: Costs & TootipText;
metadata?: string;
translations: TranslationsFor<'recipientAddress' | 'sending' | 'txFee' | 'deposit' | 'metadata' | 'output'>;
translations: TranslationsFor<
| 'recipientAddress'
| 'sending'
| 'txFee'
| 'deposit'
| 'metadata'
| 'output'
| 'expiresBy'
| 'expiresByTooltip'
| 'noLimit'
| 'utc'
>;
ownAddresses?: string[];
onFeeTooltipHover?: () => unknown;
onDepositTooltipHover?: () => unknown;
Expand All @@ -33,6 +46,7 @@ export const OutputSummaryList = ({
metadata,
deposit,
translations,
expiresBy,
ownAddresses,
onFeeTooltipHover,
onDepositTooltipHover
Expand All @@ -42,6 +56,17 @@ export const OutputSummaryList = ({
sending: translations.sending
};

const expireByText = expiresBy ? (
<Flex flexDirection="column" alignItems="flex-end">
<span>{expiresBy.utcDate}</span>
<span>
{expiresBy.utcTime} {translations.utc}
</span>
</Flex>
) : (
translations.noLimit
);

return (
<div className={styles.listContainer}>
{rows.map((row, idx) => (
Expand All @@ -54,7 +79,6 @@ export const OutputSummaryList = ({
<OutputSummary {...row} translations={outputSummaryTranslations} ownAddresses={ownAddresses} />
</div>
))}

{metadata && (
<div className={styles.metadataRow} data-testid="metadata-container">
<Text className={styles.metadataLabel} data-testid="metadata-label">
Expand All @@ -67,6 +91,18 @@ export const OutputSummaryList = ({
)}

<div className={styles.feeContainer} data-testid="summary-fee-container">
<RowContainer>
{renderLabel({
label: translations.expiresBy,
tooltipContent: translations.expiresByTooltip,
dataTestId: 'validity-interval-expires-by-label'
})}

<Text className={styles.validityIntervalExpiresBy} data-testid="validity-interval-expires-by-value">
{expireByText}
</Text>
</RowContainer>

{deposit && (
<RowContainer>
{renderLabel({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ export const transactionTypeContainer = style({
export const cardanoIcon = style([
sx({
display: 'flex',
w: '$32',
height: '$32',
}),
{
width: '32px',
height: '32px',
},
]);

export const txAmountContainer = style({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Grid, Cell } from '../grid';

import { TransactionAssets } from './dapp-transaction-assets.component';
import { TransactionSummary } from './dapp-transaction-summary.component';
import { TransactionTextField } from './dapp-transaction-text-field.component';
import { TransactionType } from './dapp-transaction-type.component';

const subtitle = `Control that displays data items in rows.`;
Expand Down Expand Up @@ -102,6 +103,13 @@ const Example = (): JSX.Element => (
tokenName={value.tokenName}
/>
))}
<Box mt="$10">
<TransactionTextField
tooltip="This is a sample tooltip text"
label="Expires by"
text="No limit"
/>
</Box>
</Layout>
);

Expand All @@ -128,6 +136,9 @@ const MainComponents = (): JSX.Element => (
/>
))}
</>
<Box mt="$10">
<TransactionTextField label="Expires by" text="No limit" />
</Box>
</Layout>
</Variants.Cell>
</Variants.Row>
Expand Down

0 comments on commit 0fd1666

Please sign in to comment.