ci(release): publish latest release

parent cd8fe9b1
IPFS hash of the deployment: IPFS hash of the deployment:
- CIDv0: `QmQRJ4TzV1zYobbsNLsA4WvSVSvpSxdRAMzWcyHjVyP5FK` - CIDv0: `QmYjLCs2fp85NtK5CYeZBisiUZdf9ZLWBfaQ4CSG67qUyq`
- CIDv1: `bafybeia647xbmz33dcdo7fwhybp54imlqc2hwkdnl53lbnurjajwiofaxy` - CIDv1: `bafybeie2mrhbxzypdlpnuhuxdtlhsdvuxtdmrawrp3hl6oufdrwyybhs5q`
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org). The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
...@@ -10,15 +10,15 @@ You can also access the Uniswap Interface from an IPFS gateway. ...@@ -10,15 +10,15 @@ You can also access the Uniswap Interface from an IPFS gateway.
Your Uniswap settings are never remembered across different URLs. Your Uniswap settings are never remembered across different URLs.
IPFS gateways: IPFS gateways:
- https://bafybeia647xbmz33dcdo7fwhybp54imlqc2hwkdnl53lbnurjajwiofaxy.ipfs.dweb.link/ - https://bafybeie2mrhbxzypdlpnuhuxdtlhsdvuxtdmrawrp3hl6oufdrwyybhs5q.ipfs.dweb.link/
- https://bafybeia647xbmz33dcdo7fwhybp54imlqc2hwkdnl53lbnurjajwiofaxy.ipfs.cf-ipfs.com/ - https://bafybeie2mrhbxzypdlpnuhuxdtlhsdvuxtdmrawrp3hl6oufdrwyybhs5q.ipfs.cf-ipfs.com/
- [ipfs://QmQRJ4TzV1zYobbsNLsA4WvSVSvpSxdRAMzWcyHjVyP5FK/](ipfs://QmQRJ4TzV1zYobbsNLsA4WvSVSvpSxdRAMzWcyHjVyP5FK/) - [ipfs://QmYjLCs2fp85NtK5CYeZBisiUZdf9ZLWBfaQ4CSG67qUyq/](ipfs://QmYjLCs2fp85NtK5CYeZBisiUZdf9ZLWBfaQ4CSG67qUyq/)
### 5.51.6 (2024-10-11) ## 5.52.0 (2024-10-11)
### Bug Fixes ### Features
* **web:** hide astrochain usdc (#12963) acd6d55 * **web:** remove red gas UI on web shared swap [prod] + 2 other hotfixes (#12948) 18013a2
web/5.51.6 web/5.52.0
\ No newline at end of file \ No newline at end of file
...@@ -11,9 +11,8 @@ import { updateSignature } from 'state/signatures/reducer' ...@@ -11,9 +11,8 @@ import { updateSignature } from 'state/signatures/reducer'
import { SignatureType } from 'state/signatures/types' import { SignatureType } from 'state/signatures/types'
import { addTransaction, finalizeTransaction } from 'state/transactions/reducer' import { addTransaction, finalizeTransaction } from 'state/transactions/reducer'
import { TransactionType } from 'state/transactions/types' import { TransactionType } from 'state/transactions/types'
import { logSwapSuccess, logUniswapXSwapSuccess } from 'tracing/swapFlowLoggers' import { logSwapFinalized, logUniswapXSwapFinalized } from 'tracing/swapFlowLoggers'
import { UniswapXOrderStatus } from 'types/uniswapx' import { UniswapXOrderStatus } from 'types/uniswapx'
import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { isL2ChainId } from 'uniswap/src/features/chains/utils' import { isL2ChainId } from 'uniswap/src/features/chains/utils'
import { useTrace } from 'utilities/src/telemetry/trace/TraceContext' import { useTrace } from 'utilities/src/telemetry/trace/TraceContext'
...@@ -52,8 +51,8 @@ function useOnActivityUpdate(): OnActivityUpdate { ...@@ -52,8 +51,8 @@ function useOnActivityUpdate(): OnActivityUpdate {
const hash = original.hash const hash = original.hash
dispatch(finalizeTransaction({ chainId, hash, ...update })) dispatch(finalizeTransaction({ chainId, hash, ...update }))
if (original.info.type === TransactionType.SWAP && update.status === TransactionStatus.Confirmed) { if (original.info.type === TransactionType.SWAP) {
logSwapSuccess(hash, chainId, analyticsContext) logSwapFinalized(hash, chainId, analyticsContext, update.status)
} }
addPopup({ type: PopupType.Transaction, hash }, hash, popupDismissalTime) addPopup({ type: PopupType.Transaction, hash }, hash, popupDismissalTime)
...@@ -68,6 +67,9 @@ function useOnActivityUpdate(): OnActivityUpdate { ...@@ -68,6 +67,9 @@ function useOnActivityUpdate(): OnActivityUpdate {
const updatedOrder = { ...original, ...update } const updatedOrder = { ...original, ...update }
dispatch(updateSignature(updatedOrder)) dispatch(updateSignature(updatedOrder))
// SignatureDetails.type should not be typed as optional, but this will be fixed when we merge activity for uniswap. The default value appeases the typechecker.
const signatureType = updatedOrder.type ?? SignatureType.SIGN_UNISWAPX_V2_ORDER
if (updatedOrder.status === UniswapXOrderStatus.FILLED) { if (updatedOrder.status === UniswapXOrderStatus.FILLED) {
const hash = updatedOrder.txHash const hash = updatedOrder.txHash
const from = original.offerer const from = original.offerer
...@@ -75,13 +77,34 @@ function useOnActivityUpdate(): OnActivityUpdate { ...@@ -75,13 +77,34 @@ function useOnActivityUpdate(): OnActivityUpdate {
dispatch(addTransaction({ chainId, from, hash, info: updatedOrder.swapInfo })) dispatch(addTransaction({ chainId, from, hash, info: updatedOrder.swapInfo }))
addPopup({ type: PopupType.Transaction, hash }, hash, popupDismissalTime) addPopup({ type: PopupType.Transaction, hash }, hash, popupDismissalTime)
// Only track swap success for Dutch orders; limit order fill-time will throw off time tracking analytics // Only track swap success for non-limit orders; limit order fill-time will throw off time tracking analytics
if (original.type !== SignatureType.SIGN_LIMIT) { if (original.type !== SignatureType.SIGN_LIMIT) {
logUniswapXSwapSuccess(hash, updatedOrder.orderHash, chainId, analyticsContext) logUniswapXSwapFinalized(
hash,
updatedOrder.orderHash,
chainId,
analyticsContext,
signatureType,
UniswapXOrderStatus.FILLED,
)
} }
} else if (original.status !== updatedOrder.status) { } else if (original.status !== updatedOrder.status) {
const orderHash = original.orderHash const orderHash = original.orderHash
addPopup({ type: PopupType.Order, orderHash }, orderHash, popupDismissalTime) addPopup({ type: PopupType.Order, orderHash }, orderHash, popupDismissalTime)
if (
updatedOrder.status === UniswapXOrderStatus.CANCELLED ||
updatedOrder.status === UniswapXOrderStatus.EXPIRED
) {
logUniswapXSwapFinalized(
undefined,
updatedOrder.orderHash,
chainId,
analyticsContext,
signatureType,
updatedOrder.status,
)
}
} }
} }
}, },
......
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import ms from 'ms' import ms from 'ms'
import { import { GetQuickQuoteArgs, PreviewTradeResult, QuickRouteResponse, QuoteState } from 'state/routing/types'
GetQuickQuoteArgs,
PreviewTradeResult,
QuickRouteResponse,
QuoteState,
RouterPreference,
} from 'state/routing/types'
import { isExactInput, transformQuickRouteToTrade } from 'state/routing/utils' import { isExactInput, transformQuickRouteToTrade } from 'state/routing/utils'
import { logSwapQuoteRequest } from 'tracing/swapFlowLoggers'
import { trace } from 'tracing/trace' import { trace } from 'tracing/trace'
import { InterfaceEventNameLocal } from 'uniswap/src/features/telemetry/constants' import { InterfaceEventNameLocal } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { logSwapQuoteFetch } from 'uniswap/src/features/transactions/swap/analytics'
const UNISWAP_GATEWAY_DNS_URL = process.env.REACT_APP_UNISWAP_GATEWAY_DNS const UNISWAP_GATEWAY_DNS_URL = process.env.REACT_APP_UNISWAP_GATEWAY_DNS
if (UNISWAP_GATEWAY_DNS_URL === undefined) { if (UNISWAP_GATEWAY_DNS_URL === undefined) {
...@@ -25,7 +19,7 @@ export const quickRouteApi = createApi({ ...@@ -25,7 +19,7 @@ export const quickRouteApi = createApi({
getQuickRoute: build.query<PreviewTradeResult, GetQuickQuoteArgs>({ getQuickRoute: build.query<PreviewTradeResult, GetQuickQuoteArgs>({
queryFn(args, _api, _extraOptions, fetch) { queryFn(args, _api, _extraOptions, fetch) {
return trace({ name: 'QuickRoute', op: 'quote.quick_route' }, async (trace) => { return trace({ name: 'QuickRoute', op: 'quote.quick_route' }, async (trace) => {
logSwapQuoteRequest(args.tokenInChainId, RouterPreference.API, true) logSwapQuoteFetch({ chainId: args.tokenInChainId, isQuickRoute: true })
const { tokenInAddress, tokenInChainId, tokenOutAddress, tokenOutChainId, amount, tradeType } = args const { tokenInAddress, tokenInChainId, tokenOutAddress, tokenOutChainId, amount, tradeType } = args
const type = isExactInput(tradeType) ? 'EXACT_IN' : 'EXACT_OUT' const type = isExactInput(tradeType) ? 'EXACT_IN' : 'EXACT_OUT'
......
...@@ -18,10 +18,10 @@ import { ...@@ -18,10 +18,10 @@ import {
UniswapXv2Config, UniswapXv2Config,
} from 'state/routing/types' } from 'state/routing/types'
import { isExactInput, transformQuoteToTrade } from 'state/routing/utils' import { isExactInput, transformQuoteToTrade } from 'state/routing/utils'
import { logSwapQuoteRequest } from 'tracing/swapFlowLoggers'
import { trace } from 'tracing/trace' import { trace } from 'tracing/trace'
import { InterfaceEventNameLocal } from 'uniswap/src/features/telemetry/constants' import { InterfaceEventNameLocal } from 'uniswap/src/features/telemetry/constants'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { logSwapQuoteFetch } from 'uniswap/src/features/transactions/swap/analytics'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
const UNISWAP_GATEWAY_DNS_URL = process.env.REACT_APP_UNISWAP_GATEWAY_DNS const UNISWAP_GATEWAY_DNS_URL = process.env.REACT_APP_UNISWAP_GATEWAY_DNS
...@@ -107,7 +107,10 @@ export const routingApi = createApi({ ...@@ -107,7 +107,10 @@ export const routingApi = createApi({
getQuote: build.query<TradeResult, GetQuoteArgs>({ getQuote: build.query<TradeResult, GetQuoteArgs>({
queryFn(args, _api, _extraOptions, fetch) { queryFn(args, _api, _extraOptions, fetch) {
return trace({ name: 'Quote', op: 'quote', data: { ...args } }, async (trace) => { return trace({ name: 'Quote', op: 'quote', data: { ...args } }, async (trace) => {
logSwapQuoteRequest(args.tokenInChainId, args.routerPreference, false) logSwapQuoteFetch({
chainId: args.tokenInChainId,
isUSDQuote: args.routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE,
})
const { const {
tokenInAddress: tokenIn, tokenInAddress: tokenIn,
tokenInChainId, tokenInChainId,
......
import { SwapEventName } from '@uniswap/analytics-events' import { SwapEventName } from '@uniswap/analytics-events'
import { INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference } from 'state/routing/types' import { SignatureType } from 'state/signatures/types'
import { logSwapQuoteRequest, logSwapSuccess, logUniswapXSwapSuccess } from 'tracing/swapFlowLoggers' import { logSwapFinalized, logUniswapXSwapFinalized } from 'tracing/swapFlowLoggers'
import { UniswapXOrderStatus } from 'types/uniswapx'
import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { maybeLogFirstSwapAction } from 'uniswap/src/features/transactions/swap/utils/maybeLogFirstSwapAction' import { maybeLogFirstSwapAction } from 'uniswap/src/features/transactions/swap/utils/maybeLogFirstSwapAction'
import { TransactionOriginType } from 'uniswap/src/features/transactions/types/transactionDetails' import { TransactionOriginType } from 'uniswap/src/features/transactions/types/transactionDetails'
...@@ -28,7 +30,7 @@ describe('swapFlowLoggers', () => { ...@@ -28,7 +30,7 @@ describe('swapFlowLoggers', () => {
const mockChainId = 1 const mockChainId = 1
const mockAnalyticsContext = { page: 'mockContext' } const mockAnalyticsContext = { page: 'mockContext' }
logSwapSuccess(mockHash, mockChainId, mockAnalyticsContext) logSwapFinalized(mockHash, mockChainId, mockAnalyticsContext, TransactionStatus.Confirmed)
expect(sendAnalyticsEvent).toHaveBeenCalledWith(SwapEventName.SWAP_TRANSACTION_COMPLETED, { expect(sendAnalyticsEvent).toHaveBeenCalledWith(SwapEventName.SWAP_TRANSACTION_COMPLETED, {
transactionOriginType: TransactionOriginType.Internal, transactionOriginType: TransactionOriginType.Internal,
...@@ -47,7 +49,14 @@ describe('swapFlowLoggers', () => { ...@@ -47,7 +49,14 @@ describe('swapFlowLoggers', () => {
const mockChainId = 1 const mockChainId = 1
const mockAnalyticsContext = { page: 'mockContext' } const mockAnalyticsContext = { page: 'mockContext' }
logUniswapXSwapSuccess(mockHash, mockOrderHash, mockChainId, mockAnalyticsContext) logUniswapXSwapFinalized(
mockHash,
mockOrderHash,
mockChainId,
mockAnalyticsContext,
SignatureType.SIGN_UNISWAPX_V2_ORDER,
UniswapXOrderStatus.FILLED,
)
expect(sendAnalyticsEvent).toHaveBeenCalledWith(SwapEventName.SWAP_TRANSACTION_COMPLETED, { expect(sendAnalyticsEvent).toHaveBeenCalledWith(SwapEventName.SWAP_TRANSACTION_COMPLETED, {
transactionOriginType: TransactionOriginType.Internal, transactionOriginType: TransactionOriginType.Internal,
...@@ -71,28 +80,4 @@ describe('swapFlowLoggers', () => { ...@@ -71,28 +80,4 @@ describe('swapFlowLoggers', () => {
...mockAnalyticsContext, ...mockAnalyticsContext,
}) })
}) })
it('logSwapQuoteRequest calls sendAnalyticsEvent with correct parameters', () => {
const mockChainId = 1
logSwapQuoteRequest(mockChainId, RouterPreference.X)
expect(sendAnalyticsEvent).toHaveBeenCalledWith(SwapEventName.SWAP_QUOTE_FETCH, {
chainId: mockChainId,
isQuickRoute: false,
time_to_first_quote_request: 100,
time_to_first_quote_request_since_first_input: 100,
})
})
it('logSwapQuoteRequest excludes perf metrics for price quotes', () => {
const mockChainId = 1
logSwapQuoteRequest(mockChainId, INTERNAL_ROUTER_PREFERENCE_PRICE)
expect(sendAnalyticsEvent).toHaveBeenCalledWith(SwapEventName.SWAP_QUOTE_FETCH, {
chainId: mockChainId,
isQuickRoute: false,
})
})
}) })
import { SwapEventName } from '@uniswap/analytics-events' import { SwapEventName } from '@uniswap/analytics-events'
import { INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference } from 'state/routing/types' import { SignatureType } from 'state/signatures/types'
import { ConfirmedTransactionDetails } from 'state/transactions/types'
import { UniswapXOrderStatus } from 'types/uniswapx'
import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { SwapRouting } from 'uniswap/src/features/telemetry/types'
import { SwapEventType, timestampTracker } from 'uniswap/src/features/transactions/swap/utils/SwapEventTimestampTracker' import { SwapEventType, timestampTracker } from 'uniswap/src/features/transactions/swap/utils/SwapEventTimestampTracker'
import { TransactionOriginType } from 'uniswap/src/features/transactions/types/transactionDetails' import { TransactionOriginType } from 'uniswap/src/features/transactions/types/transactionDetails'
import { ITraceContext } from 'utilities/src/telemetry/trace/TraceContext' import { ITraceContext } from 'utilities/src/telemetry/trace/TraceContext'
export function logSwapSuccess(hash: string, chainId: number, analyticsContext: ITraceContext) { export function logSwapFinalized(
hash: string,
chainId: number,
analyticsContext: ITraceContext,
status: ConfirmedTransactionDetails['status'],
) {
const hasSetSwapSuccess = timestampTracker.hasTimestamp(SwapEventType.FirstSwapSuccess) const hasSetSwapSuccess = timestampTracker.hasTimestamp(SwapEventType.FirstSwapSuccess)
const elapsedTime = timestampTracker.setElapsedTime(SwapEventType.FirstSwapSuccess) const elapsedTime = timestampTracker.setElapsedTime(SwapEventType.FirstSwapSuccess)
sendAnalyticsEvent(SwapEventName.SWAP_TRANSACTION_COMPLETED, { const event =
status === TransactionStatus.Confirmed
? SwapEventName.SWAP_TRANSACTION_COMPLETED
: SwapEventName.SWAP_TRANSACTION_FAILED
sendAnalyticsEvent(event, {
routing: 'classic', routing: 'classic',
// We only log the time-to-swap metric for the first swap of a session, // We only log the time-to-swap metric for the first swap of a session,
// so if it was previously set we log undefined here. // so if it was previously set we log undefined here.
...@@ -23,17 +37,32 @@ export function logSwapSuccess(hash: string, chainId: number, analyticsContext: ...@@ -23,17 +37,32 @@ export function logSwapSuccess(hash: string, chainId: number, analyticsContext:
...analyticsContext, ...analyticsContext,
}) })
} }
export function logUniswapXSwapSuccess(
hash: string, const SIGNATURE_TYPE_TO_SWAP_ROUTING: Record<SignatureType, SwapRouting> = {
[SignatureType.SIGN_LIMIT]: 'limit_order',
[SignatureType.SIGN_PRIORITY_ORDER]: 'priority_order',
[SignatureType.SIGN_UNISWAPX_V2_ORDER]: 'uniswap_x_v2',
[SignatureType.SIGN_UNISWAPX_ORDER]: 'uniswap_x',
}
export function logUniswapXSwapFinalized(
hash: string | undefined,
orderHash: string, orderHash: string,
chainId: number, chainId: number,
analyticsContext: ITraceContext, analyticsContext: ITraceContext,
signatureType: SignatureType,
status: UniswapXOrderStatus.FILLED | UniswapXOrderStatus.CANCELLED | UniswapXOrderStatus.EXPIRED,
) { ) {
const hasSetSwapSuccess = timestampTracker.hasTimestamp(SwapEventType.FirstSwapSuccess) const hasSetSwapSuccess = timestampTracker.hasTimestamp(SwapEventType.FirstSwapSuccess)
const elapsedTime = timestampTracker.setElapsedTime(SwapEventType.FirstSwapSuccess) const elapsedTime = timestampTracker.setElapsedTime(SwapEventType.FirstSwapSuccess)
sendAnalyticsEvent(SwapEventName.SWAP_TRANSACTION_COMPLETED, { const event =
routing: 'uniswap_x_v2', status === UniswapXOrderStatus.FILLED
? SwapEventName.SWAP_TRANSACTION_COMPLETED
: SwapEventName.SWAP_TRANSACTION_FAILED
sendAnalyticsEvent(event, {
routing: SIGNATURE_TYPE_TO_SWAP_ROUTING[signatureType],
order_hash: orderHash, order_hash: orderHash,
transactionOriginType: TransactionOriginType.Internal, transactionOriginType: TransactionOriginType.Internal,
// We only log the time-to-swap metric for the first swap of a session, // We only log the time-to-swap metric for the first swap of a session,
...@@ -47,27 +76,3 @@ export function logUniswapXSwapSuccess( ...@@ -47,27 +76,3 @@ export function logUniswapXSwapSuccess(
...analyticsContext, ...analyticsContext,
}) })
} }
export function logSwapQuoteRequest(
chainId: number,
routerPreference: RouterPreference | typeof INTERNAL_ROUTER_PREFERENCE_PRICE,
isQuickRoute?: boolean,
) {
let performanceMetrics = {}
if (routerPreference !== INTERNAL_ROUTER_PREFERENCE_PRICE) {
const hasSetSwapQuote = timestampTracker.hasTimestamp(SwapEventType.FirstQuoteFetchStarted)
const elapsedTime = timestampTracker.setElapsedTime(SwapEventType.FirstQuoteFetchStarted)
performanceMetrics = {
// We only log the time_to_first_quote_request metric for the first quote request of a session.
time_to_first_quote_request: hasSetSwapQuote ? undefined : elapsedTime,
time_to_first_quote_request_since_first_input: hasSetSwapQuote
? undefined
: timestampTracker.getElapsedTime(SwapEventType.FirstQuoteFetchStarted, SwapEventType.FirstSwapAction),
}
}
sendAnalyticsEvent(SwapEventName.SWAP_QUOTE_FETCH, {
chainId,
isQuickRoute: isQuickRoute ?? false,
...performanceMetrics,
})
}
import { InterfaceEventName, InterfaceModalName } from '@uniswap/analytics-events'
import { Currency } from '@uniswap/sdk-core' import { Currency } from '@uniswap/sdk-core'
import { hasStringAsync } from 'expo-clipboard' import { hasStringAsync } from 'expo-clipboard'
import { memo, useCallback, useEffect, useMemo, useState } from 'react' import { memo, useCallback, useEffect, useMemo, useState } from 'react'
...@@ -256,7 +257,11 @@ export function TokenSelectorContent({ ...@@ -256,7 +257,11 @@ export function TokenSelectorContent({
]) ])
return ( return (
<> <Trace
logImpression={isInterface} // TODO(WEB-5161): Deduplicate shared vs interface-only trace event
eventOnTrigger={InterfaceEventName.TOKEN_SELECTOR_OPENED}
modal={InterfaceModalName.TOKEN_SELECTOR}
>
<Trace logImpression element={currencyFieldName} section={SectionName.TokenSelector}> <Trace logImpression element={currencyFieldName} section={SectionName.TokenSelector}>
<Flex grow gap="$spacing8" style={scrollbarStyles}> <Flex grow gap="$spacing8" style={scrollbarStyles}>
{!isSmallScreen && ( {!isSmallScreen && (
...@@ -311,7 +316,7 @@ export function TokenSelectorContent({ ...@@ -311,7 +316,7 @@ export function TokenSelectorContent({
{isSurfaceReady && <Flex grow>{tokenSelector}</Flex>} {isSurfaceReady && <Flex grow>{tokenSelector}</Flex>}
</Flex> </Flex>
</Trace> </Trace>
</> </Trace>
) )
} }
......
...@@ -14,6 +14,7 @@ import { GasFeeResult } from 'uniswap/src/features/gas/types' ...@@ -14,6 +14,7 @@ import { GasFeeResult } from 'uniswap/src/features/gas/types'
import { NetworkFeeWarning } from 'uniswap/src/features/transactions/swap/modals/NetworkFeeWarning' import { NetworkFeeWarning } from 'uniswap/src/features/transactions/swap/modals/NetworkFeeWarning'
import { UniswapXGasBreakdown } from 'uniswap/src/features/transactions/swap/types/swapTxAndGasInfo' import { UniswapXGasBreakdown } from 'uniswap/src/features/transactions/swap/types/swapTxAndGasInfo'
import { UniverseChainId } from 'uniswap/src/types/chains' import { UniverseChainId } from 'uniswap/src/types/chains'
import { isInterface } from 'utilities/src/platform'
export function NetworkFee({ export function NetworkFee({
chainId, chainId,
...@@ -39,6 +40,7 @@ export function NetworkFee({ ...@@ -39,6 +40,7 @@ export function NetworkFee({
const uniswapXGasFeeInfo = useFormattedUniswapXGasFeeInfo(uniswapXGasBreakdown, chainId) const uniswapXGasFeeInfo = useFormattedUniswapXGasFeeInfo(uniswapXGasBreakdown, chainId)
const gasFeeHighRelativeToValue = useGasFeeHighRelativeToValue(gasFeeUSD, transactionUSDValue) const gasFeeHighRelativeToValue = useGasFeeHighRelativeToValue(gasFeeUSD, transactionUSDValue)
const showHighGasFeeUI = gasFeeHighRelativeToValue && !isInterface // Avoid high gas UI on interface
return ( return (
<Flex row alignItems="center" gap="$spacing12" justifyContent="space-between"> <Flex row alignItems="center" gap="$spacing12" justifyContent="space-between">
...@@ -60,7 +62,7 @@ export function NetworkFee({ ...@@ -60,7 +62,7 @@ export function NetworkFee({
<UniswapXFee gasFee={gasFeeFormatted} preSavingsGasFee={uniswapXGasFeeInfo?.preSavingsGasFeeFormatted} /> <UniswapXFee gasFee={gasFeeFormatted} preSavingsGasFee={uniswapXGasFeeInfo?.preSavingsGasFeeFormatted} />
) : ( ) : (
<Text <Text
color={gasFee.isLoading ? '$neutral3' : gasFeeHighRelativeToValue ? '$statusCritical' : '$neutral1'} color={gasFee.isLoading ? '$neutral3' : showHighGasFeeUI ? '$statusCritical' : '$neutral1'}
variant="body3" variant="body3"
> >
{gasFeeFormatted} {gasFeeFormatted}
......
...@@ -3,6 +3,7 @@ import { uniswapUrls } from 'uniswap/src/constants/urls' ...@@ -3,6 +3,7 @@ import { uniswapUrls } from 'uniswap/src/constants/urls'
import { TRADING_API_CACHE_KEY, fetchIndicativeQuote } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient' import { TRADING_API_CACHE_KEY, fetchIndicativeQuote } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient'
import { UseQueryApiHelperHookArgs } from 'uniswap/src/data/apiClients/types' import { UseQueryApiHelperHookArgs } from 'uniswap/src/data/apiClients/types'
import { IndicativeQuoteRequest, IndicativeQuoteResponse } from 'uniswap/src/data/tradingApi/__generated__' import { IndicativeQuoteRequest, IndicativeQuoteResponse } from 'uniswap/src/data/tradingApi/__generated__'
import { logSwapQuoteFetch } from 'uniswap/src/features/transactions/swap/analytics'
export function useTradingApiIndicativeQuoteQuery({ export function useTradingApiIndicativeQuoteQuery({
params, params,
...@@ -16,7 +17,12 @@ export function useTradingApiIndicativeQuoteQuery({ ...@@ -16,7 +17,12 @@ export function useTradingApiIndicativeQuoteQuery({
return useQuery<IndicativeQuoteResponse>({ return useQuery<IndicativeQuoteResponse>({
queryKey, queryKey,
queryFn: params queryFn: params
? async (): ReturnType<typeof fetchIndicativeQuote> => await fetchIndicativeQuote(params) ? async (): ReturnType<typeof fetchIndicativeQuote> => {
if (params.tokenInChainId) {
logSwapQuoteFetch({ chainId: params.tokenInChainId, isQuickRoute: true })
}
return await fetchIndicativeQuote(params)
}
: skipToken, : skipToken,
...rest, ...rest,
}) })
......
...@@ -9,22 +9,28 @@ import { ...@@ -9,22 +9,28 @@ import {
} from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient' } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient'
import { UseQueryWithImmediateGarbageCollectionApiHelperHookArgs } from 'uniswap/src/data/apiClients/types' import { UseQueryWithImmediateGarbageCollectionApiHelperHookArgs } from 'uniswap/src/data/apiClients/types'
import { QuoteRequest } from 'uniswap/src/data/tradingApi/__generated__' import { QuoteRequest } from 'uniswap/src/data/tradingApi/__generated__'
import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { logSwapQuoteFetch } from 'uniswap/src/features/transactions/swap/analytics'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
export function useTradingApiQuoteQuery({ export function useTradingApiQuoteQuery({
params, params,
...rest ...rest
}: UseQueryWithImmediateGarbageCollectionApiHelperHookArgs< }: UseQueryWithImmediateGarbageCollectionApiHelperHookArgs<
WithV4Flag<QuoteRequest>, WithV4Flag<QuoteRequest & { isUSDQuote?: boolean }>,
DiscriminatedQuoteResponse DiscriminatedQuoteResponse
>): UseQueryResult<DiscriminatedQuoteResponse> { >): UseQueryResult<DiscriminatedQuoteResponse> {
const queryKey = [TRADING_API_CACHE_KEY, uniswapUrls.tradingApiPaths.quote, params] const queryKey = [TRADING_API_CACHE_KEY, uniswapUrls.tradingApiPaths.quote, params]
const v4Enabled = useFeatureFlag(FeatureFlags.V4Swap)
return useQueryWithImmediateGarbageCollection<DiscriminatedQuoteResponse>({ return useQueryWithImmediateGarbageCollection<DiscriminatedQuoteResponse>({
queryKey, queryKey,
queryFn: params ? async (): ReturnType<typeof fetchQuote> => await fetchQuote({ ...params, v4Enabled }) : skipToken, queryFn: params
? async (): ReturnType<typeof fetchQuote> => {
const { isUSDQuote, ...fetchParams } = params
if (fetchParams.tokenInChainId) {
logSwapQuoteFetch({ chainId: fetchParams.tokenInChainId, isUSDQuote })
}
return await fetchQuote(fetchParams)
}
: skipToken,
...rest, ...rest,
}) })
} }
import { SwapEventName } from '@uniswap/analytics-events'
import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send'
import { logSwapQuoteFetch } from 'uniswap/src/features/transactions/swap/analytics'
jest.mock('uniswap/src/features/telemetry/send', () => ({
sendAnalyticsEvent: jest.fn(),
}))
jest.mock('uniswap/src/features/transactions/swap/utils/SwapEventTimestampTracker', () => ({
...jest.requireActual('uniswap/src/features/transactions/swap/utils/SwapEventTimestampTracker'),
timestampTracker: {
hasTimestamp: (): boolean => false,
setElapsedTime: (): number => 100,
getElapsedTime: (): number => 100,
},
}))
describe('analytics', () => {
beforeEach(() => {
jest.clearAllMocks()
})
it('logSwapQuoteRequest calls sendAnalyticsEvent with correct parameters', () => {
const mockChainId = 1
logSwapQuoteFetch({ chainId: mockChainId })
expect(sendAnalyticsEvent).toHaveBeenCalledWith(SwapEventName.SWAP_QUOTE_FETCH, {
chainId: mockChainId,
isQuickRoute: false,
time_to_first_quote_request: 100,
time_to_first_quote_request_since_first_input: 100,
})
})
it('logSwapQuoteRequest excludes perf metrics for price quotes', () => {
const mockChainId = 1
logSwapQuoteFetch({ chainId: mockChainId, isUSDQuote: true })
expect(sendAnalyticsEvent).toHaveBeenCalledWith(SwapEventName.SWAP_QUOTE_FETCH, {
chainId: mockChainId,
isQuickRoute: false,
})
})
})
...@@ -10,6 +10,7 @@ import { SwapRouting, SwapTradeBaseProperties } from 'uniswap/src/features/telem ...@@ -10,6 +10,7 @@ import { SwapRouting, SwapTradeBaseProperties } from 'uniswap/src/features/telem
import { ValueType, getCurrencyAmount } from 'uniswap/src/features/tokens/getCurrencyAmount' import { ValueType, getCurrencyAmount } from 'uniswap/src/features/tokens/getCurrencyAmount'
import { DerivedSwapInfo } from 'uniswap/src/features/transactions/swap/types/derivedSwapInfo' import { DerivedSwapInfo } from 'uniswap/src/features/transactions/swap/types/derivedSwapInfo'
import { Trade } from 'uniswap/src/features/transactions/swap/types/trade' import { Trade } from 'uniswap/src/features/transactions/swap/types/trade'
import { SwapEventType, timestampTracker } from 'uniswap/src/features/transactions/swap/utils/SwapEventTimestampTracker'
import { getSwapFeeUsd } from 'uniswap/src/features/transactions/swap/utils/getSwapFeeUsd' import { getSwapFeeUsd } from 'uniswap/src/features/transactions/swap/utils/getSwapFeeUsd'
import { isClassic } from 'uniswap/src/features/transactions/swap/utils/routing' import { isClassic } from 'uniswap/src/features/transactions/swap/utils/routing'
import { getClassicQuoteFromResponse } from 'uniswap/src/features/transactions/swap/utils/tradingApi' import { getClassicQuoteFromResponse } from 'uniswap/src/features/transactions/swap/utils/tradingApi'
...@@ -179,6 +180,31 @@ export function getBaseTradeAnalyticsPropertiesFromSwapInfo({ ...@@ -179,6 +180,31 @@ export function getBaseTradeAnalyticsPropertiesFromSwapInfo({
} }
} }
export function logSwapQuoteFetch({
chainId,
isUSDQuote = false,
isQuickRoute = false,
}: {
chainId: number
isUSDQuote?: boolean
isQuickRoute?: boolean
}): void {
let performanceMetrics = {}
if (!isUSDQuote) {
const hasSetSwapQuote = timestampTracker.hasTimestamp(SwapEventType.FirstQuoteFetchStarted)
const elapsedTime = timestampTracker.setElapsedTime(SwapEventType.FirstQuoteFetchStarted)
// We only log the time_to_first_quote_request metric for the first quote request of a session.
const time_to_first_quote_request = hasSetSwapQuote ? undefined : elapsedTime
const time_to_first_quote_request_since_first_input = hasSetSwapQuote
? undefined
: timestampTracker.getElapsedTime(SwapEventType.FirstQuoteFetchStarted, SwapEventType.FirstSwapAction)
performanceMetrics = { time_to_first_quote_request, time_to_first_quote_request_since_first_input }
}
sendAnalyticsEvent(SwapEventName.SWAP_QUOTE_FETCH, { chainId, isQuickRoute, ...performanceMetrics })
}
// eslint-disable-next-line consistent-return // eslint-disable-next-line consistent-return
export function tradeRoutingToFillType({ export function tradeRoutingToFillType({
routing, routing,
......
...@@ -103,7 +103,7 @@ function useDebouncedTrade(): Trade | IndicativeTrade | undefined { ...@@ -103,7 +103,7 @@ function useDebouncedTrade(): Trade | IndicativeTrade | undefined {
function GasRow({ gasInfo, hidden }: { gasInfo: DebouncedGasInfo; hidden?: boolean }): JSX.Element | null { function GasRow({ gasInfo, hidden }: { gasInfo: DebouncedGasInfo; hidden?: boolean }): JSX.Element | null {
if (gasInfo.fiatPriceFormatted) { if (gasInfo.fiatPriceFormatted) {
const color = gasInfo.isHighRelativeToValue ? '$statusCritical' : '$neutral2' const color = gasInfo.isHighRelativeToValue && !isInterface ? '$statusCritical' : '$neutral2' // Avoid high gas UI on interface
const uniswapXSavings = gasInfo.uniswapXGasFeeInfo?.preSavingsGasFeeFormatted const uniswapXSavings = gasInfo.uniswapXGasFeeInfo?.preSavingsGasFeeFormatted
const body = uniswapXSavings ? ( const body = uniswapXSavings ? (
<UniswapXFee gasFee={gasInfo.fiatPriceFormatted} preSavingsGasFee={uniswapXSavings} smaller={isWeb} /> <UniswapXFee gasFee={gasInfo.fiatPriceFormatted} preSavingsGasFee={uniswapXSavings} smaller={isWeb} />
......
import { TradeType } from '@uniswap/sdk-core' import { TradeType } from '@uniswap/sdk-core'
import { useMemo, useRef } from 'react' import { useMemo, useRef } from 'react'
import { FetchError } from 'uniswap/src/data/apiClients/FetchError' import { FetchError } from 'uniswap/src/data/apiClients/FetchError'
import { WithV4Flag } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient'
import { useTradingApiQuoteQuery } from 'uniswap/src/data/apiClients/tradingApi/useTradingApiQuoteQuery' import { useTradingApiQuoteQuery } from 'uniswap/src/data/apiClients/tradingApi/useTradingApiQuoteQuery'
import { QuoteRequest, TradeType as TradingApiTradeType } from 'uniswap/src/data/tradingApi/__generated__/index' import { TradeType as TradingApiTradeType } from 'uniswap/src/data/tradingApi/__generated__/index'
import { useActiveGasStrategy, useShadowGasStrategies } from 'uniswap/src/features/gas/hooks' import { useActiveGasStrategy, useShadowGasStrategies } from 'uniswap/src/features/gas/hooks'
import { areEqualGasStrategies } from 'uniswap/src/features/gas/types' import { areEqualGasStrategies } from 'uniswap/src/features/gas/types'
import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { FeatureFlags } from 'uniswap/src/features/gating/flags'
...@@ -12,6 +11,7 @@ import { useIndicativeTrade } from 'uniswap/src/features/transactions/swap/hooks ...@@ -12,6 +11,7 @@ import { useIndicativeTrade } from 'uniswap/src/features/transactions/swap/hooks
import { usePollingIntervalByChain } from 'uniswap/src/features/transactions/swap/hooks/usePollingIntervalByChain' import { usePollingIntervalByChain } from 'uniswap/src/features/transactions/swap/hooks/usePollingIntervalByChain'
import { TradeWithStatus, UseTradeArgs } from 'uniswap/src/features/transactions/swap/types/trade' import { TradeWithStatus, UseTradeArgs } from 'uniswap/src/features/transactions/swap/types/trade'
import { import {
SWAP_GAS_URGENCY_OVERRIDE,
getTokenAddressForApi, getTokenAddressForApi,
toTradingApiSupportedChainId, toTradingApiSupportedChainId,
transformTradingApiResponseToTrade, transformTradingApiResponseToTrade,
...@@ -88,7 +88,7 @@ export function useTrade({ ...@@ -88,7 +88,7 @@ export function useTrade({
const v4Enabled = useFeatureFlag(FeatureFlags.V4Swap) const v4Enabled = useFeatureFlag(FeatureFlags.V4Swap)
const quoteRequestArgs = useMemo((): WithV4Flag<QuoteRequest> | undefined => { const quoteRequestArgs = useMemo((): Parameters<typeof useTradingApiQuoteQuery>[0]['params'] | undefined => {
if (skipQuery) { if (skipQuery) {
return undefined return undefined
} }
...@@ -101,9 +101,11 @@ export function useTrade({ ...@@ -101,9 +101,11 @@ export function useTrade({
tokenIn: tokenInAddress, tokenIn: tokenInAddress,
tokenOut: tokenOutAddress, tokenOut: tokenOutAddress,
slippageTolerance: customSlippageTolerance, slippageTolerance: customSlippageTolerance,
...routingParams, urgency: SWAP_GAS_URGENCY_OVERRIDE,
gasStrategies: [activeGasStrategy, ...(shadowGasStrategies ?? [])], gasStrategies: [activeGasStrategy, ...(shadowGasStrategies ?? [])],
v4Enabled, v4Enabled,
isUSDQuote,
...routingParams,
} }
}, [ }, [
activeAccountAddress, activeAccountAddress,
...@@ -119,6 +121,7 @@ export function useTrade({ ...@@ -119,6 +121,7 @@ export function useTrade({
tokenOutAddress, tokenOutAddress,
tokenOutChainId, tokenOutChainId,
v4Enabled, v4Enabled,
isUSDQuote,
]) ])
/***** Fetch quote from trading API ******/ /***** Fetch quote from trading API ******/
......
...@@ -25,6 +25,7 @@ import { SwapGasFeeEstimation } from 'uniswap/src/features/transactions/swap/typ ...@@ -25,6 +25,7 @@ import { SwapGasFeeEstimation } from 'uniswap/src/features/transactions/swap/typ
import { ApprovalAction, TokenApprovalInfo } from 'uniswap/src/features/transactions/swap/types/trade' import { ApprovalAction, TokenApprovalInfo } from 'uniswap/src/features/transactions/swap/types/trade'
import { isUniswapX } from 'uniswap/src/features/transactions/swap/utils/routing' import { isUniswapX } from 'uniswap/src/features/transactions/swap/utils/routing'
import { import {
SWAP_GAS_URGENCY_OVERRIDE,
getBridgeQuoteFromResponse, getBridgeQuoteFromResponse,
getClassicQuoteFromResponse, getClassicQuoteFromResponse,
isClassicQuote, isClassicQuote,
...@@ -119,6 +120,7 @@ export function useTransactionRequestInfo({ ...@@ -119,6 +120,7 @@ export function useTransactionRequestInfo({
deadline, deadline,
refreshGasPrice: true, refreshGasPrice: true,
gasStrategies: [activeGasStrategy, ...(shadowGasStrategies ?? [])], gasStrategies: [activeGasStrategy, ...(shadowGasStrategies ?? [])],
urgency: SWAP_GAS_URGENCY_OVERRIDE,
v4Enabled, v4Enabled,
} }
......
...@@ -11,7 +11,7 @@ import { LearnMoreLink } from 'uniswap/src/components/text/LearnMoreLink' ...@@ -11,7 +11,7 @@ import { LearnMoreLink } from 'uniswap/src/components/text/LearnMoreLink'
import { uniswapUrls } from 'uniswap/src/constants/urls' import { uniswapUrls } from 'uniswap/src/constants/urls'
import { FormattedUniswapXGasFeeInfo } from 'uniswap/src/features/gas/types' import { FormattedUniswapXGasFeeInfo } from 'uniswap/src/features/gas/types'
import { ModalName } from 'uniswap/src/features/telemetry/constants' import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { isMobileApp } from 'utilities/src/platform' import { isInterface, isMobileApp } from 'utilities/src/platform'
export function NetworkFeeWarning({ export function NetworkFeeWarning({
gasFeeHighRelativeToValue, gasFeeHighRelativeToValue,
...@@ -28,7 +28,7 @@ export function NetworkFeeWarning({ ...@@ -28,7 +28,7 @@ export function NetworkFeeWarning({
const colors = useSporeColors() const colors = useSporeColors()
const { t } = useTranslation() const { t } = useTranslation()
const showHighGasFeeUI = gasFeeHighRelativeToValue && !uniswapXGasFeeInfo const showHighGasFeeUI = gasFeeHighRelativeToValue && !uniswapXGasFeeInfo && !isInterface // Avoid high gas UI on interface
return ( return (
<WarningInfo <WarningInfo
......
...@@ -21,6 +21,7 @@ import { ...@@ -21,6 +21,7 @@ import {
V2PoolInRoute as TradingApiV2PoolInRoute, V2PoolInRoute as TradingApiV2PoolInRoute,
V3PoolInRoute as TradingApiV3PoolInRoute, V3PoolInRoute as TradingApiV3PoolInRoute,
V4PoolInRoute as TradingApiV4PoolInRoute, V4PoolInRoute as TradingApiV4PoolInRoute,
Urgency,
V4PoolInRoute, V4PoolInRoute,
} from 'uniswap/src/data/tradingApi/__generated__/index' } from 'uniswap/src/data/tradingApi/__generated__/index'
import { NativeCurrency } from 'uniswap/src/features/tokens/NativeCurrency' import { NativeCurrency } from 'uniswap/src/features/tokens/NativeCurrency'
...@@ -42,8 +43,10 @@ import { CurrencyField } from 'uniswap/src/types/currency' ...@@ -42,8 +43,10 @@ import { CurrencyField } from 'uniswap/src/types/currency'
import { areAddressesEqual } from 'uniswap/src/utils/addresses' import { areAddressesEqual } from 'uniswap/src/utils/addresses'
import { currencyId } from 'uniswap/src/utils/currencyId' import { currencyId } from 'uniswap/src/utils/currencyId'
import { logger } from 'utilities/src/logger/logger' import { logger } from 'utilities/src/logger/logger'
import { isInterface } from 'utilities/src/platform'
export const NATIVE_ADDRESS_FOR_TRADING_API = '0x0000000000000000000000000000000000000000' export const NATIVE_ADDRESS_FOR_TRADING_API = '0x0000000000000000000000000000000000000000'
export const SWAP_GAS_URGENCY_OVERRIDE = isInterface ? Urgency.NORMAL : undefined // on Interface, use a normal urgency, else use TradingAPI default
interface TradingApiResponseToTradeArgs { interface TradingApiResponseToTradeArgs {
currencyIn: Currency currencyIn: Currency
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment