Skip to content

Commit

Permalink
feat(synapse-interface): bridge gas handler (#2437)
Browse files Browse the repository at this point in the history
* Initial gas data slice state

* Async thunk fetchGasData

* Add gas data reducers

* useFetchGasDataOnInterval added to Bridge Listener

* Explicitly declare fetching gas data in gwei

* Calculate max bridgeable amount

* On max balance factoring in gas cost

* Add comment

* Differentiate max balance vs max bridgeable balance

* Add error toaster for when max balance less than bridge fee

* refactor; calculateGasFeeInGwei()

* Move to util

* Fix lint error

* Fetch gas when fromChainId on bridge card changes

* Use exact token balance when calculate max bridgeable amount

* Conditions for showing max button

* showMaxOption to determine display

* Disable max button if gas fees more than gas balance

* Clean jsx

* AvailableBalance component to track subscript detail in bridge origin input

* Return raw and formatted gas cost in calculateGasCost

* Update comments

* Fix imports based on name cange

* Use parsed

* calculateMaxBridgeableGas

* Clean

* refactor: clean InputContainer

* Replace onMaxBalance() with onMaxBridgeableBalance()`

* Allow undefined object when destructuring

* Display when token balance is trace balance

* Typing

* showGasReserved

* Update avail balance text and color when gas cost greater than balance

* Detect when input value is less than required estimated gas

* onAvailableBalance applied

* Make opacity greater when Max button disabled

* Fix calculations for when gas input is enough to cover gas

* Clean

* Add hover tooltip for warning gas states

* Use tooltip to describe gas status

* Update tooltip text

* Add buffer to calculate gas cost, use 1.5x

* AvailableBalance component to replace label in AmountInput

* Display trace balances in AvailableBalance

* Fix naming

* Add HoverTooltip to Available Balance

* Add conditions for displaying gas error based on input

* Use full parsed balance when making comparisons for gas checks

* Show gas reserved in Available Balance

* hasOnlyZeroes to return true if string contains only zeroes and periods

* Display estimated gas cost in tool tip

* Remove Available Balance elements from AmountInput, migrated to AvailableBalance

* Display trace amount for input when displaying how much is reserved for gas

* Fetch estimated gas limit based on real bridge quote using max gas balance

* Fetching accurate gasLimit

* Fix Trace balance

* Set gasLimit to 0 if not valid bridge data avail

* ...

* Fix when gas covered msg appears

* Available Balance

* Clean available balance flow

* useGasEstimator hook to encapsulate logic to clean InputContainer

* Fix prop issue

* Remove duplicate HoverTooltip

* Move HoverTooltip to shared component folder

* Update gas fetch for wagmi v2, up limit to 1.7

* Estimated gas bridge quote fetched on load

* Update var naming

* Improve Available Balance flow

* Display estimated bridgeable balance on hover

* Show gas estimate when estimated gas cost available

* Do not show negative bridgable balance

* Separate out bridge gas limit calculations into smaller functions

* Wrap async functions with try catch

* Implement updated gas estimation flow

* Move gas estimate logic to useGasEstimator

* Move async sdk fetch functions to useGasEstimator file

* Remove unused vars

* Shorten est gas cost

* Improve code legibility in AvailableBalance

* Assign conditions to vars to reduce clutter

* Reset gas price data when fromChainId changes

* Reset fetched gas limit when fromChainId changes

* Clean

* Fix old import

* Fix max button placement

* Add loading state for useGasEstimator

* Remove available balance states for gas est

* Remove unused props

* Clean gas token detection in PortfolioTokenAsset

* Simplify onMaxBalance()

* Clean constants in InputContainer

* Clean token detail destructuring

* Include gas estimate when updating input from Portfolio token selection

* Fire error toaster if gas fees exceed balance

* Render error toaster when firing onMaxBalance callback instead of a side effect

* Clean useGasEstimator

* Ensure available balance does not show if wallet not connected, set default value if balances not loaded

* Replicate onMax behavior in Portfolio

* Use fetched gas

* Improve error handling in gas estimator

* ...

* Remove tooltip on AvailableBalance

* Remove balance prefill from Portfolio Token selection

* Update available balance in input based on max bridgeable gas toke

* Destructure estimateGasLimit callback from useGasEstimator to retrigger fetching gas estimate

* Refetch gas ata in estimateGasLimit callback

* Display total vs bridgeable balance on hover in Portfolio gas token when gas data available

* Update balance shown on hover

* Clean

* Simplify useFetchGasDataOnInterval

* Prevent unnecessary fetches, fix chain update old gas issue

* Fetch estimated bridgeable max gas amount onClick token max input, fill input once amount fetched

* Clean

* onMaxBalance will use default balanceif fetched bridgeable balance estimate returns null

* Show dust gas amounts on hover in Portfolio

* Clean

* Available balance including gas estimates moved to within Input to synchronize data

* Move gasLimit to global store to share data between Bridge components

* Display max bridgeable balance on hover in Portfolio, add onMaxBalance onClick Portfolio asset

* Portfolio and Input available balance onClick behave the same

* Fetch gas prices to provide most accurate gas cost

* Remove hover on AvailableBalance

* Fix Token Selector width

* Remove onClick Portfolio Token Balance, allow Token selection from Portfolio only without Balance input update

* Cursor default on Portfolio Token Balance

* Show available/max buttons when bridge valid selections

* Swap input to have available balance

* Clean

* Basic MaxButton onClick and hidden when loading gas data

* Show MaxButton when input is not full balance or bridgeable balance

* Implement Max button and placement in Bridge/Swap

* Hide MaxButton until connected

* Hide MaxButton conditions

* Show Max when all input selections are made

* Remove click states for AvailableBalance on Bridge/Swap

* Style Input / Max

* Max mobile size

* Fe/format amount (#2598)

* formatAmount util function, use rounded shortened values in Bridge/Swap Input

* Update Bridge/Swap page with parsed / formatted balances

* Clean imports

* Clean imports

* Swap Max spacing

* Improve html element composition

* Prevent layout shift in Swap UI
  • Loading branch information
bigboydiamonds authored May 9, 2024
1 parent 172c009 commit 4fcfcab
Show file tree
Hide file tree
Showing 27 changed files with 818 additions and 197 deletions.
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
import React, { useState } from 'react'

export const HoverTooltip = ({ children, hoverContent }) => {
export const HoverTooltip = ({
children,
hoverContent,
isActive = true,
}: {
children: React.ReactNode
hoverContent: React.ReactNode
isActive?: boolean
}) => {
const [showTooltip, setShowTooltip] = useState(false)

const activateTooltip = () => setShowTooltip(true)
const hideTooltip = () => setShowTooltip(false)

return (
<div
onMouseEnter={activateTooltip}
onMouseLeave={hideTooltip}
className="relative"
>
{children}
<Tooltip isHovered={showTooltip}>{hoverContent}</Tooltip>
</div>
)
if (!isActive) {
return <div>{children}</div>
} else {
return (
<div
onMouseEnter={activateTooltip}
onMouseLeave={hideTooltip}
className="relative w-fit"
>
{children}
{hoverContent ? (
<Tooltip isHovered={showTooltip}>{hoverContent}</Tooltip>
) : null}
</div>
)
}
}

const Tooltip = ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import Image from 'next/image'
import { Token } from '@/utils/types'
import { getParsedBalance } from '@/utils/getParsedBalance'
import { HoverTooltip } from './HoverTooltip'
import { HoverTooltip } from '../../HoverTooltip'
import GasIcon from '@/components/icons/GasIcon'

export const GasTokenAsset = ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const PortfolioAssetActionButton = ({
id="portfolio-asset-action-button"
className={`
py-1 px-6 rounded-sm
border border-synapsePurple
border border-fuchsia-400
${!isDisabled && 'cursor-pointer hover:bg-surface active:opacity-70'}
`}
onClick={selectCallback}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import React, { useCallback } from 'react'
import _ from 'lodash'
import { zeroAddress } from 'viem'
import { isNumber } from 'lodash'
import Image from 'next/image'
import { useAppDispatch } from '@/store/hooks'
import {
setFromChainId,
setFromToken,
updateFromValue,
} from '@/slices/bridge/reducer'
import { setFromChainId, setFromToken } from '@/slices/bridge/reducer'
import { Token } from '@/utils/types'
import { inputRef } from '../../StateManagedBridge/InputContainer'
import Image from 'next/image'
import { useBridgeState } from '@/slices/bridge/hooks'
import { PortfolioAssetActionButton } from './PortfolioAssetActionButton'
import { trimTrailingZeroesAfterDecimal } from '@/utils/trimTrailingZeroesAfterDecimal'
import { zeroAddress } from 'viem'
import GasIcon from '@/components/icons/GasIcon'
import { HoverTooltip } from './HoverTooltip'
import { HoverTooltip } from '@/components/HoverTooltip'
import { getParsedBalance } from '@/utils/getParsedBalance'
import { useGasEstimator } from '@/utils/hooks/useGasEstimator'
import GasIcon from '@/components/icons/GasIcon'
import { trimTrailingZeroesAfterDecimal } from '@/utils/trimTrailingZeroesAfterDecimal'
import { formatAmount } from '@/utils/formatAmount'

const handleFocusOnBridgeInput = () => {
inputRef.current?.focus()
Expand All @@ -37,30 +35,26 @@ export const PortfolioTokenAsset = ({
const dispatch = useAppDispatch()
const { fromChainId, fromToken } = useBridgeState()
const { icon, symbol, decimals, addresses } = token

const tokenDecimals = _.isNumber(decimals)
const tokenAddress = addresses[portfolioChainId]
const tokenDecimals = isNumber(decimals)
? decimals
: decimals[portfolioChainId]

const parsedBalance = getParsedBalance(balance, tokenDecimals, 3)
const parsedBalanceLong = getParsedBalance(balance, tokenDecimals, 8)
const parsedBalance = getParsedBalance(balance, tokenDecimals)
const formattedBalance = formatAmount(parsedBalance)

const isDisabled = false
const isTokenSelected =
fromToken === token && fromChainId === portfolioChainId
const isPortfolioChainSelected = fromChainId === portfolioChainId
const isTokenSelected = isPortfolioChainSelected && fromToken === token
const isGasToken = tokenAddress === zeroAddress

const handleFromSelectionCallback = useCallback(() => {
const { maxBridgeableGas } = useGasEstimator()

const handleFromSelectionCallback = useCallback(async () => {
dispatch(setFromChainId(portfolioChainId))
dispatch(setFromToken(token))
handleFocusOnBridgeInput()
dispatch(
updateFromValue(
trimTrailingZeroesAfterDecimal(getParsedBalance(balance, tokenDecimals))
)
)
}, [token, balance, portfolioChainId])

const isBridgeableGasToken = addresses[portfolioChainId] === zeroAddress
}, [token, portfolioChainId])

return (
<div
Expand All @@ -70,14 +64,7 @@ export const PortfolioTokenAsset = ({
${isTokenSelected ? 'bg-tint border-surface' : 'border-transparent'}
`}
>
<div
onClick={handleFromSelectionCallback}
className={`
flex items-center gap-2
pl-2 pr-4 py-2 cursor-pointer rounded
hover:bg-surface active:opacity-70
`}
>
<div className="flex items-center gap-2 py-2 pl-2 pr-4 rounded cursor-default">
<Image
loading="lazy"
alt={`${symbol} img`}
Expand All @@ -86,23 +73,31 @@ export const PortfolioTokenAsset = ({
/>
<HoverTooltip
hoverContent={
<div className="whitespace-nowrap">
{parsedBalanceLong} {symbol}
</div>
isPortfolioChainSelected && isGasToken && maxBridgeableGas ? (
<div className="whitespace-nowrap">
Available:{' '}
{trimTrailingZeroesAfterDecimal(maxBridgeableGas.toFixed(8))}{' '}
{symbol}
</div>
) : (
<div className="whitespace-nowrap">
{parsedBalance} {symbol}
</div>
)
}
>
<div>
{parsedBalance} {symbol}
{formattedBalance} {symbol}
</div>
</HoverTooltip>

{isBridgeableGasToken ? (
{isGasToken && (
<HoverTooltip
hoverContent={<div className="whitespace-nowrap">Gas token</div>}
>
<GasIcon className="pt-0.5 m-auto fill-secondary" />
</HoverTooltip>
) : null}
)}
</div>
<PortfolioAssetActionButton
selectCallback={handleFromSelectionCallback}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Image from 'next/image'
import { TokenAndBalance } from '@/utils/actions/fetchPortfolioBalances'
import { HoverTooltip } from './HoverTooltip'
import { HoverTooltip } from '../../HoverTooltip'

export const PortfolioTokenVisualizer = ({
portfolioTokens,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export const SearchBar = () => {
${!isMounted && 'border-opacity-30'}
${
isFocused || isSearchInputActive
? 'border-synapsePurple bg-tint'
? 'border-fuchsia-400 bg-tint'
: 'border-separator bg-transparent'
}
`}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react'
import React, { useEffect } from 'react'
import { Address } from 'viem'
import { useDispatch } from 'react-redux'
import _, { isArray } from 'lodash'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const ViewSearchAddressBanner = ({
id="view-search-address-banner"
className={`
flex justify-between p-3 mb-3
border border-synapsePurple rounded-sm
border border-fuchsia-400 rounded-sm
`}
style={{
background:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react'
import { joinClassNames } from '@/utils/joinClassNames'

export const AvailableBalance = ({
balance,
maxBridgeableBalance,
isGasToken,
isGasEstimateLoading,
isDisabled,
}: {
balance?: string
maxBridgeableBalance?: number
gasCost?: string
isGasToken: boolean
isGasEstimateLoading: boolean
isDisabled: boolean
}) => {
const labelClassName = joinClassNames({
space: 'block',
text: 'text-xxs md:text-xs',
cursor: 'cursor-default',
})

if (isDisabled) {
return null
} else if (isGasToken && isGasEstimateLoading) {
return (
<label className={labelClassName} htmlFor="inputRow">
<span className="animate-pulse text-zinc-500 dark:text-zinc-400">
calculating gas...
</span>
</label>
)
} else {
return (
<label className={labelClassName} htmlFor="inputRow">
<span className="text-zinc-500 dark:text-zinc-400">Available: </span>
{maxBridgeableBalance?.toFixed(4) ?? balance ?? '0.0'}
</label>
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const ConfirmDestinationAddressWarning = () => {
onChange={handleCheckboxChange}
className={`
cursor-pointer border rounded-[4px] border-secondary
text-synapsePurple bg-transparent outline-none
text-fuchsia-400 bg-transparent outline-none
focus:!outline-0 focus:ring-0 focus:!border-0
active:!outline-0 active:ring-0 active:!border-0
`}
Expand Down
Loading

0 comments on commit 4fcfcab

Please sign in to comment.