Skip to content

Commit

Permalink
feat(balances): update balances for priority tokens (#3417)
Browse files Browse the repository at this point in the history
  • Loading branch information
shoom3301 authored Dec 6, 2023
1 parent 1d99299 commit 1d1458c
Show file tree
Hide file tree
Showing 23 changed files with 302 additions and 138 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { CowModal } from 'common/pure/Modal'
import { TransactionErrorContent } from 'common/pure/TransactionErrorContent'

import { useTradeApproveCallback } from './useTradeApproveCallback'
import { useTradeApproveState } from './useTradeApproveState'

import { useApproveState } from '../../hooks/useApproveState'

export interface TradeApproveButtonProps {
amountToApprove: CurrencyAmount<Currency>
Expand All @@ -22,7 +23,7 @@ export function TradeApproveButton(props: TradeApproveButtonProps) {

const currency = amountToApprove.currency

const approvalState = useTradeApproveState(amountToApprove)
const approvalState = useApproveState(amountToApprove)
const tradeApproveCallback = useTradeApproveCallback(amountToApprove)
const shouldZeroApprove = useShouldZeroApprove(amountToApprove)
const zeroApprove = useZeroApprove(amountToApprove.currency)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from './TradeApproveButton'
export * from './TradeApproveModal'
export * from './useTradeApproveCallback'
export * from './useTradeApproveState'

This file was deleted.

36 changes: 20 additions & 16 deletions apps/cowswap-frontend/src/common/hooks/useApproveState.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,56 @@
import { useMemo } from 'react'

import { useTokensAllowances } from '@cowprotocol/balances-and-allowances'
import { usePrevious } from '@cowprotocol/common-hooks'
import { FractionUtils, getWrappedToken } from '@cowprotocol/common-utils'
import { useWalletInfo } from '@cowprotocol/wallet'
import { getWrappedToken } from '@cowprotocol/common-utils'
import { BigNumber } from '@ethersproject/bignumber'
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'

import { Nullish } from 'types'

import { ApprovalState } from 'legacy/hooks/useApproveCallback/useApproveCallbackMod'
import { useTokenAllowance } from 'legacy/hooks/useTokenAllowance'
import { useHasPendingApproval } from 'legacy/state/enhancedTransactions/hooks'

import { useSafeMemo } from 'common/hooks/useSafeMemo'

import { useTradeSpenderAddress } from './useTradeSpenderAddress'

function getCurrencyToApprove(amountToApprove: Nullish<CurrencyAmount<Currency>>): Token | undefined {
if (!amountToApprove) return undefined

return getWrappedToken(amountToApprove.currency)
}

export function useApproveState(amountToApprove: Nullish<CurrencyAmount<Currency>>, spender?: string): ApprovalState {
const { account } = useWalletInfo()
export function useApproveState(amountToApprove: Nullish<CurrencyAmount<Currency>>): ApprovalState {
const spender = useTradeSpenderAddress()
const token = getCurrencyToApprove(amountToApprove)
const currentAllowance = useTokenAllowance(token, account ?? undefined, spender)
const pendingApproval = useHasPendingApproval(token?.address, spender)
const tokenAddress = token?.address?.toLowerCase()
const allowances = useTokensAllowances()
const pendingApproval = useHasPendingApproval(tokenAddress, spender)

const currentAllowance = tokenAddress ? allowances.values[tokenAddress] : undefined

const approvalStateBase = useSafeMemo(() => {
if (!amountToApprove || !spender || !currentAllowance) {
if (!amountToApprove || !currentAllowance) {
return ApprovalState.UNKNOWN
}

if (FractionUtils.gte(currentAllowance, amountToApprove)) {
const amountToApproveString = amountToApprove.quotient.toString()

if (currentAllowance.gte(amountToApproveString)) {
return ApprovalState.APPROVED
}

if (pendingApproval) {
return ApprovalState.PENDING
}

if (currentAllowance.lessThan(amountToApprove)) {
if (currentAllowance.lt(amountToApproveString)) {
return ApprovalState.NOT_APPROVED
}

return ApprovalState.APPROVED
}, [amountToApprove, currentAllowance, pendingApproval, spender])
}, [amountToApprove, currentAllowance, pendingApproval])

return useAuxApprovalState(approvalStateBase, currentAllowance)
}
Expand All @@ -55,12 +62,9 @@ export function useApproveState(amountToApprove: Nullish<CurrencyAmount<Currency
*
* Solution: we check the prev approval state and also check if the allowance has been updated
*/
function useAuxApprovalState(
approvalStateBase: ApprovalState,
currentAllowance?: CurrencyAmount<Currency>
): ApprovalState {
function useAuxApprovalState(approvalStateBase: ApprovalState, currentAllowance?: BigNumber): ApprovalState {
const previousApprovalState = usePrevious(approvalStateBase)
const currentAllowanceString = currentAllowance?.quotient.toString()
const currentAllowanceString = currentAllowance?.toHexString()
const previousAllowanceString = usePrevious(currentAllowanceString)
// Has allowance actually updated?
const allowanceHasNotChanged = previousAllowanceString === currentAllowanceString
Expand Down
18 changes: 14 additions & 4 deletions apps/cowswap-frontend/src/legacy/hooks/useTokenAllowance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@ import { useMemo } from 'react'
import { useTokenContract } from '@cowprotocol/common-hooks'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'

import ms from 'ms.macro'
import useSWR from 'swr'
import { Nullish } from 'types'

const ALLOWANCES_SWR_CONFIG = { refreshInterval: ms`10s` }

/**
* @deprecated use useTokensAllowances() hook instead
*/
export function useTokenAllowance(
token: Nullish<Token>,
owner?: string,
Expand All @@ -14,11 +20,15 @@ export function useTokenAllowance(
const tokenAddress = token?.address
const contract = useTokenContract(tokenAddress, false)

const { data: allowance } = useSWR(['useTokenAllowance', tokenAddress, owner, spender], async () => {
if (!owner || !spender) return undefined
const { data: allowance } = useSWR(
['useTokenAllowance', tokenAddress, owner, spender],
async () => {
if (!owner || !spender) return undefined

return contract?.callStatic.allowance(owner, spender)
})
return contract?.callStatic.allowance(owner, spender)
},
ALLOWANCES_SWR_CONFIG
)

return useMemo(
() => (token && allowance ? CurrencyAmount.fromRawAmount(token, allowance.toString()) : undefined),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export function isTransactionRecent(tx: EnhancedTransactionDetails): boolean {
// returns whether a token has a pending approval transaction
export function useHasPendingApproval(tokenAddress: string | undefined, spender: string | undefined): boolean {
const allTransactions = useAllTransactions()

return useMemo(
() =>
typeof tokenAddress === 'string' &&
Expand All @@ -37,7 +38,12 @@ export function useHasPendingApproval(tokenAddress: string | undefined, spender:
} else {
const approval = tx.approval
if (!approval) return false
return approval.spender === spender && approval.tokenAddress === tokenAddress && isTransactionRecent(tx)

return (
approval.spender.toLowerCase() === spender.toLowerCase() &&
approval.tokenAddress.toLowerCase() === tokenAddress &&
isTransactionRecent(tx)
)
}
}),
[allTransactions, spender, tokenAddress]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,14 @@ export function OrderRow({

const showCancellationModal = orderActions.getShowCancellationModal(order)

/**
* TODO: I'm not sure about !hasValidPendingPermit
* Before the fix it was: hasValidPendingPermit === false
* In useCheckHasValidPendingPermit() we intentionally return undefined in cases when we don't know the permit status
*/
const withAllowanceWarning = hasEnoughAllowance === false && !hasValidPendingPermit
const withWarning =
(hasEnoughBalance === false || (hasEnoughAllowance === false && hasValidPendingPermit === false)) &&
(hasEnoughBalance === false || withAllowanceWarning) &&
// show the warning only for pending and scheduled orders
(status === OrderStatus.PENDING || status === OrderStatus.SCHEDULED)
const theme = useContext(ThemeContext)
Expand All @@ -185,6 +191,7 @@ export function OrderRow({
const isScheduledCreating = isOrderScheduled && Date.now() > creationTime.getTime()
const expirationTimeAgo = useTimeAgo(expirationTime, TIME_AGO_UPDATE_INTERVAL)
const creationTimeAgo = useTimeAgo(creationTime, TIME_AGO_UPDATE_INTERVAL)

// TODO: set the real value when API returns it
// const executedTimeAgo = useTimeAgo(expirationTime, TIME_AGO_UPDATE_INTERVAL)
const activityUrl = chainId ? getActivityUrl(chainId, order) : undefined
Expand Down Expand Up @@ -359,7 +366,7 @@ export function OrderRow({
{hasEnoughBalance === false && (
<BalanceWarning symbol={inputTokenSymbol} isScheduled={isOrderScheduled} />
)}
{hasEnoughAllowance === false && hasValidPendingPermit === false && (
{withAllowanceWarning && (
<AllowanceWarning
approve={() => orderActions.approveOrderToken(order.inputToken)}
symbol={inputTokenSymbol}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { BigNumber } from '@ethersproject/bignumber'

import { BalancesAndAllowances } from 'modules/tokens'

import { getOrderParams } from './getOrderParams'

import { BalancesAndAllowances } from '../../../../tokens'
import { ordersMock } from '../orders.mock'

describe('getOrderParams', () => {
const BASE_ORDER = ordersMock[0]
const BASE_BALANCES_AND_ALLOWANCES: BalancesAndAllowances = {
balances: {
[BASE_ORDER.inputToken.address]: BigNumber.from(BASE_ORDER.sellAmount),
[BASE_ORDER.inputToken.address.toLowerCase()]: BigNumber.from(BASE_ORDER.sellAmount),
},
allowances: {
[BASE_ORDER.inputToken.address]: BigNumber.from(BASE_ORDER.sellAmount),
[BASE_ORDER.inputToken.address.toLowerCase()]: BigNumber.from(BASE_ORDER.sellAmount),
},
isLoading: false,
}
Expand Down Expand Up @@ -66,7 +67,7 @@ describe('getOrderParams', () => {
const balancesAndAllowances: BalancesAndAllowances = {
...BASE_BALANCES_AND_ALLOWANCES,
balances: {
[order.inputToken.address]: BigNumber.from(String(+order.sellAmount * 0.00051)),
[order.inputToken.address.toLowerCase()]: BigNumber.from(String(+order.sellAmount * 0.00051)),
},
}
const result = getOrderParams(1, balancesAndAllowances, order)
Expand All @@ -77,7 +78,7 @@ describe('getOrderParams', () => {
const balancesAndAllowances: BalancesAndAllowances = {
...BASE_BALANCES_AND_ALLOWANCES,
balances: {
[order.inputToken.address]: BigNumber.from(String(+order.sellAmount * 0.00049)),
[order.inputToken.address.toLowerCase()]: BigNumber.from(String(+order.sellAmount * 0.00049)),
},
}
const result = getOrderParams(1, balancesAndAllowances, order)
Expand All @@ -89,7 +90,7 @@ describe('getOrderParams', () => {
const balancesAndAllowances: BalancesAndAllowances = {
...BASE_BALANCES_AND_ALLOWANCES,
allowances: {
[order.inputToken.address]: BigNumber.from(String(+order.sellAmount * 0.00051)),
[order.inputToken.address.toLowerCase()]: BigNumber.from(String(+order.sellAmount * 0.00051)),
},
}
const result = getOrderParams(1, balancesAndAllowances, order)
Expand All @@ -100,7 +101,7 @@ describe('getOrderParams', () => {
const balancesAndAllowances: BalancesAndAllowances = {
...BASE_BALANCES_AND_ALLOWANCES,
allowances: {
[order.inputToken.address]: BigNumber.from(String(+order.sellAmount * 0.00049)),
[order.inputToken.address.toLowerCase()]: BigNumber.from(String(+order.sellAmount * 0.00049)),
},
}
const result = getOrderParams(1, balancesAndAllowances, order)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ export function getOrderParams(
}

const { balances, allowances } = balancesAndAllowances
const balance = balances[order.inputToken.address]
const allowance = allowances[order.inputToken.address]
const balance = balances[order.inputToken.address.toLowerCase()]
const allowance = allowances[order.inputToken.address.toLowerCase()]

const { hasEnoughBalance, hasEnoughAllowance } = _hasEnoughBalanceAndAllowance({
partiallyFillable: order.partiallyFillable,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import { HandleSwapCallback } from 'modules/swap/pure/SwapButtons'
import { ethFlowContextAtom } from 'modules/swap/state/EthFlow/ethFlowContextAtom'
import { useWrappedToken } from 'modules/trade/hooks/useWrappedToken'

import { useTradeApproveCallback, useTradeApproveState } from 'common/containers/TradeApprove'
import { useTradeApproveCallback } from 'common/containers/TradeApprove'
import { useApproveState } from 'common/hooks/useApproveState'
import { CowModal } from 'common/pure/Modal'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'

Expand All @@ -44,7 +45,7 @@ function EthFlow({
const isExpertMode = useIsExpertMode()
const native = useNativeCurrency()
const wrapped = useWrappedToken()
const approvalState = useTradeApproveState(nativeInput || null)
const approvalState = useApproveState(nativeInput || null)

const ethFlowContext = useAtomValue(ethFlowContextAtom)
const approveCallback = useTradeApproveCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { useIsNativeIn } from 'modules/trade/hooks/useIsNativeInOrOut'
import { useIsWrappedOut } from 'modules/trade/hooks/useIsWrappedInOrOut'
import { useWrappedToken } from 'modules/trade/hooks/useWrappedToken'

import { useTradeApproveState } from 'common/containers/TradeApprove/useTradeApproveState'
import { useApproveState } from 'common/hooks/useApproveState'

import { useSafeBundleEthFlowContext } from './useSafeBundleEthFlowContext'
import { useDerivedSwapInfo, useSwapActionHandlers } from './useSwapState'
Expand Down Expand Up @@ -81,7 +81,7 @@ export function useSwapButtonContext(input: SwapButtonInput): SwapButtonsContext
const wrapUnwrapAmount = isNativeInSwap ? currencyAmountToTokenAmount(inputAmount) || undefined : inputAmount
const hasEnoughWrappedBalanceForSwap = useHasEnoughWrappedBalanceForSwap(wrapUnwrapAmount)
const wrapCallback = useWrapNativeFlow()
const approvalState = useTradeApproveState(slippageAdjustedSellAmount || null)
const approvalState = useApproveState(slippageAdjustedSellAmount || null)

const handleSwap = useHandleSwap(priceImpactParams)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export function hasEnoughBalanceAndAllowance(params: EnoughBalanceParams): UseEn

const isNativeCurrency = !!amount?.currency && getIsNativeToken(amount?.currency)
const token = amount?.currency && getWrappedToken(amount.currency)
const tokenAddress = getAddress(token)
const tokenAddress = getAddress(token)?.toLowerCase()

const enoughBalance = _enoughBalance(tokenAddress, amount, balances)
const enoughAllowance = _enoughAllowance(tokenAddress, amount, allowances, isNativeCurrency)
Expand All @@ -84,7 +84,7 @@ export function hasEnoughBalanceAndAllowance(params: EnoughBalanceParams): UseEn
}

function _enoughBalance(
tokenAddress: string | null,
tokenAddress: string | undefined,
amount: CurrencyAmount<Currency>,
balances: BalancesState['values']
): boolean | undefined {
Expand All @@ -94,7 +94,7 @@ function _enoughBalance(
}

function _enoughAllowance(
tokenAddress: string | null,
tokenAddress: string | undefined,
amount: CurrencyAmount<Currency>,
allowances: AllowancesState['values'] | undefined,
isNativeCurrency: boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function TokenListItem(props: TokenListItemProps) {

const isTokenSelected = token.address.toLowerCase() === selectedToken?.toLowerCase()

const balanceAmount = CurrencyAmount.fromRawAmount(token, balance?.toHexString() ?? 0)
const balanceAmount = balance ? CurrencyAmount.fromRawAmount(token, balance.toHexString()) : undefined

return (
<styledEl.TokenItem
Expand All @@ -41,9 +41,7 @@ export function TokenListItem(props: TokenListItemProps) {
>
<TokenInfo token={token} />
<TokenTags isUnsupported={isUnsupported} isPermitCompatible={isPermitCompatible} />
<span>
<TokenAmount amount={balanceAmount} />
</span>
<span>{balanceAmount && <TokenAmount amount={balanceAmount} />}</span>
</styledEl.TokenItem>
)
}
Loading

0 comments on commit 1d1458c

Please sign in to comment.