@@ -48,6 +54,7 @@ export const OutputContainer = () => {
disabled={true}
showValue={showValue}
isLoading={isLoading}
+ className={inputClassName}
/>
diff --git a/packages/synapse-interface/components/StateManagedBridge/hooks/useBridgeValidations.ts b/packages/synapse-interface/components/StateManagedBridge/hooks/useBridgeValidations.ts
index e64ac72587..b3f31ab0f6 100644
--- a/packages/synapse-interface/components/StateManagedBridge/hooks/useBridgeValidations.ts
+++ b/packages/synapse-interface/components/StateManagedBridge/hooks/useBridgeValidations.ts
@@ -111,7 +111,7 @@ export const useBridgeValidations = () => {
}
}
-const constructStringifiedBridgeSelections = (
+export const constructStringifiedBridgeSelections = (
originAmount,
originChainId,
originToken,
diff --git a/packages/synapse-interface/components/StateManagedBridge/hooks/useConfirmNewBridgePrice.ts b/packages/synapse-interface/components/StateManagedBridge/hooks/useConfirmNewBridgePrice.ts
new file mode 100644
index 0000000000..2dbead7f24
--- /dev/null
+++ b/packages/synapse-interface/components/StateManagedBridge/hooks/useConfirmNewBridgePrice.ts
@@ -0,0 +1,125 @@
+import { useState, useEffect, useMemo, useRef } from 'react'
+
+import { useBridgeState } from '@/slices/bridge/hooks'
+import { useBridgeQuoteState } from '@/slices/bridgeQuote/hooks'
+import { constructStringifiedBridgeSelections } from './useBridgeValidations'
+import { BridgeQuote } from '@/utils/types'
+
+export const useConfirmNewBridgePrice = () => {
+ const triggerQuoteRef = useRef
(null)
+ const bpsThreshold = 0.0001 // 1bps
+
+ const [hasQuoteOutputChanged, setHasQuoteOutputChanged] =
+ useState(false)
+ const [hasUserConfirmedChange, setHasUserConfirmedChange] =
+ useState(false)
+
+ const { bridgeQuote, previousBridgeQuote } = useBridgeQuoteState()
+ const { debouncedFromValue, fromToken, toToken, fromChainId, toChainId } =
+ useBridgeState()
+
+ const currentBridgeQuoteSelections = useMemo(
+ () =>
+ constructStringifiedBridgeSelections(
+ debouncedFromValue,
+ fromChainId,
+ fromToken,
+ toChainId,
+ toToken
+ ),
+ [debouncedFromValue, fromChainId, fromToken, toChainId, toToken]
+ )
+
+ const previousBridgeQuoteSelections = useMemo(
+ () =>
+ constructStringifiedBridgeSelections(
+ previousBridgeQuote?.inputAmountForQuote,
+ previousBridgeQuote?.originChainId,
+ previousBridgeQuote?.originTokenForQuote,
+ previousBridgeQuote?.destChainId,
+ previousBridgeQuote?.destTokenForQuote
+ ),
+ [previousBridgeQuote]
+ )
+
+ const hasSameSelectionsAsPreviousQuote = useMemo(
+ () => currentBridgeQuoteSelections === previousBridgeQuoteSelections,
+ [currentBridgeQuoteSelections, previousBridgeQuoteSelections]
+ )
+
+ const isPendingConfirmChange =
+ hasQuoteOutputChanged &&
+ hasSameSelectionsAsPreviousQuote &&
+ !hasUserConfirmedChange
+
+ useEffect(() => {
+ const validQuotes =
+ bridgeQuote?.outputAmount && previousBridgeQuote?.outputAmount
+
+ const hasBridgeModuleChanged =
+ bridgeQuote?.bridgeModuleName !==
+ (triggerQuoteRef.current?.bridgeModuleName ??
+ previousBridgeQuote?.bridgeModuleName)
+
+ const outputAmountDiffMoreThanThreshold = validQuotes
+ ? calculateOutputRelativeDifference(
+ bridgeQuote,
+ triggerQuoteRef.current ?? previousBridgeQuote
+ ) > bpsThreshold
+ : false
+
+ if (
+ validQuotes &&
+ hasSameSelectionsAsPreviousQuote &&
+ (outputAmountDiffMoreThanThreshold || hasBridgeModuleChanged)
+ ) {
+ requestUserConfirmChange(previousBridgeQuote)
+ } else {
+ resetConfirm()
+ }
+ }, [bridgeQuote, previousBridgeQuote, hasSameSelectionsAsPreviousQuote])
+
+ const requestUserConfirmChange = (previousQuote: BridgeQuote) => {
+ if (!hasQuoteOutputChanged && !hasUserConfirmedChange) {
+ triggerQuoteRef.current = previousQuote
+ setHasQuoteOutputChanged(true)
+ }
+ setHasUserConfirmedChange(false)
+ }
+
+ const resetConfirm = () => {
+ if (hasUserConfirmedChange) {
+ triggerQuoteRef.current = null
+ setHasQuoteOutputChanged(false)
+ setHasUserConfirmedChange(false)
+ }
+ }
+
+ const onUserAcceptChange = () => {
+ triggerQuoteRef.current = null
+ setHasUserConfirmedChange(true)
+ }
+
+ return {
+ isPendingConfirmChange,
+ onUserAcceptChange,
+ }
+}
+
+const calculateOutputRelativeDifference = (
+ currentQuote?: BridgeQuote,
+ previousQuote?: BridgeQuote
+) => {
+ if (!currentQuote?.outputAmountString || !previousQuote?.outputAmountString) {
+ return null
+ }
+
+ const currentOutput = parseFloat(currentQuote.outputAmountString)
+ const previousOutput = parseFloat(previousQuote.outputAmountString)
+
+ if (previousOutput === 0) {
+ return null
+ }
+
+ return (previousOutput - currentOutput) / previousOutput
+}
diff --git a/packages/synapse-interface/components/StateManagedBridge/hooks/useStaleQuoteUpdater.ts b/packages/synapse-interface/components/StateManagedBridge/hooks/useStaleQuoteUpdater.ts
new file mode 100644
index 0000000000..9a9cbf1444
--- /dev/null
+++ b/packages/synapse-interface/components/StateManagedBridge/hooks/useStaleQuoteUpdater.ts
@@ -0,0 +1,114 @@
+import { useEffect, useRef, useState } from 'react'
+
+import { BridgeQuote } from '@/utils/types'
+import { useIntervalTimer } from '@/utils/hooks/useIntervalTimer'
+
+export const useStaleQuoteUpdater = (
+ quote: BridgeQuote,
+ refreshQuoteCallback: () => Promise,
+ enabled: boolean,
+ staleTimeout: number = 15000, // in ms
+ autoRefreshDuration: number = 30000 // in ms
+) => {
+ const [isStale, setIsStale] = useState(false)
+ const autoRefreshIntervalRef = useRef(null)
+ const autoRefreshStartTimeRef = useRef(null)
+ const mouseMoveListenerRef = useRef void)>(null)
+ const manualRefreshRef = useRef(null)
+
+ useIntervalTimer(staleTimeout, !enabled)
+
+ const [mouseMoved, resetMouseMove] = useTrackMouseMove()
+
+ const clearManualRefreshTimeout = () => {
+ if (manualRefreshRef.current) {
+ clearTimeout(manualRefreshRef.current)
+ }
+ }
+
+ const clearAutoRefreshInterval = () => {
+ if (autoRefreshIntervalRef.current) {
+ clearInterval(autoRefreshIntervalRef.current)
+ }
+ }
+
+ const clearMouseMoveListener = () => {
+ if (mouseMoveListenerRef.current) {
+ mouseMoveListenerRef.current = null
+ }
+ }
+
+ useEffect(() => {
+ if (mouseMoved && autoRefreshStartTimeRef.current) {
+ autoRefreshStartTimeRef.current = null
+ resetMouseMove()
+ }
+ }, [quote])
+
+ // Start auto-refresh logic for ${autoRefreshDuration}ms seconds
+ useEffect(() => {
+ if (enabled) {
+ // If auto-refresh has not started yet, initialize the start time
+ if (autoRefreshStartTimeRef.current === null) {
+ autoRefreshStartTimeRef.current = Date.now()
+ }
+
+ const elapsedTime = Date.now() - autoRefreshStartTimeRef.current
+
+ // If ${autoRefreshDuration}ms hasn't passed, keep auto-refreshing
+ if (elapsedTime < autoRefreshDuration) {
+ clearManualRefreshTimeout()
+ clearAutoRefreshInterval()
+
+ autoRefreshIntervalRef.current = setInterval(() => {
+ refreshQuoteCallback()
+ }, staleTimeout)
+ } else {
+ // If more than ${autoRefreshDuration}ms have passed, stop auto-refreshing and switch to mousemove logic
+ clearAutoRefreshInterval()
+
+ manualRefreshRef.current = setTimeout(() => {
+ clearMouseMoveListener()
+ setIsStale(true)
+
+ const handleMouseMove = () => {
+ refreshQuoteCallback()
+ clearMouseMoveListener()
+ setIsStale(false)
+ }
+
+ document.addEventListener('mousemove', handleMouseMove, {
+ once: true,
+ })
+
+ mouseMoveListenerRef.current = handleMouseMove
+ }, staleTimeout)
+ }
+ }
+
+ return () => {
+ clearManualRefreshTimeout()
+ clearAutoRefreshInterval()
+ setIsStale(false)
+ }
+ }, [quote, enabled])
+
+ return isStale
+}
+
+export const useTrackMouseMove = (): [boolean, () => void] => {
+ const [moved, setMoved] = useState(false)
+
+ const onMove = () => setMoved(true)
+ const onReset = () => setMoved(false)
+
+ useEffect(() => {
+ document.addEventListener('mousemove', onMove)
+
+ return () => {
+ document.removeEventListener('mousemove', onMove)
+ }
+ }, [])
+
+ return [moved, onReset]
+}
diff --git a/packages/synapse-interface/components/buttons/TransactionButton.tsx b/packages/synapse-interface/components/buttons/TransactionButton.tsx
index e868868dc8..24461d6526 100644
--- a/packages/synapse-interface/components/buttons/TransactionButton.tsx
+++ b/packages/synapse-interface/components/buttons/TransactionButton.tsx
@@ -12,6 +12,7 @@ const baseClassNames = {
disabled: 'disabled:opacity-50 disabled:cursor-not-allowed',
background: 'bg-zinc-400 dark:bg-bgLight',
gradient: 'enabled:bg-gradient-to-r',
+ transition: 'transition',
}
export const TransactionButton = ({
@@ -42,8 +43,8 @@ export const TransactionButton = ({
style={style}
disabled={disabled}
className={`
- ${className}
${joinClassNames(baseClassNames)}
+ ${className}
${
isPending
? 'from-fuchsia-400 dark:from-fuchsia-900 to-purple-400 dark:to-purple-900'
diff --git a/packages/synapse-interface/components/ui/AmountInput.tsx b/packages/synapse-interface/components/ui/AmountInput.tsx
index ed352f5176..aebc3fd3a7 100644
--- a/packages/synapse-interface/components/ui/AmountInput.tsx
+++ b/packages/synapse-interface/components/ui/AmountInput.tsx
@@ -11,6 +11,7 @@ interface AmountInputTypes {
showValue: string
handleFromValueChange?: (event: React.ChangeEvent) => void
setIsTyping?: (isTyping: boolean) => void
+ className?: string
}
export function AmountInput({
@@ -20,6 +21,7 @@ export function AmountInput({
showValue,
handleFromValueChange,
setIsTyping,
+ className,
}: AmountInputTypes) {
const debouncedSetIsTyping = useCallback(
debounce((value: boolean) => setIsTyping?.(value), 600),
@@ -38,6 +40,7 @@ export function AmountInput({
placeholder: 'placeholder:text-zinc-500 placeholder:dark:text-zinc-400',
font: 'text-xl md:text-2xl font-medium',
focus: 'focus:outline-none focus:ring-0 focus:border-none',
+ custom: className,
}
return (
diff --git a/packages/synapse-interface/messages/ar.json b/packages/synapse-interface/messages/ar.json
index b52b6f617d..fc6571c49e 100644
--- a/packages/synapse-interface/messages/ar.json
+++ b/packages/synapse-interface/messages/ar.json
@@ -57,7 +57,8 @@
"ReceiveWithEllipsis": "استلام…",
"All receivable tokens": "جميع الرموز القابلة للاستلام",
"Route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}": "تم العثور على مسار لجسر {debouncedFromValue} {fromToken} على {fromChainId} إلى {toToken} على {toChainId}",
- "No route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}": "لم يتم العثور على مسار لجسر {debouncedFromValue} {fromToken} على {fromChainId} إلى {toToken} على {toChainId}"
+ "No route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}": "لم يتم العثور على مسار لجسر {debouncedFromValue} {fromToken} على {fromChainId} إلى {toToken} على {toChainId}",
+ "Confirm new quote": "تأكيد العرض الجديد"
},
"Completed": {
"to": "إلى",
diff --git a/packages/synapse-interface/messages/en-US.json b/packages/synapse-interface/messages/en-US.json
index 4784ee82d4..7c5a99c5d0 100644
--- a/packages/synapse-interface/messages/en-US.json
+++ b/packages/synapse-interface/messages/en-US.json
@@ -57,7 +57,8 @@
"ReceiveWithEllipsis": "Receive…",
"All receivable tokens": "All receivable tokens",
"Route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}": "Route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}",
- "No route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}": "No route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}"
+ "No route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}": "No route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}",
+ "Confirm new quote": "Confirm new quote"
},
"Completed": {
"to": "to",
diff --git a/packages/synapse-interface/messages/es.json b/packages/synapse-interface/messages/es.json
index dee3724b09..c6ac15e7d8 100644
--- a/packages/synapse-interface/messages/es.json
+++ b/packages/synapse-interface/messages/es.json
@@ -57,7 +57,8 @@
"ReceiveWithEllipsis": "Recibir…",
"All receivable tokens": "Todos los tokens recibibles",
"Route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}": "Ruta encontrada para el puente de {debouncedFromValue} {fromToken} en {fromChainId} a {toToken} en {toChainId}",
- "No route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}": "No se encontró ruta para el puente de {debouncedFromValue} {fromToken} en {fromChainId} a {toToken} en {toChainId}"
+ "No route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}": "No se encontró ruta para el puente de {debouncedFromValue} {fromToken} en {fromChainId} a {toToken} en {toChainId}",
+ "Confirm new quote": "Confirmar nueva cotización"
},
"Completed": {
"to": "a",
diff --git a/packages/synapse-interface/messages/fr.json b/packages/synapse-interface/messages/fr.json
index 2541e4c68f..b5f249aaf1 100644
--- a/packages/synapse-interface/messages/fr.json
+++ b/packages/synapse-interface/messages/fr.json
@@ -57,7 +57,8 @@
"ReceiveWithEllipsis": "Recevoir…",
"All receivable tokens": "Tous les jetons recevables",
"Route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}": "Route trouvée pour le transfert de {debouncedFromValue} {fromToken} sur {fromChainId} vers {toToken} sur {toChainId}",
- "No route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}": "Aucune route trouvée pour le transfert de {debouncedFromValue} {fromToken} sur {fromChainId} vers {toToken} sur {toChainId}"
+ "No route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}": "Aucune route trouvée pour le transfert de {debouncedFromValue} {fromToken} sur {fromChainId} vers {toToken} sur {toChainId}",
+ "Confirm new quote": "Confirmer la nouvelle offre"
},
"Completed": {
"to": "vers",
diff --git a/packages/synapse-interface/messages/jp.json b/packages/synapse-interface/messages/jp.json
index 1d23dd39a9..c187de083b 100644
--- a/packages/synapse-interface/messages/jp.json
+++ b/packages/synapse-interface/messages/jp.json
@@ -57,7 +57,8 @@
"ReceiveWithEllipsis": "受取…",
"All receivable tokens": "すべての受取可能トークン",
"Route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}": "{fromChainId}の{debouncedFromValue} {fromToken}から{toChainId}の{toToken}へのブリッジのルートが見つかりました",
- "No route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}": "{fromChainId}の{debouncedFromValue} {fromToken}から{toChainId}の{toToken}へのブリッジのルートが見つかりませんでした"
+ "No route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}": "{fromChainId}の{debouncedFromValue} {fromToken}から{toChainId}の{toToken}へのブリッジのルートが見つかりませんでした",
+ "Confirm new quote": "新しい見積もりを確認"
},
"Completed": {
"to": "へ",
diff --git a/packages/synapse-interface/messages/tr.json b/packages/synapse-interface/messages/tr.json
index 34ad00a1f9..dbe44d71b2 100644
--- a/packages/synapse-interface/messages/tr.json
+++ b/packages/synapse-interface/messages/tr.json
@@ -57,7 +57,8 @@
"ReceiveWithEllipsis": "Al…",
"All receivable tokens": "Tüm alınabilir tokenler",
"Route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}": "{fromChainId}'deki {debouncedFromValue} {fromToken}'ı {toChainId}'deki {toToken}'a köprüleme için rota bulundu",
- "No route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}": "{fromChainId}'deki {debouncedFromValue} {fromToken}'ı {toChainId}'deki {toToken}'a köprüleme için rota bulunamadı"
+ "No route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}": "{fromChainId}'deki {debouncedFromValue} {fromToken}'ı {toChainId}'deki {toToken}'a köprüleme için rota bulunamadı",
+ "Confirm new quote": "Yeni teklifi onayla"
},
"Completed": {
"to": "nereye",
diff --git a/packages/synapse-interface/messages/zh-CN.json b/packages/synapse-interface/messages/zh-CN.json
index 34a2b7500d..0d5cb9e02d 100644
--- a/packages/synapse-interface/messages/zh-CN.json
+++ b/packages/synapse-interface/messages/zh-CN.json
@@ -57,7 +57,8 @@
"ReceiveWithEllipsis": "接收…",
"All receivable tokens": "所有可接收代币",
"Route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}": "找到桥接路线:从 {fromChainId} 的 {fromToken} 到 {toChainId} 的 {toToken}",
- "No route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}": "未找到桥接路线:从 {fromChainId} 的 {fromToken} 到 {toChainId} 的 {toToken}"
+ "No route found for bridging {debouncedFromValue} {fromToken} on {fromChainId} to {toToken} on {toChainId}": "未找到桥接路线:从 {fromChainId} 的 {fromToken} 到 {toChainId} 的 {toToken}",
+ "Confirm new quote": "确认新报价"
},
"Completed": {
"to": "到",
diff --git a/packages/synapse-interface/pages/state-managed-bridge/index.tsx b/packages/synapse-interface/pages/state-managed-bridge/index.tsx
index 87f503de4f..315b0651bd 100644
--- a/packages/synapse-interface/pages/state-managed-bridge/index.tsx
+++ b/packages/synapse-interface/pages/state-managed-bridge/index.tsx
@@ -58,7 +58,6 @@ import { RootState } from '@/store/store'
import { getUnixTimeMinutesFromNow } from '@/utils/time'
import { isTransactionReceiptError } from '@/utils/isTransactionReceiptError'
import { wagmiConfig } from '@/wagmiConfig'
-import { useStaleQuoteUpdater } from '@/utils/hooks/useStaleQuoteUpdater'
import { useMaintenance } from '@/components/Maintenance/Maintenance'
import { screenAddress } from '@/utils/screenAddress'
import { useWalletState } from '@/slices/wallet/hooks'
@@ -66,10 +65,14 @@ import { useBridgeQuoteState } from '@/slices/bridgeQuote/hooks'
import { resetBridgeQuote } from '@/slices/bridgeQuote/reducer'
import { fetchBridgeQuote } from '@/slices/bridgeQuote/thunks'
import { useIsBridgeApproved } from '@/utils/hooks/useIsBridgeApproved'
+import { isTransactionUserRejectedError } from '@/utils/isTransactionUserRejectedError'
+import { BridgeQuoteResetTimer } from '@/components/StateManagedBridge/BridgeQuoteResetTimer'
+import { useBridgeValidations } from '@/components/StateManagedBridge/hooks/useBridgeValidations'
+import { useStaleQuoteUpdater } from '@/components/StateManagedBridge/hooks/useStaleQuoteUpdater'
const StateManagedBridge = () => {
const dispatch = useAppDispatch()
- const { address } = useAccount()
+ const { address, isConnected } = useAccount()
const { synapseSDK } = useSynapseContext()
const router = useRouter()
const { query, pathname } = router
@@ -96,6 +99,8 @@ const StateManagedBridge = () => {
const isApproved = useIsBridgeApproved()
+ const { hasValidQuote, hasSufficientBalance } = useBridgeValidations()
+
const { isWalletPending } = useWalletState()
const { showSettingsSlideOver } = useSelector(
@@ -137,8 +142,6 @@ const StateManagedBridge = () => {
// will have to handle deadlineMinutes here at later time, gets passed as optional last arg in .bridgeQuote()
- /* clear stored bridge quote before requesting new bridge quote */
- dispatch(resetBridgeQuote())
const currentTimestamp: number = getUnixTimeMinutesFromNow(0)
try {
@@ -217,11 +220,18 @@ const StateManagedBridge = () => {
}
}
- useStaleQuoteUpdater(
+ const isUpdaterEnabled =
+ isConnected &&
+ hasValidQuote &&
+ hasSufficientBalance &&
+ isApproved &&
+ !isBridgePaused &&
+ !isWalletPending
+
+ const isQuoteStale = useStaleQuoteUpdater(
bridgeQuote,
getAndSetBridgeQuote,
- isLoading,
- isWalletPending,
+ isUpdaterEnabled,
quoteTimeout
)
@@ -424,6 +434,10 @@ const StateManagedBridge = () => {
)
}
+ if (isTransactionUserRejectedError(error)) {
+ getAndSetBridgeQuote()
+ }
+
return txErrorHandler(error)
} finally {
dispatch(setIsWalletPending(false))
@@ -467,18 +481,29 @@ const StateManagedBridge = () => {
}}
disabled={isWalletPending}
/>
-
+
-
+
>
)}
diff --git a/packages/synapse-interface/slices/bridgeQuote/reducer.ts b/packages/synapse-interface/slices/bridgeQuote/reducer.ts
index 8407876035..898769f622 100644
--- a/packages/synapse-interface/slices/bridgeQuote/reducer.ts
+++ b/packages/synapse-interface/slices/bridgeQuote/reducer.ts
@@ -6,11 +6,13 @@ import { fetchBridgeQuote } from './thunks'
export interface BridgeQuoteState {
bridgeQuote: BridgeQuote
+ previousBridgeQuote: BridgeQuote | null
isLoading: boolean
}
export const initialState: BridgeQuoteState = {
bridgeQuote: EMPTY_BRIDGE_QUOTE,
+ previousBridgeQuote: null,
isLoading: false,
}
@@ -24,6 +26,9 @@ export const bridgeQuoteSlice = createSlice({
resetBridgeQuote: (state) => {
state.bridgeQuote = initialState.bridgeQuote
},
+ setPreviousBridgeQuote: (state, action: PayloadAction) => {
+ state.previousBridgeQuote = action.payload
+ },
},
extraReducers: (builder) => {
builder
@@ -44,6 +49,7 @@ export const bridgeQuoteSlice = createSlice({
},
})
-export const { resetBridgeQuote, setIsLoading } = bridgeQuoteSlice.actions
+export const { resetBridgeQuote, setIsLoading, setPreviousBridgeQuote } =
+ bridgeQuoteSlice.actions
export default bridgeQuoteSlice.reducer
diff --git a/packages/synapse-interface/store/middleware/bridgeQuoteHistoryMiddleware.ts b/packages/synapse-interface/store/middleware/bridgeQuoteHistoryMiddleware.ts
new file mode 100644
index 0000000000..f58c09ae03
--- /dev/null
+++ b/packages/synapse-interface/store/middleware/bridgeQuoteHistoryMiddleware.ts
@@ -0,0 +1,25 @@
+import {
+ Middleware,
+ MiddlewareAPI,
+ Dispatch,
+ AnyAction,
+} from '@reduxjs/toolkit'
+
+export const bridgeQuoteHistoryMiddleware: Middleware =
+ (store: MiddlewareAPI) => (next: Dispatch) => (action: AnyAction) => {
+ const previousState = store.getState()
+ const result = next(action)
+ const currentState = store.getState()
+
+ if (
+ previousState.bridgeQuote.bridgeQuote !==
+ currentState.bridgeQuote.bridgeQuote
+ ) {
+ store.dispatch({
+ type: 'bridgeQuote/setPreviousBridgeQuote',
+ payload: previousState.bridgeQuote.bridgeQuote,
+ })
+ }
+
+ return result
+ }
diff --git a/packages/synapse-interface/store/destinationAddressMiddleware.ts b/packages/synapse-interface/store/middleware/destinationAddressMiddleware.ts
similarity index 100%
rename from packages/synapse-interface/store/destinationAddressMiddleware.ts
rename to packages/synapse-interface/store/middleware/destinationAddressMiddleware.ts
diff --git a/packages/synapse-interface/store/store.ts b/packages/synapse-interface/store/store.ts
index 70f5515a6f..d8cdf3e70a 100644
--- a/packages/synapse-interface/store/store.ts
+++ b/packages/synapse-interface/store/store.ts
@@ -6,7 +6,8 @@ import { api } from '@/slices/api/slice'
import { segmentAnalyticsEvent } from '@/contexts/SegmentAnalyticsProvider'
import { storageKey, persistConfig, persistedReducer } from './reducer'
import { resetReduxCache } from '@/slices/application/actions'
-import { destinationAddressMiddleware } from '@/store/destinationAddressMiddleware'
+import { destinationAddressMiddleware } from '@/store/middleware/destinationAddressMiddleware'
+import { bridgeQuoteHistoryMiddleware } from './middleware/bridgeQuoteHistoryMiddleware'
const checkVersionAndResetCache = (): boolean => {
if (typeof window !== 'undefined') {
@@ -28,7 +29,11 @@ export const store = configureStore({
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
- }).concat(api.middleware, destinationAddressMiddleware),
+ }).concat(
+ api.middleware,
+ destinationAddressMiddleware,
+ bridgeQuoteHistoryMiddleware
+ ),
})
if (checkVersionAndResetCache()) {
diff --git a/packages/synapse-interface/utils/hooks/useStaleQuoteUpdater.ts b/packages/synapse-interface/utils/hooks/useStaleQuoteUpdater.ts
deleted file mode 100644
index 05ccd37dc3..0000000000
--- a/packages/synapse-interface/utils/hooks/useStaleQuoteUpdater.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { isNull, isNumber } from 'lodash'
-import { useEffect, useRef } from 'react'
-
-import { BridgeQuote } from '@/utils/types'
-import { calculateTimeBetween } from '@/utils/time'
-import { useIntervalTimer } from '@/utils/hooks/useIntervalTimer'
-import { convertUuidToUnix } from '@/utils/convertUuidToUnix'
-
-/**
- * Refreshes quotes based on selected stale timeout duration.
- * Will refresh quote when browser is active and wallet prompt is not pending.
- */
-export const useStaleQuoteUpdater = (
- quote: BridgeQuote,
- refreshQuoteCallback: () => Promise,
- isQuoteLoading: boolean,
- isWalletPending: boolean,
- staleTimeout: number = 15000 // Default 15_000ms or 15s
-) => {
- const eventListenerRef = useRef void)>(null)
-
- const quoteTime = quote?.id ? convertUuidToUnix(quote?.id) : null
- const isValidQuote = isNumber(quoteTime) && !isNull(quoteTime)
-
- const currentTime = useIntervalTimer(staleTimeout, !isValidQuote)
-
- useEffect(() => {
- if (isValidQuote && !isQuoteLoading && !isWalletPending) {
- const timeDifference = calculateTimeBetween(currentTime, quoteTime)
- const isStaleQuote = timeDifference >= staleTimeout
-
- if (isStaleQuote) {
- if (eventListenerRef.current) {
- document.removeEventListener('mousemove', eventListenerRef.current)
- }
-
- const newEventListener = () => {
- refreshQuoteCallback()
- eventListenerRef.current = null
- }
-
- document.addEventListener('mousemove', newEventListener, {
- once: true,
- })
-
- eventListenerRef.current = newEventListener
- }
- }
- }, [currentTime, staleTimeout])
-}
diff --git a/packages/synapse-interface/utils/time.ts b/packages/synapse-interface/utils/time.ts
index 8e30b5075a..20a8c042f0 100644
--- a/packages/synapse-interface/utils/time.ts
+++ b/packages/synapse-interface/utils/time.ts
@@ -51,3 +51,7 @@ export const isTimestampToday = (unixTimestamp: number): boolean => {
dateFromTimestamp.getFullYear() === currentDate.getFullYear()
)
}
+
+export const convertMsToSeconds = (ms: number) => {
+ return Math.ceil(ms / 1000)
+}