From cc190c4bbc655e6fc0174e826254b815814c4045 Mon Sep 17 00:00:00 2001 From: JCNoguera Date: Wed, 24 Jan 2024 20:07:18 +0100 Subject: [PATCH] feat: mark expired transactions --- .../stardust/history/TransactionCard.tsx | 2 + .../stardust/history/TransactionEntryProps.ts | 5 ++ .../stardust/history/TransactionHistory.scss | 28 +++++++++++ .../stardust/history/TransactionHistory.tsx | 2 + .../stardust/history/TransactionIdView.tsx | 47 ++++++++++++++++--- .../stardust/history/TransactionRow.tsx | 2 + .../history/transactionHistoryUtils.ts | 24 +++++++++- 7 files changed, 102 insertions(+), 8 deletions(-) diff --git a/client/src/app/components/stardust/history/TransactionCard.tsx b/client/src/app/components/stardust/history/TransactionCard.tsx index 0098943aa..7c844351c 100644 --- a/client/src/app/components/stardust/history/TransactionCard.tsx +++ b/client/src/app/components/stardust/history/TransactionCard.tsx @@ -13,6 +13,7 @@ const TransactionCard: React.FC = ({ isSpent, isFormattedAmounts, setIsFormattedAmounts, + isExpired, }) => { const valueView = ( setIsFormattedAmounts(!isFormattedAmounts)}> @@ -33,6 +34,7 @@ const TransactionCard: React.FC = ({ transactionId={transactionId} isTransactionFromStardustGenesis={isTransactionFromStardustGenesis} transactionLink={transactionLink} + isTxExpired={isExpired} /> diff --git a/client/src/app/components/stardust/history/TransactionEntryProps.ts b/client/src/app/components/stardust/history/TransactionEntryProps.ts index 08c19a4ca..0723f04ed 100644 --- a/client/src/app/components/stardust/history/TransactionEntryProps.ts +++ b/client/src/app/components/stardust/history/TransactionEntryProps.ts @@ -14,6 +14,11 @@ export interface ITransactionEntryProps { */ isSpent: boolean; + /** + * Is the transaction expired. + */ + isExpired: boolean; + /** * Are the amounts formatted. */ diff --git a/client/src/app/components/stardust/history/TransactionHistory.scss b/client/src/app/components/stardust/history/TransactionHistory.scss index fe95e75bb..96c4234f2 100644 --- a/client/src/app/components/stardust/history/TransactionHistory.scss +++ b/client/src/app/components/stardust/history/TransactionHistory.scss @@ -93,6 +93,20 @@ color: var(--expanded-color); } } + + .transaction-indicators { + display: flex; + + > * { + margin-left: 4px; + } + + .tx-indicator { + @include font-size(16px); + + vertical-align: sub; + } + } } td, @@ -138,6 +152,20 @@ color: var(--expanded-color); } } + + .transaction-indicators { + display: flex; + + > * { + margin-left: 4px; + } + + .tx-indicator { + @include font-size(20px); + + vertical-align: sub; + } + } } } } diff --git a/client/src/app/components/stardust/history/TransactionHistory.tsx b/client/src/app/components/stardust/history/TransactionHistory.tsx index 564c2a736..f07de88ed 100644 --- a/client/src/app/components/stardust/history/TransactionHistory.tsx +++ b/client/src/app/components/stardust/history/TransactionHistory.tsx @@ -60,6 +60,7 @@ const TransactionHistory: React.FC = ({ network, addres isSpent={c.isSpent} isFormattedAmounts={isFormattedAmounts} setIsFormattedAmounts={setIsFormattedAmounts} + isExpired={c.isExpired} /> ))} @@ -81,6 +82,7 @@ const TransactionHistory: React.FC = ({ network, addres isSpent={c.isSpent} isFormattedAmounts={isFormattedAmounts} setIsFormattedAmounts={setIsFormattedAmounts} + isExpired={c.isExpired} /> ); diff --git a/client/src/app/components/stardust/history/TransactionIdView.tsx b/client/src/app/components/stardust/history/TransactionIdView.tsx index 7b32149ef..77ccb5c58 100644 --- a/client/src/app/components/stardust/history/TransactionIdView.tsx +++ b/client/src/app/components/stardust/history/TransactionIdView.tsx @@ -7,9 +7,37 @@ export interface ITransactionIdProps { transactionId: string; isTransactionFromStardustGenesis: boolean; transactionLink: string; + isTxExpired?: boolean; } -const TransactionIdView: React.FC = ({ transactionId, isTransactionFromStardustGenesis, transactionLink }) => { +interface ITxIndicator { + icon: string; + message: string; + condition?: boolean; +} + +const TX_EXPIRED_MESSAGE = "Transaction has expired."; +const CHRYSALIS_TX_MESSAGE = "This link opens the transaction on Chrysalis Mainnet"; + +const TransactionIdView: React.FC = ({ + transactionId, + isTransactionFromStardustGenesis, + transactionLink, + isTxExpired, +}) => { + const indicators: ITxIndicator[] = [ + { + icon: "warning", + message: CHRYSALIS_TX_MESSAGE, + condition: isTransactionFromStardustGenesis, + }, + { + icon: "hourglass_bottom", + message: TX_EXPIRED_MESSAGE, + condition: isTxExpired, + }, + ]; + return ( <> {isTransactionFromStardustGenesis && transactionId.includes(STARDUST_SUPPLY_INCREASE_TRANSACTION_ID) ? ( @@ -17,12 +45,17 @@ const TransactionIdView: React.FC = ({ transactionId, isTra ) : ( <> - {isTransactionFromStardustGenesis && ( - - - warning - - + {indicators.length > 0 && ( +
+ {indicators.map( + ({ icon, message, condition }, index) => + condition && ( + + {icon} + + ), + )} +
)} )} diff --git a/client/src/app/components/stardust/history/TransactionRow.tsx b/client/src/app/components/stardust/history/TransactionRow.tsx index 2bb0e5d47..6ad8febbf 100644 --- a/client/src/app/components/stardust/history/TransactionRow.tsx +++ b/client/src/app/components/stardust/history/TransactionRow.tsx @@ -13,6 +13,7 @@ const TransactionRow: React.FC = ({ isSpent, isFormattedAmounts, setIsFormattedAmounts, + isExpired, }) => { const valueView = ( setIsFormattedAmounts(!isFormattedAmounts)}> @@ -29,6 +30,7 @@ const TransactionRow: React.FC = ({ transactionId={transactionId} isTransactionFromStardustGenesis={isTransactionFromStardustGenesis} transactionLink={transactionLink} + isTxExpired={isExpired} /> diff --git a/client/src/app/components/stardust/history/transactionHistoryUtils.ts b/client/src/app/components/stardust/history/transactionHistoryUtils.ts index a281a7f29..b9e687855 100644 --- a/client/src/app/components/stardust/history/transactionHistoryUtils.ts +++ b/client/src/app/components/stardust/history/transactionHistoryUtils.ts @@ -1,4 +1,4 @@ -import { CommonOutput, INodeInfoBaseToken } from "@iota/sdk-wasm/web"; +import { CommonOutput, ExpirationUnlockCondition, INodeInfoBaseToken, UnlockCondition, UnlockConditionType } from "@iota/sdk-wasm/web"; import moment from "moment/moment"; import { DateHelper } from "~helpers/dateHelper"; @@ -18,6 +18,7 @@ export interface ITransactionHistoryRecord { balanceChange: number; balanceChangeFormatted: string; outputs: OutputWithDetails[]; + isExpired: boolean; } export const groupOutputsByTransactionId = (outputsWithDetails: OutputWithDetails[]) => { @@ -73,6 +74,8 @@ export const getTransactionHistoryRecords = ( const isSpent = balanceChange <= 0; + const isExpired = isTransactionExpired(outputs, balanceChange); + calculatedTransactions.push({ isGenesisByDate: isGenesisByDate, isTransactionFromStardustGenesis: isTransactionFromStardustGenesis, @@ -84,8 +87,10 @@ export const getTransactionHistoryRecords = ( balanceChange: balanceChange, balanceChangeFormatted: (isSpent ? `-` : `+`) + formatAmount(Math.abs(balanceChange), tokenInfo, !isFormattedAmounts, 2, true), outputs: outputs, + isExpired, }); }); + return calculatedTransactions; }; @@ -111,3 +116,20 @@ export const getTransactionLink = (network: string, transactionId: string, isTra ? `/${CHRYSALIS_MAINNET}/search/${transactionId}` : `/${network}/transaction/${transactionId}`; }; + +function isExpirationUnlockCondition(unlockCondition: UnlockCondition): unlockCondition is ExpirationUnlockCondition { + return unlockCondition.type === UnlockConditionType.Expiration; +} + +function isTransactionExpired(outputs: OutputWithDetails[], balanceChange: number): boolean { + const hasExpiredOutput = outputs.some((output) => { + const outputFromDetails = output?.details?.output as CommonOutput; + + const expirationUnlockCondition = outputFromDetails.unlockConditions.find(isExpirationUnlockCondition); + const hasTimeExpired = expirationUnlockCondition && expirationUnlockCondition.unixTime < Date.now(); + + return hasTimeExpired; + }); + + return hasExpiredOutput && balanceChange <= 0; +}