From cc00b888a161d132e80e26d0a2ec1ab10e9237a8 Mon Sep 17 00:00:00 2001 From: Bartek Date: Tue, 3 Sep 2024 17:44:16 +0200 Subject: [PATCH] fix: normalize timestamp values (#1868) --- .../TransferPanel/TransferPanel.tsx | 3 +- .../src/hooks/useTransactionHistory.ts | 18 +++++----- .../src/state/app/utils.ts | 34 +++++++++++++------ .../src/state/cctpState.ts | 14 +++----- .../src/util/RetryableUtils.ts | 3 +- .../src/util/deposits/helpers.ts | 24 ++++++++----- 6 files changed, 56 insertions(+), 40 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx index 1a05e3088a..d3f2ac46a6 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx @@ -81,6 +81,7 @@ import { getSmartContractWalletTeleportTransfersNotSupportedErrorMessage } from import { useBalances } from '../../hooks/useBalances' import { captureSentryErrorWithExtraData } from '../../util/SentryUtils' import { useIsBatchTransferSupported } from '../../hooks/TransferPanel/useIsBatchTransferSupported' +import { normalizeTimestamp } from '../../state/app/utils' const networkConnectionWarningToast = () => warningToast( @@ -932,7 +933,7 @@ export function TransferPanel() { const isBatchTransfer = isBatchTransferSupported && Number(amount2) > 0 - const timestampCreated = Math.floor(Date.now() / 1000).toString() + const timestampCreated = String(normalizeTimestamp(Date.now())) const txHistoryCompatibleObject = convertBridgeSdkToMergedTransaction({ bridgeTransfer, diff --git a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts index 080c2f0eca..86eb200d34 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts @@ -20,7 +20,7 @@ import { import { isTeleportTx, Transaction } from './useTransactions' import { MergedTransaction } from '../state/app/state' import { - getStandardizedTimestamp, + normalizeTimestamp, transformDeposit, transformWithdrawal } from '../state/app/utils' @@ -90,30 +90,28 @@ export type Transfer = | MergedTransaction | TeleportFromSubgraph -function getStandardizedTimestampByTx(tx: Transfer) { +function getTransactionTimestamp(tx: Transfer) { if (isCctpTransfer(tx)) { - return (tx.createdAt ?? 0) / 1_000 + return normalizeTimestamp(tx.createdAt ?? 0) } if (isTransferTeleportFromSubgraph(tx)) { - return tx.timestamp + return normalizeTimestamp(tx.timestamp) } if (isDeposit(tx)) { - return tx.timestampCreated ?? 0 + return normalizeTimestamp(tx.timestampCreated ?? 0) } if (isWithdrawalFromSubgraph(tx)) { - return getStandardizedTimestamp(tx.l2BlockTimestamp) + return normalizeTimestamp(tx.l2BlockTimestamp) } - return getStandardizedTimestamp(tx.timestamp ?? '0') + return normalizeTimestamp(tx.timestamp?.toNumber() ?? 0) } function sortByTimestampDescending(a: Transfer, b: Transfer) { - return getStandardizedTimestampByTx(a) > getStandardizedTimestampByTx(b) - ? -1 - : 1 + return getTransactionTimestamp(a) > getTransactionTimestamp(b) ? -1 : 1 } function getMultiChainFetchList(): ChainPair[] { diff --git a/packages/arb-token-bridge-ui/src/state/app/utils.ts b/packages/arb-token-bridge-ui/src/state/app/utils.ts index 695b4bcabd..5ed645ba87 100644 --- a/packages/arb-token-bridge-ui/src/state/app/utils.ts +++ b/packages/arb-token-bridge-ui/src/state/app/utils.ts @@ -143,10 +143,10 @@ export const transformDeposit = ( direction: tx.type, status: tx.status, createdAt: tx.timestampCreated - ? getStandardizedTimestamp(tx.timestampCreated) + ? normalizeTimestamp(tx.timestampCreated) : null, resolvedAt: tx.timestampResolved - ? getStandardizedTimestamp(tx.timestampResolved) + ? normalizeTimestamp(tx.timestampResolved) : null, txId: tx.txID, asset: tx.assetName || '', @@ -189,9 +189,7 @@ export const transformWithdrawal = ( NodeBlockDeadlineStatusTypes.EXECUTE_CALL_EXCEPTION ? 'Failure' : outgoingStateToString[tx.outgoingMessageState], - createdAt: getStandardizedTimestamp( - String(BigNumber.from(tx.timestamp).toNumber() * 1000) - ), + createdAt: normalizeTimestamp(tx.timestamp.toNumber()), resolvedAt: null, txId: tx.l2TxHash || 'l2-tx-hash-not-found', asset: tx.symbol || '', @@ -306,15 +304,31 @@ export const isDepositReadyToRedeem = (tx: MergedTransaction) => { return isDeposit(tx) && tx.depositStatus === DepositStatus.L2_FAILURE } -export const getStandardizedTimestamp = (date: string | BigNumber) => { +export const normalizeTimestamp = (date: number | string) => { // because we get timestamps in different formats from subgraph/event-logs/useTxn hook, we need 1 standard format. + const TIMESTAMP_LENGTH = 13 + let timestamp = date + if (typeof date === 'string') { - if (isNaN(Number(date))) return dayjs(new Date(date)).unix() // for ISOstring type of dates -> dayjs timestamp - return Number(date) // for timestamp type of date -> dayjs timestamp + timestamp = isNaN(Number(date)) + ? dayjs(new Date(date)).unix() // for ISOstring type of dates -> dayjs timestamp + : Number(date) // for timestamp type of date -> dayjs timestamp + } + + const timestampString = String(timestamp) + + if (timestampString.length === TIMESTAMP_LENGTH) { + // correct timestamp length + return Number(timestampString) + } + + if (timestampString.length < TIMESTAMP_LENGTH) { + // add zeros at the end until correct timestamp length + return Number(timestampString.padEnd(TIMESTAMP_LENGTH, '0')) } - // BigNumber - return date.toNumber() + // remove end digits until correct timestamp length + return Number(timestampString.slice(0, TIMESTAMP_LENGTH)) } export const getStandardizedTime = (standardizedTimestamp: number) => { diff --git a/packages/arb-token-bridge-ui/src/state/cctpState.ts b/packages/arb-token-bridge-ui/src/state/cctpState.ts index d2c094dc3e..7f0eb43f84 100644 --- a/packages/arb-token-bridge-ui/src/state/cctpState.ts +++ b/packages/arb-token-bridge-ui/src/state/cctpState.ts @@ -13,7 +13,7 @@ import { } from '../util/networks' import { fetchCCTPDeposits, fetchCCTPWithdrawals } from '../util/cctp/fetchCCTP' import { DepositStatus, MergedTransaction, WithdrawalStatus } from './app/state' -import { getStandardizedTimestamp } from './app/utils' +import { normalizeTimestamp } from './app/utils' import { useAccount, useSigner } from 'wagmi' import dayjs from 'dayjs' import { @@ -101,8 +101,8 @@ function parseTransferToMergedTransaction( if ('messageReceived' in transfer) { const { messageReceived } = transfer status = 'Executed' - resolvedAt = getStandardizedTimestamp( - (parseInt(messageReceived.blockTimestamp, 10) * 1_000).toString() + resolvedAt = normalizeTimestamp( + parseInt(messageReceived.blockTimestamp, 10) ) receiveMessageTransactionHash = messageReceived.transactionHash } @@ -122,9 +122,7 @@ function parseTransferToMergedTransaction( destination: messageSent.recipient, direction: isDeposit ? 'deposit' : 'withdraw', status, - createdAt: getStandardizedTimestamp( - (parseInt(messageSent.blockTimestamp, 10) * 1_000).toString() - ), + createdAt: normalizeTimestamp(parseInt(messageSent.blockTimestamp, 10)), resolvedAt, txId: messageSent.transactionHash, asset: 'USDC', @@ -534,9 +532,7 @@ export function useClaimCctp(tx: MergedTransaction) { const receiveReceiptTx = await receiveTx.wait() const resolvedAt = - receiveReceiptTx.status === 1 - ? getStandardizedTimestamp(BigNumber.from(Date.now()).toString()) - : null + receiveReceiptTx.status === 1 ? normalizeTimestamp(Date.now()) : null await updatePendingTransaction({ ...tx, resolvedAt, diff --git a/packages/arb-token-bridge-ui/src/util/RetryableUtils.ts b/packages/arb-token-bridge-ui/src/util/RetryableUtils.ts index da1111d787..f65d704ccb 100644 --- a/packages/arb-token-bridge-ui/src/util/RetryableUtils.ts +++ b/packages/arb-token-bridge-ui/src/util/RetryableUtils.ts @@ -11,6 +11,7 @@ import { MergedTransaction, TeleporterMergedTransaction } from '../state/app/state' +import { normalizeTimestamp } from '../state/app/utils' import { isTeleportTx } from '../hooks/useTransactions' type GetRetryableTicketParams = { @@ -84,7 +85,7 @@ export const getRetryableTicketExpiration = async ({ const now = dayjs() const expiryDateResponse = await parentToChildMsg!.getTimeout() - expirationDate = Number(expiryDateResponse.toString()) * 1000 + expirationDate = normalizeTimestamp(expiryDateResponse.toNumber()) daysUntilExpired = dayjs(expirationDate).diff(now, 'days') diff --git a/packages/arb-token-bridge-ui/src/util/deposits/helpers.ts b/packages/arb-token-bridge-ui/src/util/deposits/helpers.ts index d3af5b97e3..2f9eeb4b72 100644 --- a/packages/arb-token-bridge-ui/src/util/deposits/helpers.ts +++ b/packages/arb-token-bridge-ui/src/util/deposits/helpers.ts @@ -26,6 +26,7 @@ import { fetchTeleportStatusFromTxId } from '../../token-bridge-sdk/teleport' import { getProviderForChainId } from '../../token-bridge-sdk/utils' +import { normalizeTimestamp } from '../../state/app/utils' export const updateAdditionalDepositData = async ({ depositTx, @@ -43,11 +44,13 @@ export const updateAdditionalDepositData = async ({ let timestampCreated = new Date().toISOString() if (depositTx.timestampCreated) { // if timestamp is already there in Subgraphs, take it from there - timestampCreated = String(Number(depositTx.timestampCreated) * 1000) + timestampCreated = String(normalizeTimestamp(depositTx.timestampCreated)) } else if (depositTx.blockNumber) { // if timestamp not in subgraph, fallback to onchain data timestampCreated = String( - (await parentProvider.getBlock(depositTx.blockNumber)).timestamp * 1000 + normalizeTimestamp( + (await parentProvider.getBlock(depositTx.blockNumber)).timestamp + ) ) } @@ -252,7 +255,9 @@ const updateETHDepositStatusData = async ({ : null const timestampResolved = childBlockNum - ? (await childProvider.getBlock(childBlockNum)).timestamp * 1000 + ? normalizeTimestamp( + (await childProvider.getBlock(childBlockNum)).timestamp + ) : null // return the data to populate on UI @@ -331,7 +336,9 @@ const updateTokenDepositStatusData = async ({ : null const timestampResolved = childBlockNum - ? (await childProvider.getBlock(childBlockNum)).timestamp * 1000 + ? normalizeTimestamp( + (await childProvider.getBlock(childBlockNum)).timestamp + ) : null const completeDepositTx: Transaction = { @@ -395,7 +402,7 @@ const updateClassicDepositStatusData = async ({ : null const timestampResolved = l2BlockNum - ? (await childProvider.getBlock(l2BlockNum)).timestamp * 1000 + ? normalizeTimestamp((await childProvider.getBlock(l2BlockNum)).timestamp) : null const completeDepositTx: Transaction = { @@ -422,7 +429,7 @@ async function getTimestampResolved( .getTransactionReceipt(l3TxHash) .then(tx => tx.blockNumber) .then(blockNumber => destinationChainProvider.getBlock(blockNumber)) - .then(block => String(block.timestamp * 1000)) + .then(block => normalizeTimestamp(block.timestamp)) } export async function fetchTeleporterDepositStatusData({ @@ -513,9 +520,8 @@ export async function fetchTeleporterDepositStatusData({ l2L3Redeem && l2L3Redeem.status === ParentToChildMessageStatus.REDEEMED ? l2L3Redeem.childTxReceipt.transactionHash : undefined - const timestampResolved = await getTimestampResolved( - destinationChainProvider, - l3TxID + const timestampResolved = String( + await getTimestampResolved(destinationChainProvider, l3TxID) ) // extract the new L2 tx details if we find that `l2ForwarderFactoryRetryable` has been redeemed manually