Skip to content

Commit

Permalink
Merge 62d7735 into 4c491d4
Browse files Browse the repository at this point in the history
  • Loading branch information
bigboydiamonds authored Sep 9, 2024
2 parents 4c491d4 + 62d7735 commit 02bf5bb
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ import { useBridgeQuoteState } from '@/slices/bridgeQuote/hooks'
import { setIsDestinationWarningAccepted } from '@/slices/bridgeDisplaySlice'
import { useBridgeDisplayState, useBridgeState } from '@/slices/bridge/hooks'
import { TransactionButton } from '@/components/buttons/TransactionButton'
import { useBridgeValidations } from './hooks/useBridgeValidations'
import {
useBridgeValidations,
constructStringifiedBridgeSelections,
} from './hooks/useBridgeValidations'
import { segmentAnalyticsEvent } from '@/contexts/SegmentAnalyticsProvider'
import { useConfirmNewBridgePrice } from './hooks/useConfirmNewBridgePrice'

export const BridgeTransactionButton = ({
approveTxn,
Expand Down Expand Up @@ -45,6 +49,12 @@ export const BridgeTransactionButton = ({
debouncedFromValue,
} = useBridgeState()
const { bridgeQuote, isLoading } = useBridgeQuoteState()
const {
hasSameSelectionsAsPreviousQuote,
hasQuoteOutputChanged,
hasUserConfirmedChange,
onUserAcceptChange,
} = useConfirmNewBridgePrice()

const { isWalletPending } = useWalletState()
const { showDestinationWarning, isDestinationWarningAccepted } =
Expand Down Expand Up @@ -94,6 +104,16 @@ export const BridgeTransactionButton = ({
label: `Please select an Origin token`,
onClick: null,
}
} else if (isConnected && !hasSufficientBalance) {
buttonProperties = {
label: 'Insufficient balance',
onClick: null,
}
} else if (isLoading && hasSameSelectionsAsPreviousQuote) {
buttonProperties = {
label: 'Updating quote',
onClick: null,
}
} else if (isLoading) {
buttonProperties = {
label: `Bridge ${fromToken?.symbol}`,
Expand Down Expand Up @@ -141,11 +161,6 @@ export const BridgeTransactionButton = ({
label: 'Invalid bridge quote',
onClick: null,
}
} else if (!isLoading && isConnected && !hasSufficientBalance) {
buttonProperties = {
label: 'Insufficient balance',
onClick: null,
}
} else if (destinationAddress && !isAddress(destinationAddress)) {
buttonProperties = {
label: 'Invalid Destination address',
Expand All @@ -162,6 +177,13 @@ export const BridgeTransactionButton = ({
onClick: () => switchChain({ chainId: fromChainId }),
pendingLabel: 'Switching chains',
}
} else if (hasQuoteOutputChanged && !hasUserConfirmedChange) {
buttonProperties = {
label: 'Confirm new price',
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
@@ -1,5 +1,5 @@
import { useAccount } from 'wagmi'
import { useMemo } from 'react'
import { useMemo, useEffect, useState } from 'react'

import { ChainSelector } from '@/components/ui/ChainSelector'
import { TokenSelector } from '@/components/ui/TokenSelector'
Expand All @@ -14,11 +14,13 @@ import { setToChainId, setToToken } from '@/slices/bridge/reducer'
import { useBridgeDisplayState, useBridgeState } from '@/slices/bridge/hooks'
import { useWalletState } from '@/slices/wallet/hooks'
import { useBridgeQuoteState } from '@/slices/bridgeQuote/hooks'
import { BridgeQuote } from '@/utils/types'
import { useBridgeValidations } from './hooks/useBridgeValidations'
import { useConfirmNewBridgePrice } from './hooks/useConfirmNewBridgePrice'

export const OutputContainer = () => {
const { address } = useAccount()
const { bridgeQuote, isLoading } = useBridgeQuoteState()
const { bridgeQuote, previousBridgeQuote, isLoading } = useBridgeQuoteState()
const { showDestinationAddress } = useBridgeDisplayState()
const { hasValidInput, hasValidQuote } = useBridgeValidations()

Expand Down Expand Up @@ -48,6 +50,9 @@ export const OutputContainer = () => {
showValue={showValue}
isLoading={isLoading}
/>
{hasValidQuote && !isLoading && (
<AnimatedProgressCircle bridgeQuoteId={bridgeQuote?.id} />
)}
</BridgeAmountContainer>
</BridgeSectionContainer>
)
Expand Down Expand Up @@ -88,3 +93,43 @@ const ToTokenSelector = () => {
/>
)
}

const AnimatedProgressCircle = ({ bridgeQuoteId }) => {
const [animationKey, setAnimationKey] = useState(0)

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

return (
<svg
key={animationKey}
width="36"
height="36"
viewBox="-12 -12 24 24"
stroke="currentcolor"
strokeOpacity=".33"
fill="none"
className="-rotate-90 -scale-y-100"
>
<circle r="8">
<animate
attributeName="opacity"
values="0; 0; 1"
dur="15s"
fill="freeze"
keyTimes="0; .5; 1"
/>
</circle>
<circle r="8" strokeDasharray="1" pathLength="1">
<animate
attributeName="stroke-dashoffset"
values="1; 1; 2"
dur="15s"
keyTimes="0; .67; 1"
fill="freeze"
/>
</circle>
</svg>
)
}
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,110 @@
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 [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 outputAmountDiffMoreThan1bps = validQuotes
? calculateOutputRelativeDifference(
bridgeQuote,
quoteRef.current ?? previousBridgeQuote
) > 0.0001
: false

if (
validQuotes &&
outputAmountDiffMoreThan1bps &&
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
}
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,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 {
Expand Down Expand Up @@ -202,7 +200,6 @@ const StateManagedBridge = () => {
bridgeQuote,
getAndSetBridgeQuote,
isLoading,
isWalletPending,
quoteTimeout
)

Expand Down
8 changes: 7 additions & 1 deletion packages/synapse-interface/slices/bridgeQuote/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand All @@ -24,6 +26,9 @@ export const bridgeQuoteSlice = createSlice({
resetBridgeQuote: (state) => {
state.bridgeQuote = initialState.bridgeQuote
},
setPreviousBridgeQuote: (state, action: PayloadAction<any>) => {
state.previousBridgeQuote = action.payload
},
},
extraReducers: (builder) => {
builder
Expand All @@ -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
Original file line number Diff line number Diff line change
@@ -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
}
9 changes: 7 additions & 2 deletions packages/synapse-interface/store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand All @@ -28,7 +29,11 @@ export const store = configureStore({
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
}).concat(api.middleware, destinationAddressMiddleware),
}).concat(
api.middleware,
destinationAddressMiddleware,
bridgeQuoteHistoryMiddleware
),
})

if (checkVersionAndResetCache()) {
Expand Down
Loading

0 comments on commit 02bf5bb

Please sign in to comment.