From 21e8c57ddfbadb881e56295557a82ad5cb513664 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Tue, 17 Dec 2024 21:21:56 +0500 Subject: [PATCH] fix(orders-table): display allowance warning in Safe (#5207) * fix(orders-table): display allowance warning in Safe * refactor: check order allowance sufficiency taking permit into account * chore: fix lint * fix: check permit amount only if order was never filled * fix: parse permit with try/catch --------- Co-authored-by: Anxo Rodriguez --- .../hooks/useGetOrdersToCheckPendingPermit.ts | 34 ------- .../containers/OrdersTableWidget/index.tsx | 9 +- .../OrdersTableContainer/OrderRow/index.tsx | 12 +-- .../pure/OrdersTableContainer/OrdersTable.tsx | 21 ++-- .../OrdersTableContainer/index.cosmos.tsx | 1 - .../pure/OrdersTableContainer/index.tsx | 2 - .../ordersTable/utils/getOrderParams.ts | 15 ++- .../hooks/useCheckHasValidPendingPermit.ts | 97 ------------------- .../modules/permit/hooks/useGetPermitInfo.ts | 21 ---- .../permit/hooks/useOrdersPermitStatus.ts | 7 -- .../src/modules/permit/index.ts | 2 - .../permit/state/ordersPermitStatusAtom.ts | 9 -- .../permit/updaters/PendingPermitUpdater.ts | 39 -------- .../utils/orderUtils/getOrderPermitAmount.ts | 41 ++++++++ libs/abis/src/index.ts | 1 + 15 files changed, 70 insertions(+), 241 deletions(-) delete mode 100644 apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersTableWidget/hooks/useGetOrdersToCheckPendingPermit.ts delete mode 100644 apps/cowswap-frontend/src/modules/permit/hooks/useCheckHasValidPendingPermit.ts delete mode 100644 apps/cowswap-frontend/src/modules/permit/hooks/useGetPermitInfo.ts delete mode 100644 apps/cowswap-frontend/src/modules/permit/hooks/useOrdersPermitStatus.ts delete mode 100644 apps/cowswap-frontend/src/modules/permit/state/ordersPermitStatusAtom.ts delete mode 100644 apps/cowswap-frontend/src/modules/permit/updaters/PendingPermitUpdater.ts create mode 100644 apps/cowswap-frontend/src/utils/orderUtils/getOrderPermitAmount.ts diff --git a/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersTableWidget/hooks/useGetOrdersToCheckPendingPermit.ts b/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersTableWidget/hooks/useGetOrdersToCheckPendingPermit.ts deleted file mode 100644 index f62a4120e4..0000000000 --- a/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersTableWidget/hooks/useGetOrdersToCheckPendingPermit.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { useMemo } from 'react' - -import { SupportedChainId } from '@cowprotocol/cow-sdk' - -import { BalancesAndAllowances } from 'modules/tokens' - -import { ParsedOrder } from 'utils/orderUtils/parseOrder' - -import { OrdersTableList } from './useOrdersTableList' - -import { getOrderParams } from '../../../utils/getOrderParams' -import { isParsedOrder } from '../../../utils/orderTableGroupUtils' - -export function useGetOrdersToCheckPendingPermit( - ordersList: OrdersTableList, - chainId: SupportedChainId, - balancesAndAllowances: BalancesAndAllowances -) { - return useMemo(() => { - // Pick only the pending orders - return ordersList.pending.reduce((acc: ParsedOrder[], item) => { - // Only do it for regular orders (not TWAP) - if (isParsedOrder(item)) { - const { hasEnoughAllowance } = getOrderParams(chainId, balancesAndAllowances, item) - - // Only if the order has not enough allowance - if (hasEnoughAllowance === false) { - acc.push(item) - } - } - return acc - }, []) - }, [balancesAndAllowances, chainId, ordersList.pending]) -} diff --git a/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersTableWidget/index.tsx b/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersTableWidget/index.tsx index 229753b6db..509dd0ff75 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersTableWidget/index.tsx +++ b/apps/cowswap-frontend/src/modules/ordersTable/containers/OrdersTableWidget/index.tsx @@ -12,7 +12,7 @@ import { Order } from 'legacy/state/orders/actions' import { useInjectedWidgetParams } from 'modules/injectedWidget' import { pendingOrdersPricesAtom } from 'modules/orders/state/pendingOrdersPricesAtom' import { useGetSpotPrice } from 'modules/orders/state/spotPricesAtom' -import { PendingPermitUpdater, useGetOrdersPermitStatus } from 'modules/permit' +import { BalancesAndAllowances } from 'modules/tokens' import { useCancelOrder } from 'common/hooks/useCancelOrder' import { useCategorizeRecentActivity } from 'common/hooks/useCategorizeRecentActivity' @@ -21,12 +21,10 @@ import { useNavigate } from 'common/hooks/useNavigate' import { CancellableOrder } from 'common/utils/isOrderCancellable' import { ParsedOrder } from 'utils/orderUtils/parseOrder' -import { useGetOrdersToCheckPendingPermit } from './hooks/useGetOrdersToCheckPendingPermit' import { OrdersTableList, useOrdersTableList } from './hooks/useOrdersTableList' import { useOrdersTableTokenApprove } from './hooks/useOrdersTableTokenApprove' import { useValidatePageUrlParams } from './hooks/useValidatePageUrlParams' -import { BalancesAndAllowances } from '../../../tokens' import { OPEN_TAB, ORDERS_TABLE_TABS } from '../../const/tabs' import { OrdersTableContainer } from '../../pure/OrdersTableContainer' import { OrderActions } from '../../pure/OrdersTableContainer/types' @@ -81,7 +79,6 @@ export function OrdersTableWidget({ const getSpotPrice = useGetSpotPrice() const selectReceiptOrder = useSelectReceiptOrder() const isSafeViaWc = useIsSafeViaWc() - const ordersPermitStatus = useGetOrdersPermitStatus() const injectedWidgetParams = useInjectedWidgetParams() const { currentTabId, currentPageNumber } = useMemo(() => { @@ -163,11 +160,8 @@ export function OrdersTableWidget({ useValidatePageUrlParams(orders.length, currentTabId, currentPageNumber) - const ordersToCheckPendingPermit = useGetOrdersToCheckPendingPermit(ordersList, chainId, balancesAndAllowances) - return ( <> - {children} {isOpenOrdersTab && orders.length && } diff --git a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/OrderRow/index.tsx b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/OrderRow/index.tsx index 97cc538512..8bd52a248f 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/OrderRow/index.tsx +++ b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/OrderRow/index.tsx @@ -143,7 +143,6 @@ export interface OrderRowProps { orderParams: OrderParams onClick: Command orderActions: OrderActions - hasValidPendingPermit?: boolean | undefined children?: JSX.Element } @@ -160,7 +159,6 @@ export function OrderRow({ prices, spotPrice, children, - hasValidPendingPermit, }: OrderRowProps) { const { buyAmount, rateInfoParams, hasEnoughAllowance, hasEnoughBalance, chainId } = orderParams const { creationTime, expirationTime, status } = order @@ -173,10 +171,10 @@ export function OrderRow({ }, [orderActions, order]) const alternativeOrderModalContext = useMemo( () => orderActions.getAlternativeOrderModalContext(order), - [order, orderActions] + [order, orderActions], ) - const withAllowanceWarning = hasEnoughAllowance === false && hasValidPendingPermit === false + const withAllowanceWarning = hasEnoughAllowance === false const withWarning = (hasEnoughBalance === false || withAllowanceWarning) && // show the warning only for pending and scheduled orders @@ -401,7 +399,7 @@ export function OrderRow({ function usePricesDifference( prices: OrderRowProps['prices'], spotPrice: OrderRowProps['spotPrice'], - isInverted: boolean + isInverted: boolean, ): PriceDifference { const { estimatedExecutionPrice } = prices || {} @@ -415,13 +413,13 @@ function usePricesDifference( */ function useFeeAmountDifference( { inputCurrencyAmount }: OrderRowProps['orderParams']['rateInfoParams'], - prices: OrderRowProps['prices'] + prices: OrderRowProps['prices'], ): Percent | undefined { const { feeAmount } = prices || {} return useSafeMemo( () => calculatePercentageInRelationToReference({ value: feeAmount, reference: inputCurrencyAmount }), - [feeAmount, inputCurrencyAmount] + [feeAmount, inputCurrencyAmount], ) } diff --git a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/OrdersTable.tsx b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/OrdersTable.tsx index c11b087065..d2051d80d7 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/OrdersTable.tsx +++ b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/OrdersTable.tsx @@ -13,7 +13,6 @@ import styled from 'styled-components/macro' import { PendingOrdersPrices } from 'modules/orders/state/pendingOrdersPricesAtom' import { SpotPricesKeyParams } from 'modules/orders/state/spotPricesAtom' -import { OrdersPermitStatus } from 'modules/permit' import { BalancesAndAllowances } from 'modules/tokens' import { ordersTableFeatures } from 'common/constants/featureFlags' @@ -200,7 +199,6 @@ export interface OrdersTableProps { balancesAndAllowances: BalancesAndAllowances getSpotPrice: (params: SpotPricesKeyParams) => Price | null orderActions: OrderActions - ordersPermitStatus: OrdersPermitStatus } export function OrdersTable({ @@ -214,7 +212,6 @@ export function OrdersTable({ getSpotPrice, orderActions, currentPageNumber, - ordersPermitStatus, }: OrdersTableProps) { const buildOrdersTableUrl = useGetBuildOrdersTableUrl() const [isRateInverted, setIsRateInverted] = useState(false) @@ -234,11 +231,14 @@ export function OrdersTable({ const selectedOrdersMap = useMemo(() => { if (!selectedOrders) return {} - return selectedOrders.reduce((acc, val) => { - acc[val.id] = true + return selectedOrders.reduce( + (acc, val) => { + acc[val.id] = true - return acc - }, {} as { [key: string]: true }) + return acc + }, + {} as { [key: string]: true }, + ) }, [selectedOrders]) // Explainer banner for orders @@ -258,7 +258,7 @@ export function OrdersTable({ const cancellableOrders = useMemo( () => ordersPage.filter((item) => isOrderOffChainCancellable(getParsedOrderFromTableItem(item))), - [ordersPage] + [ordersPage], ) const allOrdersSelected = useMemo(() => { @@ -294,7 +294,7 @@ export function OrdersTable({ type="checkbox" onChange={(event) => orderActions.toggleOrdersForCancellation( - event.target.checked ? tableItemsToOrders(ordersPage) : [] + event.target.checked ? tableItemsToOrders(ordersPage) : [], ) } /> @@ -408,8 +408,6 @@ export function OrdersTable({ const orderParams = getOrderParams(chainId, balancesAndAllowances, order) - const hasValidPendingPermit = ordersPermitStatus[order.id] - return ( orderActions.selectReceiptOrder(order)} - hasValidPendingPermit={hasValidPendingPermit} /> ) } else { diff --git a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/index.cosmos.tsx b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/index.cosmos.tsx index ac58ea8d83..831a9a3420 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/index.cosmos.tsx +++ b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/index.cosmos.tsx @@ -74,7 +74,6 @@ export default ( getSpotPrice={() => null} orderActions={orderActions} orderType={TabOrderTypes.LIMIT} - ordersPermitStatus={{}} injectedWidgetParams={{}} /> ) diff --git a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/index.tsx b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/index.tsx index eaa804284d..69cd2c3c78 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/index.tsx +++ b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/index.tsx @@ -197,7 +197,6 @@ export function OrdersTableContainer({ children, orderType, pendingActivities, - ordersPermitStatus, injectedWidgetParams, }: OrdersProps) { const content = () => { @@ -275,7 +274,6 @@ export function OrdersTableContainer({ balancesAndAllowances={balancesAndAllowances} getSpotPrice={getSpotPrice} orderActions={orderActions} - ordersPermitStatus={ordersPermitStatus} /> ) } diff --git a/apps/cowswap-frontend/src/modules/ordersTable/utils/getOrderParams.ts b/apps/cowswap-frontend/src/modules/ordersTable/utils/getOrderParams.ts index bb800a9232..a384d35583 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/utils/getOrderParams.ts +++ b/apps/cowswap-frontend/src/modules/ordersTable/utils/getOrderParams.ts @@ -6,6 +6,7 @@ import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core' import { BalancesAndAllowances } from 'modules/tokens' import { RateInfoParams } from 'common/pure/RateInfo' +import { getOrderPermitAmount } from 'utils/orderUtils/getOrderPermitAmount' import { ParsedOrder } from 'utils/orderUtils/parseOrder' export interface OrderParams { @@ -22,10 +23,12 @@ const PERCENTAGE_FOR_PARTIAL_FILLS = new Percent(5, 10000) // 0.05% export function getOrderParams( chainId: SupportedChainId, balancesAndAllowances: BalancesAndAllowances, - order: ParsedOrder + order: ParsedOrder, ): OrderParams { + const isOrderAtLeastOnceFilled = order.executionData.filledAmount.gt(0) const sellAmount = CurrencyAmount.fromRawAmount(order.inputToken, order.sellAmount) const buyAmount = CurrencyAmount.fromRawAmount(order.outputToken, order.buyAmount) + const permitAmount = getOrderPermitAmount(chainId, order) || undefined const rateInfoParams: RateInfoParams = { chainId, @@ -43,7 +46,8 @@ export function getOrderParams( partiallyFillable: order.partiallyFillable, sellAmount, balance, - allowance, + // If the order has been filled at least once, we should not consider the permit amount + allowance: isOrderAtLeastOnceFilled ? getBiggerAmount(allowance, permitAmount) : allowance, }) return { @@ -73,3 +77,10 @@ function _hasEnoughBalanceAndAllowance(params: { return { hasEnoughBalance, hasEnoughAllowance } } + +function getBiggerAmount(a: BigNumber | undefined, b: BigNumber | undefined): BigNumber | undefined { + if (!a) return b + if (!b) return a + + return a.gt(b) ? a : b +} diff --git a/apps/cowswap-frontend/src/modules/permit/hooks/useCheckHasValidPendingPermit.ts b/apps/cowswap-frontend/src/modules/permit/hooks/useCheckHasValidPendingPermit.ts deleted file mode 100644 index 05708c6504..0000000000 --- a/apps/cowswap-frontend/src/modules/permit/hooks/useCheckHasValidPendingPermit.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { useCallback } from 'react' - -import { SupportedChainId } from '@cowprotocol/cow-sdk' -import { checkIsCallDataAValidPermit, getPermitUtilsInstance, PermitInfo } from '@cowprotocol/permit-utils' -import { useWalletInfo } from '@cowprotocol/wallet' -import { useWalletProvider } from '@cowprotocol/wallet-provider' -import { Web3Provider } from '@ethersproject/providers' - -import { getAppDataHooks } from 'modules/appData' - -import { ParsedOrder } from 'utils/orderUtils/parseOrder' - -import { useGetPermitInfo } from './useGetPermitInfo' -import { usePreGeneratedPermitInfo } from './usePreGeneratedPermitInfo' - -import { CheckHasValidPendingPermit } from '../types' - -/** - * TODO: there are some duplicated code between this and usePermitInfo.ts - */ -export function useCheckHasValidPendingPermit(): CheckHasValidPendingPermit { - const { chainId } = useWalletInfo() - const provider = useWalletProvider() - const getPermitInfo = useGetPermitInfo(chainId) - const { allPermitInfo, isLoading: preGeneratedIsLoading } = usePreGeneratedPermitInfo() - - return useCallback( - async (order: ParsedOrder): Promise => { - if (!provider || preGeneratedIsLoading) { - // Missing required params, we can't tell - return undefined - } - - const tokenAddress = order.inputToken.address.toLowerCase() - - const permitInfo = getPermitInfo(tokenAddress) - const preGeneratedPermitInfo = allPermitInfo[tokenAddress] - - // If the token is not supported, we can say that there is no valid permit - if (preGeneratedPermitInfo?.type === 'unsupported') { - return false - } - - if (permitInfo === undefined) { - // Missing permit info, we can't tell - return undefined - } - - return checkHasValidPendingPermit(order, provider, chainId, permitInfo) - }, - [chainId, getPermitInfo, provider, preGeneratedIsLoading, allPermitInfo] - ) -} - -async function checkHasValidPendingPermit( - order: ParsedOrder, - provider: Web3Provider, - chainId: SupportedChainId, - permitInfo: PermitInfo -): Promise { - const { fullAppData, partiallyFillable, executionData } = order - const preHooks = getAppDataHooks(fullAppData)?.pre - - if ( - // No hooks === no permit - !preHooks || - // Permit is only executed for partially fillable orders in the first execution - // Thus, if there is any amount executed, partiallyFillable permit is no longer valid - (partiallyFillable && executionData.filledAmount.gt('0')) || - // Permit not supported, shouldn't even get this far - !permitInfo - ) { - // These cases we know for sure permit isn't valid or there is no permit - return false - } - - const eip2162Utils = getPermitUtilsInstance(chainId, provider, order.owner) - - const tokenAddress = order.inputToken.address - const tokenName = order.inputToken.name - - const checkedHooks = await Promise.all( - preHooks.map(({ callData }) => - checkIsCallDataAValidPermit(order.owner, chainId, eip2162Utils, tokenAddress, tokenName, callData, permitInfo) - ) - ) - - const validPermits = checkedHooks.filter((v) => v !== undefined) - - if (!validPermits.length) { - // No permits means no preHook permits, we can say that there is no valid permit - return false - } - - // Only when all permits are valid, then the order permits are still valid - return validPermits.every(Boolean) -} diff --git a/apps/cowswap-frontend/src/modules/permit/hooks/useGetPermitInfo.ts b/apps/cowswap-frontend/src/modules/permit/hooks/useGetPermitInfo.ts deleted file mode 100644 index cb5356b5b9..0000000000 --- a/apps/cowswap-frontend/src/modules/permit/hooks/useGetPermitInfo.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { useAtomValue } from 'jotai' -import { useCallback } from 'react' - -import { SupportedChainId } from '@cowprotocol/cow-sdk' - -import { permittableTokensAtom } from '../state/permittableTokensAtom' -import { IsTokenPermittableResult } from '../types' - -/** - * Returns a callback for getting PermitInfo for a given token - * - * Assumes permit info was already checked and cached. - */ -export function useGetPermitInfo(chainId: SupportedChainId): (tokenAddress: string) => IsTokenPermittableResult { - const permittableTokens = useAtomValue(permittableTokensAtom) - - return useCallback( - (tokenAddress: string) => permittableTokens[chainId]?.[tokenAddress.toLowerCase()], - [chainId, permittableTokens] - ) -} diff --git a/apps/cowswap-frontend/src/modules/permit/hooks/useOrdersPermitStatus.ts b/apps/cowswap-frontend/src/modules/permit/hooks/useOrdersPermitStatus.ts deleted file mode 100644 index b61aba3797..0000000000 --- a/apps/cowswap-frontend/src/modules/permit/hooks/useOrdersPermitStatus.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { useAtomValue } from 'jotai' - -import { ordersPermitStatusAtom } from '../state/ordersPermitStatusAtom' - -export function useGetOrdersPermitStatus() { - return useAtomValue(ordersPermitStatusAtom) -} diff --git a/apps/cowswap-frontend/src/modules/permit/index.ts b/apps/cowswap-frontend/src/modules/permit/index.ts index 712e9d3442..cb734a99e6 100644 --- a/apps/cowswap-frontend/src/modules/permit/index.ts +++ b/apps/cowswap-frontend/src/modules/permit/index.ts @@ -1,12 +1,10 @@ export * from './hooks/useAccountAgnosticPermitHookData' export * from './hooks/useGeneratePermitHook' export * from './hooks/usePermitInfo' -export * from './hooks/useOrdersPermitStatus' export * from './hooks/usePermitCompatibleTokens' export * from './hooks/useTokenSupportsPermit' export { useGetCachedPermit } from './hooks/useGetCachedPermit' export * from './types' -export * from './updaters/PendingPermitUpdater' export * from './utils/handlePermit' export * from './utils/callDataContainsPermitSigner' export * from './utils/recoverSpenderFromCalldata' diff --git a/apps/cowswap-frontend/src/modules/permit/state/ordersPermitStatusAtom.ts b/apps/cowswap-frontend/src/modules/permit/state/ordersPermitStatusAtom.ts deleted file mode 100644 index cf132cefc3..0000000000 --- a/apps/cowswap-frontend/src/modules/permit/state/ordersPermitStatusAtom.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { atom } from 'jotai' - -import { atomWithPartialUpdate } from '@cowprotocol/common-utils' - -import { OrdersPermitStatus } from '../types' - -export const { atom: ordersPermitStatusAtom, updateAtom: updateOrdersPermitStatusAtom } = atomWithPartialUpdate( - atom({}) -) diff --git a/apps/cowswap-frontend/src/modules/permit/updaters/PendingPermitUpdater.ts b/apps/cowswap-frontend/src/modules/permit/updaters/PendingPermitUpdater.ts deleted file mode 100644 index b286652ed9..0000000000 --- a/apps/cowswap-frontend/src/modules/permit/updaters/PendingPermitUpdater.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { useSetAtom } from 'jotai' -import { useEffect, useRef } from 'react' - -import { ParsedOrder } from 'utils/orderUtils/parseOrder' - -import { PENDING_ORDER_PERMIT_CHECK_INTERVAL } from '../const' -import { useCheckHasValidPendingPermit } from '../hooks/useCheckHasValidPendingPermit' -import { updateOrdersPermitStatusAtom } from '../state/ordersPermitStatusAtom' - -export type PendingPermitUpdaterProps = { - orders: ParsedOrder[] -} - -export function PendingPermitUpdater({ orders }: PendingPermitUpdaterProps): null { - const ordersRef = useRef(orders) - ordersRef.current = orders - - const checkHasValidPendingPermit = useCheckHasValidPendingPermit() - const updateOrdersPermitStatus = useSetAtom(updateOrdersPermitStatusAtom) - - useEffect(() => { - const checkOrders = () => { - console.debug(`UpdatePendingPermit: checking orders`, ordersRef.current.length) - ordersRef.current.forEach((order) => { - checkHasValidPendingPermit(order).then((status) => { - console.debug(`UpdatePendingPermit: checked order ${order.id} with status ${status}`) - updateOrdersPermitStatus({ [order.id]: status }) - }) - }) - } - - checkOrders() - const interval = setInterval(checkOrders, PENDING_ORDER_PERMIT_CHECK_INTERVAL) - - return () => clearInterval(interval) - }, [checkHasValidPendingPermit, updateOrdersPermitStatus]) - - return null -} diff --git a/apps/cowswap-frontend/src/utils/orderUtils/getOrderPermitAmount.ts b/apps/cowswap-frontend/src/utils/orderUtils/getOrderPermitAmount.ts new file mode 100644 index 0000000000..d579add099 --- /dev/null +++ b/apps/cowswap-frontend/src/utils/orderUtils/getOrderPermitAmount.ts @@ -0,0 +1,41 @@ +import { Erc20__factory } from '@cowprotocol/abis' +import type { LatestAppDataDocVersion } from '@cowprotocol/app-data' +import { COW_PROTOCOL_VAULT_RELAYER_ADDRESS, SupportedChainId } from '@cowprotocol/cow-sdk' +import { BigNumber } from '@ethersproject/bignumber' + +import { ParsedOrder } from './parseOrder' + +const erc20Interface = Erc20__factory.createInterface() + +export function getOrderPermitAmount(chainId: SupportedChainId, order: ParsedOrder): BigNumber | null { + if (!order.fullAppData) return null + + try { + const spenderAddress = COW_PROTOCOL_VAULT_RELAYER_ADDRESS[chainId].toLowerCase() + const appData: LatestAppDataDocVersion = JSON.parse(order.fullAppData) + const preHooks = appData.metadata.hooks?.pre + + if (!preHooks) return null + + const permitData = preHooks + .map((hook) => { + try { + return erc20Interface.decodeFunctionData('permit', hook.callData) + } catch { + return null + } + }) + .find((decoded) => { + return ( + decoded && + decoded.spender?.toLowerCase() === spenderAddress && + decoded.owner?.toLowerCase() === order.owner.toLowerCase() && + (decoded.deadline as BigNumber)?.toNumber() > Date.now() / 1000 + ) + }) + + return permitData?.value || null + } catch { + return null + } +} diff --git a/libs/abis/src/index.ts b/libs/abis/src/index.ts index cc6dc87148..b9ffc02a93 100644 --- a/libs/abis/src/index.ts +++ b/libs/abis/src/index.ts @@ -55,6 +55,7 @@ export type { } from './generated/legacy' export type { Erc20Interface } from './generated/legacy/Erc20' +export { Erc20__factory } from './generated/legacy/factories/Erc20__factory' // EthFlow export type { CoWSwapEthFlow } from './generated/ethflow'