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

chore: allow enough balance check to return unknown state #3106

Merged
merged 8 commits into from
Sep 7, 2023
Merged
4 changes: 2 additions & 2 deletions apps/cowswap-frontend/src/api/gnosisProtocol/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
PriceQuality,
TotalSurplus,
OrderQuoteSideKindBuy,
OrderQuoteSideKindSell
OrderQuoteSideKindSell,
} from '@cowprotocol/cow-sdk'

import { orderBookApi } from 'cowSdk'
Expand Down Expand Up @@ -225,7 +225,7 @@ export async function getProfileData(chainId: ChainId, address: string): Promise
}
}

export function getPriceQuality(props: { fast?: boolean; verifyQuote: boolean }): PriceQuality {
export function getPriceQuality(props: { fast?: boolean; verifyQuote: boolean | undefined }): PriceQuality {
const { fast = false, verifyQuote } = props
if (fast) {
return PriceQuality.FAST
Expand Down
3 changes: 2 additions & 1 deletion apps/cowswap-frontend/src/common/hooks/useNeedsApproval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ export function useNeedsApproval(amount: Nullish<CurrencyAmount<Currency>>): boo
return false
}

return !isEnoughAmount(amount, allowance)
const enoughBalance = isEnoughAmount(amount, allowance)
return enoughBalance === false
anxolin marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,9 @@ export function UnfillableOrdersUpdater(): null {

const currencyAmount = CurrencyAmount.fromRawAmount(order.inputToken, order.sellAmount)
const enoughBalance = hasEnoughBalanceAndAllowance({ account, amount: currencyAmount, balances })
const verifiedQuote = verifiedQuotesEnabled && enoughBalance

_getOrderPrice(chainId, order, enoughBalance && verifiedQuotesEnabled, strategy)
_getOrderPrice(chainId, order, verifiedQuote, strategy)
.then((quote) => {
if (quote) {
const [promisedPrice, promisedFee] = quote
Expand Down Expand Up @@ -207,7 +208,12 @@ export function UnfillableOrdersUpdater(): null {
/**
* Thin wrapper around `getBestPrice` that builds the params and returns null on failure
*/
async function _getOrderPrice(chainId: ChainId, order: Order, verifyQuote: boolean, strategy: GpPriceStrategy) {
async function _getOrderPrice(
chainId: ChainId,
order: Order,
verifyQuote: boolean | undefined,
strategy: GpPriceStrategy
) {
let baseToken, quoteToken

const amount = getRemainderAmount(order.kind, order)
Expand Down
2 changes: 1 addition & 1 deletion apps/cowswap-frontend/src/legacy/state/price/updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ export default function FeesUpdater(): null {
userAddress: account,
validTo,
isEthFlow,
priceQuality: getPriceQuality({ verifyQuote: enoughBalance && verifiedQuotesEnabled }),
priceQuality: getPriceQuality({ verifyQuote: verifiedQuotesEnabled && enoughBalance }),
}

// Don't refetch if offline.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ export function OrderRow({
const showCancellationModal = orderActions.getShowCancellationModal(order)

const withWarning =
(!hasEnoughBalance || !hasEnoughAllowance) &&
(hasEnoughBalance === false || hasEnoughAllowance === false) &&
anxolin marked this conversation as resolved.
Show resolved Hide resolved
// show the warning only for pending and scheduled orders
(status === OrderStatus.PENDING || status === OrderStatus.SCHEDULED)
const theme = useContext(ThemeContext)
Expand Down Expand Up @@ -353,10 +353,10 @@ export function OrderRow({
bgColor={theme.alert}
content={
<styledEl.WarningContent>
{!hasEnoughBalance && (
{hasEnoughBalance === false && (
<BalanceWarning symbol={inputTokenSymbol} isScheduled={isOrderScheduled} />
)}
{!hasEnoughAllowance && (
{hasEnoughAllowance === false && (
<AllowanceWarning symbol={inputTokenSymbol} isScheduled={isOrderScheduled} />
)}
</styledEl.WarningContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export interface OrderParams {
sellAmount: CurrencyAmount<Currency>
buyAmount: CurrencyAmount<Currency>
rateInfoParams: RateInfoParams
hasEnoughBalance: boolean
hasEnoughAllowance: boolean
hasEnoughBalance: boolean | undefined
hasEnoughAllowance: boolean | undefined
}

const PERCENTAGE_FOR_PARTIAL_FILLS = new Percent(5, 10000) // 0.05%
Expand All @@ -38,18 +38,20 @@ export function getOrderParams(
const balance = balances[order.inputToken.address]?.value
const allowance = allowances[order.inputToken.address]?.value

let hasEnoughBalance, hasEnoughAllowance

if (order.partiallyFillable) {
// When balance or allowance are undefined (loading state), show as true
// When loaded, check there's at least PERCENTAGE_FOR_PARTIAL_FILLS of balance/allowance to consider it as enough
const amount = sellAmount.multiply(PERCENTAGE_FOR_PARTIAL_FILLS)
hasEnoughBalance = balance === undefined || isEnoughAmount(amount, balance)
hasEnoughAllowance = allowance === undefined || isEnoughAmount(amount, allowance)
} else {
hasEnoughBalance = isEnoughAmount(sellAmount, balance)
hasEnoughAllowance = isEnoughAmount(sellAmount, allowance)
}
const [hasEnoughBalance, hasEnoughAllowance] = (() => {
if (order.partiallyFillable) {
// When balance or allowance are undefined (loading state), show as true
alfetopito marked this conversation as resolved.
Show resolved Hide resolved
// When loaded, check there's at least PERCENTAGE_FOR_PARTIAL_FILLS of balance/allowance to consider it as enough
const amount = sellAmount.multiply(PERCENTAGE_FOR_PARTIAL_FILLS)
const hasEnoughBalance = isEnoughAmount(amount, balance)
const hasEnoughAllowance = isEnoughAmount(amount, allowance)
return [hasEnoughBalance, hasEnoughAllowance]
} else {
const hasEnoughBalance = isEnoughAmount(sellAmount, balance)
const hasEnoughAllowance = isEnoughAmount(sellAmount, allowance)
return [hasEnoughBalance, hasEnoughAllowance]
}
})()

return {
chainId,
Expand Down
37 changes: 26 additions & 11 deletions apps/cowswap-frontend/src/modules/tokens/hooks/useEnoughBalance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export interface UseEnoughBalanceParams {
* @param params Parameters to check balance and optionally the allowance
* @returns true if the account has enough balance (and allowance if it applies)
*/
export function useEnoughBalanceAndAllowance(params: UseEnoughBalanceParams): boolean {
export function useEnoughBalanceAndAllowance(params: UseEnoughBalanceParams): boolean | undefined {
const { account, amount, checkAllowanceAddress } = params
const isNativeCurrency = amount?.currency.isNative
const token = amount?.currency.wrapped
Expand Down Expand Up @@ -75,22 +75,37 @@ export interface EnoughBalanceParams extends Omit<UseEnoughBalanceParams, 'check
* @param params Parameters to check balance and optionally the allowance
* @returns true if the account has enough balance (and allowance if it applies)
*/
export function hasEnoughBalanceAndAllowance(params: EnoughBalanceParams): boolean {
export function hasEnoughBalanceAndAllowance(params: EnoughBalanceParams): boolean | undefined {
const { account, amount, balances, nativeBalance, allowances } = params

if (!account || !amount) {
return undefined
}

const isNativeCurrency = amount?.currency.isNative
const token = amount?.currency.wrapped
const tokenAddress = getAddress(token)

const balance = tokenAddress ? balances[tokenAddress]?.value : undefined
const allowance = (tokenAddress && allowances && allowances[tokenAddress]?.value) || undefined
anxolin marked this conversation as resolved.
Show resolved Hide resolved

if (!account || !amount) {
return false
const enoughBalance = (() => {
const balance = tokenAddress ? balances[tokenAddress]?.value : undefined
const balanceAmount = isNativeCurrency ? nativeBalance : balance || undefined
return isEnoughAmount(amount, balanceAmount)
})()
anxolin marked this conversation as resolved.
Show resolved Hide resolved

const enoughAllowance = (() => {
if (!tokenAddress || !allowances) {
return undefined
}
if (isNativeCurrency) {
return true
}
const allowance = allowances[tokenAddress]?.value
return allowance && isEnoughAmount(amount, allowance)
})()

if (enoughBalance === undefined || enoughAllowance === undefined) {
return undefined
}

const balanceAmount = isNativeCurrency ? nativeBalance : balance || undefined
const enoughBalance = isEnoughAmount(amount, balanceAmount)
const enoughAllowance = !allowances || isNativeCurrency || (allowance && isEnoughAmount(amount, allowance)) || false

return enoughBalance && enoughAllowance
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function useQuoteParams(amount: string | null): LegacyFeeQuoteParams | un
toDecimals,
fromDecimals,
isEthFlow: false,
priceQuality: getPriceQuality({ verifyQuote: enoughBalance && verifiedQuotesEnabled }),
priceQuality: getPriceQuality({ verifyQuote: verifiedQuotesEnabled && enoughBalance }),
}

return params
Expand Down
8 changes: 3 additions & 5 deletions apps/cowswap-frontend/src/utils/isEnoughAmount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
export function isEnoughAmount(
sellAmount: CurrencyAmount<Currency>,
targetAmount: CurrencyAmount<Currency> | undefined
): boolean {
if (!targetAmount) return true
): boolean | undefined {
if (!targetAmount) return undefined

if (targetAmount.equalTo(sellAmount)) return true

return sellAmount.lessThan(targetAmount)
return sellAmount.equalTo(targetAmount) || sellAmount.lessThan(targetAmount)
}
Loading