Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(synapse-interface): refresh stale quotes #2607

Merged
merged 13 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/synapse-interface/constants/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const EMPTY_BRIDGE_QUOTE = {
estimatedTime: null,
bridgeModuleName: null,
gasDropAmount: 0n,
timestamp: null,
bigboydiamonds marked this conversation as resolved.
Show resolved Hide resolved
}

export const EMPTY_BRIDGE_QUOTE_ZERO = {
Expand All @@ -31,6 +32,7 @@ export const EMPTY_BRIDGE_QUOTE_ZERO = {
estimatedTime: null,
bridgeModuleName: null,
gasDropAmount: 0n,
timestamp: null,
}
/**
* ETH Only Bridge Config used to calculate swap fees
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import {
getBridgeModuleNames,
} from '@/components/Maintenance/Maintenance'
import { wagmiConfig } from '@/wagmiConfig'
import { useStaleQuoteRefresher } from '../../utils/hooks/useStaleQuoteRefresher'

const StateManagedBridge = () => {
const { address } = useAccount()
Expand All @@ -93,6 +94,7 @@ const StateManagedBridge = () => {
bridgeQuote,
debouncedFromValue,
destinationAddress,
isLoading: isLoadingQuote,
}: BridgeState = useBridgeState()
const { showSettingsSlideOver, showDestinationAddress } = useSelector(
(state: RootState) => state.bridgeDisplay
Expand Down Expand Up @@ -153,6 +155,7 @@ const StateManagedBridge = () => {

try {
dispatch(setIsLoading(true))
const currentTimestamp: number = getTimeMinutesFromNow(0)

const allQuotes = await synapseSDK.allBridgeQuotes(
fromChainId,
Expand Down Expand Up @@ -273,6 +276,7 @@ const StateManagedBridge = () => {
estimatedTime: estimatedTime,
bridgeModuleName: bridgeModuleName,
gasDropAmount: BigInt(gasDropAmount.toString()),
timestamp: currentTimestamp,
bigboydiamonds marked this conversation as resolved.
Show resolved Hide resolved
})
)

Expand Down Expand Up @@ -314,6 +318,8 @@ const StateManagedBridge = () => {
}
}

useStaleQuoteRefresher(bridgeQuote, isLoadingQuote, getAndSetBridgeQuote)

const approveTxn = async () => {
try {
const tx = approveToken(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { BridgeQuote, Token } from '@/utils/types'
import { formatBigIntToString } from '../bigint/format'
import { commify } from '@ethersproject/units'
import { calculateExchangeRate } from '../calculateExchangeRate'
import { getTimeMinutesFromNow } from '../time'

export interface BridgeQuoteResponse extends BridgeQuote {
destinationToken: Token
Expand All @@ -23,6 +24,7 @@ export async function fetchBridgeQuote(
synapseSDK: any
): Promise<BridgeQuoteResponse> {
if (request && synapseSDK) {
const currentTimestamp: number = getTimeMinutesFromNow(0)
try {
const {
originChainId,
Expand Down Expand Up @@ -93,6 +95,7 @@ export async function fetchBridgeQuote(
estimatedTime: estimatedTime,
bridgeModuleName: bridgeModuleName,
gasDropAmount: BigInt(gasDropAmount.toString()),
timestamp: currentTimestamp,
}
} catch (error) {
console.error('Error fetching bridge quote:', error)
Expand Down
2 changes: 1 addition & 1 deletion packages/synapse-interface/utils/formatAmount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const formatAmount = (
throw new TypeError(`"${amount}" is not a finite number`)
}
} catch ({ name, message }) {
console.error(name, message)
// console.error(name, message)
return amount
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { isNull, isNumber } from 'lodash'
import { useEffect } from 'react'

import { BridgeQuote } from '@/utils/types'
import { calculateTimeBetween } from '@/utils/time'
import { useIntervalTimer } from '@/utils/hooks/useIntervalTimer'

export const useStaleQuoteRefresher = (
quote: BridgeQuote,
isLoadingQuote: boolean,
refreshQuoteCallback: () => Promise<void>,
staleTimeout: number = 15000
bigboydiamonds marked this conversation as resolved.
Show resolved Hide resolved
) => {
const quoteTime = quote?.timestamp
const isValidQuote = isNumber(quoteTime) && !isNull(quoteTime)
const currentTime = useIntervalTimer(staleTimeout, !isValidQuote)

useEffect(() => {
if (isValidQuote && !isLoadingQuote) {
const timeDifference = calculateTimeBetween(currentTime, quoteTime)

console.log('timeDifference: ', timeDifference)
console.log('staleTimeout: ', staleTimeout)

if (timeDifference >= staleTimeout) {
console.log('refresh quote')
refreshQuoteCallback()
}
}
}, [currentTime])
Copy link
Collaborator

@abtestingalpha abtestingalpha Jun 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the other deps in here?:

[currentTime, isValidQuote, isQuoteLoading, isWalletPending, quoteTime, staleTimeout]

Copy link
Collaborator Author

@bigboydiamonds bigboydiamonds Jun 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currentTime is the only dep we would want to react to in order to maintain quote freshness for the staleTimeout defined time intervals. Including the other deps may prematurely refresh the quote in this useEffect

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isValidQuote, isQuoteLoading, isWalletPending: If I'm understanding this correctly, these flags determine if an event listener should be added? And we don't want to add them bc we don't want to re-attach if they change?

quoteTime: Is this the timestamp which staleness is calculated? Don't we need to include that bc if quoteTime changes, then the staleness logic needs to re-run?

staleTimeout: Is this the value that determines what's considered stale? So if it changes, the staleness logic would re-run here as well?

Copy link
Collaborator Author

@bigboydiamonds bigboydiamonds Jun 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, isValidQuote, isQuoteLoading, isWalletPending are used to determine if an event listener should be attached, but we don't want to react to these props because:

  • If wallet action is pending, it would refresh quote in background, but does not actually update the quote used for the transaction (prevents User confusion with output amounts)
  • Listening to isQuoteLoading could cause for infinite re-renders - if new quote is being fetched for updated inputs, this would effectively re-trigger another quote fetch
  • isValidQuote may change frequently if the User is testing out various inputs (token / amount combos) and could effectively re-trigger a quote fetch before interval time has passed

Including quoteTime would also cause infinite re-renders since the change in quoteTime would cause for the quote to refresh in the background, which then updates the quoteTime value again.

Good catch on the staleTimeout! I originally expected for staleTimeout to be a static value when using the hook, but that's an improper assumption to make. Updated to include staleTimeout in the useEffect dependencies array in f8fadd6

}
7 changes: 7 additions & 0 deletions packages/synapse-interface/utils/time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ export const getTimeMinutesBeforeNow = (minutesBeforeNow) => {
return Math.round(currentTimeSeconds - 60 * minutesBeforeNow)
}

export const calculateTimeBetween = (
timeBefore: number,
timeAfter: number
): number => {
return Math.abs(timeBefore - timeAfter) * 1000
bigboydiamonds marked this conversation as resolved.
Show resolved Hide resolved
}

export const formatDate = (date) => {
return new Intl.DateTimeFormat('en-US', {
year: 'numeric',
Expand Down
1 change: 1 addition & 0 deletions packages/synapse-interface/utils/types/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export type BridgeQuote = {
estimatedTime: number
bridgeModuleName: string
gasDropAmount: bigint
timestamp: number
}

interface TokensByChain {
Expand Down
Loading