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

feat(swap-deadline): higher swap deadline #5002

Merged
merged 9 commits into from
Oct 23, 2024
30 changes: 11 additions & 19 deletions apps/cowswap-frontend/src/api/cowProtocol/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
} from '@cowprotocol/common-utils'
import {
Address,
SupportedChainId as ChainId,
CompetitionOrderStatus,
CowEnv,
EnrichedOrder,
Expand All @@ -21,6 +20,7 @@ import {
OrderQuoteSideKindSell,
PartialApiContext,
SigningScheme,
SupportedChainId as ChainId,
TotalSurplus,
Trade,
} from '@cowprotocol/cow-sdk'
Expand All @@ -31,10 +31,13 @@ import { LegacyFeeQuoteParams as FeeQuoteParams } from 'legacy/state/price/types

import { getAppData } from 'modules/appData'

import { getQuoteValidFor } from 'utils/orderUtils/getQuoteValidFor'

import { ApiErrorCodes } from './errors/OperatorError'
import QuoteApiError, { mapOperatorErrorToQuoteError, QuoteApiErrorDetails } from './errors/QuoteError'
import { getIsOrderBookTypedError } from './getIsOrderBookTypedError'


function getProfileUrl(): Partial<Record<ChainId, string>> {
if (isLocal || isDev || isPr || isBarn) {
return {
Expand Down Expand Up @@ -70,7 +73,7 @@ function _fetchProfile(
chainId: ChainId,
url: string,
method: 'GET' | 'POST' | 'DELETE',
data?: any
data?: any,
): Promise<Response> {
const baseUrl = _getProfileApiBaseUrl(chainId)

Expand All @@ -96,19 +99,8 @@ const ETH_FLOW_AUX_QUOTE_PARAMS = {
}

function _mapNewToLegacyParams(params: FeeQuoteParams): OrderQuoteRequest {
const {
amount,
kind,
userAddress,
receiver,
validTo,
validFor,
sellToken,
buyToken,
chainId,
priceQuality,
isEthFlow,
} = params
const { amount, kind, userAddress, receiver, validFor, sellToken, buyToken, chainId, priceQuality, isEthFlow } =
params
const fallbackAddress = userAddress || ZERO_ADDRESS
const { appData, appDataHash } = _getAppDataQuoteParams(params)

Expand All @@ -122,7 +114,7 @@ function _mapNewToLegacyParams(params: FeeQuoteParams): OrderQuoteRequest {
appDataHash,
partiallyFillable: false,
priceQuality,
...(validFor ? { validFor } : { validTo }),
...getQuoteValidFor(validFor),
}

if (isEthFlow) {
Expand Down Expand Up @@ -172,7 +164,7 @@ export async function getQuote(params: FeeQuoteParams): Promise<OrderQuoteRespon
mapOperatorErrorToQuoteError({
errorType: ApiErrorCodes.SameBuyAndSellToken,
description: QuoteApiErrorDetails.SameBuyAndSellToken,
})
}),
)
}

Expand Down Expand Up @@ -208,7 +200,7 @@ export async function getOrders(
offset?: number
limit?: number
},
context: PartialApiContext
context: PartialApiContext,
): Promise<EnrichedOrder[]> {
return orderBookApi.getOrders(params, context)
}
Expand All @@ -227,7 +219,7 @@ export async function getSurplusData(chainId: ChainId, address: string): Promise

export async function getOrderCompetitionStatus(
chainId: ChainId,
orderId: string
orderId: string,
): Promise<CompetitionOrderStatus | undefined> {
try {
return await orderBookApi.getOrderCompetitionStatus(orderId, { chainId })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ export function InvalidLocalTimeWarning() {

return (
<GlobalWarning>
Local device time does is not accurate, CoW Swap most likely will not work correctly. Please adjust your device's
time.
Local device time is not accurate, CoW Swap most likely will not work correctly. Please adjust your device's time.
</GlobalWarning>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import { quoteUsingSameParameters } from './quoteUsingSameParameters'
const RENEW_FEE_QUOTES_BEFORE_EXPIRATION_TIME = ms`30s` // Will renew the quote if there's less than 30 seconds left for the quote to expire
const WAITING_TIME_BETWEEN_EQUAL_REQUESTS = ms`5s` // Prevents from sending the same request to often (max, every 5s)

type FeeQuoteParams = Omit<LegacyFeeQuoteParams, 'validTo'>

/**
* Returns if the quote has been recently checked
*/
Expand All @@ -30,7 +28,7 @@ function isExpiringSoon(quoteExpirationIsoDate: string, threshold: number): bool
*/
export function isRefetchQuoteRequired(
isLoading: boolean,
currentParams: FeeQuoteParams,
currentParams: LegacyFeeQuoteParams,
quoteInformation?: QuoteInformationObject,
): boolean {
// If there's no quote/fee information, we always re-fetch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import { LegacyFeeQuoteParams } from 'legacy/state/price/types'

import { decodeAppData } from 'modules/appData'

type FeeQuoteParams = Omit<LegacyFeeQuoteParams, 'validTo'>

/**
* Checks if the parameters for the current quote are correct
*
* Quotes are only valid for a given token-pair and amount. If any of these parameter change, the fee needs to be re-fetched
*/
export function quoteUsingSameParameters(currentParams: FeeQuoteParams, quoteInfo: QuoteInformationObject): boolean {
export function quoteUsingSameParameters(
currentParams: LegacyFeeQuoteParams,
quoteInfo: QuoteInformationObject,
): boolean {
const {
amount: currentAmount,
sellToken: currentSellToken,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { useTokensBalances } from '@cowprotocol/balances-and-allowances'
import { NATIVE_CURRENCY_ADDRESS, WRAPPED_NATIVE_CURRENCIES } from '@cowprotocol/common-const'
import { useIsWindowVisible } from '@cowprotocol/common-hooks'
import { getPromiseFulfilledValue, isSellOrder } from '@cowprotocol/common-utils'
import { timestamp } from '@cowprotocol/contracts'
import { PriceQuality, SupportedChainId as ChainId } from '@cowprotocol/cow-sdk'
import { UiOrderType } from '@cowprotocol/types'
import { useWalletInfo } from '@cowprotocol/wallet'
Expand All @@ -22,7 +21,7 @@ import {
getEstimatedExecutionPrice,
getOrderMarketPrice,
getRemainderAmount,
isOrderUnfillable,
isOrderUnfillable
} from 'legacy/state/orders/utils'
import type { LegacyFeeQuoteParams } from 'legacy/state/price/types'
import { getBestQuote } from 'legacy/utils/price'
Expand Down Expand Up @@ -222,13 +221,8 @@ async function _getOrderPrice(chainId: ChainId, order: Order, strategy: PriceStr
}

const legacyFeeQuoteParams = quoteParams as LegacyFeeQuoteParams
// Limit order may have arbitrary validTo, but API doesn't allow values greater than 1 hour
// To avoid ExcessiveValidTo error we use PRICE_QUOTE_VALID_TO_TIME
if (order.class === 'limit') {
legacyFeeQuoteParams.validFor = Math.round(PRICE_QUOTE_VALID_TO_TIME / 1000)
} else {
legacyFeeQuoteParams.validTo = timestamp(order.validTo)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validTo has been removed completely for quote requests. Always use validFor for everything now.
Quote responses still have it, as we use that as source of truth for current time.

}

legacyFeeQuoteParams.validFor = Math.round(PRICE_QUOTE_VALID_TO_TIME / 1000)

try {
return getBestQuote({ strategy, quoteParams, fetchFee: false, isPriceRefresh: false })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,20 @@ import { QuoteError } from 'legacy/state/price/actions'
import { useQuoteDispatchers } from 'legacy/state/price/hooks'
import { QuoteInformationObject } from 'legacy/state/price/reducer'
import { LegacyFeeQuoteParams, LegacyQuoteParams } from 'legacy/state/price/types'
import { useUserTransactionTTL } from 'legacy/state/user/hooks'
import { getBestQuote, getFastQuote, QuoteResult } from 'legacy/utils/price'

import { useIsEoaEthFlow } from 'modules/trade'

import { ApiErrorCodes, isValidOperatorError } from 'api/cowProtocol/errors/OperatorError'
import QuoteApiError, {
isValidQuoteError,
QuoteApiErrorCodes,
QuoteApiErrorDetails,
isValidQuoteError,
} from 'api/cowProtocol/errors/QuoteError'

import { PRICE_QUOTE_VALID_TO_TIME } from '../../common/constants/quote'
import { useUserTransactionTTL } from '../state/user/hooks'

interface HandleQuoteErrorParams {
quoteData: QuoteInformationObject | LegacyFeeQuoteParams
error: unknown
Expand Down Expand Up @@ -114,6 +116,8 @@ function handleQuoteError({ quoteData, error, addUnsupportedToken }: HandleQuote
const getBestQuoteResolveOnlyLastCall = onlyResolvesLast<QuoteResult>(getBestQuote)
const getFastQuoteResolveOnlyLastCall = onlyResolvesLast<QuoteResult>(getFastQuote)

const MAX_VALID_FOR = Math.round(PRICE_QUOTE_VALID_TO_TIME / 1000)

/**
* @returns callback that fetches a new quote and update the state
*/
Expand All @@ -131,7 +135,7 @@ export function useRefetchQuoteCallback() {
async (params: QuoteParamsForFetching) => {
const { quoteParams, isPriceRefresh } = params
// set the validTo time here
quoteParams.validFor = deadline
quoteParams.validFor = Math.min(deadline, MAX_VALID_FOR) // do not go over MAX_VALID_FOR
quoteParams.isEthFlow = isEoaEthFlow

let quoteData: LegacyFeeQuoteParams | QuoteInformationObject = quoteParams
Expand Down
1 change: 0 additions & 1 deletion apps/cowswap-frontend/src/legacy/state/price/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ interface FeeQuoteParams extends Pick<EnrichedOrder, 'sellToken' | 'buyToken' |
userAddress?: string | null
receiver?: string | null
validFor?: number
validTo?: number
}

export interface LegacyQuoteParams {
Expand Down
26 changes: 8 additions & 18 deletions apps/cowswap-frontend/src/legacy/utils/priceLegacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@ import { PriceInformation } from 'types'
import {
getPriceQuote as getPriceQuote1inch,
PriceQuote1inch,
toPriceInformation as toPriceInformation1inch,
toPriceInformation as toPriceInformation1inch
} from 'api/1inch'
import { getQuote } from 'api/cowProtocol'
import QuoteApiError, { QuoteApiErrorCodes } from 'api/cowProtocol/errors/QuoteError'
import { getQuoteValidFor } from 'utils/orderUtils/getQuoteValidFor'

import {
LegacyPriceInformationWithSource,
LegacyPriceQuoteError,
LegacyPriceQuoteParams,
LegacyPromiseRejectedResultWithSource,
LegacyQuoteParams,
LegacyQuoteParams
} from '../state/price/types'

/**
Expand Down Expand Up @@ -91,7 +92,7 @@ async function getAllPrices(params: LegacyPriceQuoteParams): Promise<AllPricesRe
* successful price quotes and errors price quotes. For each price, it also give the context (the name of the price feed)
*/
function _extractPriceAndErrorPromiseValues(
oneInchPriceResult: PromiseSettledResult<PriceQuote1inch | null>
oneInchPriceResult: PromiseSettledResult<PriceQuote1inch | null>,
): [Array<LegacyPriceInformationWithSource>, Array<LegacyPromiseRejectedResultWithSource>] {
// Prepare an array with all successful estimations
const priceQuotes: Array<LegacyPriceInformationWithSource> = []
Expand Down Expand Up @@ -131,7 +132,7 @@ function _checkFeeErrorForData(error: QuoteApiError) {
*/
export async function getBestPrice(
params: LegacyPriceQuoteParams,
options?: GetBestPriceOptions
options?: GetBestPriceOptions,
): Promise<PriceInformation> {
// Get all prices
const { oneInchPriceResult } = await getAllPrices(params)
Expand Down Expand Up @@ -181,19 +182,8 @@ export async function getBestQuoteLegacy({
fetchFee,
previousResponse,
}: Omit<LegacyQuoteParams, 'strategy'>): Promise<QuoteResult> {
const {
sellToken,
buyToken,
fromDecimals,
toDecimals,
amount,
kind,
chainId,
userAddress,
validTo,
validFor,
priceQuality,
} = quoteParams
const { sellToken, buyToken, fromDecimals, toDecimals, amount, kind, chainId, userAddress, validFor, priceQuality } =
quoteParams
const { baseToken, quoteToken } = getCanonicalMarket({ sellToken, buyToken, kind })
// Get a new fee quote (if required)
const feePromise = fetchFee || !previousResponse ? getQuote(quoteParams) : Promise.resolve(previousResponse)
Expand Down Expand Up @@ -238,7 +228,7 @@ export async function getBestQuoteLegacy({
kind,
userAddress,
priceQuality,
...(validFor ? { validFor } : { validTo }),
...getQuoteValidFor(validFor),
})
: // fee exceeds our price, is invalid
Promise.reject(FEE_EXCEEDS_FROM_ERROR)
Expand Down
6 changes: 3 additions & 3 deletions apps/cowswap-frontend/src/legacy/utils/trade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export function getOrderSubmitSummary(
params: Pick<
PostOrderParams,
'kind' | 'account' | 'inputAmount' | 'outputAmount' | 'recipient' | 'recipientAddressOrName' | 'feeAmount'
>
>,
): string {
const { kind, account, inputAmount, outputAmount, recipient, recipientAddressOrName, feeAmount } = params

Expand Down Expand Up @@ -249,7 +249,7 @@ export async function signAndPostOrder(params: PostOrderParams): Promise<AddUnse
appData: appData.fullAppData, // We sign the keccak256 hash, but we send the API the full appData string
appDataHash: appData.appDataKeccak256,
},
{ chainId }
{ chainId },
)

const pendingOrderParams: Order = mapUnsignedOrderToOrder({
Expand Down Expand Up @@ -288,7 +288,7 @@ export async function sendOrderCancellation(params: OrderCancellationParams): Pr
signature,
signingScheme,
},
{ chainId }
{ chainId },
)

cancelPendingOrder({ chainId, id: orderId })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,11 @@ import { PriceImpactDeclineError, TradeFlowContext } from 'modules/limitOrders/s
import { LimitOrdersSettingsState } from 'modules/limitOrders/state/limitOrdersSettingsAtom'
import { calculateLimitOrdersDeadline } from 'modules/limitOrders/utils/calculateLimitOrdersDeadline'
import { emitPostedOrderEvent } from 'modules/orders'
import { handlePermit } from 'modules/permit'
import { callDataContainsPermitSigner } from 'modules/permit'
import { callDataContainsPermitSigner, handlePermit } from 'modules/permit'
import { addPendingOrderStep } from 'modules/trade/utils/addPendingOrderStep'
import { logTradeFlow } from 'modules/trade/utils/logger'
import { getSwapErrorMessage } from 'modules/trade/utils/swapErrorHelper'
import { TradeFlowAnalyticsContext, tradeFlowAnalytics } from 'modules/trade/utils/tradeFlowAnalytics'
import { tradeFlowAnalytics, TradeFlowAnalyticsContext } from 'modules/trade/utils/tradeFlowAnalytics'
import { presignOrderStep } from 'modules/tradeFlow/services/swapFlow/steps/presignOrderStep'

export async function tradeFlow(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import { partialOrderUpdate } from 'legacy/state/orders/utils'
import { signAndPostOrder } from 'legacy/utils/trade'

import { emitPostedOrderEvent } from 'modules/orders'
import { handlePermit } from 'modules/permit'
import { callDataContainsPermitSigner } from 'modules/permit'
import { callDataContainsPermitSigner, handlePermit } from 'modules/permit'
import { addPendingOrderStep } from 'modules/trade/utils/addPendingOrderStep'
import { logTradeFlow } from 'modules/trade/utils/logger'
import { getSwapErrorMessage } from 'modules/trade/utils/swapErrorHelper'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { useOnClickOutside } from '@cowprotocol/common-hooks'
import { getWrappedToken, percentToBps } from '@cowprotocol/common-utils'
import { StatefulValue } from '@cowprotocol/types'
import { HelpTooltip, RowBetween, RowFixed, UI } from '@cowprotocol/ui'
import { useWalletInfo } from '@cowprotocol/wallet'
import { useIsSmartContractWallet, useWalletInfo } from '@cowprotocol/wallet'
import { TradeType } from '@cowprotocol/widget-lib'
import { Percent } from '@uniswap/sdk-core'

Expand Down Expand Up @@ -48,7 +48,8 @@ import useNativeCurrency from 'lib/hooks/useNativeCurrency'

import * as styledEl from './styled'

const MAX_DEADLINE_MINUTES = 180 // 3h
const MAX_EOA_DEADLINE_MINUTES = 60 * 3 // 3h
const MAX_SC_DEADLINE_MINUTES = 60 * 12 // 12h

enum SlippageError {
InvalidInput = 'InvalidInput',
Expand All @@ -66,6 +67,7 @@ export function TransactionSettings({ deadlineState }: TransactionSettingsProps)
const { chainId } = useWalletInfo()
const theme = useContext(ThemeContext)

const isSmartContractWallet = useIsSmartContractWallet()
const isEoaEthFlow = useIsEoaEthFlow()
const nativeCurrency = useNativeCurrency()

Expand Down Expand Up @@ -141,7 +143,7 @@ export function TransactionSettings({ deadlineState }: TransactionSettingsProps)
? // 10 minute low threshold for eth flow
MINIMUM_ETH_FLOW_DEADLINE_SECONDS
: MINIMUM_ORDER_VALID_TO_TIME_SECONDS
const maxDeadline = MAX_DEADLINE_MINUTES * 60
const maxDeadline = (isSmartContractWallet ? MAX_SC_DEADLINE_MINUTES : MAX_EOA_DEADLINE_MINUTES) * 60

const parseCustomDeadline = useCallback(
(value: string) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function getQuoteValidFor(validFor: number | undefined) {
return validFor ? { validFor } : undefined
}
Loading