diff --git a/pages/api/preflight-compliance.ts b/pages/api/preflight-compliance.ts index fcf49f204b..6e1df4d08a 100644 --- a/pages/api/preflight-compliance.ts +++ b/pages/api/preflight-compliance.ts @@ -8,11 +8,19 @@ type ComplianceApiResponse = { result: boolean; lastChecked: string; nextCheck: string; + useV37?: { + wethGateway: string; + uiPoolDataProvider: string; + }; }; type PreflightResponse = { result: boolean; nextCheck: string; + useV37?: { + wethGateway: string; + uiPoolDataProvider: string; + }; }; type ErrorResponse = { @@ -77,6 +85,7 @@ export default async function handler( return res.status(200).json({ result: data.result, nextCheck: data.nextCheck, + useV37: data.useV37, }); } catch (error) { console.error('Compliance API error:', error); diff --git a/src/components/transactions/Withdraw/WithdrawActions.tsx b/src/components/transactions/Withdraw/WithdrawActions.tsx index c765f21501..85248fbd6b 100644 --- a/src/components/transactions/Withdraw/WithdrawActions.tsx +++ b/src/components/transactions/Withdraw/WithdrawActions.tsx @@ -1,10 +1,13 @@ -import { ProtocolAction } from '@aave/contract-helpers'; +import { gasLimitRecommendations, ProtocolAction } from '@aave/contract-helpers'; import { valueToBigNumber } from '@aave/math-utils'; import { Trans } from '@lingui/macro'; import { BoxProps } from '@mui/material'; +import { BigNumber } from 'ethers'; +import { parseEther } from 'ethers/lib/utils'; import { useTransactionHandler } from 'src/helpers/useTransactionHandler'; import { ComputedReserveData } from 'src/hooks/app-data-provider/useAppDataProvider'; import { useRootStore } from 'src/store/root'; +import { useShallow } from 'zustand/shallow'; import { TxActionsWrapper } from '../TxActionsWrapper'; @@ -15,6 +18,7 @@ export interface WithdrawActionsProps extends BoxProps { isWrongNetwork: boolean; symbol: string; blocked: boolean; + nativeBalance: string; } export const WithdrawActions = ({ @@ -24,19 +28,42 @@ export const WithdrawActions = ({ isWrongNetwork, symbol, blocked, + nativeBalance, sx, }: WithdrawActionsProps) => { - const withdraw = useRootStore((state) => state.withdraw); + const [withdraw, v37Overrides] = useRootStore( + useShallow((state) => [state.withdraw, state.v37Overrides]) + ); const { action, loadingTxns, mainTxState, approvalTxState, approval, requiresApproval } = useTransactionHandler({ tryPermit: false, - handleGetTxns: async () => - withdraw({ + handleGetTxns: async () => { + const txs = await withdraw({ reserve: poolAddress, amount: amountToWithdraw, aTokenAddress: poolReserve.aTokenAddress, - }), + }); + + if (!v37Overrides) return txs; + + const mappedTxs = txs.map((tx) => ({ + ...tx, + tx: async () => { + const txData = await tx.tx(); + if (tx.txType === 'ERC20_APPROVAL') return txData; + const balance = parseEther(nativeBalance); + const gasBuffer = parseEther('0.05'); + const value = balance.gt(gasBuffer) ? balance.sub(gasBuffer).toString() : '0'; + return { + ...txData, + value, + gasLimit: BigNumber.from(gasLimitRecommendations[ProtocolAction.withdraw].recommended), + }; + }, + })); + return mappedTxs; + }, skip: !amountToWithdraw || parseFloat(amountToWithdraw) === 0 || blocked, deps: [amountToWithdraw, poolAddress], eventTxInfo: { diff --git a/src/components/transactions/Withdraw/WithdrawModalContent.tsx b/src/components/transactions/Withdraw/WithdrawModalContent.tsx index 2037b67843..cfa81148c4 100644 --- a/src/components/transactions/Withdraw/WithdrawModalContent.tsx +++ b/src/components/transactions/Withdraw/WithdrawModalContent.tsx @@ -39,6 +39,7 @@ export const WithdrawModalContent = ({ setUnwrap: setWithdrawUnWrapped, symbol, isWrongNetwork, + nativeBalance, user, }: ModalWrapperProps & { unwrap: boolean; @@ -225,6 +226,7 @@ export const WithdrawModalContent = ({ isWrongNetwork={isWrongNetwork} symbol={symbol} blocked={blockingError !== undefined || (displayRiskCheckbox && !riskCheckboxAccepted)} + nativeBalance={nativeBalance} sx={displayRiskCheckbox ? { mt: 0 } : {}} /> diff --git a/src/hooks/compliance/compliance.tsx b/src/hooks/compliance/compliance.tsx index d270b9364f..e1d869b7da 100644 --- a/src/hooks/compliance/compliance.tsx +++ b/src/hooks/compliance/compliance.tsx @@ -1,12 +1,13 @@ 'use client'; import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'; +import { useRootStore } from 'src/store/root'; import { useAccount } from 'wagmi'; import { checkCompliance } from './service-compliance'; import { useLocalStorageState } from './useLocalStorageState'; -const STORAGE_KEY = 'compliance-state'; +const STORAGE_KEY = 'compliance-state-v2'; const MIN_REFRESH_MS = 60 * 1000; // 1 minute minimum between checks export type ComplianceStatus = 'idle' | 'loading' | 'compliant' | 'non-compliant' | 'error'; @@ -29,6 +30,10 @@ type PersistedComplianceState = { [address: string]: { result: boolean; nextCheck: string; + useV37?: { + wethGateway: string; + uiPoolDataProvider: string; + }; }; }; @@ -45,6 +50,7 @@ const isExpired = (nextCheck: string | null): boolean => { export const ComplianceProvider = ({ children }: { children: React.ReactNode }) => { const { address, isConnected } = useAccount(); + const setV37Overrides = useRootStore((state) => state.setV37Overrides); const [cache, setCache] = useLocalStorageState(STORAGE_KEY, { defaultValue: {}, @@ -90,12 +96,22 @@ export const ComplianceProvider = ({ children }: { children: React.ReactNode }) errorMessage: undefined, }); + if (response.data.useV37) { + setV37Overrides({ + WETH_GATEWAY: response.data.useV37.wethGateway, + UI_POOL_DATA_PROVIDER: response.data.useV37.uiPoolDataProvider, + }); + } else { + setV37Overrides(null); + } + // Update cache for this address setCache((prev) => ({ ...prev, [walletAddress.toLowerCase()]: { result: response.data!.result, nextCheck: response.data!.nextCheck, + useV37: response.data!.useV37, }, })); @@ -126,7 +142,7 @@ export const ComplianceProvider = ({ children }: { children: React.ReactNode }) isCheckingRef.current = false; } }, - [setCache] + [setCache, setV37Overrides] ); const recheck = useCallback(async () => { @@ -159,6 +175,15 @@ export const ComplianceProvider = ({ children }: { children: React.ReactNode }) nextCheck: cached.nextCheck, }); + if (cached.useV37) { + setV37Overrides({ + WETH_GATEWAY: cached.useV37.wethGateway, + UI_POOL_DATA_PROVIDER: cached.useV37.uiPoolDataProvider, + }); + } else { + setV37Overrides(null); + } + // Schedule refresh at nextCheck time if (cached.result) { const msUntilNextCheck = Math.max( diff --git a/src/hooks/compliance/service-compliance.ts b/src/hooks/compliance/service-compliance.ts index e92bca3ddf..d2bc042aa3 100644 --- a/src/hooks/compliance/service-compliance.ts +++ b/src/hooks/compliance/service-compliance.ts @@ -1,6 +1,10 @@ export type ComplianceResult = { result: boolean; nextCheck: string; + useV37?: { + wethGateway: string; + uiPoolDataProvider: string; + }; }; export type ComplianceCheckResponse = { @@ -20,6 +24,7 @@ export const checkCompliance = async (address: string): Promise { + if (!overrides || market.chainId !== ChainId.mainnet) return market; + return { + ...market, + addresses: { + ...market.addresses, + WETH_GATEWAY: overrides.WETH_GATEWAY, + UI_POOL_DATA_PROVIDER: overrides.UI_POOL_DATA_PROVIDER, + }, + }; +}; + type TypePermitParams = { reserveAddress: string; isWrappedBaseAsset: boolean; @@ -23,8 +44,10 @@ export interface ProtocolDataSlice { currentMarketData: MarketDataType; currentChainId: number; currentNetworkConfig: NetworkConfig; + v37Overrides: V37Overrides | null; jsonRpcProvider: (chainId?: number) => providers.Provider; setCurrentMarket: (market: CustomMarket, omitQueryParameterUpdate?: boolean) => void; + setV37Overrides: (overrides: V37Overrides | null) => void; tryPermit: ({ reserveAddress, isWrappedBaseAsset }: TypePermitParams) => boolean; } @@ -41,10 +64,11 @@ export const createProtocolDataSlice: StateCreator< currentMarketData: marketsData[initialMarket], currentChainId: initialMarketData.chainId, currentNetworkConfig: getNetworkConfig(initialMarketData.chainId), + v37Overrides: null, jsonRpcProvider: (chainId) => getProvider(chainId ?? get().currentChainId), setCurrentMarket: (market, omitQueryParameterUpdate) => { if (!availableMarkets.includes(market as CustomMarket)) return; - const nextMarketData = marketsData[market]; + const nextMarketData = applyV37ToMarket(marketsData[market], get().v37Overrides); localStorage.setItem('selectedMarket', market); if (!omitQueryParameterUpdate) { setQueryParameter('marketName', market); @@ -56,6 +80,13 @@ export const createProtocolDataSlice: StateCreator< currentNetworkConfig: getNetworkConfig(nextMarketData.chainId), }); }, + setV37Overrides: (overrides) => { + const currentMarket = get().currentMarket; + set({ + v37Overrides: overrides, + currentMarketData: applyV37ToMarket(marketsData[currentMarket], overrides), + }); + }, tryPermit: ({ reserveAddress, isWrappedBaseAsset }: TypePermitParams) => { const currentNetworkConfig = get().currentNetworkConfig; const currentMarketData = get().currentMarketData; diff --git a/src/ui-config/queries.ts b/src/ui-config/queries.ts index 0931847c60..60f0b851b3 100644 --- a/src/ui-config/queries.ts +++ b/src/ui-config/queries.ts @@ -15,6 +15,7 @@ export const queryKeysFactory = { marketData.chainId, !!marketData.isFork, marketData.market, + marketData.addresses.UI_POOL_DATA_PROVIDER, ], user: (user: string) => [user], powers: (user: string, chainId: number, blockHash?: string) => [