diff --git a/packages/synapse-interface/components/Portfolio/Transaction/MostRecentTransaction.tsx b/packages/synapse-interface/components/Portfolio/Transaction/MostRecentTransaction.tsx
index adba89a3e9..257c9c633a 100644
--- a/packages/synapse-interface/components/Portfolio/Transaction/MostRecentTransaction.tsx
+++ b/packages/synapse-interface/components/Portfolio/Transaction/MostRecentTransaction.tsx
@@ -98,26 +98,19 @@ export const MostRecentTransaction = () => {
return transactions
}, [userHistoricalTransactions, fallbackQueryHistoricalTransactions])
- const lastPendingBridgeTransaction: PendingBridgeTransaction = useMemo(() => {
- return pendingBridgeTransactions?.[0]
- }, [pendingBridgeTransactions])
+ const lastPendingBridgeTransaction: PendingBridgeTransaction =
+ pendingBridgeTransactions?.[0]
- const lastPendingTransaction: BridgeTransaction = useMemo(() => {
- return pendingAwaitingCompletionTransactionsWithFallback?.[0]
- }, [pendingAwaitingCompletionTransactionsWithFallback])
+ const lastPendingTransaction: BridgeTransaction =
+ pendingAwaitingCompletionTransactionsWithFallback?.[0]
- const lastHistoricalTransaction: BridgeTransaction = useMemo(() => {
- return userHistoricalTransactionsWithFallback?.[0]
- }, [userHistoricalTransactionsWithFallback])
+ const lastHistoricalTransaction: BridgeTransaction =
+ userHistoricalTransactionsWithFallback?.[0]
const recentMinutesInUnix: number = 15 * 60
- const isLastHistoricalTransactionRecent: boolean = useMemo(
- () =>
- currentTime - lastHistoricalTransaction?.toInfo?.time <
- recentMinutesInUnix,
- [currentTime]
- )
+ const isLastHistoricalTransactionRecent: boolean =
+ currentTime - lastHistoricalTransaction?.toInfo?.time < recentMinutesInUnix
const seenLastHistoricalTransaction: boolean = useMemo(() => {
if (!seenHistoricalTransactions || !userHistoricalTransactions) {
@@ -133,136 +126,114 @@ export const MostRecentTransaction = () => {
let transaction
- return useMemo(() => {
- if (
- isUserHistoricalTransactionsLoading ||
- isUserPendingTransactionsLoading
- ) {
- return null
- }
-
- if (!masqueradeActive && lastPendingBridgeTransaction) {
- transaction = lastPendingBridgeTransaction as PendingBridgeTransaction
- return (
-
- )
- }
-
- if (!masqueradeActive && lastPendingTransaction) {
- transaction = lastPendingTransaction as BridgeTransaction
- return (
-
- )
- }
-
- if (
- !masqueradeActive &&
- lastHistoricalTransaction &&
- isLastHistoricalTransactionRecent &&
- !seenLastHistoricalTransaction
- ) {
- transaction = lastHistoricalTransaction as BridgeTransaction
- return (
-
- )
- }
- }, [
- currentTime,
- lastPendingBridgeTransaction,
- lastHistoricalTransaction,
- lastPendingTransaction,
- userHistoricalTransactions,
- isUserHistoricalTransactionsLoading,
- isUserPendingTransactionsLoading,
- seenHistoricalTransactions,
- pendingAwaitingCompletionTransactions,
- fallbackQueryHistoricalTransactions,
- fallbackQueryPendingTransactions,
- pendingBridgeTransactions,
- masqueradeActive,
- seenLastHistoricalTransaction,
- isUserHistoricalTransactionsLoading,
- isUserPendingTransactionsLoading,
- ])
+ if (isUserHistoricalTransactionsLoading || isUserPendingTransactionsLoading) {
+ return null
+ }
+
+ if (!masqueradeActive && lastPendingBridgeTransaction) {
+ transaction = lastPendingBridgeTransaction as PendingBridgeTransaction
+ return (
+
+ )
+ }
+
+ if (!masqueradeActive && lastPendingTransaction) {
+ transaction = lastPendingTransaction as BridgeTransaction
+ return (
+
+ )
+ }
+
+ if (
+ !masqueradeActive &&
+ lastHistoricalTransaction &&
+ isLastHistoricalTransactionRecent &&
+ !seenLastHistoricalTransaction
+ ) {
+ transaction = lastHistoricalTransaction as BridgeTransaction
+ return (
+
+ )
+ }
}
diff --git a/packages/synapse-interface/components/Portfolio/Transaction/PendingTransaction.tsx b/packages/synapse-interface/components/Portfolio/Transaction/PendingTransaction.tsx
index 95b406929a..66c165627c 100644
--- a/packages/synapse-interface/components/Portfolio/Transaction/PendingTransaction.tsx
+++ b/packages/synapse-interface/components/Portfolio/Transaction/PendingTransaction.tsx
@@ -8,8 +8,6 @@ import {
} from '@/slices/transactions/actions'
import { BridgeType } from '@/slices/api/generated'
import { getTimeMinutesFromNow } from '@/utils/time'
-import { ARBITRUM, ETH } from '@/constants/chains/master'
-import { USDC } from '@/constants/tokens/bridgeable'
import {
Transaction,
TransactionProps,
@@ -17,7 +15,6 @@ import {
TransactionStatus,
} from './Transaction'
import { ApplicationState } from '@/slices/application/reducer'
-import { BRIDGE_REQUIRED_CONFIRMATIONS } from '@/constants/bridge'
import { TransactionOptions } from './TransactionOptions'
import { getExplorerTxUrl, getExplorerAddressUrl } from '@/constants/urls'
import { getTransactionExplorerLink } from './components/TransactionExplorerLink'
@@ -27,6 +24,8 @@ import { useFallbackBridgeDestinationQuery } from '@/utils/hooks/useFallbackBrid
import { useSynapseContext } from '@/utils/providers/SynapseProvider'
import { DISCORD_URL } from '@/constants/urls'
import { useApplicationState } from '@/slices/application/hooks'
+import { getEstimatedBridgeTime } from '@/utils/getEstimatedBridgeTime'
+import { useBridgeTxStatus } from '@/utils/hooks/useBridgeTxStatus'
interface PendingTransactionProps extends TransactionProps {
eventType?: number
@@ -76,42 +75,11 @@ export const PendingTransaction = ({
}
}, [transactionHash, isSubmitted, isCompleted])
- const estimatedCompletionInSeconds: number = useMemo(() => {
- if (bridgeModuleName) {
- return synapseSDK.getEstimatedTime(originChain?.id, bridgeModuleName)
- }
-
- if (formattedEventType) {
- const fetchedBridgeModuleName: string =
- synapseSDK.getBridgeModuleName(formattedEventType)
- return synapseSDK.getEstimatedTime(
- originChain?.id,
- fetchedBridgeModuleName
- )
- }
- // Fallback last resort estimated duration calculation
- // Remove this when fallback origin queries return eventType
- // CCTP Classification
- if (originChain.id === ARBITRUM.id || originChain.id === ETH.id) {
- const isCCTP: boolean =
- originToken.addresses[originChain.id] === USDC.addresses[originChain.id]
- if ((eventType === 10 || eventType === 11) && isCCTP) {
- const attestationTime: number = 13 * 60
- return (
- (BRIDGE_REQUIRED_CONFIRMATIONS[originChain.id] *
- originChain.blockTime) /
- 1000 +
- attestationTime
- )
- }
- }
- // All other transactions
- return originChain
- ? (BRIDGE_REQUIRED_CONFIRMATIONS[originChain.id] *
- originChain.blockTime) /
- 1000
- : 0
- }, [originChain, eventType, originToken, bridgeModuleName, transactionHash])
+ const estimatedCompletionInSeconds = getEstimatedBridgeTime({
+ bridgeOriginChain: originChain,
+ bridgeModuleName,
+ formattedEventType,
+ })
const currentTime: number = Math.floor(Date.now() / 1000)
@@ -120,7 +88,7 @@ export const PendingTransaction = ({
if (!isSubmitted || currentTime < startedTimestamp) {
return 0
} else if (startedTimestamp < currentTime) {
- return Math.floor((currentTime - startedTimestamp) / 60)
+ return Math.ceil((currentTime - startedTimestamp) / 60)
} else {
return 0
}
@@ -162,34 +130,19 @@ export const PendingTransaction = ({
estimatedCompletionInSeconds / 60
)
- const timeRemaining: number = useMemo(() => {
- return estimatedCompletionInMinutes - initialElapsedMinutes
- }, [
- estimatedCompletionInMinutes,
- initialElapsedMinutes,
- updatedElapsedTime,
- startedTimestamp,
- transactionHash,
- ])
+ const timeRemaining: number =
+ estimatedCompletionInMinutes - initialElapsedMinutes
- const isDelayed: boolean = useMemo(() => timeRemaining < 0, [timeRemaining])
+ const isDelayed: boolean = timeRemaining < -1
- const isSignificantlyDelayed: boolean = useMemo(() => {
- if (isDelayed) {
- return timeRemaining < -5
- }
- return false
- }, [timeRemaining, estimatedCompletionInMinutes, isDelayed])
+ const isSignificantlyDelayed: boolean = isDelayed && timeRemaining < -5
// Set fallback period to extend 5 mins past estimated duration
- const useFallback: boolean = useMemo(
- () => timeRemaining >= -5 && timeRemaining <= 1 && !isCompleted,
- [timeRemaining, isCompleted]
- )
+ const useFallback: boolean =
+ timeRemaining >= -5 && timeRemaining <= 1 && !isCompleted
- const isReconnectedAndRetryFallback: boolean = useMemo(() => {
- return updatedCurrentTime - lastConnectedTimestamp < 300
- }, [lastConnectedTimestamp, updatedCurrentTime])
+ const isReconnectedAndRetryFallback: boolean =
+ updatedCurrentTime - lastConnectedTimestamp < 300
const bridgeType: BridgeType = useMemo(() => {
if (synapseSDK && formattedEventType) {
@@ -206,19 +159,15 @@ export const PendingTransaction = ({
return BridgeType.Bridge
}, [synapseSDK, bridgeModuleName, formattedEventType])
- const originFallback = useFallbackBridgeOriginQuery({
- useFallback:
- (isDelayed && useFallback) ||
- (isDelayed && isReconnectedAndRetryFallback),
+ useFallbackBridgeOriginQuery({
+ useFallback: useFallback || (isDelayed && isReconnectedAndRetryFallback),
chainId: originChain?.id,
txnHash: transactionHash,
bridgeType: bridgeType,
})
- const destinationFallback = useFallbackBridgeDestinationQuery({
- useFallback:
- (isDelayed && useFallback) ||
- (isDelayed && isReconnectedAndRetryFallback),
+ useFallbackBridgeDestinationQuery({
+ useFallback: useFallback || (isDelayed && isReconnectedAndRetryFallback),
chainId: destinationChain?.id,
address: destinationAddress,
kappa: kappa,
@@ -226,6 +175,16 @@ export const PendingTransaction = ({
bridgeType: bridgeType,
})
+ const _isComplete = useBridgeTxStatus({
+ originChainId: originChain.id,
+ destinationChainId: destinationChain.id,
+ transactionHash,
+ bridgeModuleName,
+ kappa,
+ checkStatus: isDelayed,
+ elapsedTime: updatedElapsedTime,
+ })
+
useEffect(() => {
if (!isSubmitted && transactionHash) {
const maxRetries = 3
@@ -304,7 +263,7 @@ export const PendingTransaction = ({
}
transactionHash={transactionHash}
transactionStatus={transactionStatus}
- isCompleted={isCompleted}
+ isCompleted={isCompleted ?? _isComplete}
kappa={kappa}
>
{
const sharedClass: string =
- 'flex bg-tint border-t border-surface text-sm items-center'
+ 'flex bg-tint border-t border-surface text-sm items-center rounded-b-lg'
if (transactionStatus === TransactionStatus.PENDING_WALLET_ACTION) {
return (
@@ -360,7 +321,7 @@ const TransactionStatusDetails = ({
return (
Initiating...
{isDelayed && isSignificantlyDelayed && (
<>
@@ -534,14 +495,27 @@ const TransactionStatusDetails = ({
}
if (transactionStatus === TransactionStatus.COMPLETED) {
- const handleExplorerClick = () => {
- const explorerLink: string = getTransactionExplorerLink({
- kappa,
- fromChainId: originChain.id,
- toChainId: destinationChain.id,
- })
- window.open(explorerLink, '_blank', 'noopener,noreferrer')
+ let handleExplorerClick
+
+ if (!kappa) {
+ handleExplorerClick = () => {
+ const explorerLink: string = getExplorerAddressUrl({
+ chainId: destinationChain.id,
+ address: connectedAddress as Address,
+ })
+ window.open(explorerLink, '_blank', 'noopener,noreferrer')
+ }
+ } else {
+ handleExplorerClick = () => {
+ const explorerLink: string = getTransactionExplorerLink({
+ kappa,
+ fromChainId: originChain.id,
+ toChainId: destinationChain.id,
+ })
+ window.open(explorerLink, '_blank', 'noopener,noreferrer')
+ }
}
+
return (
-
Confirmed on Synapse Explorer
+
Confirmed on Explorer
- {!destinationIsSender && (
+ {isDestinationValid && !isDestinationSender && (
to {shortenAddress(destinationAddress)}
)}
{isToday ? (
@@ -37,7 +40,9 @@ export const Completed = ({
Today
) : (
- {formattedTime}
+
+ {formattedTime ? formattedTime : 'Completed'}
+
)}
)
diff --git a/packages/synapse-interface/components/StateManagedBridge/ToTokenListOverlay.tsx b/packages/synapse-interface/components/StateManagedBridge/ToTokenListOverlay.tsx
index 1aca37c638..59fbb17864 100644
--- a/packages/synapse-interface/components/StateManagedBridge/ToTokenListOverlay.tsx
+++ b/packages/synapse-interface/components/StateManagedBridge/ToTokenListOverlay.tsx
@@ -21,7 +21,6 @@ import { CloseButton } from './components/CloseButton'
import { SearchResults } from './components/SearchResults'
import { formatBigIntToString } from '@/utils/bigint/format'
import { FetchState } from '@/slices/portfolio/actions'
-import { calculateEstimatedTransactionTime } from '@/utils/calculateEstimatedTransactionTime'
interface TokenWithRates extends Token {
exchangeRate: bigint
diff --git a/packages/synapse-interface/slices/transactions/updater.tsx b/packages/synapse-interface/slices/transactions/updater.tsx
index 180062b24b..c9d6d2b77e 100644
--- a/packages/synapse-interface/slices/transactions/updater.tsx
+++ b/packages/synapse-interface/slices/transactions/updater.tsx
@@ -61,7 +61,9 @@ export default function Updater(): null {
}: PortfolioState = usePortfolioState()
const [fetchUserHistoricalActivity, fetchedHistoricalActivity] =
- useLazyGetUserHistoricalActivityQuery({ pollingInterval: POLLING_INTERVAL })
+ useLazyGetUserHistoricalActivityQuery({
+ pollingInterval: POLLING_INTERVAL,
+ })
const [fetchUserPendingActivity, fetchedPendingActivity] =
useLazyGetUserPendingTransactionsQuery({
@@ -211,17 +213,24 @@ export default function Updater(): null {
// Store pending transactions until completed based on Explorer query
useEffect(() => {
- const hasUserPendingTransactions: boolean =
- Array.isArray(userPendingTransactions) &&
- !isUserPendingTransactionsLoading
-
- if (hasUserPendingTransactions) {
+ if (checkTransactionsExist(userPendingTransactions)) {
userPendingTransactions.forEach(
(pendingTransaction: BridgeTransaction) => {
- const isStored: boolean = pendingAwaitingCompletionTransactions?.some(
- (storedTransaction: BridgeTransaction) =>
- storedTransaction?.kappa === pendingTransaction?.kappa
- )
+ let isStored: boolean = false
+
+ if (checkTransactionsExist(pendingAwaitingCompletionTransactions)) {
+ isStored = pendingAwaitingCompletionTransactions?.some(
+ (storedTransaction: BridgeTransaction) =>
+ storedTransaction?.kappa === pendingTransaction?.kappa
+ )
+ }
+
+ if (checkTransactionsExist(fallbackQueryHistoricalTransactions)) {
+ isStored = fallbackQueryHistoricalTransactions?.some(
+ (storedTransaction: BridgeTransaction) =>
+ storedTransaction?.kappa === pendingTransaction?.kappa
+ )
+ }
if (!isStored) {
dispatch(
@@ -231,16 +240,16 @@ export default function Updater(): null {
}
)
}
- }, [userPendingTransactions])
+ }, [userPendingTransactions, fallbackQueryHistoricalTransactions])
// Handle updating stored pending transactions state throughout progress
useEffect(() => {
const hasUserHistoricalTransactions: boolean =
- Array.isArray(userHistoricalTransactions) &&
+ checkTransactionsExist(userHistoricalTransactions) &&
!isUserHistoricalTransactionsLoading
const hasPendingBridgeTransactions: boolean =
- Array.isArray(pendingBridgeTransactions) &&
+ checkTransactionsExist(pendingBridgeTransactions) &&
pendingBridgeTransactions.length > 0
if (hasUserHistoricalTransactions && activeTab === PortfolioTabs.ACTIVITY) {
@@ -320,7 +329,11 @@ export default function Updater(): null {
}
)
}
- }, [userHistoricalTransactions, activeTab])
+ }, [
+ activeTab,
+ userHistoricalTransactions,
+ fallbackQueryHistoricalTransactions,
+ ])
// Handle adding completed fallback historical transaction to seen list
useEffect(() => {
@@ -383,7 +396,7 @@ export default function Updater(): null {
*/
useEffect(() => {
const hasUserHistoricalTransactions: boolean =
- Array.isArray(userHistoricalTransactions) &&
+ checkTransactionsExist(userHistoricalTransactions) &&
!isUserHistoricalTransactionsLoading
if (
diff --git a/packages/synapse-interface/utils/calculateEstimatedTransactionTime.tsx b/packages/synapse-interface/utils/calculateEstimatedTransactionTime.tsx
deleted file mode 100644
index a1a7f9ea54..0000000000
--- a/packages/synapse-interface/utils/calculateEstimatedTransactionTime.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { Chain, Token } from './types'
-import { Address } from 'viem'
-import { BRIDGE_REQUIRED_CONFIRMATIONS } from '@/constants/bridge'
-import { tokenAddressToToken } from '@/constants/tokens'
-import { ARBITRUM, ETH } from '@/constants/chains/master'
-import { USDC } from '@/constants/tokens/bridgeable'
-import { CHAINS_BY_ID } from '@/constants/chains'
-
-// Utility function to fetch estimated transaction time
-// Returned as a number, in seconds
-export const calculateEstimatedTransactionTime = ({
- originChainId,
- originTokenAddress,
-}: {
- originChainId: number
- originTokenAddress: Address
-}): number => {
- const originChain: Chain = CHAINS_BY_ID[originChainId]
- const originToken: Token = tokenAddressToToken(
- originChainId,
- originTokenAddress
- )
- const baseEstimatedCompletionInSeconds: number =
- (BRIDGE_REQUIRED_CONFIRMATIONS[originChainId] * originChain?.blockTime) /
- 1000
-
- let estimatedCompletionInSeconds: number
-
- // Specific to CCTP Transactions
- if (originChainId === ARBITRUM.id || originChainId === ETH.id) {
- const isCCTP: boolean = originTokenAddress === USDC.addresses[originChainId]
- const attestationTime: number = 13 * 60
-
- estimatedCompletionInSeconds =
- baseEstimatedCompletionInSeconds + attestationTime
-
- return isCCTP
- ? estimatedCompletionInSeconds
- : baseEstimatedCompletionInSeconds
- }
-
- return baseEstimatedCompletionInSeconds
-}
diff --git a/packages/synapse-interface/utils/getEstimatedBridgeTime.tsx b/packages/synapse-interface/utils/getEstimatedBridgeTime.tsx
new file mode 100644
index 0000000000..69cf4eb41e
--- /dev/null
+++ b/packages/synapse-interface/utils/getEstimatedBridgeTime.tsx
@@ -0,0 +1,68 @@
+import { useSynapseContext } from './providers/SynapseProvider'
+import { BRIDGE_REQUIRED_CONFIRMATIONS } from '@/constants/bridge'
+import { Chain } from './types'
+
+enum SynapseBridgeModule {
+ BRIDGE = 'SynapseBridge',
+ CCTP = 'SynapseCCTP',
+}
+
+/**
+ * Fetches estimated duration of Bridge Transaction from Synapse SDK
+ *
+ * @param bridgeOriginChain - The selected origin chain.
+ * @param bridgeModuleName - The name of the bridge module. e.g 'Bridge' or 'CCTP'.
+ * @param formattedEventType - The name of the bridge event.
+ * @returns - The estimated time for a bridge operation, in seconds.
+ */
+export const getEstimatedBridgeTime = ({
+ bridgeOriginChain,
+ bridgeModuleName,
+ formattedEventType,
+}: {
+ bridgeOriginChain: Chain
+ bridgeModuleName?: string
+ formattedEventType?: string
+}) => {
+ const { synapseSDK } = useSynapseContext()
+
+ if (!bridgeOriginChain) return null
+
+ if (bridgeModuleName) {
+ return synapseSDK.getEstimatedTime(bridgeOriginChain.id, bridgeModuleName)
+ }
+
+ if (formattedEventType) {
+ const fetchedBridgeModuleName: string =
+ synapseSDK.getBridgeModuleName(formattedEventType)
+
+ return synapseSDK.getEstimatedTime(
+ bridgeOriginChain?.id,
+ fetchedBridgeModuleName
+ )
+ }
+
+ // Fallback estimated time when inputs invalid
+ return synapseSDK.getEstimatedTime(
+ bridgeOriginChain.id,
+ SynapseBridgeModule.BRIDGE
+ )
+}
+
+export const getEstimatedBridgeTimeInMinutes = ({
+ bridgeOriginChain,
+ bridgeModuleName,
+ formattedEventType,
+}: {
+ bridgeOriginChain: Chain
+ bridgeModuleName?: string
+ formattedEventType?: string
+}) => {
+ const estimatedBridgeTime = getEstimatedBridgeTime({
+ bridgeOriginChain,
+ bridgeModuleName,
+ formattedEventType,
+ })
+
+ return estimatedBridgeTime ? Math.ceil(estimatedBridgeTime / 60) : null
+}
diff --git a/packages/synapse-interface/utils/hooks/useBridgeTxStatus.tsx b/packages/synapse-interface/utils/hooks/useBridgeTxStatus.tsx
new file mode 100644
index 0000000000..067707e9c9
--- /dev/null
+++ b/packages/synapse-interface/utils/hooks/useBridgeTxStatus.tsx
@@ -0,0 +1,72 @@
+import { useState, useEffect } from 'react'
+import { useSynapseContext } from '@/utils/providers/SynapseProvider'
+
+interface UseBridgeTxStatusProps {
+ originChainId: number
+ destinationChainId: number
+ transactionHash: string
+ bridgeModuleName?: string
+ kappa?: string
+ checkStatus: boolean
+ elapsedTime: number // used as trigger to refetch status
+}
+
+export const useBridgeTxStatus = ({
+ originChainId,
+ destinationChainId,
+ transactionHash,
+ bridgeModuleName,
+ kappa,
+ checkStatus = false,
+ elapsedTime,
+}: UseBridgeTxStatusProps) => {
+ const [isComplete, setIsComplete] = useState(false)
+ const { synapseSDK } = useSynapseContext()
+
+ const getKappa = async (): Promise => {
+ if (!bridgeModuleName || !originChainId || !transactionHash) return
+ return await synapseSDK.getSynapseTxId(
+ originChainId,
+ bridgeModuleName,
+ transactionHash
+ )
+ }
+
+ const getBridgeTxStatus = async (
+ destinationChainId: number,
+ bridgeModuleName: string,
+ kappa: string
+ ) => {
+ if (!destinationChainId || !bridgeModuleName || !kappa) return null
+ return await synapseSDK.getBridgeTxStatus(
+ destinationChainId,
+ bridgeModuleName,
+ kappa
+ )
+ }
+
+ useEffect(() => {
+ if (!checkStatus) return
+ ;(async () => {
+ let _kappa
+
+ if (!kappa) {
+ _kappa = await getKappa()
+ } else {
+ _kappa = kappa
+ }
+
+ const txStatus = await getBridgeTxStatus(
+ destinationChainId,
+ bridgeModuleName,
+ _kappa
+ )
+
+ if (txStatus !== null) {
+ setIsComplete(txStatus)
+ }
+ })()
+ }, [elapsedTime, checkStatus])
+
+ return isComplete
+}