Skip to content

Commit

Permalink
Merge 383519d into 1891fed
Browse files Browse the repository at this point in the history
  • Loading branch information
bigboydiamonds authored Sep 23, 2024
2 parents 1891fed + 383519d commit ac66722
Show file tree
Hide file tree
Showing 22 changed files with 495 additions and 83 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { useState, useEffect, useMemo } from 'react'

import { BridgeQuote } from '@/utils/types'
import { convertMsToSeconds } from '@/utils/time'

export const BridgeQuoteResetTimer = ({
bridgeQuote,
isLoading,
isActive,
duration, // in ms
}: {
bridgeQuote: BridgeQuote
isLoading: boolean
isActive: boolean
duration: number
}) => {
const memoizedTimer = useMemo(() => {
if (!isActive) return null

if (isLoading) {
return <AnimatedLoadingCircle />
} else {
return (
<AnimatedProgressCircle
animateKey={bridgeQuote.id}
duration={duration}
/>
)
}
}, [bridgeQuote, duration, isActive])

return memoizedTimer
}

const AnimatedLoadingCircle = () => {
return (
<svg
width="24"
height="24"
viewBox="-12 -12 24 24"
stroke="currentcolor"
fill="none"
className="absolute block -rotate-90"
>
<circle r="8" pathLength="1" stroke-dashArray="0.05" stroke-opacity=".5">
<animate
attributeName="stroke-dashoffset"
to="-1"
dur="2.5s"
repeatCount="indefinite"
/>
</circle>
</svg>
)
}

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"
fill="none"
className="absolute block -rotate-90"
>
<circle r="8" pathLength="1" stroke-opacity=".25">
<animate
attributeName="stroke-dashoffset"
to="-1"
dur="2.5s"
repeatCount="indefinite"
/>
<set
attributeName="stroke-dasharray"
to="0.05"
begin={`${convertMsToSeconds(duration)}s`}
/>
<set
attributeName="stroke-opacity"
to="0.5"
begin={`${convertMsToSeconds(duration)}s`}
/>
</circle>
<circle r="8" stroke-dasharray="1" pathLength="1">
<animate
attributeName="stroke-dashoffset"
values="2; 1"
dur={`${convertMsToSeconds(duration)}s`}
fill="freeze"
/>
<animate
attributeName="stroke-opacity"
values="0; 1"
dur={`${convertMsToSeconds(duration)}s`}
/>
</circle>
</svg>
)
}
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,8 @@ export const BridgeTransactionButton = ({
debouncedFromValue,
} = useBridgeState()
const { bridgeQuote, isLoading } = useBridgeQuoteState()
const { isPendingConfirmChange, onUserAcceptChange } =
useConfirmNewBridgePrice()

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

let buttonProperties
Expand All @@ -97,6 +102,26 @@ 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 && hasValidQuote) {
buttonProperties = {
label: isPendingConfirmChange
? t('Confirm new quote')
: t('Bridge {symbol}', { symbol: fromToken?.symbol }),
pendingLabel: t('Bridge {symbol}', { symbol: fromToken?.symbol }),
onClick: null,
className: `
${
isPendingConfirmChange
? '!outline !outline-1 !outline-synapsePurple !outline-offset-[-1px] !from-bgLight !to-bgLight'
: '!bg-gradient-to-r !from-fuchsia-500 !to-purple-500 dark:!to-purple-600'
}
!opacity-100`,
}
} else if (isLoading) {
buttonProperties = {
label: t('Bridge {symbol}', { symbol: fromToken?.symbol }),
Expand Down Expand Up @@ -144,11 +169,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 +187,13 @@ export const BridgeTransactionButton = ({
onClick: () => switchChain({ chainId: fromChainId }),
pendingLabel: t('Switching chains'),
}
} else if (isApproved && hasValidQuote && isPendingConfirmChange) {
buttonProperties = {
label: t('Confirm new quote'),
onClick: () => onUserAcceptChange(),
className:
'!outline !outline-1 !outline-synapsePurple !outline-offset-[-1px] !from-bgLight !to-bgLight',
}
} 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,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<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]
)

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 &&
hasBridgeModuleChanged
) {
requestUserConfirmChange(previousBridgeQuote)
} else if (
validQuotes &&
hasSameSelectionsAsPreviousQuote &&
outputAmountDiffMoreThanThreshold
) {
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 = (
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
}
Loading

0 comments on commit ac66722

Please sign in to comment.