ci(release): publish latest release

parent 5df99a15
IPFS hash of the deployment: IPFS hash of the deployment:
- CIDv0: `QmainvWX1paa5SAQXowSshKypn1tC5d59gLN8w2coWzJwx` - CIDv0: `QmYgGWW41t5rZ973ouoVCPSVJ7xSk1ouQS8hXJaVrwBydQ`
- CIDv1: `bafybeifx7bho67bfj5b2bn7nw452bovpbv4iia5ndwi4kwvra5fcswhjr4` - CIDv1: `bafybeieztnrdyd2anpehiktteokw23mwgbjmnz42u4cpzosxjxignq2bf4`
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,95 +10,14 @@ You can also access the Uniswap Interface from an IPFS gateway. ...@@ -10,95 +10,14 @@ 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://bafybeifx7bho67bfj5b2bn7nw452bovpbv4iia5ndwi4kwvra5fcswhjr4.ipfs.dweb.link/ - https://bafybeieztnrdyd2anpehiktteokw23mwgbjmnz42u4cpzosxjxignq2bf4.ipfs.dweb.link/
- [ipfs://QmainvWX1paa5SAQXowSshKypn1tC5d59gLN8w2coWzJwx/](ipfs://QmainvWX1paa5SAQXowSshKypn1tC5d59gLN8w2coWzJwx/) - [ipfs://QmYgGWW41t5rZ973ouoVCPSVJ7xSk1ouQS8hXJaVrwBydQ/](ipfs://QmYgGWW41t5rZ973ouoVCPSVJ7xSk1ouQS8hXJaVrwBydQ/)
## 5.84.0 (2025-05-14) ## 5.85.0 (2025-05-15)
### Features ### Features
* **web:** add an e2e test for the sign up flow (#19146) c05749e * **web:** gracefully handle upgrade prompt rejection [prod] (#19757) b76e682
* **web:** add connector_id to lp txs (#19207) 50b933e
* **web:** add connector_id to send events (#19208) 7e4a30a
* **web:** add connector_id to swap analytics (#19206) 9d734f0
* **web:** add connector_id to wallet disconnect event (#19205) 6631d02
* **web:** add passkey mgmt analytics (#19172) 9d12f00
* **web:** detect delegations and log them (#19368) d22a9de
* **web:** display min/max/market for custom range position (#19485) cc4437b
* **web:** PUX add expandable swap details with tooltips (#18669) 21c0110
* **web:** PUX adding token rate to currency input (#18736) d7abe9f
* **web:** PUX adding user quote amount to trades (#18511) 10d6f6f
* **web:** PUX FOT (#18804) ae89a01
* **web:** PUX handle bridging (#19089) b7375af
* **web:** PUX max slippage shield animation (#18972) 4d635a4
* **web:** PUX max slippage tooltip (#18708) 23d955b
* **web:** PUX network cost tooltip (#18685) dff4cc1
* **web:** PUX price and route tooltips (#18681) f77ad8a
* **web:** PUX price diff (#18738) 859bca1
* **web:** PUX price impact warning (#18740) 9809ff3
* **web:** PUX you receive panel (#18664) 326c5b1
* **web:** retry with standard swap if batched fails (#19295) dbffc2b
* **web:** swap tracking (#19328) 4185c10
* **web:** track clicks of download app ctas (#19232) fbc651c
* **web:** track user prop delegate status (#19286) f2f3e52
* **web:** Update existing user cta text and remove experiment (#19349) f13e553
### Bug Fixes
* **web:** [lp] fix native buffer experiment (#18777) 7a03b16
* **web:** add create/increase params to analytic events (#19388) b2a4177
* **web:** add error handling for rewards calcs (#18988) 1923ffe
* **web:** add google fonts url to CSP style-src (#19248) f64259e
* **web:** add migration 24 for wallet capabilities (#19464) 3ffc371
* **web:** add providers to web storybook to fix AnimatedNumber story (#18632) 12b4874
* **web:** allow press on disabled web3-status-connected (#19535) b7c4ede
* **web:** avoid fetching calldata for invalid range (#19384) 552a144
* **web:** clicking swap setting icon should close settings modal if open (#19472) c248622
* **web:** correct delegation logging (#19415) ed73ce2
* **web:** correct some analytics (#19391) 26016ce
* **web:** dedupe and cache wallet capabilities calls (#19739) (#19750) ad67756
* **web:** delete DeprecatedWebButtons (#19213) 799b1c1
* **web:** downgrade two errors to warnings (#19344) 726ba31
* **web:** fix approval call for create + increase (#19223) f73b4dd
* **web:** fix key open search modal (#19332) eb41c21
* **web:** fix mweb chart panning (#19373) f3ab207
* **web:** handle isTokenOptionArray check for bridging section (#19363) de14b83
* **web:** icon bugs + lp incentive improvements (#19606) 4c7c09e
* **web:** limits mobile bug (#19271) 9d49bf8
* **web:** logging for mismatch and isDelegatedEOA (#19346) 53b3a5a
* **web:** migrate BreadcrumbNav to tamagui (#19212) 734de02
* **web:** migrate NFTTab to tamagui (#19216) d940820
* **web:** migrate OpenLimitOrdersButton to tamagui (#19217) a74ae36
* **web:** migrate TokensTab to tamagui (#19214) 622e711
* **web:** migrate UniExtensionPoolsMenu to tamagui (#19215) 6250c24
* **web:** missing permit tx (#19326) 915e983
* **web:** only support batch swaps on supported status (#19580) ce53969
* **web:** persist query client and remove extra provider (#19219) f2236dc
* **web:** preload WalletConnect provider to avoid waterfall requests (#19195) ea3bc3f
* **web:** price ux revert remove inline warning (#19439) a5574a3
* **web:** PUX auto slippage (#18805) 598d9d3
* **web:** pux design fixes (#19430) a1c6311
* **web:** remove disconnect logic in useConnect hook (#19190) 7e2b0bc
* **web:** tamagui LimitDetailActivityRow & PortfolioTabRow (#19220) 084eced
* **web:** tamagui LimitsMenu / LimitDisclaimer (#19218) 28fbc8e
* **web:** temp remove disabled prop from button (#19675) 17f177e
* **web:** update icon props to be compatible w react-feather (#19209) 2dd772b
* **web:** update url for EW sign in test (#19470) 2d1a87e
* **web:** usd amount hover state currency panel (#18841) 38a3d0c
* **web:** Use "Log In" in auth pop up (#19385) 9ea148b
* **web:** Use sign up for New User button (#19478) a0b6690
### Continuous Integration
* **web:** update sitemaps c080e0b
### Tests
* **web:** handle v2, v3, v4 pool not found graphql (#19348) 4898d1c
* **web:** use different test account (#19431) 014d910
web/5.84.0 web/5.85.0
\ No newline at end of file \ No newline at end of file
...@@ -5,9 +5,12 @@ import { PopupType } from 'components/Popups/types' ...@@ -5,9 +5,12 @@ import { PopupType } from 'components/Popups/types'
import { wagmiConfig } from 'components/Web3Provider/wagmiConfig' import { wagmiConfig } from 'components/Web3Provider/wagmiConfig'
import { HandleOnChainStepParams, getSigner, watchForInterruption } from 'state/sagas/transactions/utils' import { HandleOnChainStepParams, getSigner, watchForInterruption } from 'state/sagas/transactions/utils'
import { addTransaction } from 'state/transactions/reducer' import { addTransaction } from 'state/transactions/reducer'
import { handleGetCapabilities } from 'state/walletCapabilities/lib/handleGetCapabilities'
import { setCapabilitiesByChain } from 'state/walletCapabilities/reducer'
import { call, put } from 'typed-redux-saga' import { call, put } from 'typed-redux-saga'
import { OnChainTransactionStepBatched } from 'uniswap/src/features/transactions/steps/types' import { OnChainTransactionStepBatched } from 'uniswap/src/features/transactions/steps/types'
import { ValidatedTransactionRequest } from 'uniswap/src/features/transactions/swap/utils/trade' import { ValidatedTransactionRequest } from 'uniswap/src/features/transactions/swap/utils/trade'
import { didUserReject } from 'utils/swapErrorToUserReadableMessage'
const CURRENT_SEND_CALLS_VERSION = '2.0.0' const CURRENT_SEND_CALLS_VERSION = '2.0.0'
async function sendCalls(params: { async function sendCalls(params: {
...@@ -29,13 +32,17 @@ async function sendCalls(params: { ...@@ -29,13 +32,17 @@ async function sendCalls(params: {
} }
export function* handleAtomicSendCalls( export function* handleAtomicSendCalls(
params: Omit<HandleOnChainStepParams, 'step'> & { step: OnChainTransactionStepBatched }, params: Omit<HandleOnChainStepParams, 'step'> & {
step: OnChainTransactionStepBatched
disableOneClickSwap: () => void
},
) { ) {
const { step, info, account, ignoreInterrupt } = params const { step, info, account, ignoreInterrupt, disableOneClickSwap } = params
const from = account.address const from = account.address
const { batchedTxRequests } = step const { batchedTxRequests } = step
const chainId = batchedTxRequests[0].chainId const chainId = batchedTxRequests[0].chainId
try {
// Add a watcher to check if the transaction flow during user input // Add a watcher to check if the transaction flow during user input
const { throwIfInterrupted } = yield* watchForInterruption(ignoreInterrupt) const { throwIfInterrupted } = yield* watchForInterruption(ignoreInterrupt)
...@@ -54,4 +61,22 @@ export function* handleAtomicSendCalls( ...@@ -54,4 +61,22 @@ export function* handleAtomicSendCalls(
yield* call(throwIfInterrupted) yield* call(throwIfInterrupted)
return batchId return batchId
} catch (error) {
// Specific handling for when the user rejects
if (error.code === 5750 || isMetaMaskNonTypicalRejection(error)) {
const updatedCapabilities = yield* call(handleGetCapabilities)
if (updatedCapabilities) {
// A wallet may update its capabilities after a transaction is sent, so we refresh state here so that subsequent transactions use the updated capabilities
yield* put(setCapabilitiesByChain(updatedCapabilities))
}
// If the user tries again,
disableOneClickSwap()
}
throw error
}
}
// TODO(WEB-7784): Remove once MetaMask fixes their error -32603 response code
function isMetaMaskNonTypicalRejection(error: any): boolean {
return didUserReject(error) && error.code === -32603
} }
...@@ -6,6 +6,7 @@ import { useTotalBalancesUsdForAnalytics } from 'graphql/data/apollo/useTotalBal ...@@ -6,6 +6,7 @@ import { useTotalBalancesUsdForAnalytics } from 'graphql/data/apollo/useTotalBal
import { useAccount } from 'hooks/useAccount' import { useAccount } from 'hooks/useAccount'
import useSelectChain from 'hooks/useSelectChain' import useSelectChain from 'hooks/useSelectChain'
import { formatSwapSignedAnalyticsEventProperties } from 'lib/utils/analytics' import { formatSwapSignedAnalyticsEventProperties } from 'lib/utils/analytics'
import { useSetOverrideOneClickSwapFlag } from 'pages/Swap/settings/OneClickSwap'
import { useCallback } from 'react' import { useCallback } from 'react'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { handleAtomicSendCalls } from 'state/sagas/transactions/5792' import { handleAtomicSendCalls } from 'state/sagas/transactions/5792'
...@@ -118,9 +119,10 @@ interface HandleSwapBatchedStepParams extends Omit<HandleOnChainStepParams, 'ste ...@@ -118,9 +119,10 @@ interface HandleSwapBatchedStepParams extends Omit<HandleOnChainStepParams, 'ste
step: SwapTransactionStepBatched step: SwapTransactionStepBatched
trade: ClassicTrade | BridgeTrade trade: ClassicTrade | BridgeTrade
analytics: SwapTradeBaseProperties analytics: SwapTradeBaseProperties
disableOneClickSwap: () => void
} }
function* handleSwapTransactionBatchedStep(params: HandleSwapBatchedStepParams) { function* handleSwapTransactionBatchedStep(params: HandleSwapBatchedStepParams) {
const { trade, step } = params const { trade, step, disableOneClickSwap } = params
const info = getSwapTransactionInfo(trade) const info = getSwapTransactionInfo(trade)
...@@ -130,6 +132,7 @@ function* handleSwapTransactionBatchedStep(params: HandleSwapBatchedStepParams) ...@@ -130,6 +132,7 @@ function* handleSwapTransactionBatchedStep(params: HandleSwapBatchedStepParams)
step, step,
ignoreInterrupt: true, // We avoid interruption during the swap step, since it is too late to give user a new trade once the swap is submitted. ignoreInterrupt: true, // We avoid interruption during the swap step, since it is too late to give user a new trade once the swap is submitted.
shouldWaitForConfirmation: false, shouldWaitForConfirmation: false,
disableOneClickSwap,
}) })
handleSwapTransactionAnalytics({ ...params, batchId }) handleSwapTransactionAnalytics({ ...params, batchId })
...@@ -190,6 +193,8 @@ type SwapParams = { ...@@ -190,6 +193,8 @@ type SwapParams = {
setCurrentStep: SetCurrentStepFn setCurrentStep: SetCurrentStepFn
setSteps: (steps: TransactionStep[]) => void setSteps: (steps: TransactionStep[]) => void
getOnPressRetry: (error: Error | undefined) => (() => void) | undefined getOnPressRetry: (error: Error | undefined) => (() => void) | undefined
// TODO(WEB-7763): Upgrade jotai to v2 to avoid need for prop drilling `disableOneClickSwap`
disableOneClickSwap: () => void
onSuccess: () => void onSuccess: () => void
onFailure: (error?: Error, onPressRetry?: () => void) => void onFailure: (error?: Error, onPressRetry?: () => void) => void
v4Enabled: boolean v4Enabled: boolean
...@@ -236,6 +241,7 @@ function* classicSwap( ...@@ -236,6 +241,7 @@ function* classicSwap(
) { ) {
const { const {
account, account,
disableOneClickSwap,
setCurrentStep, setCurrentStep,
steps, steps,
swapTxContext: { trade }, swapTxContext: { trade },
...@@ -268,7 +274,14 @@ function* classicSwap( ...@@ -268,7 +274,14 @@ function* classicSwap(
break break
} }
case TransactionStepType.SwapTransactionBatched: { case TransactionStepType.SwapTransactionBatched: {
yield* call(handleSwapTransactionBatchedStep, { account, step, setCurrentStep, trade, analytics }) yield* call(handleSwapTransactionBatchedStep, {
account,
step,
setCurrentStep,
trade,
analytics,
disableOneClickSwap,
})
break break
} }
default: { default: {
...@@ -374,6 +387,7 @@ export function useSwapCallback(): SwapCallback { ...@@ -374,6 +387,7 @@ export function useSwapCallback(): SwapCallback {
const portfolioBalanceUsd = useTotalBalancesUsdForAnalytics() const portfolioBalanceUsd = useTotalBalancesUsdForAnalytics()
const disableOneClickSwap = useSetOverrideOneClickSwapFlag()
const getOnPressRetry = useGetOnPressRetry() const getOnPressRetry = useGetOnPressRetry()
return useCallback( return useCallback(
...@@ -416,6 +430,7 @@ export function useSwapCallback(): SwapCallback { ...@@ -416,6 +430,7 @@ export function useSwapCallback(): SwapCallback {
account, account,
analytics, analytics,
getOnPressRetry, getOnPressRetry,
disableOneClickSwap,
onSuccess, onSuccess,
onFailure, onFailure,
setCurrentStep, setCurrentStep,
...@@ -452,6 +467,7 @@ export function useSwapCallback(): SwapCallback { ...@@ -452,6 +467,7 @@ export function useSwapCallback(): SwapCallback {
appDispatch, appDispatch,
swapStartTimestamp, swapStartTimestamp,
getOnPressRetry, getOnPressRetry,
disableOneClickSwap,
], ],
) )
} }
...@@ -77,7 +77,7 @@ describe('walletCapabilities', () => { ...@@ -77,7 +77,7 @@ describe('walletCapabilities', () => {
expect(isAtomicBatchingSupportedByChainId(mockCapabilities, 1)).toBe(true) expect(isAtomicBatchingSupportedByChainId(mockCapabilities, 1)).toBe(true)
const readyCapabilities: GetCapabilitiesResult = { '0x1': { atomic: { status: 'ready' } } } const readyCapabilities: GetCapabilitiesResult = { '0x1': { atomic: { status: 'ready' } } }
expect(isAtomicBatchingSupportedByChainId(readyCapabilities, 1)).toBe(false) expect(isAtomicBatchingSupportedByChainId(readyCapabilities, 1)).toBe(true)
const unsupportedCapabilities: GetCapabilitiesResult = { '0x1': { atomic: { status: 'unsupported' } } } const unsupportedCapabilities: GetCapabilitiesResult = { '0x1': { atomic: { status: 'unsupported' } } }
expect(isAtomicBatchingSupportedByChainId(unsupportedCapabilities, 1)).toBe(false) expect(isAtomicBatchingSupportedByChainId(unsupportedCapabilities, 1)).toBe(false)
......
...@@ -51,7 +51,10 @@ enum AtomicBatchingStatus { ...@@ -51,7 +51,10 @@ enum AtomicBatchingStatus {
} }
export function isAtomicBatchingSupported(chainCapabilities: ChainCapabilities): boolean { export function isAtomicBatchingSupported(chainCapabilities: ChainCapabilities): boolean {
return chainCapabilities?.atomic?.status === AtomicBatchingStatus.Supported return (
chainCapabilities?.atomic?.status === AtomicBatchingStatus.Supported ||
chainCapabilities?.atomic?.status === AtomicBatchingStatus.Ready
)
} }
export function isAtomicBatchingSupportedByChainId( export function isAtomicBatchingSupportedByChainId(
......
...@@ -134,7 +134,7 @@ describe('walletCapabilities reducer', () => { ...@@ -134,7 +134,7 @@ describe('walletCapabilities reducer', () => {
// Test various chain IDs // Test various chain IDs
expect(selector(1)).toBe(true) // Chain 1 has status 'supported' expect(selector(1)).toBe(true) // Chain 1 has status 'supported'
expect(selector(2)).toBe(false) // Chain 2 has status 'ready' expect(selector(2)).toBe(true) // Chain 2 has status 'ready'
expect(selector(3)).toBe(false) // Chain 3 has status 'unsupported' expect(selector(3)).toBe(false) // Chain 3 has status 'unsupported'
expect(selector(4)).toBe(false) // Chain 4 has no atomic capability expect(selector(4)).toBe(false) // Chain 4 has no atomic capability
expect(selector(999)).toBe(false) // Chain 999 not in map expect(selector(999)).toBe(false) // Chain 999 not in map
...@@ -148,7 +148,7 @@ describe('walletCapabilities reducer', () => { ...@@ -148,7 +148,7 @@ describe('walletCapabilities reducer', () => {
// This tests that the internal helper correctly evaluates string values // This tests that the internal helper correctly evaluates string values
const testCases = [ const testCases = [
{ chainId: '0x1', atomicStatus: 'supported', expected: true }, { chainId: '0x1', atomicStatus: 'supported', expected: true },
{ chainId: '0x2', atomicStatus: 'ready', expected: false }, { chainId: '0x2', atomicStatus: 'ready', expected: true },
{ chainId: '0x3', atomicStatus: 'unsupported', expected: false }, { chainId: '0x3', atomicStatus: 'unsupported', expected: false },
{ chainId: '0x4', atomicStatus: 'something-else', expected: false }, { chainId: '0x4', atomicStatus: 'something-else', expected: false },
{ chainId: '0x5', atomicStatus: undefined, expected: false }, { chainId: '0x5', atomicStatus: undefined, expected: false },
......
...@@ -15,6 +15,8 @@ export function didUserReject(error: any): boolean { ...@@ -15,6 +15,8 @@ export function didUserReject(error: any): boolean {
const reason = getReason(error) const reason = getReason(error)
if ( if (
error?.code === 4001 || error?.code === 4001 ||
// eip-5792 upgrade rejected error https://eips.ethereum.org/EIPS/eip-5792#error-codes
error?.code === 5750 ||
// ethers v5.7.0 wrapped error // ethers v5.7.0 wrapped error
error?.code === 'ACTION_REJECTED' || error?.code === 'ACTION_REJECTED' ||
// For Rainbow : // For Rainbow :
......
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