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/input quotes #1412

Merged
merged 108 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from 91 commits
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
3e74cff
Initialize fetchBridgeQuotes in utils, add BridgeQuoteRequest data type
bigboydiamonds Sep 27, 2023
aaa28fd
Basic useBridgeQuote that continuously provides back bridge quote amount
bigboydiamonds Sep 27, 2023
58cd2ff
Simple async fetchBridgeQuote function
bigboydiamonds Sep 27, 2023
83f03ec
Adjust fetchBridgeQuote function to accept synapseSDK as a param
bigboydiamonds Sep 27, 2023
f1d1986
fetchBridgeQuotes can return multiple bridge quotes via SDK in single…
bigboydiamonds Sep 27, 2023
72d35c3
static typing
bigboydiamonds Sep 27, 2023
0a80886
Init Bridge Updater component to allow for refreshing toTokens quotes
bigboydiamonds Sep 27, 2023
dcc693b
Bridge Updater component can access bridge quotes based on current to…
bigboydiamonds Sep 27, 2023
a7c5a7e
Remove test code in ToTokenListOverlay
bigboydiamonds Sep 27, 2023
1b3cc2e
Return token Token type in fetchBridgeQuote() call to match possibleT…
bigboydiamonds Sep 27, 2023
96479d1
Update
bigboydiamonds Sep 28, 2023
9cea209
Add comment
bigboydiamonds Sep 28, 2023
bd50221
init fechAndStoreBridgeQuotes async thunk
bigboydiamonds Sep 28, 2023
09dc065
fetchAndStoreBridgeQuotes
bigboydiamonds Sep 28, 2023
1adaacd
add fetchAndStoreBridgeQuote to use for current bridge selections
bigboydiamonds Sep 28, 2023
c9d9993
Update BridgeQuoteRequest to include originToken
bigboydiamonds Sep 28, 2023
f2e086e
...
bigboydiamonds Sep 28, 2023
102e9a3
port getAndSetBridgeQuote logic into fetchBridgeQuote
bigboydiamonds Sep 28, 2023
aee1e6c
Extend BridgeQuote type into BridgeQuoteResponse to include destinati…
bigboydiamonds Sep 28, 2023
9093214
Add typing to thunks
bigboydiamonds Sep 28, 2023
ab13f33
Add store state and reducer for fetchAndStoerBridgeQuotes
bigboydiamonds Sep 28, 2023
7a8e570
Bridge Updater to dispatch fetched bridge quotes for toTokens when avail
bigboydiamonds Sep 28, 2023
b696385
Update fetchBridgeQuotes to return array of objects
bigboydiamonds Sep 29, 2023
f3bbd99
Pass in formatted exchangeRate string into SelectSpecificTokenButton
bigboydiamonds Sep 29, 2023
c93a0c4
Add OptionDetails component that displays exchangeRate for now
bigboydiamonds Sep 29, 2023
1c222d9
Prefetch exchange rates without fromValue
bigboydiamonds Sep 29, 2023
0caac53
Add state/reducer for fetchAndStoreBridgeQuotes status
bigboydiamonds Sep 29, 2023
f133d9e
Show exchangeRates only after fetch status is valid
bigboydiamonds Sep 29, 2023
e60778a
Add action and reducer to resetFetchedBridgeQuotes
bigboydiamonds Sep 29, 2023
4d93c72
Reset fetched bridge quotes if fromToken is reset or is null
bigboydiamonds Sep 29, 2023
2c2ede7
Reset fetched bridge quotes if no toChainId exists
bigboydiamonds Sep 29, 2023
605af4d
calculateEstimatedTransactionTime util function
bigboydiamonds Sep 29, 2023
ea6b3c6
Pass in estimatedDuration prop to SelectSpecificTokenButton to popula…
bigboydiamonds Sep 29, 2023
c00a458
Add comments
bigboydiamonds Sep 29, 2023
f2cdd29
Add estimatedDurationInSeconds as prop in OptionDetails component, di…
bigboydiamonds Sep 29, 2023
f2aaee6
Style estimated duration in token selection
bigboydiamonds Sep 29, 2023
96e6bbc
Add util function locateBestExchangeRateIndex
bigboydiamonds Sep 29, 2023
f31746b
Add isBestExchangeRate bool prop to SelectSpecificTokenButton
bigboydiamonds Sep 29, 2023
fc6b9ee
...
bigboydiamonds Sep 29, 2023
065dcbc
Create OptionTag with BestOptionType interface to create multiple opt…
bigboydiamonds Sep 29, 2023
b590781
Basic unstyled OptionTag is working
bigboydiamonds Sep 29, 2023
72b7fcb
Add gradient
bigboydiamonds Sep 29, 2023
a46fb3b
Style tag
bigboydiamonds Sep 30, 2023
40687dd
Render tag only if exchangeRate available
bigboydiamonds Sep 30, 2023
998a34d
Add destinationChainId in response for fetchBridgeQuotes
bigboydiamonds Oct 2, 2023
ddf9852
Ensure quote does not show unless destinationChainId matches, solve f…
bigboydiamonds Oct 2, 2023
66fc512
Style OptionTag
bigboydiamonds Oct 2, 2023
e8479af
Match bridgeQuotes based on destinationToken and not array positioning
bigboydiamonds Oct 2, 2023
873f2d6
Init getDefaultBridgeAmount util function
bigboydiamonds Oct 2, 2023
9148118
Create required enums to construct respective getDefaultBridgeAmount …
bigboydiamonds Oct 2, 2023
673e557
...
bigboydiamonds Oct 2, 2023
b57ad1f
Update locateBestExchangeRateToken to match best rate by Token
bigboydiamonds Oct 2, 2023
fa491d7
Proprogate bestExchangeRateToken changes to ToTokenListOverlay
bigboydiamonds Oct 2, 2023
1134490
clean
bigboydiamonds Oct 2, 2023
1ab1da8
Fix NaN bug
bigboydiamonds Oct 2, 2023
ee39e42
...
bigboydiamonds Oct 3, 2023
8bba04a
Clean
bigboydiamonds Oct 3, 2023
6590171
Add maxConcurrentRequests and requestDelay to limit single overload +…
bigboydiamonds Oct 3, 2023
b000008
Debounce user input in Bridge updater to prevent alternative quote fe…
bigboydiamonds Oct 3, 2023
64fce40
updateDebouncedFromValue action
bigboydiamonds Oct 3, 2023
55f6393
Add reducer
bigboydiamonds Oct 3, 2023
ff3ab45
Lift debouncedFromValue to store
bigboydiamonds Oct 3, 2023
3133d1c
Utilize debouncedFromValue throughout bridge experience
bigboydiamonds Oct 3, 2023
4036485
Create orderedPossibleTokens to create ordered list based on fetched …
bigboydiamonds Oct 4, 2023
c0b3647
Merge branch 'master' into feat/input-quotes
bigboydiamonds Oct 4, 2023
3c3dc5c
Debounce 400
bigboydiamonds Oct 4, 2023
cf36911
Debounce 300ms
bigboydiamonds Oct 4, 2023
78d2eda
400ms debounce works
bigboydiamonds Oct 4, 2023
a9a58ca
Ensure loader activates when fromValue updates, not based on debounce…
bigboydiamonds Oct 4, 2023
05c17bf
..
bigboydiamonds Oct 4, 2023
0e8d636
Sort Best Rate selection and place at top
bigboydiamonds Oct 4, 2023
e0f21d6
Add delay on bridge loading animation
bigboydiamonds Oct 4, 2023
13ada28
Add default case for getDefaultBridgeAmount switch statement
bigboydiamonds Oct 4, 2023
163e3bc
Ensure loader not triggered until debouncedFromValue populated
bigboydiamonds Oct 4, 2023
29631f9
Add isLoadingExchangeRate prop to SelectSpecificTokenButton
bigboydiamonds Oct 5, 2023
a276d9a
Show loading spinner when fetching bridge quote exchange rates
bigboydiamonds Oct 5, 2023
cf1bd9e
...
bigboydiamonds Oct 5, 2023
e0c11b8
Update name from LoadingSpinner to LoadingDots to be more descriptive'
bigboydiamonds Oct 5, 2023
c235df6
Update ButtonLoadingSpinner to ButtonLoadingDots
bigboydiamonds Oct 5, 2023
cc0f32a
Add debouncedToTokensFromValue action and reducer
bigboydiamonds Oct 5, 2023
d8d3fae
Setup debounce for alternative bridge quotes
bigboydiamonds Oct 5, 2023
9ccd385
Utilize debouncedToTokensFromValue to fetch alternate bridge quotes
bigboydiamonds Oct 5, 2023
08354e3
Separate debouncing for primary quote and alternate quotes
bigboydiamonds Oct 5, 2023
3c64605
Update semantic naming, add comments
bigboydiamonds Oct 5, 2023
bc86a5c
Update debounce times between primary/alternative
bigboydiamonds Oct 5, 2023
2519be0
Tweak debouncer for alternative quote
bigboydiamonds Oct 5, 2023
2a9ed4e
Update debounce and maxConcurrentRequests to make alternative bridge …
bigboydiamonds Oct 6, 2023
5c3ecc7
Tighten up alternative bridge quotes fetching conditions for stability
bigboydiamonds Oct 6, 2023
2b7e837
update naming
bigboydiamonds Oct 6, 2023
2599ed4
Clear quotes if user input does not exist
bigboydiamonds Oct 6, 2023
1f342be
Allow input to be zerod
bigboydiamonds Oct 6, 2023
bdfb3c2
Update trigger for useEffect updating alternative bridge quotes
bigboydiamonds Oct 6, 2023
73256d5
hasOnlyZeroes shared utils function
bigboydiamonds Oct 6, 2023
730a21d
Add try catch around fetchBridgeQuote action
bigboydiamonds Oct 6, 2023
e9bf7ca
clean
bigboydiamonds Oct 6, 2023
b6d1578
clean
bigboydiamonds Oct 6, 2023
5638ee9
Example with fetching with default values
bigboydiamonds Oct 6, 2023
be51adb
Only fetch alternative bridge quotes when user input exists and is no…
bigboydiamonds Oct 6, 2023
b292e40
Increase bridge qutoe fetching reliability after setting default to s…
bigboydiamonds Oct 6, 2023
0168ec8
Update loading status when fetching default exchange rates
bigboydiamonds Oct 6, 2023
701a22c
Only show best rate if more than one option
bigboydiamonds Oct 6, 2023
9700b57
Fix lint
bigboydiamonds Oct 6, 2023
e4e69ba
..
bigboydiamonds Oct 6, 2023
0a996b1
Disable integration tests for iniitally settting bridge origin and de…
bigboydiamonds Oct 6, 2023
8a89a87
Test max 2 concurrent requests
bigboydiamonds Oct 6, 2023
8754335
Merge branch 'master' into feat/input-quotes
bigboydiamonds Oct 10, 2023
b77650f
Set loading to false in useEffect cleanup
bigboydiamonds Oct 10, 2023
2d35210
Add error handling for when fetchBridgeQuote does not have request or…
bigboydiamonds Oct 10, 2023
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'
import Button from '@tw/Button'
import ButtonLoadingSpinner from '@components/buttons/ButtonLoadingSpinner'
import ButtonLoadingDots from '@/components/buttons/ButtonLoadingDots'
import { getMenuItemBgForCoin } from '@styles/tokens'
import { Token } from '@types'

Expand Down Expand Up @@ -155,11 +155,11 @@ const InteractiveInputRow = ({
<>
{loadingLabel ? (
<div className="flex items-center justify-center space-x-5 animate-pulse">
<ButtonLoadingSpinner className="mr-2" />
<ButtonLoadingDots className="mr-2" />
<span>{loadingLabel}</span>
</div>
) : (
<ButtonLoadingSpinner />
<ButtonLoadingDots />
)}
</>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ export const InputContainer = () => {
useEffect(() => {
if (
fromToken &&
fromToken.decimals[fromChainId] &&
stringToBigInt(fromValue, fromToken.decimals[fromChainId]) !== 0n
fromToken.decimals[fromChainId]
// && stringToBigInt(fromValue, fromToken.decimals[fromChainId]) !== 0n
// stringToBigInt(fromValue, fromToken.decimals[fromChainId]) ===
// fromTokenBalance
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react'
import { Address, useAccount } from 'wagmi'

import LoadingSpinner from '../ui/tailwind/LoadingSpinner'
import LoadingDots from '../ui/tailwind/LoadingDots'
import { ToChainSelector } from './ToChainSelector'
import { shortenAddress } from '@/utils/shortenAddress'
import { ToTokenSelector } from './ToTokenSelector'
Expand Down Expand Up @@ -47,7 +47,7 @@ export const OutputContainer = ({}) => {
<ToTokenSelector />
<div className="flex ml-4">
{isLoading ? (
<LoadingSpinner className="opacity-50" />
<LoadingDots className="opacity-50" />
) : (
<input
pattern="[0-9.]+"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import _ from 'lodash'
import { useEffect, useRef, useState } from 'react'
import { useEffect, useRef, useState, useMemo } from 'react'
import { useDispatch } from 'react-redux'
import { Address } from 'viem'
import Fuse from 'fuse.js'

import { useKeyPress } from '@hooks/useKeyPress'
import SlideSearchBox from '@pages/bridge/SlideSearchBox'
import { Token } from '@/utils/types'
import { setToToken } from '@/slices/bridge/reducer'
import { BridgeState, setToToken } from '@/slices/bridge/reducer'
import { setShowToTokenListOverlay } from '@/slices/bridgeDisplaySlice'
import { segmentAnalyticsEvent } from '@/contexts/SegmentAnalyticsProvider'
import { useBridgeState } from '@/slices/bridge/hooks'
Expand All @@ -18,16 +19,35 @@ import { CHAINS_BY_ID } from '@/constants/chains'
import useCloseOnOutsideClick from '@/utils/hooks/useCloseOnOutsideClick'
import { CloseButton } from './components/CloseButton'
import { SearchResults } from './components/SearchResults'
import { formatBigIntToString } from '@/utils/bigint/format'
import { FetchState } from '@/slices/portfolio/actions'
import { calculateEstimatedTransactionTime } from '@/utils/calculateEstimatedTransactionTime'
import {
locateBestExchangeRateToken,
BridgeQuoteResponse,
} from '@/utils/actions/fetchBridgeQuotes'

interface TokenWithExchangeRate extends Token {
exchangeRate: bigint
}

export const ToTokenListOverlay = () => {
const { fromChainId, toTokens, toChainId, toToken } = useBridgeState()
const {
fromChainId,
fromToken,
toTokens,
toChainId,
toToken,
toTokensBridgeQuotes,
toTokensBridgeQuotesStatus,
}: BridgeState = useBridgeState()

const [currentIdx, setCurrentIdx] = useState(-1)
const [searchStr, setSearchStr] = useState('')
const dispatch = useDispatch()
const overlayRef = useRef(null)

let possibleTokens = sortByPriorityRank(toTokens)
let possibleTokens: Token[] = sortByPriorityRank(toTokens)

const { toTokens: allToChainTokens } = getRoutePossibilities({
fromChainId,
Expand Down Expand Up @@ -151,10 +171,60 @@ export const ToTokenListOverlay = () => {
onClose()
}

const bridgeQuotesMatchDestination: boolean = useMemo(() => {
return (
Array.isArray(toTokensBridgeQuotes) &&
toTokensBridgeQuotes[0]?.destinationChainId === toChainId
)
}, [toTokensBridgeQuotes, toChainId])

const bestExchangeRateToken: Token = useMemo(() => {
return locateBestExchangeRateToken(toTokensBridgeQuotes)
}, [toTokensBridgeQuotes])

const orderedPossibleTokens: TokenWithExchangeRate[] | Token[] =
useMemo(() => {
if (
toTokensBridgeQuotesStatus === FetchState.VALID &&
bridgeQuotesMatchDestination &&
possibleTokens &&
possibleTokens.length > 0
) {
const bridgeQuotesMap = new Map(
toTokensBridgeQuotes.map((quote) => [quote.destinationToken, quote])
)

const tokensWithExchangeRates: TokenWithExchangeRate[] =
possibleTokens.map((token) => {
const bridgeQuote = bridgeQuotesMap.get(token)
if (bridgeQuote) {
return {
...token,
exchangeRate: bridgeQuote.exchangeRate,
}
} else {
return token as TokenWithExchangeRate
}
})

const sortedTokens = tokensWithExchangeRates.sort(
(a, b) => Number(b.exchangeRate) - Number(a.exchangeRate)
)

return sortedTokens
}
return possibleTokens
}, [
possibleTokens,
toTokensBridgeQuotes,
toTokensBridgeQuotesStatus,
bridgeQuotesMatchDestination,
])
Copy link
Contributor

Choose a reason for hiding this comment

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

This hunk introduces logic to calculate the best exchange rate token and order possible tokens based on their exchange rates. The use of useMemo here is appropriate as it prevents unnecessary recalculations when the dependencies haven't changed. However, there's a console log statement at line 224 which should be removed before merging to avoid exposing potentially sensitive information in production.

- 224:   console.log('orderedPossibleTokens: ', orderedPossibleTokens)


return (
<div
ref={overlayRef}
data-test-id="token-slide-over"
data-test-id="to-token-list-overlay"
className="max-h-full pb-4 mt-2 overflow-auto scrollbar-hide"
>
<div className="z-10 w-full px-2 ">
Expand All @@ -167,31 +237,52 @@ export const ToTokenListOverlay = () => {
<CloseButton onClick={onClose} />
</div>
</div>
{possibleTokens && possibleTokens.length > 0 && (
{orderedPossibleTokens && orderedPossibleTokens.length > 0 && (
<>
<div className="px-2 pt-2 pb-2 text-sm text-primaryTextColor ">
Receive…
</div>
<div className="px-2 pb-2 md:px-2">
{possibleTokens.map((token, idx) => {
return (
<SelectSpecificTokenButton
isOrigin={false}
key={idx}
token={token}
selectedToken={toToken}
active={idx === currentIdx}
showAllChains={false}
onClick={() => {
if (token === toToken) {
onClose()
} else {
handleSetToToken(toToken, token)
{orderedPossibleTokens.map(
(token: TokenWithExchangeRate, idx: number) => {
return (
<SelectSpecificTokenButton
isOrigin={false}
key={idx}
token={token}
selectedToken={toToken}
active={idx === currentIdx}
showAllChains={false}
isLoadingExchangeRate={
toTokensBridgeQuotesStatus === FetchState.LOADING
}
}}
/>
)
})}
isBestExchangeRate={idx === 0}
exchangeRate={formatBigIntToString(
token?.exchangeRate,
18, //manually set this for now
4
)}
estimatedDurationInSeconds={
toTokensBridgeQuotesStatus === FetchState.VALID &&
bridgeQuotesMatchDestination &&
calculateEstimatedTransactionTime({
originChainId: fromChainId,
originTokenAddress: fromToken.addresses[
fromChainId
] as Address,
})
}
onClick={() => {
if (token === toToken) {
onClose()
} else {
handleSetToToken(toToken, token)
}
}}
/>
)
}
)}
</div>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { usePortfolioBalances } from '@/slices/portfolio/hooks'
import { useBridgeState } from '@/slices/bridge/hooks'
import { CHAINS_BY_ID } from '@/constants/chains'
import { findChainIdsWithPausedToken } from '@/constants/tokens'
import LoadingDots from '@/components/ui/tailwind/LoadingDots'

const SelectSpecificTokenButton = ({
showAllChains,
Expand All @@ -21,6 +22,10 @@ const SelectSpecificTokenButton = ({
selectedToken,
onClick,
alternateBackground = false,
isLoadingExchangeRate = false,
exchangeRate,
isBestExchangeRate = false,
estimatedDurationInSeconds,
}: {
showAllChains?: boolean
isOrigin: boolean
Expand All @@ -29,6 +34,10 @@ const SelectSpecificTokenButton = ({
selectedToken: Token
onClick: () => void
alternateBackground?: boolean
isLoadingExchangeRate?: boolean
exchangeRate?: string
isBestExchangeRate?: boolean
estimatedDurationInSeconds?: number
}) => {
const ref = useRef<any>(null)
const isCurrentlySelected = selectedToken?.routeSymbol === token?.routeSymbol
Expand Down Expand Up @@ -56,6 +65,7 @@ const SelectSpecificTokenButton = ({

return (
<button
data-test-id="select-specific-token-button"
bigboydiamonds marked this conversation as resolved.
Show resolved Hide resolved
ref={ref}
tabIndex={active ? 1 : 0}
onClick={onClick}
Expand All @@ -78,10 +88,70 @@ const SelectSpecificTokenButton = ({
isOrigin={isOrigin}
showAllChains={showAllChains}
/>
{isLoadingExchangeRate ? (
<LoadingDots className="mr-8 opacity-50" />
) : (
<>
{exchangeRate && isBestExchangeRate && (
<OptionTag type={BestOptionType.RATE} />
)}

{exchangeRate && (
<OptionDetails
exchangeRate={exchangeRate}
estimatedDurationInSeconds={estimatedDurationInSeconds}
/>
)}
</>
)}
</button>
)
}

export enum BestOptionType {
RATE = 'Best rate',
SPEED = 'Fastest',
}

export const OptionTag = ({ type }: { type: BestOptionType }) => {
return (
<div
data-test-id="option-tag"
className="flex px-3 py-0.5 mr-3 text-sm whitespace-nowrap text-primary rounded-xl"
style={{
background:
'linear-gradient(to right, rgba(128, 0, 255, 0.2), rgba(255, 0, 191, 0.2))',
}}
>{`${type}`}</div>
)
}

export const OptionDetails = ({
exchangeRate,
estimatedDurationInSeconds,
}: {
exchangeRate: string
estimatedDurationInSeconds: number
}) => {
const estimatedDurationInMinutes: number = Math.floor(
estimatedDurationInSeconds / 60
)

return (
<div data-test-id="option-details" className="flex flex-col">
<div className="flex items-center font-normal">
<div className="flex text-sm text-secondary whitespace-nowrap">
1&nbsp;:&nbsp;
</div>
<div className="mb-[1px] text-primary">{exchangeRate}</div>
</div>
<div className="text-sm text-right text-secondary">
{estimatedDurationInMinutes} min
</div>
</div>
)
}

const ButtonContent = memo(
({
token,
Expand All @@ -101,7 +171,7 @@ const ButtonContent = memo(
)?.parsedBalance

return (
<div className="flex items-center w-full">
<div data-test-id="button-content" className="flex items-center w-full">
<img
bigboydiamonds marked this conversation as resolved.
Show resolved Hide resolved
alt="token image"
className="w-8 h-8 ml-2 mr-4 rounded-full"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react'
import { Address, useAccount } from 'wagmi'

import LoadingSpinner from '../ui/tailwind/LoadingSpinner'
import LoadingDots from '../ui/tailwind/LoadingDots'
import { SwapToTokenSelector } from './SwapToTokenSelector'
import { useSwapState } from '@/slices/swap/hooks'

Expand Down Expand Up @@ -30,7 +30,7 @@ export const SwapOutputContainer = ({}) => {
<SwapToTokenSelector />
<div className="flex ml-4">
{isLoading ? (
<LoadingSpinner className="opacity-50" />
<LoadingDots className="opacity-50" />
) : (
<input
pattern="[0-9.]+"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import LoadingDots from '@/components/ui/tailwind/LoadingDots'

export default function ButtonLoadingDots({
className,
}: {
className?: string
}) {
return <LoadingDots className={`opacity-50 ${className}`} />
}

This file was deleted.

Loading