Skip to content

Commit

Permalink
Merge 835003c into 2e517b3
Browse files Browse the repository at this point in the history
  • Loading branch information
bigboydiamonds authored Sep 11, 2024
2 parents 2e517b3 + 835003c commit b9451d1
Show file tree
Hide file tree
Showing 12 changed files with 381 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { BridgeQuote } from '@/utils/types'
import { useState, useEffect, useMemo } from 'react'

export const BridgeQuoteResetTimer = ({
bridgeQuote,
hasValidQuote,
isActive,
duration, // in ms
}: {
bridgeQuote: BridgeQuote
hasValidQuote: boolean
isActive: boolean
duration: number
}) => {
const memoizedTimer = useMemo(() => {
if (hasValidQuote && isActive) {
return (
<AnimatedProgressCircle
animateKey={bridgeQuote.id}
duration={duration}
/>
)
}
return null
}, [bridgeQuote, hasValidQuote, duration, isActive])

return memoizedTimer
}

const AnimatedProgressCircle = ({
animateKey,
duration,
}: {
animateKey: string
duration: number
}) => {
const [animationKey, setAnimationKey] = useState(0)

useEffect(() => {
setAnimationKey((prevKey) => prevKey + 1)
}, [animateKey])

return (
<svg
key={animationKey}
width="24"
height="24"
viewBox="-12 -12 24 24"
stroke="currentcolor"
stroke-opacity=".33"
fill="none"
className="absolute -rotate-90"
>
<circle r="8" />
<circle r="8" stroke-dasharray="1" pathLength="1">
<animate
attributeName="stroke-dashoffset"
values="2; 1"
dur={`${convertMsToSeconds(duration)}s`}
fill="freeze"
/>
</circle>
</svg>
)
}

const convertMsToSeconds = (ms: number) => {
return Math.ceil(ms / 1000)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ import { useBridgeDisplayState, useBridgeState } from '@/slices/bridge/hooks'
import { TransactionButton } from '@/components/buttons/TransactionButton'
import { useBridgeValidations } from './hooks/useBridgeValidations'
import { segmentAnalyticsEvent } from '@/contexts/SegmentAnalyticsProvider'
import { useConfirmNewBridgePrice } from './hooks/useConfirmNewBridgePrice'

export const BridgeTransactionButton = ({
approveTxn,
executeBridge,
isApproved,
isBridgePaused,
isTyping,
isQuoteStale,
}) => {
const dispatch = useAppDispatch()
const { openConnectModal } = useConnectModal()
Expand Down Expand Up @@ -48,6 +50,12 @@ export const BridgeTransactionButton = ({
debouncedFromValue,
} = useBridgeState()
const { bridgeQuote, isLoading } = useBridgeQuoteState()
const {
hasSameSelectionsAsPreviousQuote,
hasQuoteOutputChanged,
hasUserConfirmedChange,
onUserAcceptChange,
} = useConfirmNewBridgePrice()

const { isWalletPending } = useWalletState()
const { showDestinationWarning, isDestinationWarningAccepted } =
Expand All @@ -73,6 +81,7 @@ export const BridgeTransactionButton = ({
isBridgeQuoteAmountGreaterThanInputForRfq ||
(isConnected && !hasValidQuote) ||
(isConnected && !hasSufficientBalance) ||
(isConnected && isQuoteStale) ||
(destinationAddress && !isAddress(destinationAddress))

let buttonProperties
Expand All @@ -97,6 +106,16 @@ export const BridgeTransactionButton = ({
label: t('Please select an Origin token'),
onClick: null,
}
} else if (isConnected && !hasSufficientBalance) {
buttonProperties = {
label: t('Insufficient balance'),
onClick: null,
}
} else if (isLoading && hasSameSelectionsAsPreviousQuote) {
buttonProperties = {
label: t('Updating quote'),
onClick: null,
}
} else if (isLoading) {
buttonProperties = {
label: t('Bridge {symbol}', { symbol: fromToken?.symbol }),
Expand Down Expand Up @@ -144,11 +163,6 @@ export const BridgeTransactionButton = ({
label: t('Invalid bridge quote'),
onClick: null,
}
} else if (!isLoading && isConnected && !hasSufficientBalance) {
buttonProperties = {
label: t('Insufficient balance'),
onClick: null,
}
} else if (destinationAddress && !isAddress(destinationAddress)) {
buttonProperties = {
label: t('Invalid Destination address'),
Expand All @@ -167,6 +181,18 @@ export const BridgeTransactionButton = ({
onClick: () => switchChain({ chainId: fromChainId }),
pendingLabel: t('Switching chains'),
}
} else if (
hasValidQuote &&
hasQuoteOutputChanged &&
hasSameSelectionsAsPreviousQuote &&
!hasUserConfirmedChange
) {
buttonProperties = {
label: t('Confirm new quote'),
onClick: () => onUserAcceptChange(),
className:
'!border !border-synapsePurple !from-bgLight !to-bgLight !animate-pulse',
}
} else if (!isApproved && hasValidInput && hasValidQuote) {
buttonProperties = {
onClick: approveTxn,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ import { useBridgeQuoteState } from '@/slices/bridgeQuote/hooks'
import { useBridgeValidations } from './hooks/useBridgeValidations'
import { useTranslations } from 'next-intl'

export const OutputContainer = () => {
interface OutputContainerProps {
isQuoteStale: boolean
}

export const OutputContainer = ({ isQuoteStale }: OutputContainerProps) => {
const { address } = useAccount()
const { bridgeQuote, isLoading } = useBridgeQuoteState()
const { showDestinationAddress } = useBridgeDisplayState()
Expand All @@ -33,6 +37,8 @@ export const OutputContainer = () => {
}
}, [bridgeQuote, hasValidInput, hasValidQuote])

const inputClassName = isQuoteStale ? 'opacity-50' : undefined

return (
<BridgeSectionContainer>
<div className="flex items-center justify-between">
Expand All @@ -48,6 +54,7 @@ export const OutputContainer = () => {
disabled={true}
showValue={showValue}
isLoading={isLoading}
className={inputClassName}
/>
</BridgeAmountContainer>
</BridgeSectionContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export const useBridgeValidations = () => {
}
}

const constructStringifiedBridgeSelections = (
export const constructStringifiedBridgeSelections = (
originAmount,
originChainId,
originToken,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
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 quoteRef = useRef<any>(null)
const bpsThreshold = 0.0001 // 1bps

const [hasQuoteOutputChanged, setHasQuoteOutputChanged] =
useState<boolean>(false)
const [hasUserConfirmedChange, setHasUserConfirmedChange] =
useState<boolean>(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]
)

useEffect(() => {
const validQuotes =
bridgeQuote?.outputAmount && previousBridgeQuote?.outputAmount

const outputAmountDiffMoreThanThreshold = validQuotes
? calculateOutputRelativeDifference(
bridgeQuote,
quoteRef.current ?? previousBridgeQuote
) > bpsThreshold
: false

if (
validQuotes &&
outputAmountDiffMoreThanThreshold &&
hasSameSelectionsAsPreviousQuote
) {
requestUserConfirmChange(previousBridgeQuote)
} else {
resetConfirm()
}
}, [bridgeQuote, previousBridgeQuote, hasSameSelectionsAsPreviousQuote])

const requestUserConfirmChange = (previousQuote: BridgeQuote) => {
if (!hasQuoteOutputChanged && !hasUserConfirmedChange) {
quoteRef.current = previousQuote
setHasQuoteOutputChanged(true)
}
setHasUserConfirmedChange(false)
}

const resetConfirm = () => {
if (hasUserConfirmedChange) {
quoteRef.current = null
setHasQuoteOutputChanged(false)
setHasUserConfirmedChange(false)
}
}

const onUserAcceptChange = () => {
quoteRef.current = null
setHasUserConfirmedChange(true)
}

return {
hasSameSelectionsAsPreviousQuote,
hasQuoteOutputChanged,
hasUserConfirmedChange,
onUserAcceptChange,
}
}

const calculateOutputRelativeDifference = (
quoteA?: BridgeQuote,
quoteB?: BridgeQuote
) => {
if (!quoteA?.outputAmountString || !quoteB?.outputAmountString) return null

const outputA = parseFloat(quoteA.outputAmountString)
const outputB = parseFloat(quoteB.outputAmountString)

return Math.abs(outputA - outputB) / outputB
}
3 changes: 3 additions & 0 deletions packages/synapse-interface/components/ui/AmountInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface AmountInputTypes {
showValue: string
handleFromValueChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
setIsTyping?: (isTyping: boolean) => void
className?: string
}

export function AmountInput({
Expand All @@ -20,6 +21,7 @@ export function AmountInput({
showValue,
handleFromValueChange,
setIsTyping,
className,
}: AmountInputTypes) {
const debouncedSetIsTyping = useCallback(
debounce((value: boolean) => setIsTyping?.(value), 600),
Expand All @@ -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 (
Expand Down
Loading

0 comments on commit b9451d1

Please sign in to comment.