Skip to content
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { useCallback } from 'react'

import { getWrappedToken, isRejectRequestProviderError } from '@cowprotocol/common-utils'
import { useTradeSpenderAddress } from '@cowprotocol/balances-and-allowances'
import { currencyAmountToTokenAmount, getWrappedToken, isRejectRequestProviderError } from '@cowprotocol/common-utils'
import { Currency, CurrencyAmount } from '@cowprotocol/currency'
import { useWalletInfo } from '@cowprotocol/wallet'
import { WidgetHookEvents } from '@cowprotocol/widget-lib'

import { callWidgetHook } from 'modules/injectedWidget'
import { useGeneratePermitHook, usePermitInfo } from 'modules/permit'
import { TradeType } from 'modules/trade'

Expand All @@ -14,12 +17,30 @@ export function useGeneratePermitInAdvanceToTrade(amountToApprove: CurrencyAmoun
const updateApproveProgressModalState = useUpdateApproveProgressModalState()
const resetApproveProgressModalState = useResetApproveProgressModalState()
const { account } = useWalletInfo()
const tradeSpenderAddress = useTradeSpenderAddress()

const token = getWrappedToken(amountToApprove.currency)
const permitInfo = usePermitInfo(token, TradeType.SWAP)

return useCallback(async () => {
if (!account || !permitInfo) return false
if (!account || !permitInfo || !tradeSpenderAddress) return false

const amountRaw = BigInt(amountToApprove.quotient.toString())

const tokenAmount = currencyAmountToTokenAmount(amountToApprove)
const isWidgetHookPassed = await callWidgetHook(WidgetHookEvents.ON_BEFORE_APPROVAL, {
chainId: tokenAmount.currency.chainId,
sellToken: {
...tokenAmount.currency,
name: tokenAmount.currency.name || '',
symbol: tokenAmount.currency.symbol || '',
},
sellAmount: amountRaw.toString(),
walletAddress: account,
spenderAddress: tradeSpenderAddress,
})

if (!isWidgetHookPassed) return false

const preSignCallback = (): void =>
updateApproveProgressModalState({
Expand All @@ -33,7 +54,7 @@ export function useGeneratePermitInAdvanceToTrade(amountToApprove: CurrencyAmoun
inputToken: { name: token.name || '', address: token.address as `0x${string}` },
account,
permitInfo,
amount: BigInt(amountToApprove.quotient.toString()),
amount: amountRaw,
preSignCallback,
postSignCallback: resetApproveProgressModalState,
})
Expand All @@ -54,6 +75,7 @@ export function useGeneratePermitInAdvanceToTrade(amountToApprove: CurrencyAmoun
resetApproveProgressModalState,
token.address,
token.name,
tradeSpenderAddress,
updateApproveProgressModalState,
])
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { useUpdateLimitOrdersRawState } from 'modules/limitOrders/hooks/useLimit
import { useSafeBundleFlowContext } from 'modules/limitOrders/hooks/useSafeBundleFlowContext'
import { safeBundleFlow } from 'modules/limitOrders/services/safeBundleFlow'
import { tradeFlow } from 'modules/limitOrders/services/tradeFlow'
import { PriceImpactDeclineError, TradeFlowContext } from 'modules/limitOrders/services/types'
import { PriceImpactDeclineError, TradeFlowContext, WidgetHookDeclineError } from 'modules/limitOrders/services/types'
import { LimitOrdersSettingsState } from 'modules/limitOrders/state/limitOrdersSettingsAtom'
import { partiallyFillableOverrideAtom } from 'modules/limitOrders/state/partiallyFillableOverride'
import { calculateLimitOrdersDeadline } from 'modules/limitOrders/utils/calculateLimitOrdersDeadline'
Expand Down Expand Up @@ -202,6 +202,10 @@ export function useHandleOrderPlacement(
})
.catch((error) => {
if (error instanceof PriceImpactDeclineError) return
if (error instanceof WidgetHookDeclineError) {
tradeConfirmActions.onDismiss()
return
}

if (error instanceof OperatorError) {
tradeConfirmActions.onError(error.message || error.description)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { captureError, ERROR_TYPES, normalizeError, reportPermitWithDefaultSigner } from '@cowprotocol/common-utils'
import { SigningScheme } from '@cowprotocol/cow-sdk'
import {
captureError,
COW_PROTOCOL_VAULT_RELAYER_ADDRESS,
currencyAmountToTokenAmount,
ERROR_TYPES,
normalizeError,
reportPermitWithDefaultSigner,
} from '@cowprotocol/common-utils'
import { SigningScheme, SupportedChainId } from '@cowprotocol/cow-sdk'
import { Percent } from '@cowprotocol/currency'
import { isSupportedPermitInfo } from '@cowprotocol/permit-utils'
import { Command, UiOrderType } from '@cowprotocol/types'
import { WidgetHookEvents } from '@cowprotocol/widget-lib'

import { tradingSdk } from 'tradingSdk/tradingSdk'
import { sendTransaction } from 'wagmi/actions'
Expand All @@ -11,8 +19,9 @@ import { PriceImpact } from 'legacy/hooks/usePriceImpact'
import { partialOrderUpdate } from 'legacy/state/orders/utils'
import { mapUnsignedOrderToOrder, wrapErrorInOperatorError } from 'legacy/utils/trade'

import { callWidgetHook } from 'modules/injectedWidget'
import { LOW_RATE_THRESHOLD_PERCENT } from 'modules/limitOrders/const/trade'
import { PriceImpactDeclineError, TradeFlowContext } from 'modules/limitOrders/services/types'
import { PriceImpactDeclineError, TradeFlowContext, WidgetHookDeclineError } from 'modules/limitOrders/services/types'
import { LimitOrdersSettingsState } from 'modules/limitOrders/state/limitOrdersSettingsAtom'
import { calculateLimitOrdersDeadline } from 'modules/limitOrders/utils/calculateLimitOrdersDeadline'
import { emitPostedOrderEvent } from 'modules/orders'
Expand All @@ -27,7 +36,7 @@ import { getSwapErrorMessage } from 'common/utils/getSwapErrorMessage'
import type { Hex } from 'viem'

// TODO: Break down this large function into smaller functions
// eslint-disable-next-line max-lines-per-function
// eslint-disable-next-line max-lines-per-function, complexity
export async function tradeFlow(
params: TradeFlowContext,
priceImpact: PriceImpact,
Expand Down Expand Up @@ -73,7 +82,28 @@ export async function tradeFlow(

try {
logTradeFlow('LIMIT ORDER FLOW', 'STEP 2: handle permit')
if (isSupportedPermitInfo(permitInfo)) await beforePermit()
if (isSupportedPermitInfo(permitInfo)) {
const cachedPermit = await params.getCachedPermit(sellToken.address)

if (!cachedPermit) {
const sellTokenAmount = currencyAmountToTokenAmount(inputAmount)
const isWidgetHookPassed = await callWidgetHook(WidgetHookEvents.ON_BEFORE_APPROVAL, {
chainId: sellTokenAmount.currency.chainId,
sellToken: {
...sellTokenAmount.currency,
name: sellTokenAmount.currency.name || '',
symbol: sellTokenAmount.currency.symbol || '',
},
sellAmount: (permitAmountToSign ?? 0n).toString(),
walletAddress: account,
spenderAddress: COW_PROTOCOL_VAULT_RELAYER_ADDRESS[chainId as SupportedChainId],
})

if (!isWidgetHookPassed) throw new WidgetHookDeclineError()
}

await beforePermit()
}

postOrderParams.appData = await handlePermit({
permitInfo,
Expand Down Expand Up @@ -196,6 +226,11 @@ export async function tradeFlow(
} catch (err: unknown) {
const error = normalizeError(err)

// Expected abort path: skip generic swap-error analytics so widget-hook declines don't pollute telemetry.
if (error instanceof WidgetHookDeclineError) {
throw error
}

logTradeFlow('LIMIT ORDER FLOW', 'STEP 9: ERROR: ', error)
const swapErrorMessage = getSwapErrorMessage(error)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ export interface SafeBundleFlowContext extends TradeFlowContext {
}

export class PriceImpactDeclineError extends Error {}

export class WidgetHookDeclineError extends Error {}
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import {
captureError,
COW_PROTOCOL_VAULT_RELAYER_ADDRESS,
currencyAmountToTokenAmount,
delay,
ERROR_TYPES,
getCurrencyAddress,
normalizeError,
reportPermitWithDefaultSigner,
} from '@cowprotocol/common-utils'
import { SigningScheme, SigningStepManager } from '@cowprotocol/cow-sdk'
import { SigningScheme, SigningStepManager, SupportedChainId } from '@cowprotocol/cow-sdk'
import { Percent } from '@cowprotocol/currency'
import { isSupportedPermitInfo } from '@cowprotocol/permit-utils'
import { CoWShedEip1271SignatureInvalid } from '@cowprotocol/sdk-cow-shed'
import { UiOrderType } from '@cowprotocol/types'
import { WidgetHookEvents } from '@cowprotocol/widget-lib'

import { SigningSteps } from 'entities/trade'
import ms from 'ms.macro'
Expand All @@ -21,6 +24,7 @@ import { PriceImpact } from 'legacy/hooks/usePriceImpact'
import { partialOrderUpdate } from 'legacy/state/orders/utils'
import { mapUnsignedOrderToOrder, wrapErrorInOperatorError } from 'legacy/utils/trade'

import { callWidgetHook } from 'modules/injectedWidget'
import { emitPostedOrderEvent } from 'modules/orders'
import { callDataContainsPermitSigner, handlePermit } from 'modules/permit'
import { addPendingOrderStep } from 'modules/trade/utils/addPendingOrderStep'
Expand Down Expand Up @@ -86,13 +90,28 @@ export async function swapFlow(

try {
logTradeFlow('SWAP FLOW', 'STEP 2: handle permit')
const { appData, account, isSafeWallet, recipientAddressOrName, kind } = orderParams

if (shouldSignPermit) {
const sellTokenAmount = currencyAmountToTokenAmount(inputAmount)
const isWidgetHookPassed = await callWidgetHook(WidgetHookEvents.ON_BEFORE_APPROVAL, {
chainId: sellTokenAmount.currency.chainId,
sellToken: {
...sellTokenAmount.currency,
name: sellTokenAmount.currency.name || '',
symbol: sellTokenAmount.currency.symbol || '',
},
sellAmount: (permitAmountToSign ?? 0n).toString(),
walletAddress: account,
spenderAddress: COW_PROTOCOL_VAULT_RELAYER_ADDRESS[chainId as SupportedChainId],
})

if (!isWidgetHookPassed) return false

setSigningStep(isBridgingOrder ? '1/3' : '1/2', SigningSteps.PermitSigning)
tradeConfirmActions.requestPermitSignature(tradeAmounts)
}

const { appData, account, isSafeWallet, recipientAddressOrName, inputAmount, outputAmount, kind } = orderParams

orderParams.appData = await handlePermit({
appData,
typedHooks,
Expand Down
Loading