diff --git a/.gitignore b/.gitignore index d4f47cb0313..1c63e3e0263 100644 --- a/.gitignore +++ b/.gitignore @@ -42,7 +42,6 @@ dist/out-tsc/* # Locale backup/generated files packages/uniswap/src/i18n/locales/source/*_old.json -packages/uniswap/src/i18n/locales/@types/resources.d.ts # ci .ci-cache/ diff --git a/RELEASE b/RELEASE index 71903e863f0..46c2376c870 100644 --- a/RELEASE +++ b/RELEASE @@ -1,6 +1,6 @@ IPFS hash of the deployment: -- CIDv0: `QmdrE1xXozbrZCb5iS5iGpjWkdJh4y7iBGHqSC1rGPcH6i` -- CIDv1: `bafybeihgolhp73f4twnioaonb2dvdyu2t44mxjdqx7pvnxfb7ibcosxcam` +- CIDv0: `QmbKgKkW8gPc6Fc4CCbruxzdbgE968pGuSMwaFPPS57kdg` +- CIDv1: `bafybeiga5awmbukdwmpf4jskzekpy76rzhk4blbqcmzvr745un6k6qulvm` The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org). @@ -10,15 +10,63 @@ You can also access the Uniswap Interface from an IPFS gateway. Your Uniswap settings are never remembered across different URLs. IPFS gateways: -- https://bafybeihgolhp73f4twnioaonb2dvdyu2t44mxjdqx7pvnxfb7ibcosxcam.ipfs.dweb.link/ -- https://bafybeihgolhp73f4twnioaonb2dvdyu2t44mxjdqx7pvnxfb7ibcosxcam.ipfs.cf-ipfs.com/ -- [ipfs://QmdrE1xXozbrZCb5iS5iGpjWkdJh4y7iBGHqSC1rGPcH6i/](ipfs://QmdrE1xXozbrZCb5iS5iGpjWkdJh4y7iBGHqSC1rGPcH6i/) +- https://bafybeiga5awmbukdwmpf4jskzekpy76rzhk4blbqcmzvr745un6k6qulvm.ipfs.dweb.link/ +- https://bafybeiga5awmbukdwmpf4jskzekpy76rzhk4blbqcmzvr745un6k6qulvm.ipfs.cf-ipfs.com/ +- [ipfs://QmbKgKkW8gPc6Fc4CCbruxzdbgE968pGuSMwaFPPS57kdg/](ipfs://QmbKgKkW8gPc6Fc4CCbruxzdbgE968pGuSMwaFPPS57kdg/) -### 5.50.4 (2024-10-10) +## 5.51.0 (2024-10-10) + + +### Features + +* **web:** add network costs to the addliquidity modal (#12557) 9beb5ce +* **web:** add steps to the modal (#12431) 65639b4 +* **web:** add support for permit (#12575) b9d9579 +* **web:** add testnet modal (#12636) 350d97e +* **web:** adding create modal (#12441) 3150f48 +* **web:** balance fetch switch on testnet mode (#12576) dfa44bd +* **web:** call TradingAPI claim fees endpoint for v3/v4 (#12444) 8acafa7 +* **web:** deposit tokens step (#12377) 4c747d8 +* **web:** disable testnet on disconnect (#12750) 9f0bdbb +* **web:** disable tx flows on explore pages (#12656) 6ed0b42 +* **web:** enable testnet mode on web with flag (#12505) dd7f272 +* **web:** hook up LP saga + confirm steps to the decrease flow (#12508) 3f0ff49 +* **web:** integrate with trading API for create flow (v2,v3) (#12434) 7283d2e +* **web:** loading state for new PosDP (#12634) 688ea88 +* **web:** LP migrate page - update query and loading state (#12652) 88b336e +* **web:** positions pagination and claim fees modal (#12341) 5f1eba2 +* **web:** testnet mode (#12566) abe823d +* **web:** testnet portfolio row (#12616) ae5e5f5 +* **web:** unichain intro modal (#12554) 16fdb94 +* **web:** use single position query for PosDP (#12517) e242d90 +* **web:** use universe currency logo for testnets (#12607) d0767bd +* **web:** use v3 uncollected fee values from Data API (#12555) 2a376be +* **web:** v4 create position v2 (#12419) be27df2 +* **web:** v4 price range charts (#12525) cf9f515 ### Bug Fixes -* **web:** pass account to getSigner instead of using default [prod] (#12883) 688582d +* **web:** 10 08 fix web log step and original error on web staging (#12825) c895745 +* **web:** add requestId and quoteId to post /order request (#12612) e08233f +* **web:** astro chain ui info (#12671) 6923e48 +* **web:** default to crypto in send when on testnet (#12758) 5839f86 +* **web:** dont respect url flag overrides on prod (#12545) 3a68877 +* **web:** fix tick formatting + position details page (#12648) b606211 +* **web:** fixing v4 create bugs (#12514) 9da665d +* **web:** hide gas for testnet unichain eth (#12759) 2dfd2d5 +* **web:** invalidate local activity cache for updates to transaction or signature state (#12650) f119f63 +* **web:** move connected wallet chain logic to universe (#12717) 5f78d74 +* **web:** native currency logo fetching on web (#12387) 81eb2ac +* **web:** pass account to getSigner instead of using default [staging] (#12884) a11364b +* **web:** rm prod arb+eth quicknode urls (#12646) d36d711 +* **web:** simplify/combine the liquidity saga logic (#12540) 5b72b61 +* **web:** ui nits and bugs (#12718) 6330bd4 +* **web:** uniswapx toasts/polling (#12658) 53dc7b1 + + +### Continuous Integration + +* **web:** update sitemaps 82367d7 diff --git a/VERSION b/VERSION index f38c9594378..69039a9cebb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -web/5.50.4 \ No newline at end of file +web/5.51.0 \ No newline at end of file diff --git a/apps/extension/package.json b/apps/extension/package.json index fba0fa255ca..0d305770164 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -13,10 +13,11 @@ "@svgr/webpack": "8.0.1", "@tamagui/core": "1.108.4", "@types/uuid": "9.0.1", - "@uniswap/analytics-events": "2.37.0", + "@uniswap/analytics-events": "2.38.0", "@uniswap/uniswapx-sdk": "^2.1.0-beta.14", - "@uniswap/universal-router-sdk": "2.2.0", - "@uniswap/v3-sdk": "3.14.0", + "@uniswap/universal-router-sdk": "4.2.0", + "@uniswap/v3-sdk": "3.17.0", + "@uniswap/v4-sdk": "1.10.0", "dotenv-webpack": "8.0.1", "ethers": "5.7.2", "eventemitter3": "5.0.1", diff --git a/apps/extension/src/app/PopupApp.tsx b/apps/extension/src/app/PopupApp.tsx index 48c59f4e6ff..c592bdd2050 100644 --- a/apps/extension/src/app/PopupApp.tsx +++ b/apps/extension/src/app/PopupApp.tsx @@ -27,6 +27,7 @@ import i18n from 'uniswap/src/i18n/i18n' import { ExtensionScreens } from 'uniswap/src/types/screens/extension' import { logger } from 'utilities/src/logger/logger' import { ErrorBoundary } from 'wallet/src/components/ErrorBoundary/ErrorBoundary' +import { useTestnetModeForLoggingAndAnalytics } from 'wallet/src/features/testnetMode/hooks' import { SharedWalletProvider } from 'wallet/src/providers/SharedWalletProvider' getLocalUserId() @@ -54,6 +55,7 @@ function PopupContent(): JSX.Element { useEffect(() => { dispatch(syncAppWithDeviceLanguage()) }, [dispatch]) + useTestnetModeForLoggingAndAnalytics() const searchParams = new URLSearchParams(window.location.search) const tabId = searchParams.get('tabId') diff --git a/apps/extension/src/app/SidebarApp.tsx b/apps/extension/src/app/SidebarApp.tsx index 3bca63a3f59..9b211e113d8 100644 --- a/apps/extension/src/app/SidebarApp.tsx +++ b/apps/extension/src/app/SidebarApp.tsx @@ -51,6 +51,7 @@ import { logger } from 'utilities/src/logger/logger' import { ONE_SECOND_MS } from 'utilities/src/time/time' import { useInterval } from 'utilities/src/time/timing' import { ErrorBoundary } from 'wallet/src/components/ErrorBoundary/ErrorBoundary' +import { useTestnetModeForLoggingAndAnalytics } from 'wallet/src/features/testnetMode/hooks' import { SharedWalletProvider } from 'wallet/src/providers/SharedWalletProvider' getLocalUserId() @@ -202,6 +203,7 @@ function useDappRequestPortListener(): void { function SidebarWrapper(): JSX.Element { const dispatch = useDispatch() useDappRequestPortListener() + useTestnetModeForLoggingAndAnalytics() useEffect(() => { dispatch(syncAppWithDeviceLanguage()) diff --git a/apps/extension/src/app/UnitagClaimApp.tsx b/apps/extension/src/app/UnitagClaimApp.tsx index af9137f6b56..f023b229d42 100644 --- a/apps/extension/src/app/UnitagClaimApp.tsx +++ b/apps/extension/src/app/UnitagClaimApp.tsx @@ -26,6 +26,7 @@ import { UnitagUpdaterContextProvider } from 'uniswap/src/features/unitags/conte import i18n from 'uniswap/src/i18n/i18n' import { logger } from 'utilities/src/logger/logger' import { ErrorBoundary } from 'wallet/src/components/ErrorBoundary/ErrorBoundary' +import { useTestnetModeForLoggingAndAnalytics } from 'wallet/src/features/testnetMode/hooks' import { SharedWalletProvider } from 'wallet/src/providers/SharedWalletProvider' getLocalUserId() @@ -58,6 +59,7 @@ router.subscribe((state) => { setRouter(router) function UnitagClaimAppInner(): JSX.Element { + useTestnetModeForLoggingAndAnalytics() return ( (undefined) diff --git a/apps/extension/src/app/features/dapp/actions.ts b/apps/extension/src/app/features/dapp/actions.ts index f1722087294..41b0f1c9e74 100644 --- a/apps/extension/src/app/features/dapp/actions.ts +++ b/apps/extension/src/app/features/dapp/actions.ts @@ -7,11 +7,11 @@ import { UpdateConnectionRequest, } from 'src/background/messagePassing/types/requests' import { chainIdToHexadecimalString } from 'uniswap/src/features/chains/utils' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { Account } from 'wallet/src/features/wallet/accounts/types' import { getProviderSync } from 'wallet/src/features/wallet/context' -export async function saveDappChain(dappUrl: string, chainId: WalletChainId): Promise { +export async function saveDappChain(dappUrl: string, chainId: UniverseChainId): Promise { dappStore.updateDappLatestChainId(dappUrl, chainId) const provider = getProviderSync(chainId) diff --git a/apps/extension/src/app/features/dapp/changeChain.test.ts b/apps/extension/src/app/features/dapp/changeChain.test.ts index e792b2a9b58..2e8b0f5269b 100644 --- a/apps/extension/src/app/features/dapp/changeChain.test.ts +++ b/apps/extension/src/app/features/dapp/changeChain.test.ts @@ -6,7 +6,7 @@ import { DappResponseType } from 'src/app/features/dappRequests/types/DappReques import { chainIdToHexadecimalString } from 'uniswap/src/features/chains/utils' import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' // Mock dependencies jest.mock('@ethersproject/providers') @@ -18,7 +18,7 @@ jest.mock('uniswap/src/features/chains/utils') describe('changeChain', () => { const mockRequestId = 'test-request-id' const mockProviderUrl = 'http://localhost:8545' - const mockChainId = 1 as WalletChainId + const mockChainId = 1 as UniverseChainId let mockProvider: JsonRpcProvider diff --git a/apps/extension/src/app/features/dapp/changeChain.ts b/apps/extension/src/app/features/dapp/changeChain.ts index a7a14a0408a..dd8b91a22fa 100644 --- a/apps/extension/src/app/features/dapp/changeChain.ts +++ b/apps/extension/src/app/features/dapp/changeChain.ts @@ -9,7 +9,7 @@ import { import { chainIdToHexadecimalString } from 'uniswap/src/features/chains/utils' import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' export function changeChain({ activeConnectedAddress, @@ -22,7 +22,7 @@ export function changeChain({ dappUrl: string | undefined provider: JsonRpcProvider | undefined | null requestId: string - updatedChainId: WalletChainId | null + updatedChainId: UniverseChainId | null }): ChangeChainResponse | ErrorResponse { if (!updatedChainId) { return { diff --git a/apps/extension/src/app/features/dapp/hooks.ts b/apps/extension/src/app/features/dapp/hooks.ts index bd3d0d7a077..562b2736e10 100644 --- a/apps/extension/src/app/features/dapp/hooks.ts +++ b/apps/extension/src/app/features/dapp/hooks.ts @@ -1,6 +1,6 @@ import { useEffect, useReducer, useState } from 'react' import { DappInfo, DappStoreEvent, dappStore } from 'src/app/features/dapp/store' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { Account } from 'wallet/src/features/wallet/accounts/types' import { useActiveAccountAddress } from 'wallet/src/features/wallet/hooks' @@ -26,7 +26,7 @@ export function useDappInfo(dappUrl: string | undefined): DappInfo | undefined { return info } -export function useDappLastChainId(dappUrl: string | undefined): WalletChainId | undefined { +export function useDappLastChainId(dappUrl: string | undefined): UniverseChainId | undefined { return useDappInfo(dappUrl)?.lastChainId } diff --git a/apps/extension/src/app/features/dapp/store.ts b/apps/extension/src/app/features/dapp/store.ts index 5c9c3dee6fa..a325c894b6a 100644 --- a/apps/extension/src/app/features/dapp/store.ts +++ b/apps/extension/src/app/features/dapp/store.ts @@ -1,13 +1,13 @@ import { cloneDeep } from '@apollo/client/utilities' import EventEmitter from 'eventemitter3' import { getOrderedConnectedAddresses, isConnectedAccount } from 'src/app/features/dapp/utils' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { Account } from 'wallet/src/features/wallet/accounts/types' const STATE_STORAGE_KEY = 'dappState' export interface DappInfo { - lastChainId: WalletChainId + lastChainId: UniverseChainId connectedAccounts: Account[] activeConnectedAddress: Address iconUrl?: string @@ -153,7 +153,7 @@ function updateDappIconUrl(dappUrl: string, newIconUrl?: string): void { } // TODO(WALL-4643): if we migrate to immer, let's avoid iterating over the the object here -function updateDappLatestChainId(dappUrl: string, chainId: WalletChainId): void { +function updateDappLatestChainId(dappUrl: string, chainId: UniverseChainId): void { // Never directly mutate state, as some of its fields could have `writable: false` state = Object.fromEntries( Object.entries(state).map(([key, dappUrlState]) => { @@ -172,6 +172,7 @@ function saveDappActiveAccount(dappUrl: string, account: Account, initialPropert ...state, [dappUrl]: { ...state[dappUrl], + // TODO: WALL-4919: Remove hardcoded Mainnet lastChainId: state[dappUrl]?.lastChainId ?? UniverseChainId.Mainnet, activeConnectedAddress: account.address, connectedAccounts: ((): Account[] => { diff --git a/apps/extension/src/app/features/dappRequests/DappRequestContent.tsx b/apps/extension/src/app/features/dappRequests/DappRequestContent.tsx index 21d4e86f69c..2818ef7e8d9 100644 --- a/apps/extension/src/app/features/dappRequests/DappRequestContent.tsx +++ b/apps/extension/src/app/features/dappRequests/DappRequestContent.tsx @@ -2,16 +2,16 @@ import { PropsWithChildren, useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useDappLastChainId } from 'src/app/features/dapp/hooks' import { useDappRequestQueueContext } from 'src/app/features/dappRequests/DappRequestQueueContext' -import { NetworksFooter } from 'src/app/features/dappRequests/requestContent/NetworksFooter' import { DappRequestStoreItem } from 'src/app/features/dappRequests/slice' import { Anchor, AnimatePresence, Button, Flex, Text, UniversalImage, UniversalImageResizeMode, styled } from 'ui/src' import { borderRadii, iconSizes } from 'ui/src/theme' -import { useUSDValue } from 'uniswap/src/features/gas/hooks' +import { useUSDValueOfGasFee } from 'uniswap/src/features/gas/hooks' import { GasFeeResult } from 'uniswap/src/features/gas/types' import { hasSufficientFundsIncludingGas } from 'uniswap/src/features/gas/utils' import { useOnChainNativeCurrencyBalance } from 'uniswap/src/features/portfolio/api' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { TransactionTypeInfo } from 'uniswap/src/features/transactions/types/transactionDetails' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { extractNameFromUrl } from 'utilities/src/format/extractNameFromUrl' import { formatDappURL } from 'utilities/src/format/urls' import { logger } from 'utilities/src/logger/logger' @@ -26,7 +26,7 @@ interface DappRequestHeaderProps { } interface DappRequestFooterProps { - chainId?: WalletChainId + chainId?: UniverseChainId connectedAccountAddress?: string confirmText: string maybeCloseOnConfirm?: boolean @@ -150,13 +150,13 @@ export function DappRequestFooter({ maybeCloseOnConfirm, onCancel, onConfirm, - showAllNetworks, showNetworkCost, transactionGasFeeResult, isUniswapX, }: DappRequestFooterProps): JSX.Element { const { t } = useTranslation() const activeAccount = useActiveAccountWithThrow() + const { defaultChainId } = useEnabledChains() const { dappUrl, currentAccount, @@ -174,8 +174,8 @@ export function DappRequestFooter({ throw error } - const currentChainId = chainId || activeChain || UniverseChainId.Mainnet - const gasFeeUSD = useUSDValue(currentChainId, transactionGasFeeResult?.value) + const currentChainId = chainId || activeChain || defaultChainId + const { value: gasFeeUSD } = useUSDValueOfGasFee(currentChainId, transactionGasFeeResult?.value) const { balance: nativeBalance } = useOnChainNativeCurrencyBalance(currentChainId, currentAccount.address) const hasSufficientGas = hasSufficientFundsIncludingGas({ @@ -225,12 +225,11 @@ export function DappRequestFooter({ {showNetworkCost && ( )} - {showAllNetworks && } outputCurrencyInfo: Maybe - chainId: WalletChainId | null + chainId: UniverseChainId | null transactionGasFeeResult?: GasFeeResult onCancel?: () => Promise onConfirm?: () => Promise diff --git a/apps/extension/src/app/features/dappRequests/requestContent/EthSend/Swap/SwapRequestContent.tsx b/apps/extension/src/app/features/dappRequests/requestContent/EthSend/Swap/SwapRequestContent.tsx index a1fa06da81a..a750f280b0f 100644 --- a/apps/extension/src/app/features/dappRequests/requestContent/EthSend/Swap/SwapRequestContent.tsx +++ b/apps/extension/src/app/features/dappRequests/requestContent/EthSend/Swap/SwapRequestContent.tsx @@ -2,90 +2,18 @@ import { useDappLastChainId } from 'src/app/features/dapp/hooks' import { useDappRequestQueueContext } from 'src/app/features/dappRequests/DappRequestQueueContext' import { SwapDisplay } from 'src/app/features/dappRequests/requestContent/EthSend/Swap/SwapDisplay' import { ETH_ADDRESS } from 'src/app/features/dappRequests/requestContent/EthSend/Swap/constants' -import { formatUnits } from 'src/app/features/dappRequests/requestContent/EthSend/Swap/utils' +import { formatUnits, useSwapDetails } from 'src/app/features/dappRequests/requestContent/EthSend/Swap/utils' import { SignTypedDataRequest, SwapSendTransactionRequest } from 'src/app/features/dappRequests/types/DappRequestTypes' -import { - AmountInMaxParam, - AmountInParam, - AmountOutMinParam, - AmountOutParam, - Param, - UniversalRouterCommand, - isAmountInMaxParam, - isAmountInParam, - isAmountOutMinParam, - isAmountOutParam, - isURCommandASwap, -} from 'src/app/features/dappRequests/types/UniversalRouterTypes' import { DEFAULT_NATIVE_ADDRESS } from 'uniswap/src/constants/chains' import { toSupportedChainId } from 'uniswap/src/features/chains/utils' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { GasFeeResult } from 'uniswap/src/features/gas/types' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { useCurrencyInfo, useNativeCurrencyInfo } from 'uniswap/src/features/tokens/useCurrencyInfo' import { TransactionType, TransactionTypeInfo } from 'uniswap/src/features/transactions/types/transactionDetails' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' import { buildCurrencyId } from 'uniswap/src/utils/currencyId' import { assert } from 'utilities/src/errors' -function extractPathValues(commands: UniversalRouterCommand[]): { - inputAddress: string | undefined - outputAddress: string | undefined -} { - let inputAddress: string | undefined - let outputAddress: string | undefined - for (const command of commands) { - const param: Param | undefined = command.params.find(({ name }) => name === 'path') - if (!param) { - continue - } - // matches V2SwapExact[In|Out] - if (command.commandName.startsWith('V2SwapExact')) { - const path = param.value as string[] - const first = path[0] - if (first && !inputAddress) { - inputAddress = first - } - const last = path[path.length - 1] - if (last) { - outputAddress = last - } - } - // matches V3SwapExact[In|Out] - if (command.commandName.startsWith('V3SwapExact')) { - const path = param.value as { fee: number; tokenIn: string; tokenOut: string }[] - const first = path[0] - if (first && !inputAddress) { - inputAddress = first.tokenIn - } - const last = path[path.length - 1] - if (last) { - outputAddress = last.tokenOut - } - } - } - return { inputAddress, outputAddress } -} - -function useSwapCurrencyIdentifiers( - request: SwapSendTransactionRequest, - dappUrl: string, -): { inputIdentifier: string | undefined; outputIdentifier: string | undefined } { - const activeChain = useDappLastChainId(dappUrl) - return getSwapCurrencyIdentifiers(request, activeChain) -} - -export function getSwapCurrencyIdentifiers( - request: SwapSendTransactionRequest, - activeChain: WalletChainId | undefined, -): { inputIdentifier: string | undefined; outputIdentifier: string | undefined } { - const { inputAddress, outputAddress } = extractPathValues(request.parsedCalldata.commands) - - const inputIdentifier = activeChain && inputAddress ? buildCurrencyId(activeChain, inputAddress) : undefined - const outputIdentifier = activeChain && outputAddress ? buildCurrencyId(activeChain, outputAddress) : undefined - - return { inputIdentifier, outputIdentifier } -} - function getTransactionTypeInfo({ inputCurrencyInfo, outputCurrencyInfo, @@ -124,16 +52,17 @@ export function SwapRequestContent({ onConfirm, }: SwapRequestContentProps): JSX.Element { const { dappUrl } = useDappRequestQueueContext() - const activeChain = useDappLastChainId(dappUrl) || UniverseChainId.Mainnet + const { defaultChainId } = useEnabledChains() + const activeChain = useDappLastChainId(dappUrl) || defaultChainId - const { inputIdentifier, outputIdentifier } = useSwapCurrencyIdentifiers(dappRequest, dappUrl) + const { inputIdentifier, outputIdentifier, inputValue, outputValue } = useSwapDetails(dappRequest, dappUrl) const inputCurrencyInfo = useCurrencyInfo(inputIdentifier) const outputCurrencyInfo = useCurrencyInfo(outputIdentifier) - const isFirstCommandWrappingEth = dappRequest.parsedCalldata.commands[0]?.commandName === 'WrapEth' + const isFirstCommandWrappingEth = dappRequest.parsedCalldata.commands[0]?.commandName === 'WRAP_ETH' const isLastCommandUnwrappingEth = - dappRequest.parsedCalldata.commands[dappRequest.parsedCalldata.commands.length - 1]?.commandName === 'UnwrapWeth' + dappRequest.parsedCalldata.commands[dappRequest.parsedCalldata.commands.length - 1]?.commandName === 'UNWRAP_WETH' const nativeCurrencyInfo = useNativeCurrencyInfo(activeChain) const nativeCurrency = nativeCurrencyInfo?.currency @@ -145,50 +74,13 @@ export function SwapRequestContent({ const currencyInfo0 = nativeInput ? nativeCurrencyInfo : inputCurrencyInfo const currencyInfo1 = nativeOutput ? nativeCurrencyInfo : outputCurrencyInfo - const firstSwapCommand = dappRequest.parsedCalldata.commands.find(isURCommandASwap) - const lastSwapCommand = dappRequest.parsedCalldata.commands.findLast(isURCommandASwap) - - assert( - firstSwapCommand && lastSwapCommand, - 'SwapRequestContent: All swaps must have a defined input and output Universal Router command.', - ) - - function isAmountInOrMaxParam(param: Param): param is AmountInParam | AmountInMaxParam { - return isAmountInParam(param) || isAmountInMaxParam(param) - } - - function isAmountOutMinOrOutParam(param: Param): param is AmountOutMinParam | AmountOutParam { - return isAmountOutMinParam(param) || isAmountOutParam(param) - } - - // Ideally we would render some UI that makes it clear when you can expect minAmountOut instead of rendering what might look like a bad deal - const firstAmountInParam = firstSwapCommand?.params.find(isAmountInOrMaxParam) - const lastAmountOutParam = lastSwapCommand?.params.find(isAmountOutMinOrOutParam) - - assert( - firstAmountInParam && lastAmountOutParam, - 'SwapRequestContent: All swaps must have a defined input and output amount parameter.', - ) - - const inputAmount = formatUnits( - firstAmountInParam?.value || '0', // should always be defined--`assert` above catches this case - inputCurrencyInfo?.currency.decimals || 18, - ) - const outputAmount = formatUnits( - lastAmountOutParam?.value || '0', // should always be defined--`assert` above catches this case - outputCurrencyInfo?.currency.decimals || 18, - ) + const inputAmount = formatUnits(inputValue, inputCurrencyInfo?.currency.decimals || 18) + const outputAmount = formatUnits(outputValue, outputCurrencyInfo?.currency.decimals || 18) // TODO (EXT-1083): add USDC values to SwapTransactionTypeInfo and display on notification toast // Need the raw uint256 amounts, not the exact floating point amounts - const inputAmountRaw = formatUnits( - firstAmountInParam?.value || '0', // should always be defined--`assert` above catches this case - 0, - ) - const outputAmountRaw = formatUnits( - lastAmountOutParam?.value || '0', // should always be defined--`assert` above catches this case - 0, - ) + const inputAmountRaw = formatUnits(inputValue, 0) + const outputAmountRaw = formatUnits(outputValue, 0) const transactionTypeInfo = getTransactionTypeInfo({ inputCurrencyInfo: currencyInfo0, outputCurrencyInfo: currencyInfo1, @@ -213,9 +105,10 @@ export function SwapRequestContent({ // this is a special cased version of SwapRequestContent used for UniswapX swaps export function UniswapXSwapRequestContent({ dappRequest }: { dappRequest: SignTypedDataRequest }): JSX.Element { + const { defaultChainId } = useEnabledChains() const parsedTypedData = JSON.parse(dappRequest.typedData) const { chainId: domainChainId } = parsedTypedData?.domain || {} - const activeChain = toSupportedChainId(domainChainId) || UniverseChainId.Mainnet + const activeChain = toSupportedChainId(domainChainId) || defaultChainId const { token: inputToken, amount: firstAmountInParam } = parsedTypedData?.message?.permitted || {} const { token: outputToken, startAmount: lastAmountOutParam } = diff --git a/apps/extension/src/app/features/dappRequests/requestContent/EthSend/Swap/universalRouter.ts b/apps/extension/src/app/features/dappRequests/requestContent/EthSend/Swap/universalRouter.ts deleted file mode 100644 index d1a19f49f3b..00000000000 --- a/apps/extension/src/app/features/dappRequests/requestContent/EthSend/Swap/universalRouter.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { SwapRouter } from '@uniswap/universal-router-sdk' -import { ethers } from 'ethers' -import { - ABI_DEFINITION, - CommandName, - CommandType, - Subparser, - UniversalRouterCall, - UniversalRouterCommand, -} from 'src/app/features/dappRequests/types/UniversalRouterTypes' - -export function parseCalldata(calldata: string): UniversalRouterCall { - const iface = SwapRouter.INTERFACE - const txDescription = iface.parseTransaction({ data: calldata }) - const { commands, inputs } = txDescription.args - // map hex string to bytes - const commandTypes: CommandType[] = [] - - // Start iterating from the third character to skip the "0x" prefix - for (let i = 2; i < commands.length; i += 2) { - // Get two characters from the hexString - const byte = commands.substr(i, 2) - - // Convert it to a number and add it to the values array - commandTypes.push(parseInt(byte, 16) as CommandType) - } - - const parsedCommands = commandTypes.map((commandType: CommandType, i: number): UniversalRouterCommand => { - const abiDef = ABI_DEFINITION[commandType] - const rawParams = ethers.utils.defaultAbiCoder.decode( - abiDef.map((command) => command.type), - inputs[i], - ) - const params = rawParams.map((param, j: number) => { - const fragment = abiDef[j] - if (fragment && fragment.subparser === Subparser.V3PathExactIn) { - return { - name: fragment.name, - value: parseV3PathExactIn(param), - } - } else if (fragment && fragment.subparser === Subparser.V3PathExactOut) { - return { - name: fragment.name, - value: parseV3PathExactOut(param), - } - } else { - return { - name: fragment?.name || '', - value: param, - } - } - }) - return { - commandName: CommandType[commandType] as CommandName, - commandType, - params, - } - }) - return { commands: parsedCommands } -} - -export type V3PathItem = { - readonly tokenIn: string - readonly tokenOut: string - readonly fee: number -} - -export function parseV3PathExactIn(path: string): readonly V3PathItem[] { - const strippedPath = path.replace('0x', '') - let tokenIn = ethers.utils.getAddress(strippedPath.substr(0, 40)) - let loc = 40 - const res = [] - while (loc < strippedPath.length) { - const feeAndTokenOut = strippedPath.substr(loc, 46) - const fee = parseInt(feeAndTokenOut.substr(0, 6), 16) - const tokenOut = ethers.utils.getAddress(feeAndTokenOut.substr(6, 40)) - - res.push({ - tokenIn, - tokenOut, - fee, - }) - tokenIn = tokenOut - loc += 46 - } - - return res -} - -export function parseV3PathExactOut(path: string): readonly V3PathItem[] { - const strippedPath = path.replace('0x', '') - let tokenIn = ethers.utils.getAddress(strippedPath.substr(strippedPath.length - 40, 40)) - let loc = strippedPath.length - 86 // 86 = (20 addr + 3 fee + 20 addr) * 2 (for hex characters) - const res = [] - while (loc >= 0) { - const feeAndTokenOut = strippedPath.substr(loc, 46) - const tokenOut = ethers.utils.getAddress(feeAndTokenOut.substr(0, 40)) - const fee = parseInt(feeAndTokenOut.substr(40, 6), 16) - - res.push({ - tokenIn, - tokenOut, - fee, - }) - tokenIn = tokenOut - loc -= 46 - } - - return res -} diff --git a/apps/extension/src/app/features/dappRequests/requestContent/EthSend/Swap/utils.ts b/apps/extension/src/app/features/dappRequests/requestContent/EthSend/Swap/utils.ts index 639f1aba029..6a3f05f66ed 100644 --- a/apps/extension/src/app/features/dappRequests/requestContent/EthSend/Swap/utils.ts +++ b/apps/extension/src/app/features/dappRequests/requestContent/EthSend/Swap/utils.ts @@ -1,11 +1,35 @@ +/* eslint-disable max-depth */ +/* eslint-disable complexity */ +import { CommandType } from '@uniswap/universal-router-sdk' import { BigNumber, BigNumberish } from 'ethers' import { formatUnits as formatUnitsEthers } from 'ethers/lib/utils' +import { useDappLastChainId } from 'src/app/features/dapp/hooks' import { CONTRACT_BALANCE, MAX_UINT160, MAX_UINT256, } from 'src/app/features/dappRequests/requestContent/EthSend/Swap/constants' -import { CommandType, UniversalRouterCall } from 'src/app/features/dappRequests/types/UniversalRouterTypes' +import { SwapSendTransactionRequest } from 'src/app/features/dappRequests/types/DappRequestTypes' +import { + AmountInMaxParam, + AmountInParam, + AmountOutMinParam, + AmountOutParam, + Param, + UniversalRouterCall, + UniversalRouterCommand, + V4SwapExactInParamSchema, + V4SwapExactInSingleParamSchema, + V4SwapExactOutParamSchema, + V4SwapExactOutSingleParamSchema, + isAmountInMaxParam, + isAmountInParam, + isAmountOutMinParam, + isAmountOutParam, + isURCommandASwap, +} from 'src/app/features/dappRequests/types/UniversalRouterTypes' +import { buildCurrencyId } from 'uniswap/src/utils/currencyId' +import { assert } from 'utilities/src/errors' export type MinimalToken = { address: string @@ -24,14 +48,14 @@ export function findErc20TokensToPrepare(urCall: UniversalRouterCall): string[] const tokenAddresses: string[] = [] urCall.commands.forEach((command) => { switch (command.commandType) { - case CommandType.V2SwapExactIn: - case CommandType.V2SwapExactOut: { + case CommandType.V2_SWAP_EXACT_IN: + case CommandType.V2_SWAP_EXACT_OUT: { const tokensInPath: string[] | undefined = command.params.find((param) => param.name === 'path')?.value tokensInPath?.forEach((tokenAddr: string) => tokenAddresses.push(tokenAddr)) break } - case CommandType.V3SwapExactIn: - case CommandType.V3SwapExactOut: { + case CommandType.V3_SWAP_EXACT_IN: + case CommandType.V3_SWAP_EXACT_OUT: { const pools: V3TokenInPath[] | undefined = command.params.find((param) => param.name === 'path')?.value pools?.forEach(({ tokenIn, tokenOut }) => { tokenAddresses.push(tokenIn) @@ -39,7 +63,7 @@ export function findErc20TokensToPrepare(urCall: UniversalRouterCall): string[] }) break } - case CommandType.PayPortion: + case CommandType.PAY_PORTION: case CommandType.SWEEP: case CommandType.TRANSFER: { const tokenAddr = command.params.find((param) => param.name === 'token')?.value @@ -68,3 +92,265 @@ export function formatUnits(amount: BigNumberish, units: number): string { return formatUnitsEthers(amount, units) } + +export function useSwapDetails( + request: SwapSendTransactionRequest, + dappUrl: string, +): { + inputIdentifier: string | undefined + outputIdentifier: string | undefined + inputValue: string + outputValue: string +} { + const activeChain = useDappLastChainId(dappUrl) + let inputAddress: string | undefined + let outputAddress: string | undefined + let inputValue: string = '0' + let outputValue: string = '0' + + // Attempt to find a V4_SWAP command + const v4Command = request.parsedCalldata.commands.find((command) => command.commandName.startsWith('V4_SWAP')) + + if (v4Command) { + // Extract details using the V4 helper + const v4Details = getTokenDetailsFromV4SwapCommands(v4Command) + inputAddress = v4Details.inputAddress + outputAddress = v4Details.outputAddress + inputValue = v4Details.inputValue || '0' + outputValue = v4Details.outputValue || '0' + } else { + // Fallback to V2/V3 extraction + const addresses = extractTokenAddresses(request.parsedCalldata.commands) + const amounts = getTokenAmounts(request.parsedCalldata.commands) + + inputAddress = addresses.inputAddress + outputAddress = addresses.outputAddress + inputValue = amounts.inputValue + outputValue = amounts.outputValue + } + + const inputIdentifier = activeChain && inputAddress ? buildCurrencyId(activeChain, inputAddress) : undefined + + const outputIdentifier = activeChain && outputAddress ? buildCurrencyId(activeChain, outputAddress) : undefined + + return { inputIdentifier, outputIdentifier, inputValue, outputValue } +} + +// Existing Helper Function to Extract Token Addresses (for V2/V3) +function extractTokenAddresses(commands: UniversalRouterCommand[]): { + inputAddress: string | undefined + outputAddress: string | undefined +} { + let inputAddress: string | undefined + let outputAddress: string | undefined + + for (const command of commands) { + const result = getTokenAddressesFromV2V3SwapCommands(command) + if (result.inputAddress) { + inputAddress = result.inputAddress + } + if (result.outputAddress) { + outputAddress = result.outputAddress + } + } + + return { inputAddress, outputAddress } +} + +function getTokenAmounts(commands: UniversalRouterCommand[]): { + inputValue: string + outputValue: string +} { + const firstSwapCommand = commands.find(isURCommandASwap) + const lastSwapCommand = commands.findLast(isURCommandASwap) + + assert( + firstSwapCommand && lastSwapCommand, + 'SwapRequestContent: All swaps must have a defined input and output Universal Router command.', + ) + + const firstAmountInParam = firstSwapCommand?.params.find(isAmountInOrMaxParam) + const lastAmountOutParam = lastSwapCommand?.params.find(isAmountOutMinOrOutParam) + + assert( + firstAmountInParam && lastAmountOutParam, + 'SwapRequestContent: All swaps must have a defined input and output amount parameter.', + ) + + return { + inputValue: firstAmountInParam?.value || '0', // Safe due to assert + outputValue: lastAmountOutParam?.value || '0', // Safe due to assert + } +} + +// Predicate Functions +export function isAmountInOrMaxParam(param: Param): param is AmountInParam | AmountInMaxParam { + return isAmountInParam(param) || isAmountInMaxParam(param) +} + +export function isAmountOutMinOrOutParam(param: Param): param is AmountOutMinParam | AmountOutParam { + return isAmountOutMinParam(param) || isAmountOutParam(param) +} + +// Helper Function to Extract Addresses from V2 and V3 Swap Commands +function getTokenAddressesFromV2V3SwapCommands(command: UniversalRouterCommand): { + inputAddress?: string + outputAddress?: string +} { + let inputAddress: string | undefined + let outputAddress: string | undefined + + const pathParam = command.params.find(({ name }) => name === 'path') + if (!pathParam) { + return { inputAddress, outputAddress } + } + + if (command.commandName.startsWith('V2_SWAP_EXACT')) { + const path = pathParam.value as string[] + if (path.length > 0) { + inputAddress = path[0] + outputAddress = path[path.length - 1] + } + } else if (command.commandName.startsWith('V3_SWAP_EXACT')) { + const path = pathParam.value as { fee: number; tokenIn: string; tokenOut: string }[] + if (path.length > 0) { + const first = path[0] + if (first) { + inputAddress = first.tokenIn + } + const last = path[path.length - 1] + if (last) { + outputAddress = last.tokenOut + } + } + } + + // Future handling for V4_SWAP can be added here + + return { inputAddress, outputAddress } +} + +export function getTokenDetailsFromV4SwapCommands(command: UniversalRouterCommand): { + inputAddress?: string + outputAddress?: string + inputValue?: string + outputValue?: string +} { + let inputAddress: string | undefined + let outputAddress: string | undefined + let inputValue: string | undefined + let outputValue: string | undefined + + if (command.commandName !== 'V4_SWAP') { + return { inputAddress, outputAddress, inputValue, outputValue } + } + + for (const param of command.params) { + switch (param.name) { + case 'SWAP_EXACT_IN': + { + const parsed = V4SwapExactInParamSchema.safeParse(param) + if (!parsed.success) { + break + } + + for (const p of parsed.data.value) { + if (p.name === 'swap') { + const swap = p.value + + inputAddress = swap.currencyIn + inputValue = swap.amountIn + outputValue = swap.amountOutMinimum + + const lastPath = swap.path[swap.path.length - 1] + if (lastPath) { + outputAddress = lastPath.intermediateCurrency + } + } + } + } + break + + case 'SWAP_EXACT_OUT': + { + const parsed = V4SwapExactOutParamSchema.safeParse(param) + if (!parsed.success) { + break + } + + for (const p of parsed.data.value) { + if (p.name === 'swap') { + const swap = p.value + + outputAddress = swap.currencyOut + outputValue = swap.amountOut + inputValue = swap.amountInMaximum + + const firstPath = swap.path[0] + if (firstPath) { + inputAddress = firstPath.intermediateCurrency + } + } + } + } + break + + case 'SWAP_EXACT_IN_SINGLE': + { + const parsed = V4SwapExactInSingleParamSchema.safeParse(param) + if (!parsed.success) { + break + } + + for (const p of parsed.data.value) { + if (p.name === 'swap') { + const swap = p.value + + inputValue = swap.amountIn + outputValue = swap.amountOutMinimum + + if (swap.zeroForOne) { + inputAddress = swap.poolKey.currency0 + outputAddress = swap.poolKey.currency1 + } else { + inputAddress = swap.poolKey.currency1 + outputAddress = swap.poolKey.currency0 + } + } + } + } + break + + case 'SWAP_EXACT_OUT_SINGLE': + { + const parsed = V4SwapExactOutSingleParamSchema.safeParse(param) + if (!parsed.success) { + break + } + + for (const p of parsed.data.value) { + if (p.name === 'swap') { + const swap = p.value + + outputValue = swap.amountOut + inputValue = swap.amountInMaximum + + if (swap.zeroForOne) { + inputAddress = swap.poolKey.currency0 + outputAddress = swap.poolKey.currency1 + } else { + inputAddress = swap.poolKey.currency1 + outputAddress = swap.poolKey.currency0 + } + } + } + } + break + + default: + break + } + } + + return { inputAddress, outputAddress, inputValue, outputValue } +} diff --git a/apps/extension/src/app/features/dappRequests/requestContent/NetworkFooter.test.tsx b/apps/extension/src/app/features/dappRequests/requestContent/NetworkFooter.test.tsx deleted file mode 100644 index 582a57f48e7..00000000000 --- a/apps/extension/src/app/features/dappRequests/requestContent/NetworkFooter.test.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { NetworksFooter } from 'src/app/features/dappRequests/requestContent/NetworksFooter' -import { cleanup, render } from 'src/test/test-utils' - -describe(NetworksFooter, () => { - it('renders without error', async () => { - const tree = render() - - expect(tree).toMatchSnapshot() - cleanup() - }) -}) diff --git a/apps/extension/src/app/features/dappRequests/requestContent/NetworksFooter.tsx b/apps/extension/src/app/features/dappRequests/requestContent/NetworksFooter.tsx deleted file mode 100644 index 70130bc007f..00000000000 --- a/apps/extension/src/app/features/dappRequests/requestContent/NetworksFooter.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { useTranslation } from 'react-i18next' -import { Flex, Text } from 'ui/src' -import { iconSizes } from 'ui/src/theme' -import { NetworksInSeries } from 'uniswap/src/components/network/NetworkFilter' -import { WALLET_SUPPORTED_CHAIN_IDS } from 'uniswap/src/types/chains' - -export function NetworksFooter(): JSX.Element { - const { t } = useTranslation() - - return ( - - - - {t('extension.connection.networks')} - - - - - ) -} diff --git a/apps/extension/src/app/features/dappRequests/requestContent/SignTypeData/SignTypedDataRequestContent.tsx b/apps/extension/src/app/features/dappRequests/requestContent/SignTypeData/SignTypedDataRequestContent.tsx index 2576bc24ecf..2b67beaa063 100644 --- a/apps/extension/src/app/features/dappRequests/requestContent/SignTypeData/SignTypedDataRequestContent.tsx +++ b/apps/extension/src/app/features/dappRequests/requestContent/SignTypeData/SignTypedDataRequestContent.tsx @@ -4,9 +4,9 @@ import { UniswapXSwapRequestContent } from 'src/app/features/dappRequests/reques import { DomainContent } from 'src/app/features/dappRequests/requestContent/SignTypeData/DomainContent' import { MaybeExplorerLinkedAddress } from 'src/app/features/dappRequests/requestContent/SignTypeData/MaybeExplorerLinkedAddress' import { Permit2RequestContent } from 'src/app/features/dappRequests/requestContent/SignTypeData/Permit2/Permit2RequestContent' -import { SignTypedDataRequest, isUniswapXSwapRequest } from 'src/app/features/dappRequests/types/DappRequestTypes' +import { SignTypedDataRequest } from 'src/app/features/dappRequests/types/DappRequestTypes' import { EIP712Message, isEIP712TypedData } from 'src/app/features/dappRequests/types/EIP712Types' -import { isPermit2 } from 'src/app/features/dappRequests/types/Permit2Types' +import { isPermit2, isUniswapXSwapRequest } from 'src/app/features/dappRequests/types/Permit2Types' import { Flex, Text } from 'ui/src' import { toSupportedChainId } from 'uniswap/src/features/chains/utils' import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking' @@ -53,7 +53,7 @@ export function SignTypedDataRequestContent({ dappRequest }: SignTypedDataReques const chainId = toSupportedChainId(domainChainId) // this check needs to happen before isPermit2 since uniswapX requests are Permit2 requests - if (isUniswapXSwapRequest(dappRequest, chainId || undefined)) { + if (isUniswapXSwapRequest(parsedTypedData)) { return } diff --git a/apps/extension/src/app/features/dappRequests/requestContent/WrapContent.tsx b/apps/extension/src/app/features/dappRequests/requestContent/WrapContent.tsx index 8c5b908ccec..957eee3531f 100644 --- a/apps/extension/src/app/features/dappRequests/requestContent/WrapContent.tsx +++ b/apps/extension/src/app/features/dappRequests/requestContent/WrapContent.tsx @@ -4,10 +4,8 @@ import { useDappLastChainId } from 'src/app/features/dapp/hooks' import { DappRequestStoreItem } from 'src/app/features/dappRequests/slice' import { SendTransactionRequest } from 'src/app/features/dappRequests/types/DappRequestTypes' import { Flex, Text } from 'ui/src' -import { useTransactionGasFee, useUSDValue } from 'uniswap/src/features/gas/hooks' -import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' -import { UniverseChainId } from 'uniswap/src/types/chains' -import { NumberType } from 'utilities/src/format/types' +import { useGasFeeFormattedAmounts, useTransactionGasFee } from 'uniswap/src/features/gas/hooks' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { useActiveAccountAddressWithThrow, useDisplayName } from 'wallet/src/features/wallet/hooks' export const WrapTransactionDetails = ({ @@ -18,13 +16,13 @@ export const WrapTransactionDetails = ({ dappUrl: string }): JSX.Element => { const { t } = useTranslation() - const { convertFiatAmountFormatted } = useLocalizationContext() + const { defaultChainId } = useEnabledChains() const activeAddress = useActiveAccountAddressWithThrow() const displayName = useDisplayName(activeAddress) const sendTransactionRequest = request.dappRequest as SendTransactionRequest - const chainId = useDappLastChainId(dappUrl) || UniverseChainId.Mainnet + const chainId = useDappLastChainId(dappUrl) || defaultChainId const txRequest = useMemo( () => ({ ...sendTransactionRequest.transaction, chainId }), @@ -33,7 +31,11 @@ export const WrapTransactionDetails = ({ const networkFee = useTransactionGasFee(txRequest) - const gasFeeUSD = useUSDValue(chainId, networkFee.value) + const { gasFeeFormatted } = useGasFeeFormattedAmounts({ + gasFee: networkFee, + chainId, + placeholder: undefined, + }) return ( @@ -84,7 +86,7 @@ export const WrapTransactionDetails = ({ {t('transaction.networkCost.label')} - {convertFiatAmountFormatted(gasFeeUSD, NumberType.FiatGasPrice)} + {gasFeeFormatted} diff --git a/apps/extension/src/app/features/dappRequests/requestContent/__snapshots__/NetworkFooter.test.tsx.snap b/apps/extension/src/app/features/dappRequests/requestContent/__snapshots__/NetworkFooter.test.tsx.snap deleted file mode 100644 index 8947f280de6..00000000000 --- a/apps/extension/src/app/features/dappRequests/requestContent/__snapshots__/NetworkFooter.test.tsx.snap +++ /dev/null @@ -1,661 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`NetworksFooter renders without error 1`] = ` -{ - "asFragment": [Function], - "baseElement": -
- - - -
-
- - Networks - -
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
- - - -
- , - "container":
- - - -
-
- - Networks - -
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
- - - -
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "store": { - "@@observable": [Function], - "dispatch": [Function], - "getState": [Function], - "replaceReducer": [Function], - "subscribe": [Function], - }, - "unmount": [Function], -} -`; diff --git a/apps/extension/src/app/features/dappRequests/types/DappRequestTypes.ts b/apps/extension/src/app/features/dappRequests/types/DappRequestTypes.ts index 0efa450270b..5c5ea68c3fc 100644 --- a/apps/extension/src/app/features/dappRequests/types/DappRequestTypes.ts +++ b/apps/extension/src/app/features/dappRequests/types/DappRequestTypes.ts @@ -1,4 +1,3 @@ -import { REACTOR_ADDRESS_MAPPING } from '@uniswap/uniswapx-sdk' import { EthereumRpcErrorSchema } from 'src/app/features/dappRequests/types/ErrorTypes' import { EthersTransactionRequestSchema, @@ -8,11 +7,7 @@ import { NonfungiblePositionManagerCallSchema } from 'src/app/features/dappReque import { UniversalRouterCallSchema } from 'src/app/features/dappRequests/types/UniversalRouterTypes' import { HomeTabs } from 'src/app/navigation/constants' import { MessageSchema } from 'src/background/messagePassing/messageTypes' -import { - PermissionRequestSchema, - PermissionSchema, -} from 'src/contentScript/WindowEthereumRequestTypes' -import { UniverseChainId } from 'uniswap/src/types/chains' +import { PermissionRequestSchema, PermissionSchema } from 'src/contentScript/WindowEthereumRequestTypes' import { z } from 'zod' // ENUMS @@ -52,7 +47,6 @@ export enum UniswapOpenSidebarTab { Tokens = 'tokens', } - // SCHEMAS + TYPES export const BaseDappRequestSchema = MessageSchema.extend({ @@ -117,9 +111,7 @@ export type ApproveSendTransactionRequest = z.infer +export type ContractInteractionSendTransactionRequest = z.infer const SwapSendTransactionRequestSchema = BaseSendTransactionRequestSchema.extend({ contractInteractions: z.literal(EthSendTransactionRPCActions.Swap), @@ -138,10 +130,9 @@ const LPSendTransactionRequestSchema = BaseSendTransactionRequestSchema.extend({ }) export type LPSendTransactionRequest = z.infer -const UnknownContractInteractionSendTransactionRequestSchema = - BaseSendTransactionRequestSchema.extend({ - contractInteractions: z.literal(EthSendTransactionRPCActions.Unknown).optional(), - }) +const UnknownContractInteractionSendTransactionRequestSchema = BaseSendTransactionRequestSchema.extend({ + contractInteractions: z.literal(EthSendTransactionRPCActions.Unknown).optional(), +}) export type UnknownContractInteractionSendTransactionRequest = z.infer< typeof UnknownContractInteractionSendTransactionRequestSchema > @@ -318,15 +309,11 @@ export function isErrorResponse(response: unknown): response is ErrorResponse { return ErrorResponseSchema.safeParse(response).success } -export function isValidSendTransactionResponse( - response: unknown -): response is SendTransactionResponse { +export function isValidSendTransactionResponse(response: unknown): response is SendTransactionResponse { return SendTransactionResponseSchema.safeParse(response).success } -export function isValidSignTransactionResponse( - response: unknown -): response is SignTransactionResponse { +export function isValidSignTransactionResponse(response: unknown): response is SignTransactionResponse { return SignTransactionResponseSchema.safeParse(response).success } @@ -350,35 +337,22 @@ export function isValidAccountResponse(response: unknown): response is AccountRe return AccountResponseSchema.safeParse(response).success } -export function isValidGetPermissionsResponse( - response: unknown -): response is GetPermissionsResponse { +export function isValidGetPermissionsResponse(response: unknown): response is GetPermissionsResponse { return GetPermissionsResponseSchema.safeParse(response).success } -export function isValidRequestPermissionsResponse( - response: unknown -): response is RequestPermissionsResponse { +export function isValidRequestPermissionsResponse(response: unknown): response is RequestPermissionsResponse { return RequestPermissionsResponseSchema.safeParse(response).success } -export function isApproveRequest( - request: SendTransactionRequest -): request is ApproveSendTransactionRequest { +export function isApproveRequest(request: SendTransactionRequest): request is ApproveSendTransactionRequest { return ApproveSendTransactionRequestSchema.safeParse(request).success } -export function isSwapRequest( - request: SendTransactionRequest -): request is SwapSendTransactionRequest { +export function isSwapRequest(request: SendTransactionRequest): request is SwapSendTransactionRequest { return SwapSendTransactionRequestSchema.safeParse(request).success } -export function isUniswapXSwapRequest(request: SignTypedDataRequest, chainId: UniverseChainId = UniverseChainId.Mainnet): boolean { - const parsedTypedData = JSON.parse(request.typedData) - return parsedTypedData?.message?.spender.toLowerCase() === REACTOR_ADDRESS_MAPPING[chainId]?.Dutch_V2?.toLowerCase() -} - export function isSignTypedDataRequest(request: DappRequest): request is SignTypedDataRequest { return SignTypedDataRequestSchema.safeParse(request).success } @@ -407,8 +381,6 @@ export function isRequestAccountRequest(request: DappRequest): request is Reques return RequestAccountRequestSchema.safeParse(request).success } -export function isRequestPermissionsRequest( - request: DappRequest -): request is RequestPermissionsRequest { +export function isRequestPermissionsRequest(request: DappRequest): request is RequestPermissionsRequest { return RequestPermissionsRequestSchema.safeParse(request).success } diff --git a/apps/extension/src/app/features/dappRequests/types/Permit2Types.ts b/apps/extension/src/app/features/dappRequests/types/Permit2Types.ts index 76341d49a5f..c825b50336a 100644 --- a/apps/extension/src/app/features/dappRequests/types/Permit2Types.ts +++ b/apps/extension/src/app/features/dappRequests/types/Permit2Types.ts @@ -1,3 +1,4 @@ +import { REACTOR_ADDRESS_MAPPING } from '@uniswap/uniswapx-sdk' import { TypeDefinitionSchema } from 'src/app/features/dappRequests/types/EIP712Types' import { z } from 'zod' @@ -39,3 +40,24 @@ type Permit2 = z.infer export function isPermit2(data: unknown): data is Permit2 { return Permit2Schema.safeParse(data).success } + +export const UniswapXSchema = Permit2Schema.refine( + (data) => { + try { + const { message, domain } = data + const spender = message.spender?.toLowerCase() + const uniswapXAddress = REACTOR_ADDRESS_MAPPING?.[Number(domain.chainId)]?.Dutch_V2?.toLowerCase() + return uniswapXAddress && spender === uniswapXAddress + } catch { + return false + } + }, + { + message: 'Invalid UniswapX request', + } +) +type UniswapX = z.infer + +export function isUniswapXSwapRequest(data: unknown): data is UniswapX { + return UniswapXSchema.safeParse(data).success +} diff --git a/apps/extension/src/app/features/dappRequests/types/UniversalRouterTypes.ts b/apps/extension/src/app/features/dappRequests/types/UniversalRouterTypes.ts index c4a4f67a68c..7fe7dd60987 100644 --- a/apps/extension/src/app/features/dappRequests/types/UniversalRouterTypes.ts +++ b/apps/extension/src/app/features/dappRequests/types/UniversalRouterTypes.ts @@ -1,255 +1,9 @@ import { FeeAmount as FeeAmountV3 } from '@uniswap/v3-sdk' import { BigNumberSchema } from 'src/app/features/dappRequests/types/EthersTypes' import { z } from 'zod' - -// ENUMS - -// TODO: import from UR-sdk -export enum CommandType { - V3SwapExactIn = 0x00, - V3SwapExactOut = 0x01, - Permit2TransferFrom = 0x02, - Permit2PermitBatch = 0x03, - SWEEP = 0x04, - TRANSFER = 0x05, - PayPortion = 0x06, - - V2SwapExactIn = 0x08, - V2SwapExactOut = 0x09, - Permit2Permit = 0x0a, - WrapEth = 0x0b, - UnwrapWeth = 0x0c, - Permit2TransferFromBatch = 0x0d, - BalanceCheckErc20 = 0x0e, - - // NFT-related command types - SEAPORT = 0x10, - LooksRare721 = 0x11, - NFTX = 0x12, - CRYPTOPUNKS = 0x13, - LooksRare1155 = 0x14, - OwnerCheck721 = 0x15, - OwnerCheck1155 = 0x16, - SweepErc721 = 0x17, - - X2y2721 = 0x18, - SUDOSWAP = 0x19, - NFT20 = 0x1a, - X2y21155 = 0x1b, - FOUNDATION = 0x1c, - SweepErc1155 = 0x1d, - ElementMarket = 0x1e, - - ExecuteSubPlan = 0x20, - Seaportv14 = 0x21, - ApproveErc20 = 0x22, -} - -export enum Subparser { - V3PathExactIn, - V3PathExactOut, -} - -const PERMIT_STRUCT = - '((address token,uint160 amount,uint48 expiration,uint48 nonce) details,address spender,uint256 sigDeadline)' - -const PERMIT_BATCH_STRUCT = - '((address token,uint160 amount,uint48 expiration,uint48 nonce)[] details,address spender,uint256 sigDeadline)' - -const PERMIT2_TRANSFER_FROM_STRUCT = '(address from,address to,uint160 amount,address token)' -const PERMIT2_TRANSFER_FROM_BATCH_STRUCT = PERMIT2_TRANSFER_FROM_STRUCT + '[]' - -export const ABI_DEFINITION: { readonly [key in CommandType]: readonly ParamType[] } = { - // Batch Reverts - [CommandType.ExecuteSubPlan]: [ - { name: 'commands', type: 'bytes' }, - { name: 'inputs', type: 'bytes[]' }, - ], - - // Permit2 Actions - [CommandType.Permit2Permit]: [ - { name: 'permit', type: PERMIT_STRUCT }, - { name: 'signature', type: 'bytes' }, - ], - [CommandType.Permit2PermitBatch]: [ - { name: 'permit', type: PERMIT_BATCH_STRUCT }, - { name: 'signature', type: 'bytes' }, - ], - [CommandType.Permit2TransferFrom]: [ - { name: 'token', type: 'address' }, - { name: 'recipient', type: 'address' }, - { name: 'amount', type: 'uint160' }, - ], - [CommandType.Permit2TransferFromBatch]: [ - { - name: 'transferFrom', - type: PERMIT2_TRANSFER_FROM_BATCH_STRUCT, - }, - ], - - // Uniswap Actions - [CommandType.V3SwapExactIn]: [ - { name: 'recipient', type: 'address' }, - { name: 'amountIn', type: 'uint256' }, - { name: 'amountOutMin', type: 'uint256' }, - { name: 'path', subparser: Subparser.V3PathExactIn, type: 'bytes' }, - { name: 'payerIsUser', type: 'bool' }, - ], - [CommandType.V3SwapExactOut]: [ - { name: 'recipient', type: 'address' }, - { name: 'amountOut', type: 'uint256' }, - { name: 'amountInMax', type: 'uint256' }, - { name: 'path', subparser: Subparser.V3PathExactOut, type: 'bytes' }, - { name: 'payerIsUser', type: 'bool' }, - ], - [CommandType.V2SwapExactIn]: [ - { name: 'recipient', type: 'address' }, - { name: 'amountIn', type: 'uint256' }, - { name: 'amountOutMin', type: 'uint256' }, - { name: 'path', type: 'address[]' }, - { name: 'payerIsUser', type: 'bool' }, - ], - [CommandType.V2SwapExactOut]: [ - { name: 'recipient', type: 'address' }, - { name: 'amountOut', type: 'uint256' }, - { name: 'amountInMax', type: 'uint256' }, - { name: 'path', type: 'address[]' }, - { name: 'payerIsUser', type: 'bool' }, - ], - - // Token Actions and Checks - [CommandType.WrapEth]: [ - { name: 'recipient', type: 'address' }, - { name: 'amountMin', type: 'uint256' }, - ], - [CommandType.UnwrapWeth]: [ - { name: 'recipient', type: 'address' }, - { name: 'amountMin', type: 'uint256' }, - ], - [CommandType.SWEEP]: [ - { name: 'token', type: 'address' }, - { name: 'recipient', type: 'address' }, - { name: 'amountMin', type: 'uint256' }, - ], - [CommandType.SweepErc721]: [ - { name: 'token', type: 'address' }, - { name: 'recipient', type: 'address' }, - { name: 'id', type: 'uint256' }, - ], - [CommandType.SweepErc1155]: [ - { name: 'token', type: 'address' }, - { name: 'recipient', type: 'address' }, - { name: 'id', type: 'uint256' }, - { name: 'amount', type: 'uint256' }, - ], - [CommandType.TRANSFER]: [ - { name: 'token', type: 'address' }, - { name: 'recipient', type: 'address' }, - { name: 'value', type: 'uint256' }, - ], - [CommandType.PayPortion]: [ - { name: 'token', type: 'address' }, - { name: 'recipient', type: 'address' }, - { name: 'bips', type: 'uint256' }, - ], - [CommandType.BalanceCheckErc20]: [ - { name: 'owner', type: 'address' }, - { name: 'token', type: 'address' }, - { name: 'minBalance', type: 'uint256' }, - ], - [CommandType.OwnerCheck721]: [ - { name: 'owner', type: 'address' }, - { name: 'token', type: 'address' }, - { name: 'id', type: 'uint256' }, - ], - [CommandType.OwnerCheck1155]: [ - { name: 'owner', type: 'address' }, - { name: 'token', type: 'address' }, - { name: 'id', type: 'uint256' }, - { name: 'minBalance', type: 'uint256' }, - ], - [CommandType.ApproveErc20]: [ - { name: 'token', type: 'address' }, - { name: 'spenderId', type: 'uint256' }, - ], - - // NFT Markets - [CommandType.SEAPORT]: [ - { name: 'value', type: 'uint256' }, - { name: 'data', type: 'bytes' }, - ], - [CommandType.Seaportv14]: [ - { name: 'value', type: 'uint256' }, - { name: 'data', type: 'bytes' }, - ], - [CommandType.NFTX]: [ - { name: 'value', type: 'uint256' }, - { name: 'data', type: 'bytes' }, - ], - [CommandType.LooksRare721]: [ - { name: 'value', type: 'uint256' }, - { name: 'data', type: 'bytes' }, - { name: 'recipient', type: 'address' }, - { name: 'token', type: 'address' }, - { name: 'id', type: 'uint256' }, - ], - [CommandType.LooksRare1155]: [ - { name: 'value', type: 'uint256' }, - { name: 'data', type: 'bytes' }, - { name: 'recipient', type: 'address' }, - { name: 'token', type: 'address' }, - { name: 'id', type: 'uint256' }, - ], - [CommandType.X2y2721]: [ - { name: 'value', type: 'uint256' }, - { name: 'data', type: 'bytes' }, - { name: 'recipient', type: 'address' }, - { name: 'token', type: 'address' }, - { name: 'id', type: 'uint256' }, - ], - [CommandType.X2y21155]: [ - { name: 'value', type: 'uint256' }, - { name: 'data', type: 'bytes' }, - { name: 'recipient', type: 'address' }, - { name: 'token', type: 'address' }, - { name: 'id', type: 'uint256' }, - ], - [CommandType.FOUNDATION]: [ - { name: 'value', type: 'uint256' }, - { name: 'data', type: 'bytes' }, - { name: 'recipient', type: 'address' }, - { name: 'token', type: 'address' }, - { name: 'id', type: 'uint256' }, - ], - [CommandType.SUDOSWAP]: [ - { name: 'value', type: 'uint256' }, - { name: 'data', type: 'bytes' }, - ], - [CommandType.NFT20]: [ - { name: 'value', type: 'uint256' }, - { name: 'data', type: 'bytes' }, - ], - [CommandType.CRYPTOPUNKS]: [ - { name: 'punkId', type: 'uint256' }, - { name: 'recipient', type: 'address' }, - { name: 'value', type: 'uint256' }, - ], - [CommandType.ElementMarket]: [ - { name: 'value', type: 'uint256' }, - { name: 'data', type: 'bytes' }, - ], -} +import { CommandType } from '@uniswap/universal-router-sdk' // SCHEMAS + TYPES -export const SubparserSchema = z.nativeEnum(Subparser) - -export const ParamTypeSchema = z.object({ - name: z.string(), - type: z.string(), - subparser: SubparserSchema.optional(), -}) -export type ParamType = z.infer - export const CommandNameSchema = z.enum( Object.keys(CommandType) as [keyof typeof CommandType, ...Array] ) @@ -306,6 +60,123 @@ const V3PathParamSchema = z.object({ }) export type V3Path = z.infer + +// V4 PARAMS + +// Define PoolKey which is used for the exact single swaps +const PoolKeySchema = z.object({ + currency0: z.string().refine((val) => val.startsWith('0x'), { + message: "currency0 must start with '0x'", + }), + currency1: z.string().refine((val) => val.startsWith('0x'), { + message: "currency1 must start with '0x'", + }), + fee: z.number(), + tickSpacing: z.number(), + hooks: z.string(), +}) + +// V4 SWAP_EXACT_IN_SINGLE +const V4SwapExactInSingleSwapSchema = z.object({ + poolKey: PoolKeySchema, + zeroForOne: z.boolean(), + amountIn: BigNumberSchema, + amountOutMinimum: BigNumberSchema, + sqrtPriceLimitX96: BigNumberSchema, + hookData: z.string(), +}) + +export const V4SwapExactInSingleParamSchema = z.object({ + name: z.literal('SWAP_EXACT_IN_SINGLE'), + value: z.array( + z.object({ + name: z.literal('swap'), + value: V4SwapExactInSingleSwapSchema, + }), + ), +}) +export type V4SwapExactInSingleParam = z.infer + +// V4 SWAP_EXACT_OUT_SINGLE +const SwapExactOutSingleSwapSchema = z.object({ + poolKey: PoolKeySchema, + zeroForOne: z.boolean(), + amountOut: BigNumberSchema, + amountInMaximum: BigNumberSchema, + sqrtPriceLimitX96: BigNumberSchema, + hookData: z.string(), +}) + +export const V4SwapExactOutSingleParamSchema = z.object({ + name: z.literal('SWAP_EXACT_OUT_SINGLE'), + value: z.array( + z.object({ + name: z.literal('swap'), + value: SwapExactOutSingleSwapSchema, + }), + ), +}) +export type V4SwapExactOutSingleParam = z.infer + +// Define PathKey which is used for exact swaps with multiple hops +const PathKeySchema = z.object({ + intermediateCurrency: z.string().refine((val) => val.startsWith('0x'), { + message: "intermediateCurrency must start with '0x'", + }), + fee: z.number(), + tickSpacing: z.number(), + hooks: z.string().refine((val) => val.startsWith('0x'), { + message: "hooks must start with '0x'", + }), + hookData: z.string(), +}); + +// V4 SWAP_EXACT_IN +const V4SwapExactInSchema = z.object({ + currencyIn: z.string().refine((val) => val.startsWith('0x'), { + message: "currencyIn must start with '0x'", + }), + path: z.array(PathKeySchema), + amountIn: BigNumberSchema, + amountOutMinimum: BigNumberSchema, +}); + +export const V4SwapExactInParamSchema = z.object({ + name: z.literal('SWAP_EXACT_IN'), + value: z.array( + z.object({ + name: z.literal('swap'), + value: V4SwapExactInSchema, + }), + ), +}) +export type V4SwapExactInParam = z.infer + +// V4 SWAP_EXACT_OUT +const V4SwapExactOutSchema = z.object({ + currencyOut: z.string().refine((val) => val.startsWith('0x'), { + message: "currencyOut must start with '0x'", + }), + path: z.array(PathKeySchema), + amountOut: BigNumberSchema, + amountInMaximum: BigNumberSchema, +}); + +export const V4SwapExactOutParamSchema = z.object({ + name: z.literal('SWAP_EXACT_OUT'), + value: z.array( + z.object({ + name: z.literal('swap'), + value: V4SwapExactOutSchema, + }), + ), +}); +export type V4SwapExactOutParam = z.infer; + + +// END V4 PARAMS + + const PayerIsUserParamSchema = z.object({ name: z.literal('payerIsUser'), value: z.boolean(), @@ -331,38 +202,51 @@ export const FallbackCommandSchema = z.object({ export type FallbackCommand = z.infer const V2SwapExactInCommandSchema = z.object({ - commandName: z.literal('V2SwapExactIn'), - commandType: z.literal(CommandType.V2SwapExactIn), + commandName: z.literal('V2_SWAP_EXACT_IN'), + commandType: z.literal(CommandType.V2_SWAP_EXACT_IN), params: z.array(ParamSchema), }) export type V2SwapExactInCommand = z.infer const V2SwapExactOutCommandSchema = z.object({ - commandName: z.literal('V2SwapExactOut'), - commandType: z.literal(CommandType.V2SwapExactOut), + commandName: z.literal('V2_SWAP_EXACT_OUT'), + commandType: z.literal(CommandType.V2_SWAP_EXACT_OUT), params: z.array(ParamSchema), }) export type V2SwapExactOutCommand = z.infer const V3SwapExactInCommandSchema = z.object({ - commandName: z.literal('V3SwapExactIn'), - commandType: z.literal(CommandType.V3SwapExactIn), + commandName: z.literal('V3_SWAP_EXACT_IN'), + commandType: z.literal(CommandType.V3_SWAP_EXACT_IN), params: z.array(ParamSchema), }) export type V3SwapExactInCommand = z.infer const V3SwapExactOutCommandSchema = z.object({ - commandName: z.literal('V3SwapExactOut'), - commandType: z.literal(CommandType.V3SwapExactOut), + commandName: z.literal('V3_SWAP_EXACT_OUT'), + commandType: z.literal(CommandType.V3_SWAP_EXACT_OUT), params: z.array(ParamSchema), }) export type V3SwapExactOutCommand = z.infer +const V4SwapCommandSchema = z.object({ + commandName: z.literal('V4_SWAP'), + commandType: z.literal(CommandType.V4_SWAP), + params: z.array(z.union([ + V4SwapExactInParamSchema, + V4SwapExactOutParamSchema, + V4SwapExactInSingleParamSchema, + V4SwapExactOutSingleParamSchema, + ])), +}) +export type V4SwapCommand = z.infer + export const UniversalRouterSwapCommandSchema = z.union([ V2SwapExactInCommandSchema, V2SwapExactOutCommandSchema, V3SwapExactInCommandSchema, V3SwapExactOutCommandSchema, + V4SwapCommandSchema ]) export type UniversalRouterSwapCommand = z.infer @@ -372,6 +256,7 @@ const UniversalRouterCommandSchema = z.union([ V2SwapExactOutCommandSchema, V3SwapExactInCommandSchema, V3SwapExactOutCommandSchema, + V4SwapCommandSchema ]) export type UniversalRouterCommand = z.infer diff --git a/apps/extension/src/app/features/for/utils.ts b/apps/extension/src/app/features/for/utils.ts index 9911dfdee93..a86e419c81f 100644 --- a/apps/extension/src/app/features/for/utils.ts +++ b/apps/extension/src/app/features/for/utils.ts @@ -6,7 +6,7 @@ import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { uniswapUrls } from 'uniswap/src/constants/urls' import { ElementNameType } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { ExtensionScreens } from 'uniswap/src/types/screens/extension' import { logger } from 'utilities/src/logger/logger' @@ -23,7 +23,7 @@ export function useInterfaceBuyNavigator(element?: ElementNameType): () => void } } -export function navigateToInterfaceFiatOnRamp(chainId?: WalletChainId): void { +export function navigateToInterfaceFiatOnRamp(chainId?: UniverseChainId): void { const chainParam = chainId ? `?chain=${UNIVERSE_CHAIN_INFO[chainId].urlParam}` : '' focusOrCreateUniswapInterfaceTab({ url: `${uniswapUrls.webInterfaceBuyUrl}${chainParam}`, diff --git a/apps/extension/src/app/features/home/PortfolioActionButtons.tsx b/apps/extension/src/app/features/home/PortfolioActionButtons.tsx index d87f7086222..f2ad5a07d6f 100644 --- a/apps/extension/src/app/features/home/PortfolioActionButtons.tsx +++ b/apps/extension/src/app/features/home/PortfolioActionButtons.tsx @@ -1,13 +1,15 @@ import { SharedEventName } from '@uniswap/analytics-events' -import { cloneElement, memo } from 'react' +import { cloneElement, memo, useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { useInterfaceBuyNavigator } from 'src/app/features/for/utils' import { AppRoutes } from 'src/app/navigation/constants' import { navigate } from 'src/app/navigation/state' import { Flex, Text, getTokenValue, useMedia } from 'ui/src' import { ArrowDownCircle, Buy, CoinConvert, SendAction } from 'ui/src/components/icons' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { ElementName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' +import { TestnetModeModal } from 'uniswap/src/features/testnets/TestnetModeModal' import { ExtensionScreens } from 'uniswap/src/types/screens/extension' const ICON_COLOR = '$accent1' @@ -70,6 +72,7 @@ function ActionButton({ label, Icon, onClick, url }: ActionButtonProps): JSX.Ele export const PortfolioActionButtons = memo(function _PortfolioActionButtons(): JSX.Element { const { t } = useTranslation() const media = useMedia() + const { isTestnetModeEnabled } = useEnabledChains() const onSendClick = (): void => { sendAnalyticsEvent(SharedEventName.ELEMENT_CLICKED, { @@ -95,12 +98,30 @@ export const PortfolioActionButtons = memo(function _PortfolioActionButtons(): J navigate(AppRoutes.Receive) } - const onBuyClick = useInterfaceBuyNavigator(ElementName.Buy) + const [isTestnetWarningModalOpen, setIsTestnetWarningModalOpen] = useState(false) + const handleTestnetWarningModalClose = useCallback(() => { + setIsTestnetWarningModalOpen(false) + }, []) + + const onBuyNavigate = useInterfaceBuyNavigator(ElementName.Buy) + const onBuyClick = (): void => { + if (isTestnetModeEnabled) { + setIsTestnetWarningModalOpen(true) + } else { + onBuyNavigate() + } + } const isGrid = media.sm return ( + } label={t('home.label.swap')} onClick={onSwapClick} /> } label={t('home.label.buy')} onClick={onBuyClick} /> diff --git a/apps/extension/src/app/features/home/PortfolioHeader.tsx b/apps/extension/src/app/features/home/PortfolioHeader.tsx index 3da06d2689d..b885db8fbb4 100644 --- a/apps/extension/src/app/features/home/PortfolioHeader.tsx +++ b/apps/extension/src/app/features/home/PortfolioHeader.tsx @@ -18,7 +18,7 @@ import { useAvatar } from 'uniswap/src/features/address/avatar' import { ElementName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { TestID } from 'uniswap/src/test/fixtures/testIDs' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { ExtensionScreens } from 'uniswap/src/types/screens/extension' import { sanitizeAddressText, shortenAddress } from 'uniswap/src/utils/addresses' import { setClipboard } from 'uniswap/src/utils/clipboard' @@ -240,7 +240,7 @@ function ConnectionStatusIcon({ dappUrl, }: { isConnected: boolean - lastChainId?: WalletChainId + lastChainId?: UniverseChainId dappIconUrl?: string dappUrl?: string }): JSX.Element { diff --git a/apps/extension/src/app/features/home/SwitchNetworksModal.tsx b/apps/extension/src/app/features/home/SwitchNetworksModal.tsx index dce2b2bc9b9..17021e3ce96 100644 --- a/apps/extension/src/app/features/home/SwitchNetworksModal.tsx +++ b/apps/extension/src/app/features/home/SwitchNetworksModal.tsx @@ -7,24 +7,29 @@ import { extractUrlHost } from 'src/app/features/dappRequests/utils' import { PopupName, closePopup } from 'src/app/features/popups/slice' import { Anchor, Button, Flex, Popover, Separator, Text, getTokenValue } from 'ui/src' import { Check, Power } from 'ui/src/components/icons' +import { usePreventOverflowBelowFold } from 'ui/src/hooks/usePreventOverflowBelowFold' import { iconSizes } from 'ui/src/theme' import { NetworkLogo } from 'uniswap/src/components/CurrencyLogo/NetworkLogo' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { ExtensionEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' -import { WALLET_SUPPORTED_CHAIN_IDS, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { pushNotification } from 'wallet/src/features/notifications/slice' import { AppNotificationType } from 'wallet/src/features/notifications/types' import { useActiveAccountWithThrow } from 'wallet/src/features/wallet/hooks' +const BUTTON_OFFSET = 20 + export function SwitchNetworksModal(): JSX.Element { const { t } = useTranslation() const dispatch = useDispatch() const { dappUrl, dappIconUrl } = useDappContext() const activeWalletAccount = useActiveAccountWithThrow() const activeChain = useDappLastChainId(dappUrl) + const { chains: enabledChains } = useEnabledChains() - const onNetworkClicked = async (chainId: WalletChainId): Promise => { + const onNetworkClicked = async (chainId: UniverseChainId): Promise => { await saveDappChain(dappUrl, chainId) sendAnalyticsEvent(ExtensionEventName.SidebarSwitchChain, { previousChainId: activeChain, @@ -39,14 +44,18 @@ export function SwitchNetworksModal(): JSX.Element { sendAnalyticsEvent(ExtensionEventName.SidebarDisconnect) } + const { ref, maxHeight } = usePreventOverflowBelowFold() + return ( {t('extension.connection.titleConnected')} @@ -63,35 +72,37 @@ export function SwitchNetworksModal(): JSX.Element { - {WALLET_SUPPORTED_CHAIN_IDS.map((chain: WalletChainId) => { - return ( - - - - ) - })} + {activeChain === chain ? ( + + + + ) : null} + + + + ) + })} +
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
- You can receive tokens & NFTs on Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast, and BNB Chain. + You can send and receive tokens and NFTs on all of our 12 supported networks. -
- - Learn more - -
+ + Networks + + + + +

+

@@ -12292,294 +12036,38 @@ exports[`ReceiveScreen renders without error 1`] = `
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
- You can receive tokens & NFTs on Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast, and BNB Chain. + You can send and receive tokens and NFTs on all of our 12 supported networks. -
- - Learn more - -
+ + Networks + + + + +

+

diff --git a/apps/extension/src/app/features/send/SendFormScreen/RecipientPanel.tsx b/apps/extension/src/app/features/send/SendFormScreen/RecipientPanel.tsx index b24e82a4877..e095a26bb55 100644 --- a/apps/extension/src/app/features/send/SendFormScreen/RecipientPanel.tsx +++ b/apps/extension/src/app/features/send/SendFormScreen/RecipientPanel.tsx @@ -4,7 +4,7 @@ import { Flex, Separator, Text, TouchableArea } from 'ui/src' import { RotatableChevron, WalletFilled } from 'ui/src/components/icons' import { iconSizes, spacing } from 'ui/src/theme' import { SearchTextInput } from 'uniswap/src/features/search/SearchTextInput' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { RecipientList } from 'wallet/src/components/RecipientSearch/RecipientList' import { RecipientSelectSpeedBumps } from 'wallet/src/components/RecipientSearch/RecipientSelectSpeedBumps' import { useFilteredRecipientSections } from 'wallet/src/components/RecipientSearch/hooks' @@ -12,7 +12,7 @@ import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' import { useSendContext } from 'wallet/src/features/transactions/contexts/SendContext' type RecipientPanelProps = { - chainId?: WalletChainId + chainId?: UniverseChainId } export function RecipientPanel({ chainId }: RecipientPanelProps): JSX.Element { diff --git a/apps/extension/src/app/features/send/SendFormScreen/SendFormScreen.tsx b/apps/extension/src/app/features/send/SendFormScreen/SendFormScreen.tsx index a998a439b45..b05f5f768d8 100644 --- a/apps/extension/src/app/features/send/SendFormScreen/SendFormScreen.tsx +++ b/apps/extension/src/app/features/send/SendFormScreen/SendFormScreen.tsx @@ -14,7 +14,7 @@ import { useUSDTokenUpdater } from 'uniswap/src/features/transactions/hooks/useU import { BlockedAddressWarning } from 'uniswap/src/features/transactions/modals/BlockedAddressWarning' import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice' import { useIsBlocked } from 'uniswap/src/features/trm/hooks' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { createTransactionId } from 'uniswap/src/utils/createTransactionId' import { useSendContext } from 'wallet/src/features/transactions/contexts/SendContext' @@ -169,7 +169,7 @@ export function SendFormScreen(): JSX.Element { py="$spacing12" {...inputShadowProps} > - + {!showRecipientSelector && ( <> @@ -185,7 +185,7 @@ export function SendFormScreen(): JSX.Element { /> )} - + )} diff --git a/apps/extension/src/app/features/settings/SettingsRecoveryPhraseScreen/RemoveRecoveryPhraseVerify.tsx b/apps/extension/src/app/features/settings/SettingsRecoveryPhraseScreen/RemoveRecoveryPhraseVerify.tsx index b27b7af6d87..fd6df030a74 100644 --- a/apps/extension/src/app/features/settings/SettingsRecoveryPhraseScreen/RemoveRecoveryPhraseVerify.tsx +++ b/apps/extension/src/app/features/settings/SettingsRecoveryPhraseScreen/RemoveRecoveryPhraseVerify.tsx @@ -8,6 +8,7 @@ import { SettingsRecoveryPhrase } from 'src/app/features/settings/SettingsRecove import { focusOrCreateOnboardingTab } from 'src/app/navigation/utils' import { Flex, LabeledCheckbox, Text, inputStyles } from 'ui/src' import { TrashFilled } from 'ui/src/components/icons' +import { setIsTestnetModeEnabled } from 'uniswap/src/features/settings/slice' import { WalletEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { logger } from 'utilities/src/logger/logger' @@ -58,6 +59,7 @@ export function RemoveRecoveryPhraseVerify(): JSX.Element { await Keyring.removePassword() await removeAllDappConnectionsFromExtension() + await dispatch(setIsTestnetModeEnabled(false)) await dispatch( editAccountActions.trigger({ diff --git a/apps/extension/src/app/features/settings/SettingsScreen.tsx b/apps/extension/src/app/features/settings/SettingsScreen.tsx index 3e9c7c6936b..ba2c905ed8f 100644 --- a/apps/extension/src/app/features/settings/SettingsScreen.tsx +++ b/apps/extension/src/app/features/settings/SettingsScreen.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { useDispatch } from 'react-redux' import { Link } from 'react-router-dom' @@ -38,9 +38,23 @@ import { uniswapUrls } from 'uniswap/src/constants/urls' import { resetUniswapBehaviorHistory } from 'uniswap/src/features/behaviorHistory/slice' import { FiatCurrency, ORDERED_CURRENCIES } from 'uniswap/src/features/fiatCurrency/constants' import { getFiatCurrencyName, useAppFiatCurrencyInfo } from 'uniswap/src/features/fiatCurrency/hooks' +import { FeatureFlags } from 'uniswap/src/features/gating/flags' +import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { useCurrentLanguageInfo } from 'uniswap/src/features/language/hooks' -import { useHideSmallBalancesSetting, useHideSpamTokensSetting } from 'uniswap/src/features/settings/hooks' -import { setCurrentFiatCurrency, setHideSmallBalances, setHideSpamTokens } from 'uniswap/src/features/settings/slice' +import { + useEnabledChains, + useHideSmallBalancesSetting, + useHideSpamTokensSetting, +} from 'uniswap/src/features/settings/hooks' +import { + setCurrentFiatCurrency, + setHideSmallBalances, + setHideSpamTokens, + setIsTestnetModeEnabled, +} from 'uniswap/src/features/settings/slice' +import { WalletEventName } from 'uniswap/src/features/telemetry/constants' +import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' +import { TestnetModeModal } from 'uniswap/src/features/testnets/TestnetModeModal' import { isDevEnv } from 'utilities/src/environment/env' import noop from 'utilities/src/react/noop' import { SettingsLanguageModal } from 'wallet/src/components/settings/language/SettingsLanguageModal' @@ -58,12 +72,15 @@ export function SettingsScreen(): JSX.Element { const appFiatCurrencyInfo = useAppFiatCurrencyInfo() const [isLanguageModalOpen, setIsLanguageModalOpen] = useState(false) + const [isTestnetModalOpen, setIsTestnetModalOpen] = useState(false) const onPressLockWallet = async (): Promise => { navigateBack() await dispatch(authActions.trigger({ type: AuthActionType.Lock })) } + // TODO(WALL-4908): consider wrapping handlers in useCallback + const hideSpamTokens = useHideSpamTokensSetting() const handleSpamTokensToggle = async (): Promise => { await dispatch(setHideSpamTokens(!hideSpamTokens)) @@ -74,9 +91,36 @@ export function SettingsScreen(): JSX.Element { await dispatch(setHideSmallBalances(!hideSmallBalances)) } + const isTestnetModeFlagEnabled = useFeatureFlag(FeatureFlags.TestnetMode) + const { isTestnetModeEnabled } = useEnabledChains() + const handleTestnetModeToggle = async (isChecked: boolean): Promise => { + const fireAnalytic = (): void => { + sendAnalyticsEvent(WalletEventName.TestnetModeToggled, { + enabled: isChecked, + }) + } + + // trigger before toggling on (ie disabling analytics) + if (isChecked) { + // doesn't fire on time without await and i have no idea why + await fireAnalytic() + } + + dispatch(setIsTestnetModeEnabled(isChecked)) + setIsTestnetModalOpen(isChecked) + + // trigger after toggling off (ie enabling analytics) + if (!isChecked) { + // updateState() + fireAnalytic() + } + } + const handleTestnetModalClose = useCallback(() => setIsTestnetModalOpen(false), []) + return ( <> {isLanguageModalOpen ? setIsLanguageModalOpen(false)} /> : undefined} + @@ -131,26 +175,38 @@ export function SettingsScreen(): JSX.Element { /> - navigateTo(`${AppRoutes.Settings}/${SettingsRoutes.ManageConnections}`)} - /> + navigateTo(`${AppRoutes.Settings}/${SettingsRoutes.ManageConnections}`)} + /> navigateTo(`${AppRoutes.Settings}/${SettingsRoutes.Privacy}`)} /> + <> + {isTestnetModeFlagEnabled && ( + + )} + @@ -250,11 +306,13 @@ function SettingsToggleRow({ Icon, title, checked, + disabled, onCheckedChange, }: { title: string Icon: GeneratedIcon checked: boolean + disabled?: boolean onCheckedChange: (checked: boolean) => void }): JSX.Element { return ( @@ -270,7 +328,7 @@ function SettingsToggleRow({ {title} - + ) } diff --git a/apps/extension/src/app/features/swap/SwapFlowScreen.tsx b/apps/extension/src/app/features/swap/SwapFlowScreen.tsx index fa626880567..cd4cf05d075 100644 --- a/apps/extension/src/app/features/swap/SwapFlowScreen.tsx +++ b/apps/extension/src/app/features/swap/SwapFlowScreen.tsx @@ -1,6 +1,7 @@ import { useExtensionNavigation } from 'src/app/navigation/utils' import { Flex } from 'ui/src' import { useHighestBalanceNativeCurrencyId } from 'uniswap/src/features/dataApi/balances' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { useSwapPrefilledState } from 'uniswap/src/features/transactions/swap/hooks/useSwapPrefilledState' import { prepareSwapFormState } from 'uniswap/src/features/transactions/types/transactionState' import { WalletSwapFlow } from 'wallet/src/features/transactions/swap/WalletSwapFlow' @@ -8,9 +9,10 @@ import { useActiveAccountWithThrow } from 'wallet/src/features/wallet/hooks' export function SwapFlowScreen(): JSX.Element { const { navigateBack, locationState } = useExtensionNavigation() + const { defaultChainId } = useEnabledChains() const account = useActiveAccountWithThrow() const inputCurrencyId = useHighestBalanceNativeCurrencyId(account.address) - const initialState = prepareSwapFormState({ inputCurrencyId }) + const initialState = prepareSwapFormState({ inputCurrencyId, defaultChainId }) const swapPrefilledState = useSwapPrefilledState(locationState?.initialTransactionState ?? initialState) diff --git a/apps/extension/src/app/navigation/SideBarNavigationProvider.tsx b/apps/extension/src/app/navigation/SideBarNavigationProvider.tsx index 307ab90de8f..cdb174b6a0d 100644 --- a/apps/extension/src/app/navigation/SideBarNavigationProvider.tsx +++ b/apps/extension/src/app/navigation/SideBarNavigationProvider.tsx @@ -5,9 +5,9 @@ import { useCopyToClipboard } from 'src/app/hooks/useOnCopyToClipboard' import { AppRoutes, HomeQueryParams, HomeTabs } from 'src/app/navigation/constants' import { navigate } from 'src/app/navigation/state' import { SidebarLocationState, focusOrCreateTokensExploreTab } from 'src/app/navigation/utils' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { WalletEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' -import { UniverseChainId } from 'uniswap/src/types/chains' import { ShareableEntity } from 'uniswap/src/types/sharing' import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking' import { logger } from 'utilities/src/logger/logger' @@ -156,13 +156,17 @@ function useNavigateToSend(): (args: NavigateToSendFlowArgs) => void { } function useNavigateToSwapFlow(): (args: NavigateToSwapFlowArgs) => void { - return useCallback((args: NavigateToSwapFlowArgs): void => { - const initialState = getNavigateToSwapFlowArgsInitialState(args) + const { defaultChainId } = useEnabledChains() + return useCallback( + (args: NavigateToSwapFlowArgs): void => { + const initialState = getNavigateToSwapFlowArgsInitialState(args, defaultChainId) - const state: SidebarLocationState = initialState ? { initialTransactionState: initialState } : undefined + const state: SidebarLocationState = initialState ? { initialTransactionState: initialState } : undefined - navigate(AppRoutes.Swap, { state }) - }, []) + navigate(AppRoutes.Swap, { state }) + }, + [defaultChainId], + ) } function useNavigateToTokenDetails(): (currencyId: string) => void { @@ -172,10 +176,14 @@ function useNavigateToTokenDetails(): (currencyId: string) => void { } function useNavigateToNftDetails(): (args: NavigateToNftItemArgs) => void { - return useCallback(({ address, tokenId, chainId }: NavigateToNftItemArgs): void => { - // eslint-disable-next-line security/detect-non-literal-fs-filename - window.open(getExplorerLink(chainId ?? UniverseChainId.Mainnet, `${address}/${tokenId}`, ExplorerDataType.NFT)) - }, []) + const { defaultChainId } = useEnabledChains() + return useCallback( + ({ address, tokenId, chainId }: NavigateToNftItemArgs): void => { + // eslint-disable-next-line security/detect-non-literal-fs-filename + window.open(getExplorerLink(chainId ?? defaultChainId, `${address}/${tokenId}`, ExplorerDataType.NFT)) + }, + [defaultChainId], + ) } function useNavigateToBuyOrReceiveWithEmptyWallet(): () => void { diff --git a/apps/extension/src/app/navigation/navigation.tsx b/apps/extension/src/app/navigation/navigation.tsx index 114e677cb15..510f72486c0 100644 --- a/apps/extension/src/app/navigation/navigation.tsx +++ b/apps/extension/src/app/navigation/navigation.tsx @@ -14,6 +14,7 @@ import { useRouterState } from 'src/app/navigation/state' import { focusOrCreateOnboardingTab } from 'src/app/navigation/utils' import { isOnboardedSelector } from 'src/app/utils/isOnboardedSelector' import { AnimatePresence, Flex, SpinningLoader, styled } from 'ui/src' +import { TestnetModeBanner } from 'uniswap/src/components/banners/TestnetModeBanner' import { useIsChromeWindowFocusedWithTimeout } from 'uniswap/src/extension/useIsChromeWindowFocused' import { useAsyncData, usePrevious } from 'utilities/src/react/hooks' import { ONE_SECOND_MS } from 'utilities/src/time/time' @@ -110,6 +111,7 @@ export function WebNavigation(): JSX.Element { ]} > + {isLoggedIn === null ? ( ) : isLoggedIn === true ? ( diff --git a/apps/extension/src/background/utils/getCalldataInfoFromTransaction.ts b/apps/extension/src/background/utils/getCalldataInfoFromTransaction.ts index 338f3a4a545..f4e13a8dbe2 100644 --- a/apps/extension/src/background/utils/getCalldataInfoFromTransaction.ts +++ b/apps/extension/src/background/utils/getCalldataInfoFromTransaction.ts @@ -1,9 +1,9 @@ -import { parseCalldata as parseURCalldata } from 'src/app/features/dappRequests/requestContent/EthSend/Swap/universalRouter' +import { CommandParser, UniversalRouterCall } from '@uniswap/universal-router-sdk' +import { V4BaseActionsParser, V4RouterCall } from '@uniswap/v4-sdk' import { EthSendTransactionRPCActions } from 'src/app/features/dappRequests/types/DappRequestTypes' import { EthersTransactionRequest } from 'src/app/features/dappRequests/types/EthersTypes' import { parseCalldata as parseNfPMCalldata } from 'src/app/features/dappRequests/types/NonfungiblePositionManager' import { NonfungiblePositionManagerCall } from 'src/app/features/dappRequests/types/NonfungiblePositionManagerTypes' -import { UniversalRouterCall } from 'src/app/features/dappRequests/types/UniversalRouterTypes' import methodHashToFunctionSignature from 'utilities/src/calldata/methodHashToFunctionSignature' import noop from 'utilities/src/react/noop' @@ -11,7 +11,7 @@ interface GetCalldataInfoFromTransactionReturnValue { functionSignature: string | undefined contractInteractions: EthSendTransactionRPCActions to: string | undefined - parsedCalldata?: UniversalRouterCall | NonfungiblePositionManagerCall + parsedCalldata?: V4RouterCall | UniversalRouterCall | NonfungiblePositionManagerCall } function getCalldataInfoFromTransaction( @@ -32,7 +32,19 @@ function getCalldataInfoFromTransaction( return result } try { - const URCalldata = parseURCalldata(transaction.data) + const v4Calldata = V4BaseActionsParser.parseCalldata(transaction.data) + + if (v4Calldata) { + result.contractInteractions = EthSendTransactionRPCActions.Swap + result.parsedCalldata = v4Calldata + return result + } + } catch (_e) { + noop() + } + try { + const URCalldata = CommandParser.parseCalldata(transaction.data) + if (URCalldata) { result.contractInteractions = EthSendTransactionRPCActions.Swap result.parsedCalldata = URCalldata diff --git a/apps/extension/src/contentScript/methodHandlers/ExtensionEthMethodHandler.ts b/apps/extension/src/contentScript/methodHandlers/ExtensionEthMethodHandler.ts index e62c7871e2a..54a4dbcee94 100644 --- a/apps/extension/src/contentScript/methodHandlers/ExtensionEthMethodHandler.ts +++ b/apps/extension/src/contentScript/methodHandlers/ExtensionEthMethodHandler.ts @@ -310,6 +310,7 @@ export class ExtensionEthMethodHandler extends BaseMethodHandler { + // TODO: WALL-4919: Remove hardcoded Mainnet // Defaults to mainnet for unconnected dapps const chainId = this.getChainId() ?? chainIdToHexadecimalString(UniverseChainId.Mainnet) diff --git a/apps/extension/src/manifest.json b/apps/extension/src/manifest.json index b382fb9e62a..cc23509098a 100644 --- a/apps/extension/src/manifest.json +++ b/apps/extension/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "Uniswap Extension", "description": "The Uniswap Extension is a self-custody crypto wallet that's built for swapping.", - "version": "1.7.0", + "version": "1.8.0", "minimum_chrome_version": "116", "icons": { "16": "assets/icon16.png", diff --git a/apps/mobile/android/app/build.gradle b/apps/mobile/android/app/build.gradle index 883b1405283..0ccf6a64a89 100644 --- a/apps/mobile/android/app/build.gradle +++ b/apps/mobile/android/app/build.gradle @@ -90,9 +90,9 @@ if (isCI && datadogPropertiesAvailable && !isDetox) { apply from: "../../../../node_modules/@datadog/mobile-react-native/datadog-sourcemaps.gradle" } -def devVersionName = "1.37" -def betaVersionName = "1.37" -def prodVersionName = "1.37" +def devVersionName = "1.38" +def betaVersionName = "1.38" +def prodVersionName = "1.38" android { ndkVersion rootProject.ext.ndkVersion diff --git a/apps/mobile/ios/Uniswap.xcodeproj/project.pbxproj b/apps/mobile/ios/Uniswap.xcodeproj/project.pbxproj index 2607152847c..4cf97a4a607 100644 --- a/apps/mobile/ios/Uniswap.xcodeproj/project.pbxproj +++ b/apps/mobile/ios/Uniswap.xcodeproj/project.pbxproj @@ -2167,7 +2167,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; @@ -2220,7 +2220,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore; @@ -2273,7 +2273,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore; @@ -2326,7 +2326,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCore; @@ -2364,7 +2364,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; @@ -2400,7 +2400,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests; @@ -2435,7 +2435,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests; @@ -2470,7 +2470,7 @@ GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = schemes.WidgetsCoreTests; @@ -2517,7 +2517,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; @@ -2563,7 +2563,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.widgets; @@ -2609,12 +2609,12 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.widgets; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.uniswap.mobile.dev.widgets"; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.uniswap.mobile.dev.widgets 1727888879"; SKIP_INSTALL = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; @@ -2655,7 +2655,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.widgets; @@ -2697,7 +2697,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; @@ -2740,7 +2740,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.WidgetIntentExtension; @@ -2783,12 +2783,12 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.WidgetIntentExtension; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.uniswap.mobile.dev.WidgetIntentExtension"; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.uniswap.mobile.dev.WidgetIntentExtension 1727888886"; SKIP_INSTALL = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; @@ -2826,7 +2826,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.WidgetIntentExtension; @@ -2862,7 +2862,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -2900,7 +2900,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -3078,7 +3078,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; @@ -3122,7 +3122,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.OneSignalNotificationServiceExtension; @@ -3222,7 +3222,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -3293,7 +3293,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.beta.OneSignalNotificationServiceExtension; @@ -3393,7 +3393,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -3464,12 +3464,12 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.37; + MARKETING_VERSION = 1.38; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.uniswap.mobile.dev.OneSignalNotificationServiceExtension; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.uniswap.mobile.dev.OneSignalNotificationServiceExtension"; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.uniswap.mobile.dev.OneSignalNotificationServiceExtension 1727888864"; SKIP_INSTALL = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/apps/mobile/ios/WidgetIntentExtension/IntentHandler.swift b/apps/mobile/ios/WidgetIntentExtension/IntentHandler.swift index 9fa75c6a2c3..d8e8bd62414 100644 --- a/apps/mobile/ios/WidgetIntentExtension/IntentHandler.swift +++ b/apps/mobile/ios/WidgetIntentExtension/IntentHandler.swift @@ -11,15 +11,15 @@ import OSLog class IntentHandler: INExtension, TokenPriceConfigurationIntentHandling { - + enum Section: String { case top = ".top" case favorite = ".favorite" case owned = ".owned" } - + lazy var ETHTokenResponse = TokenResponse(chain: WidgetConstants.ethereumChain, symbol: WidgetConstants.ethereumSymbol, name: "Ethereum") - + func tokenResponseToIntentToken(_ result: TokenResponse, section: Section) -> IntentToken { let intentToken: IntentToken = IntentToken(identifier: result.name + section.rawValue, display: "\(result.name)", subtitle: "\(result.symbol)", image: nil) intentToken.name = result.name @@ -28,9 +28,9 @@ class IntentHandler: INExtension, TokenPriceConfigurationIntentHandling { intentToken.chain = result.chain return intentToken } - + // Dedupes the tokens list and keeps the first instance of the token in the list. - // If there are two of the same tokens and 1 is mainnet, use the mainet token + // If there are two of the same tokens and 1 is mainnet, use the mainnet token func dedupeTokens(_ intentTokens: [IntentToken]) -> [IntentToken] { var dedupedTokens: [IntentToken] = [] for intentToken in intentTokens { @@ -44,16 +44,19 @@ class IntentHandler: INExtension, TokenPriceConfigurationIntentHandling { } return dedupedTokens } - + func provideSelectedTokenOptionsCollection(for intent: TokenPriceConfigurationIntent) async throws -> INObjectCollection { let favorites = UniswapUserDefaults.readFavorites() let addresses = UniswapUserDefaults.readAccounts().accounts.filter{$0.isSigner}.map{$0.address} - - async let pendingOwnedTokensResponses = try DataQueries.fetchWalletsTokensData(addresses: addresses) + // [WAll-4969] In the future should read chains from app state + let chains = ["ETHEREUM", "POLYGON", "ARBITRUM", "OPTIMISM", "BASE", "BNB", "BLAST", "ZORA", "CELO", "AVALANCHE", "ZKSYNC", "WORLDCHAIN"] + + + async let pendingOwnedTokensResponses = try DataQueries.fetchWalletsTokensData(addresses: addresses, chains: chains) async let pendingFavoriteTokenReponses = try DataQueries.fetchTokensData(tokenInputs: favorites.favorites) async let pendingTopTokensResponse = try DataQueries.fetchTopTokensData() let (ownedTokenResponses ,favoriteTokenReponses, topTokensResponse) = await (try pendingOwnedTokensResponses, try pendingFavoriteTokenReponses, try pendingTopTokensResponse) - + let ownedTokens = dedupeTokens(ownedTokenResponses.map {tokenResponseToIntentToken($0, section: Section.owned)}) let favoriteTokens = favoriteTokenReponses.map {tokenResponseToIntentToken($0, section: Section.favorite)} let topTokens = topTokensResponse.map { (result) -> IntentToken in @@ -63,20 +66,20 @@ class IntentHandler: INExtension, TokenPriceConfigurationIntentHandling { } return tokenResponseToIntentToken(result, section: Section.top) } - + let ownedSection = INObjectSection(title: "Your Tokens", items: ownedTokens) let favoriteSection = INObjectSection(title: "Favorite Tokens", items: favoriteTokens) let topTokensSection = INObjectSection(title: "Top Tokens", items: topTokens) - + return INObjectCollection(sections: [ownedSection, favoriteSection, topTokensSection]) } - + func defaultSelectedToken(for intent: TokenPriceConfigurationIntent) -> IntentToken? { return tokenResponseToIntentToken(ETHTokenResponse, section: Section.top) } - + override func handler(for intent: INIntent) -> Any { return self } - + } diff --git a/apps/mobile/ios/WidgetsCore/Utils/DataQueries.swift b/apps/mobile/ios/WidgetsCore/Utils/DataQueries.swift index d147fc40791..cb660a98fa3 100644 --- a/apps/mobile/ios/WidgetsCore/Utils/DataQueries.swift +++ b/apps/mobile/ios/WidgetsCore/Utils/DataQueries.swift @@ -97,9 +97,10 @@ public class DataQueries { } } - public static func fetchWalletsTokensData(addresses: [String], maxLength: Int = 25) async throws -> [TokenResponse] { + public static func fetchWalletsTokensData(addresses: [String], chains: [String], maxLength: Int = 25) async throws -> [TokenResponse] { + let gqlChains = chains.map { GraphQLEnum(MobileSchema.Chain(rawValue: $0)!) } return try await withCheckedThrowingContinuation { continuation in - Network.shared.apollo.fetch(query: MobileSchema.MultiplePortfolioBalancesQuery(ownerAddresses: addresses, valueModifiers: GraphQLNullable.null)){ result in + Network.shared.apollo.fetch(query: MobileSchema.MultiplePortfolioBalancesQuery(ownerAddresses: addresses, valueModifiers: GraphQLNullable.null, chains: gqlChains)){ result in switch result { case .success(let graphQLResult): // Takes all the signer accounts and sums up the balances of the tokens, then sorts them by descending order, ignoring spam @@ -124,16 +125,16 @@ public class DataQueries { } } } - + public static func fetchCurrencyConversion(toCurrency: String) async throws -> CurrencyConversionResponse { return try await withCheckedThrowingContinuation { continuation in let usdResponse = CurrencyConversionResponse(conversionRate: 1, currency: WidgetConstants.currencyUsd) - + // Assuming all server currency amounts are in USD if (toCurrency == WidgetConstants.currencyUsd) { return continuation.resume(returning: usdResponse) } - + Network.shared.apollo.fetch( query: MobileSchema.ConvertQuery( fromCurrency: GraphQLEnum(MobileSchema.Currency.usd), @@ -144,7 +145,7 @@ public class DataQueries { case .success(let graphQLResult): let conversionRate = graphQLResult.data?.convert?.value let currency = graphQLResult.data?.convert?.currency?.rawValue - + continuation.resume( returning: conversionRate == nil || currency == nil ? usdResponse : CurrencyConversionResponse( diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 65eb329c695..5791e66eeb2 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -86,9 +86,9 @@ "@shopify/react-native-skia": "1.2.0", "@sparkfabrik/react-native-idfa-aaid": "1.2.0", "@uniswap/analytics": "1.7.0", - "@uniswap/analytics-events": "2.37.0", + "@uniswap/analytics-events": "2.38.0", "@uniswap/ethers-rs-mobile": "0.0.5", - "@uniswap/sdk-core": "5.3.0", + "@uniswap/sdk-core": "5.8.0", "@walletconnect/core": "2.11.2", "@walletconnect/react-native-compat": "2.11.2", "@walletconnect/utils": "2.11.2", diff --git a/apps/mobile/src/app/App.tsx b/apps/mobile/src/app/App.tsx index cf8b22bdff5..10d9dcf1cb8 100644 --- a/apps/mobile/src/app/App.tsx +++ b/apps/mobile/src/app/App.tsx @@ -47,6 +47,7 @@ import { import { useAppStateTrigger } from 'src/utils/useAppStateTrigger' import { getSentryEnvironment, getSentryTracesSamplingRate, getStatsigEnvironmentTier } from 'src/utils/version' import { flexStyles, useHapticFeedback, useIsDarkMode } from 'ui/src' +import { TestnetModeBanner } from 'uniswap/src/components/banners/TestnetModeBanner' import { config } from 'uniswap/src/config' import { uniswapUrls } from 'uniswap/src/constants/urls' import { selectFavoriteTokens } from 'uniswap/src/features/favorites/selectors' @@ -78,6 +79,7 @@ import { useAsyncData } from 'utilities/src/react/hooks' import { AnalyticsNavigationContextProvider } from 'utilities/src/telemetry/trace/AnalyticsNavigationContext' import { ErrorBoundary } from 'wallet/src/components/ErrorBoundary/ErrorBoundary' import { selectAllowAnalytics } from 'wallet/src/features/telemetry/selectors' +import { useTestnetModeForLoggingAndAnalytics } from 'wallet/src/features/testnetMode/hooks' // eslint-disable-next-line no-restricted-imports import { usePersistedApolloClient } from 'wallet/src/data/apollo/usePersistedApolloClient' import { initFirebaseAppCheck } from 'wallet/src/features/appCheck/appCheck' @@ -359,6 +361,8 @@ function AppInner(): JSX.Element { const hapticsUserEnabled = useSelector(selectHapticsEnabled) const { setHapticsEnabled } = useHapticFeedback() + useTestnetModeForLoggingAndAnalytics() + // handles AppsFlyer enable/disable based on the allow analytics toggle useEffect(() => { if (allowAnalytics) { @@ -397,6 +401,7 @@ function AppInner(): JSX.Element { <> {openAIAssistantEnabled && } + diff --git a/apps/mobile/src/app/MobileWalletNavigationProvider.tsx b/apps/mobile/src/app/MobileWalletNavigationProvider.tsx index 4e528667203..d69d3001565 100644 --- a/apps/mobile/src/app/MobileWalletNavigationProvider.tsx +++ b/apps/mobile/src/app/MobileWalletNavigationProvider.tsx @@ -7,6 +7,7 @@ import { closeModal, openModal } from 'src/features/modals/modalSlice' import { HomeScreenTabIndex } from 'src/screens/HomeScreenTabIndex' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { ModalName, WalletEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { MobileScreens } from 'uniswap/src/types/screens/mobile' @@ -141,13 +142,14 @@ function useNavigateToSend(): (args: NavigateToSendFlowArgs) => void { function useNavigateToSwapFlow(): (args: NavigateToSwapFlowArgs) => void { const dispatch = useDispatch() + const { defaultChainId } = useEnabledChains() return useCallback( (args: NavigateToSwapFlowArgs): void => { - const initialState = getNavigateToSwapFlowArgsInitialState(args) + const initialState = getNavigateToSwapFlowArgsInitialState(args, defaultChainId) dispatch(closeModal({ name: ModalName.Swap })) dispatch(openModal({ name: ModalName.Swap, initialState })) }, - [dispatch], + [dispatch, defaultChainId], ) } diff --git a/apps/mobile/src/app/migrations.test.ts b/apps/mobile/src/app/migrations.test.ts index dd24c2be003..58c6c9edf99 100644 --- a/apps/mobile/src/app/migrations.test.ts +++ b/apps/mobile/src/app/migrations.test.ts @@ -102,7 +102,7 @@ import { initialTokensState } from 'uniswap/src/features/tokens/slice/slice' import { initialTransactionsState } from 'uniswap/src/features/transactions/slice' import { TransactionStatus, TransactionType } from 'uniswap/src/features/transactions/types/transactionDetails' import { transactionDetails } from 'uniswap/src/test/fixtures' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { getAllKeysOfNestedObject } from 'utilities/src/primitives/objects' import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants' import { initialAppearanceSettingsState } from 'wallet/src/features/appearance/slice' @@ -264,7 +264,7 @@ describe('Redux state migrations', () => { } const txDetails1 = { - chainId: UniverseChainId.Goerli, + chainId: UniverseChainId.Optimism, id: '1', from: '0xKingHodler', options: { @@ -294,7 +294,7 @@ describe('Redux state migrations', () => { [UniverseChainId.Mainnet]: { '0': txDetails0, }, - [UniverseChainId.Goerli]: { + [UniverseChainId.Optimism]: { '1': txDetails1, }, }, @@ -313,8 +313,8 @@ describe('Redux state migrations', () => { TransactionStatus.Pending, ) expect(newSchema.transactions['0xKingHodler'][UniverseChainId.Mainnet]).toBeUndefined() - expect(newSchema.transactions['0xKingHodler'][UniverseChainId.Goerli]['0']).toBeUndefined() - expect(newSchema.transactions['0xKingHodler'][UniverseChainId.Goerli]['1'].from).toEqual('0xKingHodler') + expect(newSchema.transactions['0xKingHodler'][UniverseChainId.Optimism]['0']).toBeUndefined() + expect(newSchema.transactions['0xKingHodler'][UniverseChainId.Optimism]['1'].from).toEqual('0xKingHodler') expect(newSchema.notifications.lastTxNotificationUpdate).toBeDefined() expect(newSchema.notifications.lastTxNotificationUpdate['0xShadowySuperCoder'][UniverseChainId.Mainnet]).toEqual( @@ -681,6 +681,11 @@ describe('Redux state migrations', () => { }) it('migrates from v18 to v19', () => { + const ROPSTEN = 3 as UniverseChainId + const RINKEBY = 4 as UniverseChainId + const GOERLI = 5 as UniverseChainId + const KOVAN = 42 as UniverseChainId + const TEST_ADDRESS = '0xShadowySuperCoder' const txDetails0 = { chainId: UniverseChainId.Mainnet, @@ -708,7 +713,7 @@ describe('Redux state migrations', () => { const TEST_ADDRESS_2 = '0xKingHodler' const txDetails1 = { - chainId: UniverseChainId.Goerli, + chainId: GOERLI, id: '1', from: TEST_ADDRESS_2, options: { @@ -731,16 +736,16 @@ describe('Redux state migrations', () => { hash: '0x123', } - const ROPSTEN = 3 as WalletChainId - const RINKEBY = 4 as WalletChainId - const KOVAN = 42 as WalletChainId - const transactions = { [TEST_ADDRESS]: { [UniverseChainId.Mainnet]: { '0': txDetails0, }, - [UniverseChainId.Goerli]: { + [UniverseChainId.Base]: { + '0': txDetails0, + '1': txDetails1, + }, + [GOERLI]: { '0': txDetails0, '1': txDetails1, }, @@ -779,22 +784,26 @@ describe('Redux state migrations', () => { const blocks = { byChainId: { [UniverseChainId.Mainnet]: { latestBlockNumber: 123456789 }, - [UniverseChainId.Goerli]: { latestBlockNumber: 123456789 }, + [UniverseChainId.Optimism]: { latestBlockNumber: 123456789 }, + [UniverseChainId.ArbitrumOne]: { latestBlockNumber: 123456789 }, + [UniverseChainId.Base]: { latestBlockNumber: 123456789 }, + [GOERLI]: { latestBlockNumber: 123456789 }, [ROPSTEN]: { latestBlockNumber: 123456789 }, [RINKEBY]: { latestBlockNumber: 123456789 }, [KOVAN]: { latestBlockNumber: 123456789 }, - [UniverseChainId.Optimism]: { latestBlockNumber: 123456789 }, }, } const chains = { byChainId: { + [UniverseChainId.Mainnet]: { isActive: true }, + [UniverseChainId.Optimism]: { isActive: true }, [UniverseChainId.ArbitrumOne]: { isActive: true }, - [UniverseChainId.Goerli]: { isActive: true }, + [UniverseChainId.Base]: { isActive: true }, + [GOERLI]: { isActive: true }, [ROPSTEN]: { isActive: true }, [RINKEBY]: { isActive: true }, [KOVAN]: { isActive: true }, - [UniverseChainId.Optimism]: { isActive: true }, }, } @@ -808,7 +817,8 @@ describe('Redux state migrations', () => { const v19 = migrations[19](v18Stub) expect(v19.transactions[TEST_ADDRESS][UniverseChainId.Mainnet]).toBeDefined() - expect(v19.transactions[TEST_ADDRESS][UniverseChainId.Goerli]).toBeDefined() + expect(v19.transactions[TEST_ADDRESS][UniverseChainId.Base]).toBeDefined() + expect(v19.transactions[TEST_ADDRESS][GOERLI]).toBeUndefined() expect(v19.transactions[TEST_ADDRESS][ROPSTEN]).toBeUndefined() expect(v19.transactions[TEST_ADDRESS][RINKEBY]).toBeUndefined() expect(v19.transactions[TEST_ADDRESS][KOVAN]).toBeUndefined() @@ -820,15 +830,19 @@ describe('Redux state migrations', () => { expect(v19.transactions[TEST_ADDRESS_2][KOVAN]).toBeUndefined() expect(v19.blocks.byChainId[UniverseChainId.Mainnet]).toBeDefined() - expect(v19.blocks.byChainId[UniverseChainId.Goerli]).toBeDefined() expect(v19.blocks.byChainId[UniverseChainId.Optimism]).toBeDefined() + expect(v19.blocks.byChainId[UniverseChainId.ArbitrumOne]).toBeDefined() + expect(v19.blocks.byChainId[UniverseChainId.Base]).toBeDefined() + expect(v19.blocks.byChainId[GOERLI]).toBeUndefined() expect(v19.blocks.byChainId[ROPSTEN]).toBeUndefined() expect(v19.blocks.byChainId[RINKEBY]).toBeUndefined() expect(v19.blocks.byChainId[KOVAN]).toBeUndefined() - expect(v19.chains.byChainId[UniverseChainId.ArbitrumOne]).toBeDefined() - expect(v19.chains.byChainId[UniverseChainId.Goerli]).toBeDefined() + expect(v19.chains.byChainId[UniverseChainId.Mainnet]).toBeDefined() expect(v19.chains.byChainId[UniverseChainId.Optimism]).toBeDefined() + expect(v19.chains.byChainId[UniverseChainId.ArbitrumOne]).toBeDefined() + expect(v19.chains.byChainId[UniverseChainId.Base]).toBeDefined() + expect(v19.chains.byChainId[GOERLI]).toBeUndefined() expect(v19.chains.byChainId[ROPSTEN]).toBeUndefined() expect(v19.chains.byChainId[RINKEBY]).toBeUndefined() expect(v19.chains.byChainId[KOVAN]).toBeUndefined() @@ -977,7 +991,7 @@ describe('Redux state migrations', () => { '0': oldFiatOnRampTxDetails, '1': txDetailsConfirmed, }, - [UniverseChainId.Goerli]: { + [UniverseChainId.Base]: { '0': { ...oldFiatOnRampTxDetails, status: TransactionStatus.Failed }, '1': txDetailsConfirmed, }, @@ -1008,7 +1022,7 @@ describe('Redux state migrations', () => { // expect fiat onramp txdetails to change expect(v30.transactions[account.address][UniverseChainId.Mainnet]['0'].typeInfo).toEqual(expectedTypeInfo) - expect(v30.transactions[account.address][UniverseChainId.Goerli]['0']).toBeUndefined() + expect(v30.transactions[account.address][UniverseChainId.Base]['0']).toBeUndefined() expect(v30.transactions[account.address][UniverseChainId.ArbitrumOne]).toBeUndefined() // does not create an object for chain expect(v30.transactions['0xshadowySuperCoder'][UniverseChainId.ArbitrumOne]['0'].typeInfo).toEqual(expectedTypeInfo) expect(v30.transactions['0xshadowySuperCoder'][UniverseChainId.Optimism]['0'].typeInfo).toEqual(expectedTypeInfo) @@ -1016,7 +1030,7 @@ describe('Redux state migrations', () => { expect(v30.transactions['0xdeleteMe']).toBe(undefined) // expect non-for txDetails to not change expect(v30.transactions[account.address][UniverseChainId.Mainnet]['1']).toEqual(txDetailsConfirmed) - expect(v30.transactions[account.address][UniverseChainId.Goerli]['1']).toEqual(txDetailsConfirmed) + expect(v30.transactions[account.address][UniverseChainId.Base]['1']).toEqual(txDetailsConfirmed) expect(v30.transactions['0xshadowySuperCoder'][UniverseChainId.ArbitrumOne]['1']).toEqual(txDetailsConfirmed) expect(v30.transactions['0xshadowySuperCoder'][UniverseChainId.Optimism]['2']).toEqual(txDetailsConfirmed) }) @@ -1508,7 +1522,7 @@ describe('Redux state migrations', () => { '0': oldFiatOnRampTxDetails, '1': txDetailsConfirmed, }, - [UniverseChainId.Goerli]: { + [UniverseChainId.Optimism]: { '0': oldFiatOnRampTxDetails, '1': { ...oldFiatOnRampTxDetails, @@ -1535,16 +1549,16 @@ describe('Redux state migrations', () => { expect(v74.transactions[account.address][UniverseChainId.Mainnet]['0']).toBe(undefined) expect(v74.transactions[account.address][UniverseChainId.Mainnet]['1']).toEqual(txDetailsConfirmed) - expect(v74.transactions[account.address][UniverseChainId.Goerli]['0']).toBe(undefined) - expect(v74.transactions[account.address][UniverseChainId.Goerli]['1'].typeInfo).toEqual({ + expect(v74.transactions[account.address][UniverseChainId.Optimism]['0']).toBe(undefined) + expect(v74.transactions[account.address][UniverseChainId.Optimism]['1'].typeInfo).toEqual({ ...oldFiatOnRampTxDetails.typeInfo, type: TransactionType.Send, }) - expect(v74.transactions[account.address][UniverseChainId.Goerli]['2'].typeInfo).toEqual({ + expect(v74.transactions[account.address][UniverseChainId.Optimism]['2'].typeInfo).toEqual({ ...oldFiatOnRampTxDetails.typeInfo, type: TransactionType.Receive, }) - expect(v74.transactions[account.address][UniverseChainId.Goerli]['3']).toEqual(txDetailsConfirmed) + expect(v74.transactions[account.address][UniverseChainId.Optimism]['3']).toEqual(txDetailsConfirmed) }) it('migrates from v74 to v75', () => { diff --git a/apps/mobile/src/app/migrations.ts b/apps/mobile/src/app/migrations.ts index 66ce24dc095..6291a472718 100644 --- a/apps/mobile/src/app/migrations.ts +++ b/apps/mobile/src/app/migrations.ts @@ -15,7 +15,7 @@ import { TransactionStatus, TransactionType, } from 'uniswap/src/features/transactions/types/transactionDetails' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { getNFTAssetKey } from 'wallet/src/features/nfts/utils' import { Account } from 'wallet/src/features/wallet/accounts/types' import { SwapProtectionSetting } from 'wallet/src/features/wallet/slice' @@ -264,11 +264,11 @@ export const migrations = { const chainState: | { - byChainId: Partial> + byChainId: Partial> } | undefined = newState?.chains const newChainState = Object.keys(chainState?.byChainId ?? {}).reduce<{ - byChainId: Partial> + byChainId: Partial> }>( (tempState, chainIdString) => { const chainId = toSupportedChainId(chainIdString) diff --git a/apps/mobile/src/app/modals/AccountSwitcherModal.tsx b/apps/mobile/src/app/modals/AccountSwitcherModal.tsx index 844744c5fa7..f735fde063e 100644 --- a/apps/mobile/src/app/modals/AccountSwitcherModal.tsx +++ b/apps/mobile/src/app/modals/AccountSwitcherModal.tsx @@ -8,7 +8,7 @@ import { AccountList } from 'src/components/accounts/AccountList' import { isCloudStorageAvailable } from 'src/features/CloudBackup/RNCloudStorageBackupsManager' import { closeModal, openModal } from 'src/features/modals/modalSlice' import { selectModalState } from 'src/features/modals/selectModalState' -import { Button, Flex, Text, TouchableArea, useDeviceInsets, useSporeColors } from 'ui/src' +import { Button, Flex, Text, TouchableArea, useSporeColors } from 'ui/src' import { useDeviceDimensions } from 'ui/src/hooks/useDeviceDimensions' import { spacing } from 'ui/src/theme' import { ActionSheetModal, MenuItemProp } from 'uniswap/src/components/modals/ActionSheetModal' @@ -16,6 +16,7 @@ import { Modal } from 'uniswap/src/components/modals/Modal' import { AccountType } from 'uniswap/src/features/accounts/types' import { ElementName, ModalName, WalletEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding' import { MobileScreens, OnboardingScreens } from 'uniswap/src/types/screens/mobile' @@ -56,7 +57,7 @@ export function AccountSwitcherModal(): JSX.Element { * TODO [MOB-259] Once testing works with the Modal stop exporting this component. */ export function AccountSwitcher({ onClose }: { onClose: () => void }): JSX.Element | null { - const insets = useDeviceInsets() + const insets = useAppInsets() const dimensions = useDeviceDimensions() const { t } = useTranslation() const activeAccountAddress = useActiveAccountAddress() diff --git a/apps/mobile/src/app/modals/ExperimentsModal.tsx b/apps/mobile/src/app/modals/ExperimentsModal.tsx index 2b5654a16dc..e15d32c7f5a 100644 --- a/apps/mobile/src/app/modals/ExperimentsModal.tsx +++ b/apps/mobile/src/app/modals/ExperimentsModal.tsx @@ -6,15 +6,16 @@ import { Action } from 'redux' import { closeModal } from 'src/features/modals/modalSlice' import { selectCustomEndpoint } from 'src/features/tweaks/selectors' import { setCustomEndpoint } from 'src/features/tweaks/slice' -import { Accordion, Button, Flex, Text, useDeviceInsets } from 'ui/src' +import { Accordion, Button, Flex, Text } from 'ui/src' import { spacing } from 'ui/src/theme' import { TextInput } from 'uniswap/src/components/input/TextInput' import { Modal } from 'uniswap/src/components/modals/Modal' import { ModalName } from 'uniswap/src/features/telemetry/constants' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { AccordionHeader, GatingOverrides } from 'wallet/src/components/gating/GatingOverrides' export function ExperimentsModal(): JSX.Element { - const insets = useDeviceInsets() + const insets = useAppInsets() const dispatch = useDispatch() const customEndpoint = useSelector(selectCustomEndpoint) diff --git a/apps/mobile/src/app/navigation/NavBar.tsx b/apps/mobile/src/app/navigation/NavBar.tsx index 982bfe45bac..a0016687595 100644 --- a/apps/mobile/src/app/navigation/NavBar.tsx +++ b/apps/mobile/src/app/navigation/NavBar.tsx @@ -21,7 +21,6 @@ import { LinearGradient, Text, TouchableArea, - useDeviceInsets, useHapticFeedback, useIsDarkMode, useSporeColors, @@ -30,9 +29,12 @@ import { Search } from 'ui/src/components/icons' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { borderRadii, fonts, opacify } from 'ui/src/theme' import { useHighestBalanceNativeCurrencyId } from 'uniswap/src/features/dataApi/balances' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { ElementName, ModalName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' +import { TestnetModeModal } from 'uniswap/src/features/testnets/TestnetModeModal' import { prepareSwapFormState } from 'uniswap/src/features/transactions/types/transactionState' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { isAndroid, isIOS } from 'utilities/src/platform' @@ -56,7 +58,7 @@ function sendSwapPressAnalyticsEvent(): void { } export function NavBar(): JSX.Element { - const insets = useDeviceInsets() + const insets = useAppInsets() const { width: screenWidth } = useSafeAreaFrame() const [isNarrow, setIsNarrow] = useState(false) const [exploreButtonLayout, setExploreButtonLayout] = useState(null) @@ -133,6 +135,7 @@ const SwapFAB = memo(function _SwapFAB({ activeScale = 0.96, onSwapLayout }: Swa const { t } = useTranslation() const dispatch = useDispatch() const { hapticFeedback } = useHapticFeedback() + const { defaultChainId } = useEnabledChains() const isDarkMode = useIsDarkMode() const activeAccountAddress = useActiveAccountAddressWithThrow() @@ -142,12 +145,12 @@ const SwapFAB = memo(function _SwapFAB({ activeScale = 0.96, onSwapLayout }: Swa dispatch( openModal({ name: ModalName.Swap, - initialState: prepareSwapFormState({ inputCurrencyId }), + initialState: prepareSwapFormState({ inputCurrencyId, defaultChainId }), }), ) await hapticFeedback.impact() - }, [dispatch, inputCurrencyId, hapticFeedback]) + }, [dispatch, inputCurrencyId, hapticFeedback, defaultChainId]) const scale = useSharedValue(1) const animatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }] }), [scale]) @@ -213,8 +216,18 @@ function ExploreTabBarButton({ activeScale = 0.98, onLayout, isNarrow }: Explore const colors = useSporeColors() const isDarkMode = useIsDarkMode() const { t } = useTranslation() + const { isTestnetModeEnabled } = useEnabledChains() + const [isTestnetWarningModalOpen, setIsTestnetWarningModalOpen] = useState(false) + + const handleTestnetWarningModalClose = useCallback(() => { + setIsTestnetWarningModalOpen(false) + }, []) const onPress = (): void => { + if (isTestnetModeEnabled) { + setIsTestnetWarningModalOpen(true) + return + } dispatch(openModal({ name: ModalName.Explore })) dispatch(setHasUsedExplore(true)) } @@ -256,8 +269,8 @@ function ExploreTabBarButton({ activeScale = 0.98, onLayout, isNarrow }: Explore hapticFeedback activeOpacity={1} style={[styles.searchBar, { borderRadius: borderRadii.roundedFull }]} - onPress={onPress} > + { await load({ variables: { address, + chains: gqlChains, }, }) }, - [load], + [gqlChains, load], ) const navigate = useCallback( @@ -47,12 +50,13 @@ export function useEagerExternalProfileNavigation(): { const navigation = useExploreStackNavigation() const [load] = useTransactionListLazyQuery() + const { gqlChains } = useEnabledChains() const preload = useCallback( async (address: string) => { - await load({ variables: { address } }) + await load({ variables: { address, chains: gqlChains } }) }, - [load], + [gqlChains, load], ) const navigate = useCallback( @@ -70,16 +74,18 @@ export function useEagerExternalProfileRootNavigation(): { navigate: (address: string, callback: () => void) => Promise } { const [load] = useTransactionListLazyQuery() + const { gqlChains } = useEnabledChains() const preload = useCallback( async (address: string) => { await load({ variables: { address, + chains: gqlChains, }, }) }, - [load], + [gqlChains, load], ) const navigate = useCallback(async (address: string, callback?: () => void) => { diff --git a/apps/mobile/src/app/navigation/navigation.tsx b/apps/mobile/src/app/navigation/navigation.tsx index bc2738b3c12..01c2a3bea9d 100644 --- a/apps/mobile/src/app/navigation/navigation.tsx +++ b/apps/mobile/src/app/navigation/navigation.tsx @@ -62,10 +62,11 @@ import { SettingsWalletEdit } from 'src/screens/SettingsWalletEdit' import { SettingsWalletManageConnection } from 'src/screens/SettingsWalletManageConnection' import { TokenDetailsScreen } from 'src/screens/TokenDetailsScreen' import { WebViewScreen } from 'src/screens/WebViewScreen' -import { useDeviceInsets, useSporeColors } from 'ui/src' +import { useSporeColors } from 'ui/src' import { spacing } from 'ui/src/theme' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { OnboardingEntryPoint } from 'uniswap/src/types/onboarding' import { FiatOnRampScreens, @@ -289,7 +290,7 @@ export function OnboardingStackNavigator(): JSX.Element { export function UnitagStackNavigator(): JSX.Element { const colors = useSporeColors() - const insets = useDeviceInsets() + const insets = useAppInsets() return ( diff --git a/apps/mobile/src/components/PriceExplorer/PriceExplorer.tsx b/apps/mobile/src/components/PriceExplorer/PriceExplorer.tsx index 1095cdccaec..c24d7d0c63c 100644 --- a/apps/mobile/src/components/PriceExplorer/PriceExplorer.tsx +++ b/apps/mobile/src/components/PriceExplorer/PriceExplorer.tsx @@ -1,4 +1,4 @@ -import { PropsWithChildren, ReactElement, memo, useMemo } from 'react' +import React, { PropsWithChildren, ReactElement, memo, useMemo } from 'react' import { I18nManager } from 'react-native' import { SharedValue, useDerivedValue } from 'react-native-reanimated' import { LineChart, LineChartProvider } from 'react-native-wagmi-charts' @@ -11,10 +11,12 @@ import { useLineChartPrice } from 'src/components/PriceExplorer/usePrice' import { PriceNumberOfDigits, TokenSpotData, useTokenPriceHistory } from 'src/components/PriceExplorer/usePriceHistory' import { Loader } from 'src/components/loading/loaders' import { Flex, SegmentedControl, Text, useHapticFeedback } from 'ui/src' +import GraphCurve from 'ui/src/assets/backgrounds/graph-curve.svg' import { spacing } from 'ui/src/theme' import { HistoryDuration } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { useAppFiatCurrencyInfo } from 'uniswap/src/features/fiatCurrency/hooks' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import Trace from 'uniswap/src/features/telemetry/Trace' import { ElementNameType } from 'uniswap/src/features/telemetry/constants' import { TestID } from 'uniswap/src/test/fixtures/testIDs' @@ -72,6 +74,9 @@ export const PriceExplorer = memo(function PriceExplorer({ useTokenPriceHistory(currencyId) const { hapticFeedback } = useHapticFeedback() + const { isTestnetModeEnabled } = useEnabledChains() + const { chartHeight, chartWidth } = useChartDimensions() + const { convertFiatAmount } = useLocalizationContext() const conversionRate = convertFiatAmount(1).amount const shouldShowAnimatedDot = @@ -98,6 +103,10 @@ export const PriceExplorer = memo(function PriceExplorer({ ) }, [data, convertedSpotValue]) + if (isTestnetModeEnabled) { + return + } + if (!loading && (!convertedPriceHistory || (!convertedSpot && selectedDuration === HistoryDuration.Day))) { // Propagate retry up while refetching, if available const refetchAndRetry = (): void => { diff --git a/apps/mobile/src/components/RecipientSelect/RecipientSelect.tsx b/apps/mobile/src/components/RecipientSelect/RecipientSelect.tsx index 68778e6af54..d8c4507a7d9 100644 --- a/apps/mobile/src/components/RecipientSelect/RecipientSelect.tsx +++ b/apps/mobile/src/components/RecipientSelect/RecipientSelect.tsx @@ -8,7 +8,7 @@ import ScanQRIcon from 'ui/src/assets/icons/scan.svg' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { iconSizes } from 'ui/src/theme' import { TestID } from 'uniswap/src/test/fixtures/testIDs' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { dismissNativeKeyboard } from 'utilities/src/device/keyboard' import { RecipientList } from 'wallet/src/components/RecipientSearch/RecipientList' import { RecipientSelectSpeedBumps } from 'wallet/src/components/RecipientSearch/RecipientSelectSpeedBumps' @@ -20,7 +20,7 @@ interface RecipientSelectProps { onHideRecipientSelector: () => void recipient?: string focusInput?: boolean - chainId?: WalletChainId + chainId?: UniverseChainId renderedInModal?: boolean hideBackButton?: boolean } diff --git a/apps/mobile/src/components/RecipientSelect/hooks.test.ts b/apps/mobile/src/components/RecipientSelect/hooks.test.ts index ccf0e8893aa..adab8fb57a0 100644 --- a/apps/mobile/src/components/RecipientSelect/hooks.test.ts +++ b/apps/mobile/src/components/RecipientSelect/hooks.test.ts @@ -12,7 +12,7 @@ import { sendTokenTransactionInfo, transactionDetails, } from 'uniswap/src/test/fixtures' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { useRecipients } from 'wallet/src/components/RecipientSearch/hooks' import { SwapProtectionSetting } from 'wallet/src/features/wallet/slice' import { signerMnemonicAccount } from 'wallet/src/test/fixtures' @@ -219,9 +219,9 @@ describe(useRecipients, () => { preloadedState: getPreloadedState({ transactions: { [activeAccount.address]: { - [UniverseChainId.Base as WalletChainId]: [sendTxDetailsPending, sendTxDetailsConfirmed], - [UniverseChainId.Mainnet as WalletChainId]: [sendTxDetailsConfirmed, sendTxDetailsFailed], - [UniverseChainId.Bnb as WalletChainId]: [sendTxDetailsPending, sendTxDetailsConfirmed], + [UniverseChainId.Base as UniverseChainId]: [sendTxDetailsPending, sendTxDetailsConfirmed], + [UniverseChainId.Mainnet as UniverseChainId]: [sendTxDetailsConfirmed, sendTxDetailsFailed], + [UniverseChainId.Bnb as UniverseChainId]: [sendTxDetailsPending, sendTxDetailsConfirmed], }, }, }), @@ -271,9 +271,9 @@ describe(useRecipients, () => { preloadedState: getPreloadedState({ transactions: { [activeAccount.address]: { - [UniverseChainId.Base as WalletChainId]: [sendTxDetailsPending, sendTxDetailsConfirmed], - [UniverseChainId.Mainnet as WalletChainId]: [sendTxDetailsConfirmed, sendTxDetailsFailed], - [UniverseChainId.Bnb as WalletChainId]: [sendTxDetailsPending, sendTxDetailsConfirmed], + [UniverseChainId.Base as UniverseChainId]: [sendTxDetailsPending, sendTxDetailsConfirmed], + [UniverseChainId.Mainnet as UniverseChainId]: [sendTxDetailsConfirmed, sendTxDetailsFailed], + [UniverseChainId.Bnb as UniverseChainId]: [sendTxDetailsPending, sendTxDetailsConfirmed], }, }, }), @@ -363,9 +363,9 @@ describe(useRecipients, () => { hasInactiveAccounts: true, transactions: { [activeAccount.address]: { - [UniverseChainId.Base as WalletChainId]: [sendTxDetailsPending, sendTxDetailsConfirmed], - [UniverseChainId.Mainnet as WalletChainId]: [sendTxDetailsConfirmed, sendTxDetailsFailed], - [UniverseChainId.Bnb as WalletChainId]: [sendTxDetailsPending, sendTxDetailsConfirmed], + [UniverseChainId.Base as UniverseChainId]: [sendTxDetailsPending, sendTxDetailsConfirmed], + [UniverseChainId.Mainnet as UniverseChainId]: [sendTxDetailsConfirmed, sendTxDetailsFailed], + [UniverseChainId.Bnb as UniverseChainId]: [sendTxDetailsPending, sendTxDetailsConfirmed], }, }, }), @@ -393,9 +393,9 @@ describe(useRecipients, () => { hasInactiveAccounts: true, transactions: { [activeAccount.address]: { - [UniverseChainId.Base as WalletChainId]: [sendTxDetailsPending, sendTxDetailsConfirmed], - [UniverseChainId.Mainnet as WalletChainId]: [sendTxDetailsConfirmed, sendTxDetailsFailed], - [UniverseChainId.Bnb as WalletChainId]: [sendTxDetailsPending, sendTxDetailsConfirmed], + [UniverseChainId.Base as UniverseChainId]: [sendTxDetailsPending, sendTxDetailsConfirmed], + [UniverseChainId.Mainnet as UniverseChainId]: [sendTxDetailsConfirmed, sendTxDetailsFailed], + [UniverseChainId.Bnb as UniverseChainId]: [sendTxDetailsPending, sendTxDetailsConfirmed], }, }, }), diff --git a/apps/mobile/src/components/RemoveWallet/RemoveWalletModal.tsx b/apps/mobile/src/components/RemoveWallet/RemoveWalletModal.tsx index 67eb5be575b..b8a17caaa83 100644 --- a/apps/mobile/src/components/RemoveWallet/RemoveWalletModal.tsx +++ b/apps/mobile/src/components/RemoveWallet/RemoveWalletModal.tsx @@ -15,6 +15,7 @@ import { Button, Flex, SpinningLoader, Text, ThemeKeys, useSporeColors } from 'u import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { iconSizes, opacify } from 'ui/src/theme' import { Modal } from 'uniswap/src/components/modals/Modal' +import { setIsTestnetModeEnabled } from 'uniswap/src/features/settings/slice' import { ElementName, ModalName, WalletEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding' @@ -61,6 +62,7 @@ export function RemoveWalletModal(): JSX.Element | null { if (!hasAccountsLeft) { // user has no accounts left, so we bring onboarding back dispatch(setFinishedOnboarding({ finishedOnboarding: false })) + dispatch(setIsTestnetModeEnabled(false)) navigateToOnboardingImportMethod() } else if (isReplacing) { // there are account left and it's replacing, user has view-only accounts left diff --git a/apps/mobile/src/components/Requests/ConnectedDapps/DappConnectionItem.tsx b/apps/mobile/src/components/Requests/ConnectedDapps/DappConnectionItem.tsx index 8335d4db240..68b40364cb8 100644 --- a/apps/mobile/src/components/Requests/ConnectedDapps/DappConnectionItem.tsx +++ b/apps/mobile/src/components/Requests/ConnectedDapps/DappConnectionItem.tsx @@ -128,14 +128,7 @@ export function DappConnectionItem({ onLongPress={disableOnPress} onPress={(): void => onPressChangeNetwork(session)} > - + diff --git a/apps/mobile/src/components/Requests/ModalWithOverlay/ModalWithOverlay.tsx b/apps/mobile/src/components/Requests/ModalWithOverlay/ModalWithOverlay.tsx index 6955772abb9..19fb3b64159 100644 --- a/apps/mobile/src/components/Requests/ModalWithOverlay/ModalWithOverlay.tsx +++ b/apps/mobile/src/components/Requests/ModalWithOverlay/ModalWithOverlay.tsx @@ -13,10 +13,11 @@ import { } from 'react-native' import { AnimatedStyle, useDerivedValue } from 'react-native-reanimated' import { ScrollDownOverlay } from 'src/components/Requests/ModalWithOverlay/ScrollDownOverlay' -import { Button, Flex, useDeviceInsets } from 'ui/src' +import { Button, Flex } from 'ui/src' import { spacing } from 'ui/src/theme' import { Modal } from 'uniswap/src/components/modals/Modal' import { ModalProps } from 'uniswap/src/components/modals/ModalProps' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { TestID } from 'uniswap/src/test/fixtures/testIDs' const MEASURE_LAYOUT_TIMEOUT = 100 @@ -156,7 +157,7 @@ function ModalFooter({ onConfirm, }: ModalFooterProps): JSX.Element { const { t } = useTranslation() - const insets = useDeviceInsets() + const insets = useAppInsets() const { animatedPosition, animatedHandleHeight, animatedFooterHeight, animatedContainerHeight } = useBottomSheetInternal() diff --git a/apps/mobile/src/components/Requests/RequestModal/RequestDetails.tsx b/apps/mobile/src/components/Requests/RequestModal/RequestDetails.tsx index b5f9f121d44..77e0e99d452 100644 --- a/apps/mobile/src/components/Requests/RequestModal/RequestDetails.tsx +++ b/apps/mobile/src/components/Requests/RequestModal/RequestDetails.tsx @@ -8,7 +8,8 @@ import { Flex, Text, useSporeColors } from 'ui/src' import { TextVariantTokens, iconSizes } from 'ui/src/theme' import { toSupportedChainId } from 'uniswap/src/features/chains/utils' import { useENS } from 'uniswap/src/features/ens/useENS' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' +import { UniverseChainId } from 'uniswap/src/types/chains' import { EthMethod, EthTransaction } from 'uniswap/src/types/walletConnect' import { getValidAddress, shortenAddress } from 'uniswap/src/utils/addresses' import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking' @@ -38,7 +39,8 @@ type AddressButtonProps = { const AddressButton = ({ address, chainId, ...rest }: AddressButtonProps): JSX.Element => { const { name } = useENS(chainId, address, false) const colors = useSporeColors() - const supportedChainId = toSupportedChainId(chainId) ?? UniverseChainId.Mainnet + const { defaultChainId } = useEnabledChains() + const supportedChainId = toSupportedChainId(chainId) ?? defaultChainId return ( @@ -99,9 +97,21 @@ export function WalletConnectRequestModalContent({ )} - - + diff --git a/apps/mobile/src/components/Requests/RequestModal/hooks.ts b/apps/mobile/src/components/Requests/RequestModal/hooks.ts index b56108523e8..1c4071c415a 100644 --- a/apps/mobile/src/components/Requests/RequestModal/hooks.ts +++ b/apps/mobile/src/components/Requests/RequestModal/hooks.ts @@ -2,9 +2,10 @@ import { useMemo } from 'react' import { GasFeeResult } from 'uniswap/src/features/gas/types' import { hasSufficientFundsIncludingGas } from 'uniswap/src/features/gas/utils' import { useOnChainNativeCurrencyBalance } from 'uniswap/src/features/portfolio/api' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { NativeCurrency } from 'uniswap/src/features/tokens/NativeCurrency' import { ValueType, getCurrencyAmount } from 'uniswap/src/features/tokens/getCurrencyAmount' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' export function useHasSufficientFunds({ account, @@ -13,12 +14,13 @@ export function useHasSufficientFunds({ value, }: { account?: string - chainId?: WalletChainId + chainId?: UniverseChainId gasFee: GasFeeResult value?: string }): boolean { - const nativeCurrency = NativeCurrency.onChain(chainId || UniverseChainId.Mainnet) - const { balance: nativeBalance } = useOnChainNativeCurrencyBalance(chainId ?? UniverseChainId.Mainnet, account) + const { defaultChainId } = useEnabledChains() + const nativeCurrency = NativeCurrency.onChain(chainId || defaultChainId) + const { balance: nativeBalance } = useOnChainNativeCurrencyBalance(chainId ?? defaultChainId, account) const hasSufficientFunds = useMemo(() => { const transactionAmount = diff --git a/apps/mobile/src/components/Requests/ScanSheet/PendingConnectionModal.tsx b/apps/mobile/src/components/Requests/ScanSheet/PendingConnectionModal.tsx index ab8937a9a60..7b8adb9acd7 100644 --- a/apps/mobile/src/components/Requests/ScanSheet/PendingConnectionModal.tsx +++ b/apps/mobile/src/components/Requests/ScanSheet/PendingConnectionModal.tsx @@ -20,11 +20,9 @@ import { import { Flex, Text, TouchableArea, useSporeColors } from 'ui/src' import { Check, RotatableChevron, X } from 'ui/src/components/icons' import { iconSizes } from 'ui/src/theme' -import { NetworkLogos } from 'uniswap/src/components/network/NetworkLogos' import { MobileEventName, ModalName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { TestID } from 'uniswap/src/test/fixtures/testIDs' -import { WalletChainId } from 'uniswap/src/types/chains' import { WCEventType, WCRequestOutcome, WalletConnectEvent } from 'uniswap/src/types/walletConnect' import { formatDappURL } from 'utilities/src/format/urls' import { ONE_SECOND_MS } from 'utilities/src/time/time' @@ -110,19 +108,6 @@ const SitePermissions = (): JSX.Element => { ) } -const NetworksRow = ({ chains }: { chains: WalletChainId[] }): JSX.Element => { - const { t } = useTranslation() - - return ( - - - {t('walletConnect.permissions.networks')} - - - - ) -} - type SwitchAccountProps = { activeAddress: string setModalState: (state: PendingConnectionModalState.SwitchAccount) => void @@ -297,7 +282,6 @@ function PendingConnectionModalContent({ - diff --git a/apps/mobile/src/components/Requests/ScanSheet/PendingConnectionSwitchNetworkModal.tsx b/apps/mobile/src/components/Requests/ScanSheet/PendingConnectionSwitchNetworkModal.tsx index 34fed7c78f2..f9374f0f6f4 100644 --- a/apps/mobile/src/components/Requests/ScanSheet/PendingConnectionSwitchNetworkModal.tsx +++ b/apps/mobile/src/components/Requests/ScanSheet/PendingConnectionSwitchNetworkModal.tsx @@ -6,22 +6,24 @@ import { iconSizes } from 'ui/src/theme' import { NetworkLogo } from 'uniswap/src/components/CurrencyLogo/NetworkLogo' import { ActionSheetModal } from 'uniswap/src/components/modals/ActionSheetModal' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { ElementName, ModalName } from 'uniswap/src/features/telemetry/constants' -import { WALLET_SUPPORTED_CHAIN_IDS, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' type Props = { - selectedChainId: WalletChainId - onPressChain: (chainId: WalletChainId) => void + selectedChainId: UniverseChainId + onPressChain: (chainId: UniverseChainId) => void onClose: () => void } export const PendingConnectionSwitchNetworkModal = ({ selectedChainId, onPressChain, onClose }: Props): JSX.Element => { const colors = useSporeColors() const { t } = useTranslation() + const { chains: enabledChains } = useEnabledChains() const options = useMemo( () => - WALLET_SUPPORTED_CHAIN_IDS.map((chainId) => { + enabledChains.map((chainId) => { const info = UNIVERSE_CHAIN_INFO[chainId] return { key: `${ElementName.NetworkButton}-${chainId}`, @@ -44,7 +46,7 @@ export const PendingConnectionSwitchNetworkModal = ({ selectedChainId, onPressCh ), } }), - [selectedChainId, onPressChain, colors.accent1], + [selectedChainId, onPressChain, colors.accent1, enabledChains], ) return ( diff --git a/apps/mobile/src/components/Settings/SettingsRow.tsx b/apps/mobile/src/components/Settings/SettingsRow.tsx index d3331021636..d703dcbf6d1 100644 --- a/apps/mobile/src/components/Settings/SettingsRow.tsx +++ b/apps/mobile/src/components/Settings/SettingsRow.tsx @@ -35,6 +35,7 @@ export interface SettingsSectionItem { screenProps?: ValueOf | NavigatorScreenParams externalLink?: string action?: JSX.Element + disabled?: boolean text: string subText?: string icon: JSX.Element @@ -55,6 +56,7 @@ export function SettingsRow({ modal, screenProps, externalLink, + disabled, action, icon, text, @@ -103,7 +105,7 @@ export function SettingsRow({ {onToggle && typeof isToggleEnabled === 'boolean' ? ( - + ) : screen || modal ? ( {currentSetting ? ( diff --git a/apps/mobile/src/components/TokenBalanceList/TokenBalanceList.tsx b/apps/mobile/src/components/TokenBalanceList/TokenBalanceList.tsx index af17f53b4a5..2c620f19ab1 100644 --- a/apps/mobile/src/components/TokenBalanceList/TokenBalanceList.tsx +++ b/apps/mobile/src/components/TokenBalanceList/TokenBalanceList.tsx @@ -9,19 +9,20 @@ import { useAppStackNavigation } from 'src/app/navigation/types' import { TokenBalanceItemContextMenu } from 'src/components/TokenBalanceList/TokenBalanceItemContextMenu' import { useAdaptiveFooter } from 'src/components/home/hooks' import { TAB_BAR_HEIGHT, TAB_VIEW_SCROLL_THROTTLE, TabProps } from 'src/components/layout/TabHelpers' -import { Flex, Loader, useDeviceInsets, useSporeColors } from 'ui/src' +import { Flex, Loader, useSporeColors } from 'ui/src' import { ShieldCheck } from 'ui/src/components/icons' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { zIndices } from 'ui/src/theme' import { BaseCard } from 'uniswap/src/components/BaseCard/BaseCard' +import { InfoLinkModal } from 'uniswap/src/components/modals/InfoLinkModal' import { uniswapUrls } from 'uniswap/src/constants/urls' import { ModalName, WalletEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { CurrencyId } from 'uniswap/src/types/currency' import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { isAndroid } from 'utilities/src/platform' import { InformationBanner } from 'wallet/src/components/banners/InformationBanner' -import { InfoLinkModal } from 'wallet/src/components/modals/InfoLinkModal' import { isError, isNonPollingRequestInFlight } from 'wallet/src/data/utils' import { HiddenTokensRow } from 'wallet/src/features/portfolio/HiddenTokensRow' import { TokenBalanceItem } from 'wallet/src/features/portfolio/TokenBalanceItem' @@ -73,7 +74,7 @@ export const TokenBalanceListInner = forwardRef, T ) { const { t } = useTranslation() const colors = useSporeColors() - const insets = useDeviceInsets() + const insets = useAppInsets() const { rows, balancesById, networkStatus, refetch } = useTokenBalanceListContext() const hasError = isError(networkStatus, !!balancesById) diff --git a/apps/mobile/src/components/TokenDetails/BuyNativeTokenModal.tsx b/apps/mobile/src/components/TokenDetails/BuyNativeTokenModal.tsx index 7a5939bbe18..8c179397a8b 100644 --- a/apps/mobile/src/components/TokenDetails/BuyNativeTokenModal.tsx +++ b/apps/mobile/src/components/TokenDetails/BuyNativeTokenModal.tsx @@ -8,14 +8,14 @@ import { uniswapUrls } from 'uniswap/src/constants/urls' import { ModalName } from 'uniswap/src/features/telemetry/constants' import { useCurrencyInfo, useNativeCurrencyInfo } from 'uniswap/src/features/tokens/useCurrencyInfo' import { BuyNativeTokenButton } from 'uniswap/src/features/transactions/InsufficientNativeTokenWarning/BuyNativeTokenButton' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' export function BuyNativeTokenModal({ chainId, currencyId, onClose, }: { - chainId: WalletChainId + chainId: UniverseChainId currencyId: string onClose: () => void }): JSX.Element | null { @@ -44,7 +44,7 @@ export function BuyNativeTokenModal({ - + @@ -109,10 +113,9 @@ export function CurrentChainBalance({ {isReadonly ? t('token.balances.viewOnly', { ownerAddress: displayName }) : t('token.balances.main')} - {convertFiatAmountFormatted(balance.balanceUSD, NumberType.FiatTokenDetails)} + {isTestnetModeEnabled ? tokenBalance : fiatBalance} - {formatNumberOrString({ value: balance.quantity, type: NumberType.TokenNonTx })}{' '} - {getSymbolDisplayText(balance.currencyInfo.currency.symbol)} + {!isTestnetModeEnabled && tokenBalance} diff --git a/apps/mobile/src/components/TokenDetails/TokenDetailsLinks.tsx b/apps/mobile/src/components/TokenDetails/TokenDetailsLinks.tsx index 6e94a353f4d..17c181acb2f 100644 --- a/apps/mobile/src/components/TokenDetails/TokenDetailsLinks.tsx +++ b/apps/mobile/src/components/TokenDetails/TokenDetailsLinks.tsx @@ -8,9 +8,9 @@ import GlobeIcon from 'ui/src/assets/icons/globe-filled.svg' import TwitterIcon from 'ui/src/assets/icons/x-twitter.svg' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { TokenDetailsScreenQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { ElementName } from 'uniswap/src/features/telemetry/constants' import { TestID } from 'uniswap/src/test/fixtures/testIDs' -import { UniverseChainId } from 'uniswap/src/types/chains' import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking' import { currencyIdToAddress, currencyIdToChain, isDefaultNativeAddress } from 'wallet/src/utils/currencyId' import { getTwitterLink } from 'wallet/src/utils/linking' @@ -23,9 +23,10 @@ export function TokenDetailsLinks({ data: TokenDetailsScreenQuery | undefined }): JSX.Element { const { t } = useTranslation() + const { defaultChainId } = useEnabledChains() const { homepageUrl, twitterName } = data?.token?.project ?? {} - const chainId = currencyIdToChain(currencyId) ?? UniverseChainId.Mainnet + const chainId = currencyIdToChain(currencyId) ?? defaultChainId const address = currencyIdToAddress(currencyId) const explorerLink = getExplorerLink(chainId, address, ExplorerDataType.TOKEN) const explorerName = UNIVERSE_CHAIN_INFO[chainId].explorer.name diff --git a/apps/mobile/src/components/TokenDetails/hooks.test.ts b/apps/mobile/src/components/TokenDetails/hooks.test.ts index 076c9accd97..d6f2a92af7c 100644 --- a/apps/mobile/src/components/TokenDetails/hooks.test.ts +++ b/apps/mobile/src/components/TokenDetails/hooks.test.ts @@ -1,9 +1,11 @@ -import { useCrossChainBalances, useTokenDetailsNavigation } from 'src/components/TokenDetails/hooks' +import { useTokenDetailsNavigation } from 'src/components/TokenDetails/hooks' import { preloadedMobileState } from 'src/test/fixtures' import { act, renderHook, waitFor } from 'src/test/test-utils' +import { useCrossChainBalances } from 'uniswap/src/data/balances/hooks/useCrossChainBalances' import { currencyIdToContractInput } from 'uniswap/src/features/dataApi/utils' import { SAMPLE_CURRENCY_ID_1, + SAMPLE_SEED_ADDRESS_1, portfolio, portfolioBalances, tokenBalance, @@ -32,9 +34,17 @@ jest.mock('@react-navigation/native', () => { describe(useCrossChainBalances, () => { describe('currentChainBalance', () => { it('returns null if there are no balances for the specified currency', async () => { - const { result } = renderHook(() => useCrossChainBalances(SAMPLE_CURRENCY_ID_1, null), { - preloadedState: preloadedMobileState(), - }) + const { result } = renderHook( + () => + useCrossChainBalances({ + address: SAMPLE_SEED_ADDRESS_1, + currencyId: SAMPLE_CURRENCY_ID_1, + crossChainTokens: null, + }), + { + preloadedState: preloadedMobileState(), + }, + ) await act(() => undefined) @@ -51,10 +61,18 @@ describe(useCrossChainBalances, () => { const { resolvers } = queryResolvers({ portfolios: () => [Portfolio], }) - const { result } = renderHook(() => useCrossChainBalances(currentChainBalance.currencyInfo.currencyId, null), { - preloadedState: preloadedMobileState(), - resolvers, - }) + const { result } = renderHook( + () => + useCrossChainBalances({ + address: SAMPLE_SEED_ADDRESS_1, + currencyId: currentChainBalance.currencyInfo.currencyId, + crossChainTokens: null, + }), + { + preloadedState: preloadedMobileState(), + resolvers, + }, + ) await waitFor(() => { expect(result.current).toEqual( @@ -68,9 +86,17 @@ describe(useCrossChainBalances, () => { describe('otherChainBalances', () => { it('returns null if there are no bridged currencies', async () => { - const { result } = renderHook(() => useCrossChainBalances(SAMPLE_CURRENCY_ID_1, null), { - preloadedState: preloadedMobileState(), - }) + const { result } = renderHook( + () => + useCrossChainBalances({ + address: SAMPLE_SEED_ADDRESS_1, + currencyId: SAMPLE_CURRENCY_ID_1, + crossChainTokens: null, + }), + { + preloadedState: preloadedMobileState(), + }, + ) await act(() => undefined) @@ -97,7 +123,12 @@ describe(useCrossChainBalances, () => { }) const { result } = renderHook( - () => useCrossChainBalances(currentChainBalance!.currencyInfo.currencyId, bridgeInfo), + () => + useCrossChainBalances({ + address: SAMPLE_SEED_ADDRESS_1, + currencyId: currentChainBalance!.currencyInfo.currencyId, + crossChainTokens: bridgeInfo, + }), { preloadedState: preloadedMobileState(), resolvers, diff --git a/apps/mobile/src/components/TokenDetails/hooks.ts b/apps/mobile/src/components/TokenDetails/hooks.ts index 1e5775b0a1e..227ed1beb5e 100644 --- a/apps/mobile/src/components/TokenDetails/hooks.ts +++ b/apps/mobile/src/components/TokenDetails/hooks.ts @@ -1,52 +1,9 @@ import { useCallback, useMemo } from 'react' import { useAppStackNavigation } from 'src/app/navigation/types' -import { useBalances } from 'src/features/dataApi/balances' -import { - Chain, - useTokenDetailsScreenLazyQuery, -} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { fromGraphQLChain } from 'uniswap/src/features/chains/utils' -import { PortfolioBalance } from 'uniswap/src/features/dataApi/types' +import { useTokenDetailsScreenLazyQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { currencyIdToContractInput } from 'uniswap/src/features/dataApi/utils' import { CurrencyId } from 'uniswap/src/types/currency' import { MobileScreens } from 'uniswap/src/types/screens/mobile' -import { buildCurrencyId, buildNativeCurrencyId, currencyIdToChain } from 'wallet/src/utils/currencyId' - -/** Helper hook to retrieve balances across chains for a given currency, for the active account. */ -export function useCrossChainBalances( - currencyId: string, - bridgeInfo: Maybe<{ chain: Chain; address?: Maybe }[]>, -): { - currentChainBalance: PortfolioBalance | null - otherChainBalances: PortfolioBalance[] | null -} { - const currentChainBalance = useBalances([currencyId])?.[0] ?? null - const currentChainId = currencyIdToChain(currencyId) - - const bridgedCurrencyIds = useMemo( - () => - bridgeInfo - ?.map(({ chain, address }) => { - const chainId = fromGraphQLChain(chain) - if (!chainId || chainId === currentChainId) { - return null - } - if (!address) { - return buildNativeCurrencyId(chainId) - } - return buildCurrencyId(chainId, address) - }) - .filter((b): b is string => !!b), - - [bridgeInfo, currentChainId], - ) - const otherChainBalances = useBalances(bridgedCurrencyIds) - - return { - currentChainBalance, - otherChainBalances, - } -} /** Utility hook to simplify navigating to token details screen */ export function useTokenDetailsNavigation(): { diff --git a/apps/mobile/src/components/accounts/AccountList.graphql b/apps/mobile/src/components/accounts/AccountList.graphql index 6f5fbfc7f52..6cb994db00d 100644 --- a/apps/mobile/src/components/accounts/AccountList.graphql +++ b/apps/mobile/src/components/accounts/AccountList.graphql @@ -1,10 +1,11 @@ query AccountList( $addresses: [String!]! $valueModifiers: [PortfolioValueModifier!] + $chains: [Chain!] ) { portfolios( ownerAddresses: $addresses - chains: [ETHEREUM, POLYGON, ARBITRUM, OPTIMISM, BASE, BNB, BLAST, ZORA, CELO, AVALANCHE, ZKSYNC] + chains: $chains valueModifiers: $valueModifiers ) { id diff --git a/apps/mobile/src/components/explore/ExploreSections.tsx b/apps/mobile/src/components/explore/ExploreSections.tsx index 446ffdb57eb..014bcc85def 100644 --- a/apps/mobile/src/components/explore/ExploreSections.tsx +++ b/apps/mobile/src/components/explore/ExploreSections.tsx @@ -17,7 +17,7 @@ import { getTokensOrderByValues, } from 'src/features/explore/utils' import { usePollOnFocusOnly } from 'src/utils/hooks' -import { Flex, Loader, Text, useDeviceInsets } from 'ui/src' +import { Flex, Loader, Text } from 'ui/src' import { BaseCard } from 'uniswap/src/components/BaseCard/BaseCard' import { getWrappedNativeAddress } from 'uniswap/src/constants/addresses' import { PollingInterval } from 'uniswap/src/constants/misc' @@ -29,8 +29,9 @@ import { import { fromGraphQLChain } from 'uniswap/src/features/chains/utils' import { usePersistedError } from 'uniswap/src/features/dataApi/utils' import { selectHasFavoriteTokens, selectHasWatchedWallets } from 'uniswap/src/features/favorites/selectors' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { MobileEventName } from 'uniswap/src/features/telemetry/constants' -import { UniverseChainId } from 'uniswap/src/types/chains' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { areAddressesEqual } from 'uniswap/src/utils/addresses' import { buildCurrencyId, buildNativeCurrencyId } from 'uniswap/src/utils/currencyId' import { selectTokensOrderBy } from 'wallet/src/features/wallet/selectors' @@ -43,10 +44,11 @@ type GqlToken = NonNullable[0] export function ExploreSections({ listRef }: ExploreSectionsProps): JSX.Element { const { t } = useTranslation() - const insets = useDeviceInsets() + const insets = useAppInsets() const scrollY = useSharedValue(0) const headerRef = useRef(null) const visibleListHeight = useSharedValue(0) + const { defaultChainId } = useEnabledChains() // Top tokens sorting const orderBy = useSelector(selectTokensOrderBy) @@ -82,7 +84,7 @@ export function ExploreSections({ listRef }: ExploreSectionsProps): JSX.Element // when eth data is not fully available, we do not replace weth with eth const { eth } = data - const wethAddress = getWrappedNativeAddress(UniverseChainId.Mainnet) + const wethAddress = getWrappedNativeAddress(defaultChainId) const isWeth = (token: GqlToken): boolean => areAddressesEqual(token?.address, wethAddress) && token?.chain === Chain.Ethereum @@ -113,7 +115,7 @@ export function ExploreSections({ listRef }: ExploreSectionsProps): JSX.Element // Apply client side sort order const compareFn = getClientTokensOrderByCompareFn(clientOrderBy) return topTokens.sort(compareFn) - }, [data, clientOrderBy]) + }, [data, clientOrderBy, defaultChainId]) const renderItem: ListRenderItem = useCallback( ({ item, index }: ListRenderItemInfo) => { diff --git a/apps/mobile/src/components/explore/FavoriteTokenCard.tsx b/apps/mobile/src/components/explore/FavoriteTokenCard.tsx index 7fb7b1eb6a7..3705b44f617 100644 --- a/apps/mobile/src/components/explore/FavoriteTokenCard.tsx +++ b/apps/mobile/src/components/explore/FavoriteTokenCard.tsx @@ -20,8 +20,8 @@ import { fromGraphQLChain } from 'uniswap/src/features/chains/utils' import { currencyIdToContractInput } from 'uniswap/src/features/dataApi/utils' import { removeFavoriteToken } from 'uniswap/src/features/favorites/slice' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { SectionName } from 'uniswap/src/features/telemetry/constants' -import { UniverseChainId } from 'uniswap/src/types/chains' import { getSymbolDisplayText } from 'uniswap/src/utils/currency' import { NumberType } from 'utilities/src/format/types' import { RelativeChange } from 'wallet/src/components/text/RelativeChange' @@ -46,6 +46,7 @@ function FavoriteTokenCard({ ...rest }: FavoriteTokenCardProps): JSX.Element { const dispatch = useDispatch() + const { defaultChainId } = useEnabledChains() const tokenDetailsNavigation = useTokenDetailsNavigation() const { convertFiatAmountFormatted } = useLocalizationContext() @@ -61,7 +62,7 @@ function FavoriteTokenCard({ const token = data?.token // Mirror behavior in top tokens list, use first chain the token is on for the symbol - const chainId = fromGraphQLChain(token?.chain) ?? UniverseChainId.Mainnet + const chainId = fromGraphQLChain(token?.chain) ?? defaultChainId const price = convertFiatAmountFormatted(token?.market?.price?.value, NumberType.FiatTokenPrice) const pricePercentChange = token?.market?.pricePercentChange?.value diff --git a/apps/mobile/src/components/explore/FavoriteWalletCard.test.tsx b/apps/mobile/src/components/explore/FavoriteWalletCard.test.tsx index a7ea8deb8c8..31b9003fedb 100644 --- a/apps/mobile/src/components/explore/FavoriteWalletCard.test.tsx +++ b/apps/mobile/src/components/explore/FavoriteWalletCard.test.tsx @@ -134,6 +134,7 @@ describe('FavoriteWalletCard', () => { [defaultProps.address]: signerMnemonicAccount({ address: defaultProps.address }), }, }, + userSettings: {}, }) const { getByTestId } = render(, { store, diff --git a/apps/mobile/src/components/explore/TokenItemData.ts b/apps/mobile/src/components/explore/TokenItemData.ts index c27f02cb66e..2d6d48050a0 100644 --- a/apps/mobile/src/components/explore/TokenItemData.ts +++ b/apps/mobile/src/components/explore/TokenItemData.ts @@ -1,9 +1,9 @@ -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' export type TokenItemData = { name: string logoUrl: string - chainId: WalletChainId + chainId: UniverseChainId address: Address | null symbol: string price?: number diff --git a/apps/mobile/src/components/explore/hooks.test.ts b/apps/mobile/src/components/explore/hooks.test.ts index 9d6085808f7..8af0a902c90 100644 --- a/apps/mobile/src/components/explore/hooks.test.ts +++ b/apps/mobile/src/components/explore/hooks.test.ts @@ -131,7 +131,7 @@ describe(useExploreTokenContextMenu, () => { }) it("dispatches add to favorites redux action when 'Favorite token' is pressed", async () => { - const store = mockStore({ favorites: { tokens: [] }, appearance: { theme: 'system' } }) + const store = mockStore({ favorites: { tokens: [] }, appearance: { theme: 'system' }, userSettings: {} }) const { result } = renderHookWithProviders(() => useExploreTokenContextMenu(tokenMenuParams), { resolvers, store, @@ -158,6 +158,7 @@ describe(useExploreTokenContextMenu, () => { const store = mockStore({ favorites: { tokens: [tokenMenuParams.currencyId.toLowerCase()] }, appearance: { theme: 'system' }, + userSettings: {}, }) const { result } = renderHookWithProviders(() => useExploreTokenContextMenu(tokenMenuParams), { resolvers, @@ -186,6 +187,7 @@ describe(useExploreTokenContextMenu, () => { const store = mockStore({ favorites: { tokens: [] }, selectedAppearanceSettings: { theme: 'system' }, + userSettings: {}, }) const { result } = renderHookWithProviders(() => useExploreTokenContextMenu(tokenMenuParams), { store, diff --git a/apps/mobile/src/components/explore/hooks.ts b/apps/mobile/src/components/explore/hooks.ts index 12fa6c9e75f..63f4b1ad5ab 100644 --- a/apps/mobile/src/components/explore/hooks.ts +++ b/apps/mobile/src/components/explore/hooks.ts @@ -11,7 +11,7 @@ import { AssetType } from 'uniswap/src/entities/assets' import { ElementName, ModalName, SectionNameType } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { TransactionState } from 'uniswap/src/features/transactions/types/transactionState' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField, CurrencyId } from 'uniswap/src/types/currency' import { currencyIdToAddress } from 'uniswap/src/utils/currencyId' import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants' @@ -19,7 +19,7 @@ import { useWalletNavigation } from 'wallet/src/contexts/WalletNavigationContext interface TokenMenuParams { currencyId: CurrencyId - chainId: WalletChainId + chainId: UniverseChainId analyticsSection: SectionNameType // token, which are in favorite section would have it defined onEditFavorites?: () => void diff --git a/apps/mobile/src/components/explore/search/SearchResultsSection.tsx b/apps/mobile/src/components/explore/search/SearchResultsSection.tsx index 71a0b043a33..891b08d6925 100644 --- a/apps/mobile/src/components/explore/search/SearchResultsSection.tsx +++ b/apps/mobile/src/components/explore/search/SearchResultsSection.tsx @@ -30,6 +30,7 @@ import { SearchResultType, TokenSearchResult, } from 'uniswap/src/features/search/SearchResult' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import i18n from 'uniswap/src/i18n/i18n' import { UniverseChainId } from 'uniswap/src/types/chains' import { getValidAddress } from 'uniswap/src/utils/addresses' @@ -53,17 +54,18 @@ const NFTHeaderItem: SearchResultOrHeader = { type: SEARCH_RESULT_HEADER_KEY, title: i18n.t('explore.search.section.nft'), } -const EtherscanHeaderItem: SearchResultOrHeader = { +const EtherscanHeaderItem: (chainId: UniverseChainId) => SearchResultOrHeader = (chainId: UniverseChainId) => ({ type: SEARCH_RESULT_HEADER_KEY, title: i18n.t('explore.search.action.viewEtherscan', { - blockExplorerName: UNIVERSE_CHAIN_INFO[UniverseChainId.Mainnet].explorer.name, + blockExplorerName: UNIVERSE_CHAIN_INFO[chainId].explorer.name, }), -} +}) const IGNORED_ERRORS = ['Subgraph provider undefined not supported'] export function SearchResultsSection({ searchQuery }: { searchQuery: string }): JSX.Element { const { t } = useTranslation() + const { defaultChainId } = useEnabledChains() // Search for matching tokens const { @@ -143,7 +145,7 @@ export function SearchResultsSection({ searchQuery }: { searchQuery: string }): // Add etherscan items at end if (validAddress) { - searchResultItems.push(EtherscanHeaderItem, { + searchResultItems.push(EtherscanHeaderItem(defaultChainId), { type: SearchResultType.Etherscan, address: validAddress, }) @@ -157,6 +159,7 @@ export function SearchResultsSection({ searchQuery }: { searchQuery: string }): tokenResults, validAddress, walletSearchResults, + defaultChainId, ]) // Don't wait for wallet search results if there are already token search results, do wait for token results diff --git a/apps/mobile/src/components/explore/search/hooks.ts b/apps/mobile/src/components/explore/search/hooks.ts index 088e181831e..6e6d87edd64 100644 --- a/apps/mobile/src/components/explore/search/hooks.ts +++ b/apps/mobile/src/components/explore/search/hooks.ts @@ -1,6 +1,7 @@ import { useMemo } from 'react' import { useENS } from 'uniswap/src/features/ens/useENS' import { SearchResultType, WalletSearchResult } from 'uniswap/src/features/search/SearchResult' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { useUnitagByAddress, useUnitagByName } from 'uniswap/src/features/unitags/hooks' import { UniverseChainId } from 'uniswap/src/types/chains' import { getValidAddress } from 'uniswap/src/utils/addresses' @@ -13,6 +14,7 @@ export function useWalletSearchResults(query: string): { exactENSMatch: boolean exactUnitagMatch: boolean } { + const { defaultChainId } = useEnabledChains() const validAddress: Address | undefined = useMemo(() => getValidAddress(query, true, false) ?? undefined, [query]) const querySkippedIfValidAddress = validAddress ? null : query @@ -40,7 +42,7 @@ export function useWalletSearchResults(query: string): { // Search for matching EOA wallet address const { isSmartContractAddress, loading: loadingIsSmartContractAddress } = useIsSmartContractAddress( validAddress, - UniverseChainId.Mainnet, + defaultChainId, ) const hasENSResult = dotEthName && dotEthAddress diff --git a/apps/mobile/src/components/explore/search/items/SearchEtherscanItem.tsx b/apps/mobile/src/components/explore/search/items/SearchEtherscanItem.tsx index 9c2496211b0..c535c009db8 100644 --- a/apps/mobile/src/components/explore/search/items/SearchEtherscanItem.tsx +++ b/apps/mobile/src/components/explore/search/items/SearchEtherscanItem.tsx @@ -6,8 +6,8 @@ import { Arrow } from 'ui/src/components/arrow/Arrow' import { iconSizes } from 'ui/src/theme' import { EtherscanSearchResult } from 'uniswap/src/features/search/SearchResult' import { addToSearchHistory } from 'uniswap/src/features/search/searchHistorySlice' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { TestID } from 'uniswap/src/test/fixtures/testIDs' -import { UniverseChainId } from 'uniswap/src/types/chains' import { shortenAddress } from 'uniswap/src/utils/addresses' import { ExplorerDataType, getExplorerLink, openUri } from 'uniswap/src/utils/linking' @@ -18,11 +18,12 @@ type SearchEtherscanItemProps = { export function SearchEtherscanItem({ etherscanResult }: SearchEtherscanItemProps): JSX.Element { const colors = useSporeColors() const dispatch = useDispatch() + const { defaultChainId } = useEnabledChains() const { address } = etherscanResult const onPressViewEtherscan = async (): Promise => { - const explorerLink = getExplorerLink(UniverseChainId.Mainnet, address, ExplorerDataType.ADDRESS) + const explorerLink = getExplorerLink(defaultChainId, address, ExplorerDataType.ADDRESS) await openUri(explorerLink) dispatch( addToSearchHistory({ @@ -31,7 +32,7 @@ export function SearchEtherscanItem({ etherscanResult }: SearchEtherscanItemProp ) } - const EtherscanIcon = getBlockExplorerIcon(UniverseChainId.Mainnet) + const EtherscanIcon = getBlockExplorerIcon(defaultChainId) return ( Array.from(watchedWalletsSet), [watchedWalletsSet]) diff --git a/apps/mobile/src/components/home/HomeExploreTab.tsx b/apps/mobile/src/components/home/HomeExploreTab.tsx index e180d44e312..e39faed5d06 100644 --- a/apps/mobile/src/components/home/HomeExploreTab.tsx +++ b/apps/mobile/src/components/home/HomeExploreTab.tsx @@ -8,7 +8,7 @@ import { TokenItemData } from 'src/components/explore/TokenItemData' import { useAdaptiveFooter } from 'src/components/home/hooks' import { AnimatedFlatList } from 'src/components/layout/AnimatedFlatList' import { TAB_BAR_HEIGHT, TabProps } from 'src/components/layout/TabHelpers' -import { AnimatePresence, Flex, LinearGradient, Text, useDeviceInsets, useIsDarkMode, useSporeColors } from 'ui/src' +import { AnimatePresence, Flex, LinearGradient, Text, useIsDarkMode, useSporeColors } from 'ui/src' import { SwirlyArrowDown } from 'ui/src/components/icons' import { spacing, zIndices } from 'ui/src/theme' import { @@ -22,6 +22,7 @@ import { useAppFiatCurrency } from 'uniswap/src/features/fiatCurrency/hooks' import { Experiments, OnboardingRedesignHomeScreenProperties } from 'uniswap/src/features/gating/experiments' import { useExperimentValue } from 'uniswap/src/features/gating/hooks' import { MobileEventName } from 'uniswap/src/features/telemetry/constants' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { useTranslation } from 'uniswap/src/i18n' import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { isAndroid } from 'utilities/src/platform' @@ -37,7 +38,7 @@ export const HomeExploreTab = memo( ) { const isDarkMode = useIsDarkMode() const colors = useSporeColors() - const insets = useDeviceInsets() + const insets = useAppInsets() const appFiatCurrency = useAppFiatCurrency() const [maxTokenPriceWrapperWidth, setMaxTokenPriceWrapperWidth] = useState(0) diff --git a/apps/mobile/src/components/home/NftsTab.tsx b/apps/mobile/src/components/home/NftsTab.tsx index 12234e2a9db..a0588a20b80 100644 --- a/apps/mobile/src/components/home/NftsTab.tsx +++ b/apps/mobile/src/components/home/NftsTab.tsx @@ -4,8 +4,9 @@ import { RefreshControl } from 'react-native' import { useAppStackNavigation } from 'src/app/navigation/types' import { useAdaptiveFooter } from 'src/components/home/hooks' import { TAB_BAR_HEIGHT, TabProps } from 'src/components/layout/TabHelpers' -import { Flex, useDeviceInsets, useSporeColors } from 'ui/src' +import { Flex, useSporeColors } from 'ui/src' import { GQLQueries } from 'uniswap/src/data/graphql/uniswap-data-api/queries' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { isAndroid } from 'utilities/src/platform' @@ -30,7 +31,7 @@ export const NftsTab = memo( ref, ) { const colors = useSporeColors() - const insets = useDeviceInsets() + const insets = useAppInsets() const navigation = useAppStackNavigation() const { onContentSizeChange, footerHeight, adaptiveFooter } = useAdaptiveFooter( diff --git a/apps/mobile/src/components/home/hooks.tsx b/apps/mobile/src/components/home/hooks.tsx index 16764ff2b39..111633bad0b 100644 --- a/apps/mobile/src/components/home/hooks.tsx +++ b/apps/mobile/src/components/home/hooks.tsx @@ -2,8 +2,8 @@ import { useCallback, useEffect, useMemo } from 'react' import { StyleProp, ViewStyle } from 'react-native' import Animated, { SharedValue, useAnimatedStyle, useSharedValue } from 'react-native-reanimated' import { TAB_BAR_HEIGHT } from 'src/components/layout/TabHelpers' -import { useDeviceInsets } from 'ui/src' import { useDeviceDimensions } from 'ui/src/hooks/useDeviceDimensions' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { useActiveAccount } from 'wallet/src/features/wallet/hooks' export function useAdaptiveFooter(contentContainerStyle?: StyleProp): { @@ -12,7 +12,7 @@ export function useAdaptiveFooter(contentContainerStyle?: StyleProp): adaptiveFooter: JSX.Element } { const { fullHeight } = useDeviceDimensions() - const insets = useDeviceInsets() + const insets = useAppInsets() // Content is rendered under the navigation bar but not under the status bar const maxContentHeight = fullHeight - insets.top // Use maxContentHeight as the initial value to properly position the TabBar diff --git a/apps/mobile/src/components/icons/BlockExplorerIcon.tsx b/apps/mobile/src/components/icons/BlockExplorerIcon.tsx index 44651ad7cc7..b36d0f3c753 100644 --- a/apps/mobile/src/components/icons/BlockExplorerIcon.tsx +++ b/apps/mobile/src/components/icons/BlockExplorerIcon.tsx @@ -4,13 +4,13 @@ import { useIsDarkMode } from 'ui/src' import { IconSizeTokens } from 'ui/src/theme' import { UNIVERSE_CHAIN_LOGO } from 'uniswap/src/assets/chainLogos' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' type IconComponentProps = SvgProps & { size?: IconSizeTokens | number } -const iconsCache = new Map>() +const iconsCache = new Map>() -function buildIconComponent(chainId: WalletChainId): React.FC { +function buildIconComponent(chainId: UniverseChainId): React.FC { const explorer = UNIVERSE_CHAIN_INFO[chainId].explorer const exlorerLogos = UNIVERSE_CHAIN_LOGO[chainId].explorer @@ -23,6 +23,6 @@ function buildIconComponent(chainId: WalletChainId): React.FC { +export function getBlockExplorerIcon(chainId: UniverseChainId): React.FC { return iconsCache.get(chainId) ?? buildIconComponent(chainId) } diff --git a/apps/mobile/src/components/layout/Screen.tsx b/apps/mobile/src/components/layout/Screen.tsx index 7dd87c1d712..352f78874b9 100644 --- a/apps/mobile/src/components/layout/Screen.tsx +++ b/apps/mobile/src/components/layout/Screen.tsx @@ -1,6 +1,7 @@ import React, { useMemo } from 'react' import { Edge, NativeSafeAreaViewProps } from 'react-native-safe-area-context' -import { Flex, FlexProps, useDeviceInsets } from 'ui/src' +import { Flex, FlexProps } from 'ui/src' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' // Used to determine amount of top padding for short screens export const SHORT_SCREEN_HEADER_HEIGHT_RATIO = 0.88 @@ -21,7 +22,7 @@ function SafeAreaWithInsets({ children, edges, noInsets, ...rest }: ScreenProps) // This is a known issue with react-native-safe-area-context, and the solution is to use // the useSafeAreaInsets hook instead. See: // https://github.com/th3rdwave/react-native-safe-area-context/issues/114 - const insets = useDeviceInsets() // useDeviceInsets uses useSafeAreaInsets internally + const insets = useAppInsets() // useAppInsets uses useSafeAreaInsets internally const safeAreaStyles = useMemo(() => { const style: { [key: string]: number } = {} diff --git a/apps/mobile/src/components/layout/screens/ScrollHeader.tsx b/apps/mobile/src/components/layout/screens/ScrollHeader.tsx index f8fb8275d45..5eb4fd860d7 100644 --- a/apps/mobile/src/components/layout/screens/ScrollHeader.tsx +++ b/apps/mobile/src/components/layout/screens/ScrollHeader.tsx @@ -4,9 +4,10 @@ import { StyleProp, ViewStyle } from 'react-native' import Animated, { Extrapolate, SharedValue, interpolate, useAnimatedStyle } from 'react-native-reanimated' import { BackButton } from 'src/components/buttons/BackButton' import { WithScrollToTop } from 'src/components/layout/screens/WithScrollToTop' -import { ColorTokens, Flex, useDeviceInsets } from 'ui/src' +import { ColorTokens, Flex } from 'ui/src' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { iconSizes, zIndices } from 'ui/src/theme' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' type ScrollHeaderProps = { scrollY: SharedValue @@ -49,7 +50,7 @@ export function ScrollHeader({ } }) - const insets = useDeviceInsets() + const insets = useAppInsets() const headerRowStyles = useMemo(() => { return fullScreen ? { diff --git a/apps/mobile/src/features/appLoading/SplashScreen.tsx b/apps/mobile/src/features/appLoading/SplashScreen.tsx index 7f7eea0cab4..c887dbf1d05 100644 --- a/apps/mobile/src/features/appLoading/SplashScreen.tsx +++ b/apps/mobile/src/features/appLoading/SplashScreen.tsx @@ -1,15 +1,16 @@ import React from 'react' import { Image, StyleSheet } from 'react-native' -import { Flex, useDeviceInsets, useIsDarkMode } from 'ui/src' +import { Flex, useIsDarkMode } from 'ui/src' import { UNISWAP_LOGO_LARGE } from 'ui/src/assets' import { useDeviceDimensions } from 'ui/src/hooks/useDeviceDimensions' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { isAndroid } from 'utilities/src/platform' export const SPLASH_SCREEN = { uri: 'SplashScreen' } export function SplashScreen(): JSX.Element { const dimensions = useDeviceDimensions() - const insets = useDeviceInsets() + const insets = useAppInsets() const isDarkMode = useIsDarkMode() return ( diff --git a/apps/mobile/src/features/contracts/useContract.ts b/apps/mobile/src/features/contracts/useContract.ts index 36e93fc39c8..4f533e9a86e 100644 --- a/apps/mobile/src/features/contracts/useContract.ts +++ b/apps/mobile/src/features/contracts/useContract.ts @@ -4,12 +4,12 @@ import { Contract, ContractInterface } from 'ethers' import { useMemo } from 'react' import ERC20_ABI from 'uniswap/src/abis/erc20.json' import { Erc20 } from 'uniswap/src/abis/types' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { logger } from 'utilities/src/logger/logger' import { useContractManager, useProvider } from 'wallet/src/features/wallet/context' export function useContract( - chainId: WalletChainId, + chainId: UniverseChainId, addressOrAddressMap: string | { [chainId: number]: string } | undefined, ABI: ContractInterface, ): T | null { @@ -38,6 +38,6 @@ export function useContract( }, [chainId, addressOrAddressMap, ABI, provider, contractsManager]) as T } -export function useTokenContract(chainId: WalletChainId, tokenAddress?: Address): Erc20 | null { +export function useTokenContract(chainId: UniverseChainId, tokenAddress?: Address): Erc20 | null { return useContract(chainId, tokenAddress, ERC20_ABI) } diff --git a/apps/mobile/src/features/dataApi/balances.test.ts b/apps/mobile/src/features/dataApi/balances.test.ts deleted file mode 100644 index 9f57128baee..00000000000 --- a/apps/mobile/src/features/dataApi/balances.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { useBalances } from 'src/features/dataApi/balances' -import { preloadedMobileState } from 'src/test/fixtures' -import { act, renderHook, waitFor } from 'src/test/test-utils' -import { SAMPLE_CURRENCY_ID_1, portfolio, portfolioBalances } from 'uniswap/src/test/fixtures' -import { queryResolvers } from 'uniswap/src/test/utils' - -const preloadedState = preloadedMobileState() - -describe(useBalances, () => { - it('returns null if no currency was specified', async () => { - const { result } = renderHook(() => useBalances(undefined), { - preloadedState, - }) - - await act(() => undefined) - - expect(result.current).toEqual(null) - }) - - it('returns empty array if no balances are available', async () => { - const { result } = renderHook(() => useBalances([SAMPLE_CURRENCY_ID_1]), { - preloadedState, - }) - - expect(result.current).toEqual(null) // null while data is loading - - await act(() => undefined) - - expect(result.current).toEqual([]) // empty array when data is loaded - }) - - it('returns balances for specified currencies if they exist in the portfolio', async () => { - const Portfolio = portfolio() - const balances = portfolioBalances({ portfolio: Portfolio }) - const { resolvers } = queryResolvers({ - portfolios: () => [Portfolio], - }) - const { result } = renderHook(() => useBalances(balances.map(({ currencyInfo: { currencyId } }) => currencyId)), { - preloadedState, - resolvers, - }) - - await waitFor(() => { - // The response contains only the first currency as the second one is not in the portfolio - expect(result.current).toEqual(balances) - }) - }) -}) diff --git a/apps/mobile/src/features/deepLinking/handleSwapLinkSaga.test.ts b/apps/mobile/src/features/deepLinking/handleSwapLinkSaga.test.ts index fce137acd7e..bbe447ef340 100644 --- a/apps/mobile/src/features/deepLinking/handleSwapLinkSaga.test.ts +++ b/apps/mobile/src/features/deepLinking/handleSwapLinkSaga.test.ts @@ -6,7 +6,7 @@ import { DAI, UNI } from 'uniswap/src/constants/tokens' import { AssetType } from 'uniswap/src/entities/assets' import { ModalName } from 'uniswap/src/features/telemetry/constants' import { TransactionState } from 'uniswap/src/features/transactions/types/transactionState' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { signerMnemonicAccount } from 'wallet/src/test/fixtures' @@ -14,7 +14,7 @@ const account = signerMnemonicAccount() const formSwapUrl = ( userAddress?: Address, - chain?: WalletChainId | number, + chain?: UniverseChainId | number, inputAddress?: string, outputAddress?: string, currencyField?: string, @@ -30,7 +30,7 @@ const formSwapUrl = ( ) const formTransactionState = ( - chain?: WalletChainId, + chain?: UniverseChainId, inputAddress?: string, outputAddress?: string, currencyField?: string, @@ -38,12 +38,12 @@ const formTransactionState = ( ): { input: { address: string | undefined - chainId: WalletChainId | undefined + chainId: UniverseChainId | undefined type: AssetType } output: { address: string | undefined - chainId: WalletChainId | undefined + chainId: UniverseChainId | undefined type: AssetType } exactCurrencyField: string | undefined diff --git a/apps/mobile/src/features/deepLinking/handleSwapLinkSaga.ts b/apps/mobile/src/features/deepLinking/handleSwapLinkSaga.ts index 26903b34b30..d1049892aad 100644 --- a/apps/mobile/src/features/deepLinking/handleSwapLinkSaga.ts +++ b/apps/mobile/src/features/deepLinking/handleSwapLinkSaga.ts @@ -4,7 +4,7 @@ import { put } from 'typed-redux-saga' import { AssetType, CurrencyAsset } from 'uniswap/src/entities/assets' import { ModalName } from 'uniswap/src/features/telemetry/constants' import { TransactionState } from 'uniswap/src/features/transactions/types/transactionState' -import { WALLET_SUPPORTED_CHAIN_IDS, WalletChainId } from 'uniswap/src/types/chains' +import { SUPPORTED_CHAIN_IDS, UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { getValidAddress } from 'uniswap/src/utils/addresses' import { currencyIdToAddress, currencyIdToChain } from 'uniswap/src/utils/currencyId' @@ -55,10 +55,10 @@ const parseAndValidateSwapParams = (url: URL) => { throw new Error('No outputCurrencyId') } - const inputChain = currencyIdToChain(inputCurrencyId) as WalletChainId + const inputChain = currencyIdToChain(inputCurrencyId) as UniverseChainId const inputAddress = currencyIdToAddress(inputCurrencyId) - const outputChain = currencyIdToChain(outputCurrencyId) as WalletChainId + const outputChain = currencyIdToChain(outputCurrencyId) as UniverseChainId const outputAddress = currencyIdToAddress(outputCurrencyId) if (!inputChain || !inputAddress) { @@ -77,11 +77,11 @@ const parseAndValidateSwapParams = (url: URL) => { throw new Error('Invalid tokenAddress provided within outputCurrencyId') } - if (!WALLET_SUPPORTED_CHAIN_IDS.includes(inputChain)) { + if (!SUPPORTED_CHAIN_IDS.includes(inputChain)) { throw new Error('Invalid inputCurrencyId. Chain ID is not supported') } - if (!WALLET_SUPPORTED_CHAIN_IDS.includes(outputChain)) { + if (!SUPPORTED_CHAIN_IDS.includes(outputChain)) { throw new Error('Invalid outputCurrencyId. Chain ID is not supported') } diff --git a/apps/mobile/src/features/externalProfile/ProfileContextMenu.tsx b/apps/mobile/src/features/externalProfile/ProfileContextMenu.tsx index 2c055e36395..6da36f574b7 100644 --- a/apps/mobile/src/features/externalProfile/ProfileContextMenu.tsx +++ b/apps/mobile/src/features/externalProfile/ProfileContextMenu.tsx @@ -10,10 +10,10 @@ import { Flex, TouchableArea, useHapticFeedback } from 'ui/src' import { iconSizes } from 'ui/src/theme' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { uniswapUrls } from 'uniswap/src/constants/urls' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { ElementName, WalletEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { useUnitagByAddress } from 'uniswap/src/features/unitags/hooks' -import { UniverseChainId } from 'uniswap/src/types/chains' import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { ShareableEntity } from 'uniswap/src/types/sharing' import { setClipboard } from 'uniswap/src/utils/clipboard' @@ -34,6 +34,7 @@ export function ProfileContextMenu({ address }: { address: Address }): JSX.Eleme const dispatch = useDispatch() const { unitag } = useUnitagByAddress(address) const { hapticFeedback } = useHapticFeedback() + const { defaultChainId } = useEnabledChains() const onPressCopyAddress = useCallback(async () => { if (!address) { @@ -49,8 +50,8 @@ export function ProfileContextMenu({ address }: { address: Address }): JSX.Eleme }, [address, dispatch, hapticFeedback]) const openExplorerLink = useCallback(async () => { - await openUri(getExplorerLink(UniverseChainId.Mainnet, address, ExplorerDataType.ADDRESS)) - }, [address]) + await openUri(getExplorerLink(defaultChainId, address, ExplorerDataType.ADDRESS)) + }, [address, defaultChainId]) const onReportProfile = useCallback(async () => { const params = new URLSearchParams() @@ -84,7 +85,7 @@ export function ProfileContextMenu({ address }: { address: Address }): JSX.Eleme const options: MenuAction[] = [ { title: t('account.wallet.action.viewExplorer', { - blockExplorerName: UNIVERSE_CHAIN_INFO[UniverseChainId.Mainnet].explorer.name, + blockExplorerName: UNIVERSE_CHAIN_INFO[defaultChainId].explorer.name, }), action: openExplorerLink, systemIcon: 'link', @@ -108,7 +109,7 @@ export function ProfileContextMenu({ address }: { address: Address }): JSX.Eleme }) } return options - }, [onPressCopyAddress, onPressShare, onReportProfile, openExplorerLink, t, unitag]) + }, [onPressCopyAddress, onPressShare, onReportProfile, openExplorerLink, t, unitag, defaultChainId]) return ( ): JSX.Element { const navigation = useOnboardingStackNavigation() const headerHeight = useHeaderHeight() - const insets = useDeviceInsets() + const insets = useAppInsets() const media = useMedia() const gapSize = media.short ? '$none' : '$spacing16' diff --git a/apps/mobile/src/features/onboarding/OptionCard.tsx b/apps/mobile/src/features/onboarding/OptionCard.tsx index 41f528beac1..3c23b69235c 100644 --- a/apps/mobile/src/features/onboarding/OptionCard.tsx +++ b/apps/mobile/src/features/onboarding/OptionCard.tsx @@ -52,20 +52,28 @@ export function OptionCard({ height={iconSizes.icon28} justifyContent="center" width={iconSizes.icon28} + {...(badgeText && { mt: '$spacing4' })} > {icon} - + {title} {badgeText && ( - - + + {badgeText} diff --git a/apps/mobile/src/features/send/SendFormScreen.tsx b/apps/mobile/src/features/send/SendFormScreen.tsx index 0ef051d6f97..4dd5241e9ee 100644 --- a/apps/mobile/src/features/send/SendFormScreen.tsx +++ b/apps/mobile/src/features/send/SendFormScreen.tsx @@ -21,7 +21,7 @@ import { TransactionModalInnerContainer, } from 'uniswap/src/features/transactions/TransactionModal/TransactionModal' import { useTransactionModalContext } from 'uniswap/src/features/transactions/TransactionModal/TransactionModalContext' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { useSendContext } from 'wallet/src/features/transactions/contexts/SendContext' import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks' @@ -70,7 +70,7 @@ function SendFormScreenContent({ hideContent }: { hideContent: boolean }): JSX.E > Form. @@ -35,7 +35,7 @@ function SendRecipientSelectFullScreenContent({ hideContent }: { hideContent: bo {!hideContent && ( ): JSX.Element { const { unitag, address, profilePictureUri } = route.params const dimensions = useDeviceDimensions() - const insets = useDeviceInsets() + const insets = useAppInsets() const { t } = useTranslation() const boxWidth = dimensions.fullWidth - insets.left - insets.right - spacing.spacing32 diff --git a/apps/mobile/src/features/walletConnect/saga.ts b/apps/mobile/src/features/walletConnect/saga.ts index c1394ae8291..0c6781fabc9 100644 --- a/apps/mobile/src/features/walletConnect/saga.ts +++ b/apps/mobile/src/features/walletConnect/saga.ts @@ -25,7 +25,7 @@ import { call, fork, put, select, take } from 'typed-redux-saga' import { config } from 'uniswap/src/config' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import i18n from 'uniswap/src/i18n/i18n' -import { WALLET_SUPPORTED_CHAIN_IDS, WalletChainId } from 'uniswap/src/types/chains' +import { COMBINED_CHAIN_IDS, UniverseChainId } from 'uniswap/src/types/chains' import { EthEvent, EthMethod } from 'uniswap/src/types/walletConnect' import { logger } from 'utilities/src/logger/logger' import { selectAccounts, selectActiveAccountAddress } from 'wallet/src/features/wallet/selectors' @@ -142,7 +142,7 @@ function* handleSessionProposal(proposal: ProposalTypes.Struct) { } = proposal try { - const supportedEip155Chains = WALLET_SUPPORTED_CHAIN_IDS.map((chainId) => `eip155:${chainId}`) + const supportedEip155Chains = COMBINED_CHAIN_IDS.map((chainId) => `eip155:${chainId}`) const accounts = supportedEip155Chains.map((chain) => `${chain}:${activeAccountAddress}`) const namespaces = buildApprovedNamespaces({ @@ -164,7 +164,7 @@ function* handleSessionProposal(proposal: ProposalTypes.Struct) { }) // Extract chains from approved namespaces to show in UI for pending session - const proposalChainIds: WalletChainId[] = [] + const proposalChainIds: UniverseChainId[] = [] Object.entries(namespaces).forEach(([key, namespace]) => { const { chains } = namespace // EVM chain(s) are specified in either `eip155:CHAIN` or chains array @@ -194,7 +194,7 @@ function* handleSessionProposal(proposal: ProposalTypes.Struct) { reason: getSdkError('UNSUPPORTED_CHAINS'), }) - const chainLabels = WALLET_SUPPORTED_CHAIN_IDS.map((chainId) => UNIVERSE_CHAIN_INFO[chainId].label).join(', ') + const chainLabels = COMBINED_CHAIN_IDS.map((chainId) => UNIVERSE_CHAIN_INFO[chainId].label).join(', ') const confirmed = yield* call( showAlert, @@ -308,7 +308,7 @@ function* populateActiveSessions() { } // Get all chains for session namespaces, supporting `eip155:CHAIN_ID` and `eip155` namespace formats - const chains: WalletChainId[] = [] + const chains: UniverseChainId[] = [] Object.entries(session.namespaces).forEach(([key, namespace]) => { const eip155Chains = key.includes(':') ? [key] : namespace.chains chains.push(...(getSupportedWalletConnectChains(eip155Chains) ?? [])) diff --git a/apps/mobile/src/features/walletConnect/signWcRequestSaga.ts b/apps/mobile/src/features/walletConnect/signWcRequestSaga.ts index 77228ec274b..1014d99d235 100644 --- a/apps/mobile/src/features/walletConnect/signWcRequestSaga.ts +++ b/apps/mobile/src/features/walletConnect/signWcRequestSaga.ts @@ -3,8 +3,9 @@ import { wcWeb3Wallet } from 'src/features/walletConnect/saga' import { TransactionRequest, UwuLinkErc20Request } from 'src/features/walletConnect/walletConnectSlice' import { call, put } from 'typed-redux-saga' import { AssetType } from 'uniswap/src/entities/assets' +import { getEnabledChainIdsSaga } from 'uniswap/src/features/settings/saga' import { TransactionOriginType, TransactionType } from 'uniswap/src/features/transactions/types/transactionDetails' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { DappInfo, EthMethod, EthSignMethod, UwULinkMethod, WalletConnectEvent } from 'uniswap/src/types/walletConnect' import { createSaga } from 'uniswap/src/utils/saga' import { logger } from 'utilities/src/logger/logger' @@ -22,7 +23,7 @@ type SignMessageParams = { account: Account method: EthSignMethod dapp: DappInfo - chainId: WalletChainId + chainId: UniverseChainId } type SignTransactionParams = { @@ -32,12 +33,13 @@ type SignTransactionParams = { account: Account method: EthMethod.EthSendTransaction dapp: DappInfo - chainId: WalletChainId + chainId: UniverseChainId request: TransactionRequest | UwuLinkErc20Request } export function* signWcRequest(params: SignMessageParams | SignTransactionParams) { const { sessionId, requestInternalId, account, method, chainId } = params + const { defaultChainId } = yield* getEnabledChainIdsSaga() try { const signerManager = yield* call(getSignerManager) let signature = '' @@ -57,7 +59,7 @@ export function* signWcRequest(params: SignMessageParams | SignTransactionParams signature = yield* call(signTypedDataMessage, params.message, account, signerManager) } else if (method === EthMethod.EthSendTransaction && params.request.type === UwULinkMethod.Erc20Send) { const txParams: SendTransactionParams = { - chainId: params.transaction.chainId || UniverseChainId.Mainnet, + chainId: params.transaction.chainId || defaultChainId, account, options: { request: params.transaction, @@ -75,7 +77,7 @@ export function* signWcRequest(params: SignMessageParams | SignTransactionParams signature = transactionResponse.hash } else if (method === EthMethod.EthSendTransaction) { const txParams: SendTransactionParams = { - chainId: params.transaction.chainId || UniverseChainId.Mainnet, + chainId: params.transaction.chainId || defaultChainId, account, options: { request: params.transaction, diff --git a/apps/mobile/src/features/walletConnect/utils.ts b/apps/mobile/src/features/walletConnect/utils.ts index 44003a0dfa4..ae468155096 100644 --- a/apps/mobile/src/features/walletConnect/utils.ts +++ b/apps/mobile/src/features/walletConnect/utils.ts @@ -4,7 +4,7 @@ import { utils } from 'ethers' import { wcWeb3Wallet } from 'src/features/walletConnect/saga' import { SignRequest, TransactionRequest } from 'src/features/walletConnect/walletConnectSlice' import { toSupportedChainId } from 'uniswap/src/features/chains/utils' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { EthMethod, EthSignMethod } from 'uniswap/src/types/walletConnect' import { logger } from 'utilities/src/logger/logger' @@ -39,21 +39,21 @@ export const getSessionNamespaces = ( /** * Convert list of chains from a WalletConnect namespace to a list of supported ChainIds * @param {string[]} chains list of chain strings as received from WalletConnect (ex. "eip155:1") - * @returns {WalletChainId[]} list of supported ChainIds + * @returns {UniverseChainId[]} list of supported ChainIds */ -export const getSupportedWalletConnectChains = (chains?: string[]): WalletChainId[] | undefined => { +export const getSupportedWalletConnectChains = (chains?: string[]): UniverseChainId[] | undefined => { if (!chains) { return undefined } - return chains.map((chain) => getChainIdFromEIP155String(chain)).filter((c): c is WalletChainId => Boolean(c)) + return chains.map((chain) => getChainIdFromEIP155String(chain)).filter((c): c is UniverseChainId => Boolean(c)) } /** * Convert chain from `eip155:[CHAIN_ID]` format to supported ChainId. * Returns null if chain doesn't match correct `eip155:` format or is an unsupported chain. */ -export const getChainIdFromEIP155String = (chain: string): WalletChainId | null => { +export const getChainIdFromEIP155String = (chain: string): UniverseChainId | null => { const chainStr = chain.startsWith('eip155:') ? chain.split(':')[1] : undefined return toSupportedChainId(chainStr) } @@ -85,7 +85,7 @@ export const parseSignRequest = ( method: EthSignMethod, topic: string, internalId: number, - chainId: WalletChainId, + chainId: UniverseChainId, dapp: SignClientTypes.Metadata, requestParams: Web3WalletTypes.SessionRequest['params']['request']['params'], ): { account: Address; request: SignRequest } => { @@ -118,7 +118,7 @@ export const parseSignRequest = ( * @param {EthMethod.EthSendTransaction} method type of method to sign (only support `eth_signTransaction`) * @param {string} topic id for the WalletConnect session * @param {number} internalId id for the WalletConnect transaction request - * @param {WalletChainId} chainId chain the signature is being requested on + * @param {UniverseChainId} chainId chain the signature is being requested on * @param {SignClientTypes.Metadata} dapp metadata for the dapp requesting the transaction * @param {Web3WalletTypes.SessionRequest['params']['request']['params']} requestParams parameters of the request * @returns {{Address, TransactionRequest}} address of the account receiving the request and formatted TransactionRequest object @@ -127,7 +127,7 @@ export const parseTransactionRequest = ( method: EthMethod.EthSendTransaction, topic: string, internalId: number, - chainId: WalletChainId, + chainId: UniverseChainId, dapp: SignClientTypes.Metadata, requestParams: Web3WalletTypes.SessionRequest['params']['request']['params'], ): { account: Address; request: TransactionRequest } => { diff --git a/apps/mobile/src/features/walletConnect/walletConnectSlice.ts b/apps/mobile/src/features/walletConnect/walletConnectSlice.ts index 3c32ad23661..11605f5c7ff 100644 --- a/apps/mobile/src/features/walletConnect/walletConnectSlice.ts +++ b/apps/mobile/src/features/walletConnect/walletConnectSlice.ts @@ -1,18 +1,18 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { ProposalTypes, SessionTypes } from '@walletconnect/types' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { DappInfo, EthMethod, EthSignMethod, EthTransaction, UwULinkMethod } from 'uniswap/src/types/walletConnect' export type WalletConnectPendingSession = { id: string - chains: WalletChainId[] + chains: UniverseChainId[] dapp: DappInfo proposalNamespaces: ProposalTypes.RequiredNamespaces } export type WalletConnectSession = { id: string - chains: WalletChainId[] + chains: UniverseChainId[] dapp: DappInfo namespaces: SessionTypes.Namespaces } @@ -26,7 +26,7 @@ interface BaseRequest { internalId: string account: string dapp: DappInfo - chainId: WalletChainId + chainId: UniverseChainId } export interface SignRequest extends BaseRequest { diff --git a/apps/mobile/src/screens/DevScreen.tsx b/apps/mobile/src/screens/DevScreen.tsx index 28832bd445b..259e29e582d 100644 --- a/apps/mobile/src/screens/DevScreen.tsx +++ b/apps/mobile/src/screens/DevScreen.tsx @@ -5,10 +5,11 @@ import { useDispatch, useSelector } from 'react-redux' import { navigate } from 'src/app/navigation/rootNavigation' import { BackButton } from 'src/components/buttons/BackButton' import { Screen } from 'src/components/layout/Screen' -import { Flex, Switch, Text, TouchableArea, useDeviceInsets } from 'ui/src' +import { Flex, Switch, Text, TouchableArea } from 'ui/src' import { CheckmarkCircle, CopyAlt } from 'ui/src/components/icons' import { iconSizes, spacing } from 'ui/src/theme' import { resetDismissedWarnings } from 'uniswap/src/features/tokens/slice/slice' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { setClipboard } from 'uniswap/src/utils/clipboard' import { logger } from 'utilities/src/logger/logger' @@ -24,7 +25,7 @@ import { selectSortedSignerMnemonicAccounts } from 'wallet/src/features/wallet/s import { resetWallet } from 'wallet/src/features/wallet/slice' export function DevScreen(): JSX.Element { - const insets = useDeviceInsets() + const insets = useAppInsets() const dispatch = useDispatch() const activeAccount = useActiveAccount() const [rtlEnabled, setRTLEnabled] = useState(I18nManager.isRTL) diff --git a/apps/mobile/src/screens/ExternalProfileScreen.tsx b/apps/mobile/src/screens/ExternalProfileScreen.tsx index bacaffe39b7..02b4d6bbe31 100644 --- a/apps/mobile/src/screens/ExternalProfileScreen.tsx +++ b/apps/mobile/src/screens/ExternalProfileScreen.tsx @@ -12,10 +12,11 @@ import { Screen } from 'src/components/layout/Screen' import { TAB_STYLES, TabContentProps, TabLabel } from 'src/components/layout/TabHelpers' import { ProfileHeader } from 'src/features/externalProfile/ProfileHeader' import { ExploreModalAwareView } from 'src/screens/ModalAwareView' -import { Flex, useDeviceInsets, useSporeColors } from 'ui/src' +import { Flex, useSporeColors } from 'ui/src' import { spacing } from 'ui/src/theme' import Trace from 'uniswap/src/features/telemetry/Trace' import { SectionName, SectionNameType } from 'uniswap/src/features/telemetry/constants' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { useDisplayName } from 'wallet/src/features/wallet/hooks' import { DisplayNameType } from 'wallet/src/features/wallet/types' @@ -33,7 +34,7 @@ export function ExternalProfileScreen({ const { t } = useTranslation() const colors = useSporeColors() const [tabIndex, setIndex] = useState(0) - const insets = useDeviceInsets() + const insets = useAppInsets() const displayName = useDisplayName(address) diff --git a/apps/mobile/src/screens/HomeScreen.tsx b/apps/mobile/src/screens/HomeScreen.tsx index faaa7c8e5cb..6da97c3a584 100644 --- a/apps/mobile/src/screens/HomeScreen.tsx +++ b/apps/mobile/src/screens/HomeScreen.tsx @@ -48,7 +48,7 @@ import { removePendingSession } from 'src/features/walletConnect/walletConnectSl import { HomeScreenTabIndex } from 'src/screens/HomeScreenTabIndex' import { hideSplashScreen } from 'src/utils/splashScreen' import { useOpenBackupReminderModal } from 'src/utils/useOpenBackupReminderModal' -import { Flex, Text, TouchableArea, useDeviceInsets, useHapticFeedback, useMedia, useSporeColors } from 'ui/src' +import { Flex, Text, TouchableArea, useHapticFeedback, useMedia, useSporeColors } from 'ui/src' import ReceiveIcon from 'ui/src/assets/icons/arrow-down-circle.svg' import BuyIcon from 'ui/src/assets/icons/buy.svg' import ScanIcon from 'ui/src/assets/icons/scan-home.svg' @@ -62,6 +62,7 @@ import { useCexTransferProviders } from 'uniswap/src/features/fiatOnRamp/useCexT import { Experiments, OnboardingRedesignHomeScreenProperties } from 'uniswap/src/features/gating/experiments' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { getExperimentValue, useFeatureFlag } from 'uniswap/src/features/gating/hooks' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import Trace from 'uniswap/src/features/telemetry/Trace' import { ElementName, @@ -71,6 +72,8 @@ import { SectionName, SectionNameType, } from 'uniswap/src/features/telemetry/constants' +import { TestnetModeModal } from 'uniswap/src/features/testnets/TestnetModeModal' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { useTimeout } from 'utilities/src/time/timing' @@ -100,7 +103,7 @@ export function HomeScreen(props?: AppStackScreenProp): JSX. const { t } = useTranslation() const colors = useSporeColors() const media = useMedia() - const insets = useDeviceInsets() + const insets = useAppInsets() const dimensions = useDeviceDimensions() const dispatch = useDispatch() const isFocused = useIsFocused() @@ -141,7 +144,7 @@ export function HomeScreen(props?: AppStackScreenProp): JSX. const [routeTabIndex, setRouteTabIndex] = useState(props?.route?.params?.tab ?? HomeScreenTabIndex.Tokens) // Ensures that tabIndex has the proper value between the empty state and non-empty state - const tabIndex = showOnboardingHomeRedesign ? 0 : routeTabIndex + const tabIndex = showOnboardingHomeRedesign ? HomeScreenTabIndex.Tokens : routeTabIndex // Necessary to declare these as direct dependencies due to race condition with initializing react-i18next and useMemo const tokensTitle = t('home.tokens.title') @@ -343,16 +346,7 @@ export function HomeScreen(props?: AppStackScreenProp): JSX. const disableForKorea = useFeatureFlag(FeatureFlags.DisableFiatOnRampKorea) const cexTransferProviders = useCexTransferProviders() - - const onPressBuy = useCallback( - () => - dispatch( - openModal({ - name: disableForKorea ? ModalName.KoreaCexTransferInfoModal : ModalName.FiatOnRampAggregator, - }), - ), - [dispatch, disableForKorea], - ) + const { isTestnetModeEnabled } = useEnabledChains() const onPressScan = useCallback(() => { // in case we received a pending session from a previous scan after closing modal @@ -379,6 +373,24 @@ export function HomeScreen(props?: AppStackScreenProp): JSX. const receiveLabel = t('home.label.receive') const scanLabel = t('home.label.scan') + const [isTestnetWarningModalOpen, setIsTestnetWarningModalOpen] = useState(false) + + const handleTestnetWarningModalClose = useCallback(() => { + setIsTestnetWarningModalOpen(false) + }, []) + + const onPressBuy = useCallback((): void => { + if (isTestnetModeEnabled) { + setIsTestnetWarningModalOpen(true) + return + } + dispatch( + openModal({ + name: disableForKorea ? ModalName.KoreaCexTransferInfoModal : ModalName.FiatOnRampAggregator, + }), + ) + }, [dispatch, isTestnetModeEnabled, disableForKorea]) + const actions = useMemo( (): QuickAction[] => [ { @@ -441,6 +453,12 @@ export function HomeScreen(props?: AppStackScreenProp): JSX. pb={showOnboardingIntroCards ? '$spacing8' : '$spacing16'} px="$spacing12" > + @@ -460,6 +478,9 @@ export function HomeScreen(props?: AppStackScreenProp): JSX. ) }, [ + handleTestnetWarningModalClose, + isTestnetWarningModalOpen, + t, showOnboardingIntroCards, activeAccount.address, isSignerAccount, diff --git a/apps/mobile/src/screens/Import/WatchWalletScreen.tsx b/apps/mobile/src/screens/Import/WatchWalletScreen.tsx index 9b433e2ea4e..8634fbfc458 100644 --- a/apps/mobile/src/screens/Import/WatchWalletScreen.tsx +++ b/apps/mobile/src/screens/Import/WatchWalletScreen.tsx @@ -13,6 +13,7 @@ import { Button, Flex, Text } from 'ui/src' import { Eye, GraduationCap } from 'ui/src/components/icons' import { usePortfolioBalances } from 'uniswap/src/features/dataApi/balances' import { useENS } from 'uniswap/src/features/ens/useENS' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { ElementName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { TestID } from 'uniswap/src/test/fixtures/testIDs' @@ -74,6 +75,7 @@ export function WatchWalletScreen({ navigation, route: { params } }: Props): JSX const dispatch = useDispatch() const accounts = useAccounts() const initialAccounts = useRef(accounts) + const { defaultChainId } = useEnabledChains() useNavigationHeader(navigation) @@ -88,7 +90,7 @@ export function WatchWalletScreen({ navigation, route: { params } }: Props): JSX const validAddress = getValidAddress(normalizedValue, true, false) const { isSmartContractAddress, loading } = useIsSmartContractAddress( (validAddress || resolvedAddress) ?? undefined, - UniverseChainId.Mainnet, + defaultChainId, ) const address = isSmartContractAddress ? (validAddress || resolvedAddress) ?? undefined : undefined // Allow smart contracts with non-null balances diff --git a/apps/mobile/src/screens/Import/useOnDeviceRecoveryData.ts b/apps/mobile/src/screens/Import/useOnDeviceRecoveryData.ts index 4e4fc469b85..b77a4f3eb45 100644 --- a/apps/mobile/src/screens/Import/useOnDeviceRecoveryData.ts +++ b/apps/mobile/src/screens/Import/useOnDeviceRecoveryData.ts @@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from 'react' import { useMultiplePortfolioBalancesQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' // eslint-disable-next-line no-restricted-imports import { usePortfolioValueModifiers } from 'uniswap/src/features/dataApi/balances' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { useUnitagByAddress } from 'uniswap/src/features/unitags/hooks' import { areAddressesEqual } from 'uniswap/src/utils/addresses' import { logger } from 'utilities/src/logger/logger' @@ -85,11 +86,14 @@ export function useOnDeviceRecoveryData(mnemonicId: string | undefined): { [addressesWithIndex], ) + const { gqlChains } = useEnabledChains() + const valueModifiers = usePortfolioValueModifiers(addresses) const { data: balancesData, loading: balancesLoading } = useMultiplePortfolioBalancesQuery({ variables: { ownerAddresses: addresses, valueModifiers, + chains: gqlChains, }, skip: !addresses.length, }) @@ -98,7 +102,7 @@ export function useOnDeviceRecoveryData(mnemonicId: string | undefined): { const { loading: ensLoading, ensMap } = useAddressesEnsNames(addresses) - // Need to fetch unitags for each deriviation index and cannot use a fetch due (see comment at top of func) + // Need to fetch unitags for each derivation index and cannot use a fetch due (see comment at top of func) const unitagStates: Array> = Array(NUMBER_OF_WALLETS_TO_GENERATE) unitagStates[0] = useUnitagByAddress(addresses[0]) diff --git a/apps/mobile/src/screens/ModalAwareView.tsx b/apps/mobile/src/screens/ModalAwareView.tsx index d0a2e9d91d7..634420333f8 100644 --- a/apps/mobile/src/screens/ModalAwareView.tsx +++ b/apps/mobile/src/screens/ModalAwareView.tsx @@ -4,9 +4,10 @@ import { View } from 'react-native' import { useSelector } from 'react-redux' import { HorizontalEdgeGestureTarget } from 'src/components/layout/screens/EdgeGestureTarget' import { selectModalState } from 'src/features/modals/selectModalState' -import { Flex, flexStyles, useDeviceInsets, useSporeColors } from 'ui/src' +import { Flex, flexStyles, useSporeColors } from 'ui/src' import { HandleBar } from 'uniswap/src/components/modals/HandleBar' import { ModalName } from 'uniswap/src/features/telemetry/constants' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' /** * Wrapper view to correctly render screens within Modal as needed. This is required * to enable both full screen, and bottom sheet drag gestures on a screen within a modal. @@ -19,7 +20,7 @@ import { ModalName } from 'uniswap/src/features/telemetry/constants' export function ExploreModalAwareView({ children }: { children: JSX.Element }): JSX.Element { const inModal = useSelector(selectModalState(ModalName.Explore)).isOpen const colors = useSporeColors() - const insets = useDeviceInsets() + const insets = useAppInsets() if (inModal) { return ( diff --git a/apps/mobile/src/screens/NFTCollectionScreen.tsx b/apps/mobile/src/screens/NFTCollectionScreen.tsx index d7592972e7e..5bce2388f83 100644 --- a/apps/mobile/src/screens/NFTCollectionScreen.tsx +++ b/apps/mobile/src/screens/NFTCollectionScreen.tsx @@ -12,7 +12,7 @@ import { ListPriceBadge } from 'src/features/nfts/collection/ListPriceCard' import { NFTCollectionContextMenu } from 'src/features/nfts/collection/NFTCollectionContextMenu' import { NFTCollectionHeader, NFT_BANNER_HEIGHT } from 'src/features/nfts/collection/NFTCollectionHeader' import { ExploreModalAwareView } from 'src/screens/ModalAwareView' -import { Flex, ImpactFeedbackStyle, Text, TouchableArea, useDeviceInsets } from 'ui/src' +import { Flex, ImpactFeedbackStyle, Text, TouchableArea } from 'ui/src' import { AnimatedBottomSheetFlashList, AnimatedFlashList } from 'ui/src/components/AnimatedFlashList/AnimatedFlashList' import { useDeviceDimensions } from 'ui/src/hooks/useDeviceDimensions' import { iconSizes, spacing } from 'ui/src/theme' @@ -22,6 +22,7 @@ import { useNftCollectionScreenQuery, } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import Trace from 'uniswap/src/features/telemetry/Trace' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { isIOS } from 'utilities/src/platform' import { isError } from 'wallet/src/data/utils' @@ -74,7 +75,7 @@ export function NFTCollectionScreen({ renderedInModal = false, }: NFTCollectionScreenProps): ReactElement { const { t } = useTranslation() - const insets = useDeviceInsets() + const insets = useAppInsets() const dimensions = useDeviceDimensions() const navigation = useAppStackNavigation() diff --git a/apps/mobile/src/screens/Onboarding/__snapshots__/BackupScreen.test.tsx.snap b/apps/mobile/src/screens/Onboarding/__snapshots__/BackupScreen.test.tsx.snap index 8937e59a106..0638737f1f4 100644 --- a/apps/mobile/src/screens/Onboarding/__snapshots__/BackupScreen.test.tsx.snap +++ b/apps/mobile/src/screens/Onboarding/__snapshots__/BackupScreen.test.tsx.snap @@ -298,6 +298,7 @@ exports[`BackupScreen renders backup options when none are completed 1`] = ` "flexDirection": "column", "height": 28, "justifyContent": "center", + "marginTop": 4, "width": 28, } } @@ -402,6 +403,8 @@ exports[`BackupScreen renders backup options when none are completed 1`] = ` { "flex": 1, "flexDirection": "column", + "paddingBottom": 4, + "paddingTop": 4, } } > @@ -426,6 +429,7 @@ exports[`BackupScreen renders backup options when none are completed 1`] = ` style={ { "alignItems": "center", + "alignSelf": "flex-start", "backgroundColor": "#FC72FF1f", "borderBottomLeftRadius": 8, "borderBottomRightRadius": 8, @@ -433,8 +437,11 @@ exports[`BackupScreen renders backup options when none are completed 1`] = ` "borderTopRightRadius": 8, "flexDirection": "column", "justifyContent": "center", + "maxWidth": "30%", + "paddingBottom": 8, "paddingLeft": 8, "paddingRight": 8, + "paddingTop": 8, } } > @@ -448,6 +455,7 @@ exports[`BackupScreen renders backup options when none are completed 1`] = ` "fontSize": 15, "fontWeight": "500", "lineHeight": 17.25, + "textAlign": "center", } } suppressHighlighting={true} @@ -1100,6 +1108,7 @@ exports[`BackupScreen renders backup options when some are completed 1`] = ` "flexDirection": "column", "height": 28, "justifyContent": "center", + "marginTop": 4, "width": 28, } } @@ -1204,6 +1213,8 @@ exports[`BackupScreen renders backup options when some are completed 1`] = ` { "flex": 1, "flexDirection": "column", + "paddingBottom": 4, + "paddingTop": 4, } } > @@ -1228,6 +1239,7 @@ exports[`BackupScreen renders backup options when some are completed 1`] = ` style={ { "alignItems": "center", + "alignSelf": "flex-start", "backgroundColor": "#FC72FF1f", "borderBottomLeftRadius": 8, "borderBottomRightRadius": 8, @@ -1235,8 +1247,11 @@ exports[`BackupScreen renders backup options when some are completed 1`] = ` "borderTopRightRadius": 8, "flexDirection": "column", "justifyContent": "center", + "maxWidth": "30%", + "paddingBottom": 8, "paddingLeft": 8, "paddingRight": 8, + "paddingTop": 8, } } > @@ -1250,6 +1265,7 @@ exports[`BackupScreen renders backup options when some are completed 1`] = ` "fontSize": 15, "fontWeight": "500", "lineHeight": 17.25, + "textAlign": "center", } } suppressHighlighting={true} diff --git a/apps/mobile/src/screens/SettingsScreen.tsx b/apps/mobile/src/screens/SettingsScreen.tsx index 09724b3227d..7aafa17ba48 100644 --- a/apps/mobile/src/screens/SettingsScreen.tsx +++ b/apps/mobile/src/screens/SettingsScreen.tsx @@ -1,5 +1,5 @@ import { useNavigation } from '@react-navigation/core' -import { default as React, useCallback, useMemo } from 'react' +import { default as React, useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { ListRenderItemInfo, SectionList } from 'react-native' import { SvgProps } from 'react-native-svg' @@ -20,7 +20,7 @@ import { APP_FEEDBACK_LINK } from 'src/constants/urls' import { useBiometricContext } from 'src/features/biometrics/context' import { useBiometricName, useDeviceSupportsBiometricAuth } from 'src/features/biometrics/hooks' import { useWalletRestore } from 'src/features/wallet/hooks' -import { Flex, IconProps, Text, useDeviceInsets, useSporeColors } from 'ui/src' +import { Flex, IconProps, Text, useSporeColors } from 'ui/src' import BookOpenIcon from 'ui/src/assets/icons/book-open.svg' import ContrastIcon from 'ui/src/assets/icons/contrast.svg' import FaceIdIcon from 'ui/src/assets/icons/faceid.svg' @@ -38,14 +38,24 @@ import { OSDynamicCloudIcon, ShieldQuestion, WavePulse, + Wrench, } from 'ui/src/components/icons' import { iconSizes, spacing } from 'ui/src/theme' import { uniswapUrls } from 'uniswap/src/constants/urls' import { useAppFiatCurrencyInfo } from 'uniswap/src/features/fiatCurrency/hooks' +import { FeatureFlags } from 'uniswap/src/features/gating/flags' +import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { useCurrentLanguageInfo } from 'uniswap/src/features/language/hooks' -import { useHideSmallBalancesSetting, useHideSpamTokensSetting } from 'uniswap/src/features/settings/hooks' -import { setHideSmallBalances, setHideSpamTokens } from 'uniswap/src/features/settings/slice' -import { ModalName } from 'uniswap/src/features/telemetry/constants' +import { + useEnabledChains, + useHideSmallBalancesSetting, + useHideSpamTokensSetting, +} from 'uniswap/src/features/settings/hooks' +import { setHideSmallBalances, setHideSpamTokens, setIsTestnetModeEnabled } from 'uniswap/src/features/settings/slice' +import { ModalName, WalletEventName } from 'uniswap/src/features/telemetry/constants' +import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' +import { TestnetModeModal } from 'uniswap/src/features/testnets/TestnetModeModal' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding' import { MobileScreens, OnboardingScreens } from 'uniswap/src/types/screens/mobile' import { getCloudProviderName } from 'uniswap/src/utils/cloud-backup/getCloudProviderName' @@ -64,7 +74,7 @@ export function SettingsScreen(): JSX.Element { const navigation = useNavigation() const dispatch = useDispatch() const colors = useSporeColors() - const insets = useDeviceInsets() + const insets = useAppInsets() const { deviceSupportsBiometrics } = useBiometricContext() const { t } = useTranslation() @@ -97,6 +107,33 @@ export function SettingsScreen(): JSX.Element { }, AVOID_RENDER_DURING_ANIMATION_MS) }, [dispatch, hapticsUserEnabled]) + const isTestnetModeFlagEnabled = useFeatureFlag(FeatureFlags.TestnetMode) + const [isTestnetModalOpen, setIsTestnetModalOpen] = useState(false) + const { isTestnetModeEnabled } = useEnabledChains() + const handleTestnetModeToggle = useCallback((): void => { + const newIsTestnetMode = !isTestnetModeEnabled + + const fireAnalytic = (): void => + sendAnalyticsEvent(WalletEventName.TestnetModeToggled, { + enabled: newIsTestnetMode, + }) + + setTimeout(() => { + // trigger before toggling on (ie disabling analytics) + if (newIsTestnetMode) { + fireAnalytic() + } + + setIsTestnetModalOpen(newIsTestnetMode) + dispatch(setIsTestnetModeEnabled(newIsTestnetMode)) + + // trigger after toggling off (ie enabling analytics) + if (!newIsTestnetMode) { + fireAnalytic() + } + }, AVOID_RENDER_DURING_ANIMATION_MS) + }, [dispatch, isTestnetModeEnabled]) + // Signer account info const signerAccount = useSignerAccounts()[0] // We sync backup state across all accounts under the same mnemonic, so can check status with any account. @@ -151,13 +188,15 @@ export function SettingsScreen(): JSX.Element { { text: t('settings.setting.smallBalances.title'), icon: , - isToggleEnabled: hideSmallBalances, + isToggleEnabled: hideSmallBalances && !isTestnetModeEnabled, + disabled: isTestnetModeEnabled, onToggle: onToggleHideSmallBalances, }, { text: t('settings.setting.unknownTokens.title'), icon: , - isToggleEnabled: hideSpamTokens, + isToggleEnabled: hideSpamTokens && !isTestnetModeEnabled, + disabled: isTestnetModeEnabled, onToggle: onToggleHideSpamTokens, }, { @@ -171,7 +210,16 @@ export function SettingsScreen(): JSX.Element { text: t('settings.setting.privacy.title'), icon: , }, - // @TODO: [MOB-250] add back testnet toggle once we support testnets + ...(isTestnetModeFlagEnabled + ? [ + { + text: t('settings.setting.wallet.testnetMode.title'), + icon: , + isToggleEnabled: isTestnetModeEnabled, + onToggle: handleTestnetModeToggle, + }, + ] + : []), ], }, { @@ -298,6 +346,9 @@ export function SettingsScreen(): JSX.Element { signerAccount?.address, walletNeedsRestore, hasCloudBackup, + isTestnetModeFlagEnabled, + isTestnetModeEnabled, + handleTestnetModeToggle, ]) const renderItem = ({ @@ -312,8 +363,11 @@ export function SettingsScreen(): JSX.Element { return } + const handleModalClose = useCallback(() => setIsTestnetModalOpen(false), []) + return ( {t('settings.title')}}> + () @@ -218,7 +219,8 @@ const renderItemSeparator = (): JSX.Element => function AddressDisplayHeader({ address }: { address: Address }): JSX.Element { const { t } = useTranslation() - const ensName = useENS(UniverseChainId.Mainnet, address)?.name + const { defaultChainId } = useEnabledChains() + const ensName = useENS(defaultChainId, address)?.name const { unitag } = useUnitagByAddress(address) const onPressEditProfile = (): void => { diff --git a/apps/mobile/src/screens/TokenDetailsScreen.tsx b/apps/mobile/src/screens/TokenDetailsScreen.tsx index d918ae0eac1..07c8e645c4e 100644 --- a/apps/mobile/src/screens/TokenDetailsScreen.tsx +++ b/apps/mobile/src/screens/TokenDetailsScreen.tsx @@ -1,5 +1,6 @@ /* eslint-disable complexity */ import { ReactNavigationPerformanceView } from '@shopify/react-native-performance-navigation' +import { t } from 'i18next' import React, { useCallback, useMemo, useState } from 'react' import { FadeInDown, FadeOutDown } from 'react-native-reanimated' import { useSelector } from 'react-redux' @@ -12,19 +13,19 @@ import { TokenDetailsActionButtons } from 'src/components/TokenDetails/TokenDeta import { TokenDetailsHeader } from 'src/components/TokenDetails/TokenDetailsHeader' import { TokenDetailsLinks } from 'src/components/TokenDetails/TokenDetailsLinks' import { TokenDetailsStats } from 'src/components/TokenDetails/TokenDetailsStats' -import { useCrossChainBalances } from 'src/components/TokenDetails/hooks' import { HeaderScrollScreen } from 'src/components/layout/screens/HeaderScrollScreen' import { Loader } from 'src/components/loading/loaders' import { selectModalState } from 'src/features/modals/selectModalState' import { HeaderRightElement, HeaderTitleElement } from 'src/screens/TokenDetailsHeaders' import { useSkeletonLoading } from 'src/utils/useSkeletonLoading' -import { Flex, Separator, useDeviceInsets, useIsDarkMode, useSporeColors } from 'ui/src' +import { Flex, Separator, useIsDarkMode, useSporeColors } from 'ui/src' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { fonts } from 'ui/src/theme' import { useExtractedTokenColor } from 'ui/src/utils/colors' import { BaseCard } from 'uniswap/src/components/BaseCard/BaseCard' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { PollingInterval } from 'uniswap/src/constants/misc' +import { useCrossChainBalances } from 'uniswap/src/data/balances/hooks/useCrossChainBalances' import { SafetyLevel, TokenDetailsScreenQuery, @@ -40,13 +41,16 @@ import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { Language } from 'uniswap/src/features/language/constants' import { useCurrentLanguage } from 'uniswap/src/features/language/hooks' import { useOnChainNativeCurrencyBalance } from 'uniswap/src/features/portfolio/api' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import Trace from 'uniswap/src/features/telemetry/Trace' import { ModalName } from 'uniswap/src/features/telemetry/constants' +import { TestnetModeModal } from 'uniswap/src/features/testnets/TestnetModeModal' import { TokenWarningCard } from 'uniswap/src/features/tokens/TokenWarningCard' import TokenWarningModal from 'uniswap/src/features/tokens/TokenWarningModal' import { useDismissedTokenWarnings } from 'uniswap/src/features/tokens/slice/hooks' import { useCurrencyInfo } from 'uniswap/src/features/tokens/useCurrencyInfo' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { MobileScreens } from 'uniswap/src/types/screens/mobile' import { @@ -134,11 +138,16 @@ function TokenDetails({ showSkeleton: boolean }): JSX.Element { const colors = useSporeColors() - const insets = useDeviceInsets() + const insets = useAppInsets() const isDarkMode = useIsDarkMode() const tokenProtectionEnabled = useFeatureFlag(FeatureFlags.TokenProtection) + const { defaultChainId } = useEnabledChains() - const currencyChainId = (currencyIdToChain(_currencyId) as WalletChainId) ?? UniverseChainId.Mainnet + const address = useActiveAccountAddressWithThrow() + const { isTestnetModeEnabled, chains: enabledChains } = useEnabledChains() + + const currencyChainId = (currencyIdToChain(_currencyId) as UniverseChainId) ?? defaultChainId + const isChainEnabled = !!enabledChains.find((chain) => chain === currencyIdToChain(_currencyId)) const currencyAddress = currencyIdToAddress(_currencyId) const token = data?.token @@ -148,7 +157,11 @@ function TokenDetails({ const currencyInfo = useCurrencyInfo(_currencyId) const crossChainTokens = token?.project?.tokens - const { currentChainBalance, otherChainBalances } = useCrossChainBalances(_currencyId, crossChainTokens) + const { currentChainBalance, otherChainBalances } = useCrossChainBalances({ + address, + currencyId: _currencyId, + crossChainTokens, + }) const hasTokenBalance = Boolean(currentChainBalance) const isNativeCurrency = isNativeCurrencyAddress(currencyChainId, currencyAddress) @@ -195,11 +208,19 @@ function TokenDetails({ const isBlocked = safetyLevel === SafetyLevel.Blocked || currencyInfo?.safetyInfo?.tokenList === TokenList.Blocked const bridgingTokenWithHighestBalance = useBridgingTokenWithHighestBalance({ + address, currencyAddress, currencyChainId, - otherChainBalances, }) + const [isTestnetWarningModalOpen, setIsTestnetWarningModalOpen] = useState(false) + const closeTestnetWarningModalClose = useCallback(() => { + setIsTestnetWarningModalOpen(false) + }, []) + const openTestnetWarningModal = useCallback(() => { + setIsTestnetWarningModalOpen(true) + }, []) + const onPressSend = useCallback(() => { // Do not show warning modal speedbump if user is trying to send tokens they own navigateToSend({ currencyAddress, chainId: currencyChainId }) @@ -327,11 +348,13 @@ function TokenDetails({ {tokenProtectionEnabled && ( )} - + {isChainEnabled && ( + + )} {showSkeleton ? ( @@ -350,12 +373,12 @@ function TokenDetails({ {!loading && !tokenColorLoading ? ( onPressSwap(CurrencyField.INPUT)} - onPressDisabled={openTokenWarningModal} + onPressDisabled={isTestnetModeEnabled ? openTestnetWarningModal : openTokenWarningModal} /> ) : null} @@ -373,6 +396,13 @@ function TokenDetails({ /> )} + + {showBuyNativeTokenModal && ( ImportSpecifier[imported.name='ChainId']`, - message: "Don't use ChainId from @uniswap/sdk-core. Use the InterfaceChainId from universe/uniswap.", + message: "Don't use ChainId from @uniswap/sdk-core. Use the UniverseChainId from universe/uniswap.", }, // TODO(WEB-4251) - remove useWeb3React rules once web3 react is removed { diff --git a/apps/web/package.json b/apps/web/package.json index c047a05b66f..69e9bf09c35 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -185,25 +185,26 @@ "@types/react-scroll-sync": "0.8.7", "@types/react-window-infinite-loader": "1.0.6", "@uniswap/analytics": "1.7.0", - "@uniswap/analytics-events": "2.37.0", + "@uniswap/analytics-events": "2.38.0", "@uniswap/client-explore": "0.0.9", "@uniswap/client-pools": "0.0.5", "@uniswap/liquidity-staker": "1.0.2", "@uniswap/merkle-distributor": "1.0.1", "@uniswap/permit2-sdk": "1.3.0", "@uniswap/redux-multicall": "1.1.8", - "@uniswap/router-sdk": "1.9.2", - "@uniswap/sdk-core": "5.3.0", + "@uniswap/router-sdk": "1.14.2", + "@uniswap/sdk-core": "5.8.0", "@uniswap/smart-order-router": "3.17.3", "@uniswap/token-lists": "1.0.0-beta.33", "@uniswap/uniswapx-sdk": "^2.1.0-beta.14", - "@uniswap/universal-router-sdk": "2.2.0", + "@uniswap/universal-router-sdk": "4.2.0", "@uniswap/v2-core": "1.0.1", "@uniswap/v2-periphery": "1.1.0-beta.0", - "@uniswap/v2-sdk": "4.3.2", + "@uniswap/v2-sdk": "4.6.0", "@uniswap/v3-core": "1.0.1", "@uniswap/v3-periphery": "1.4.4", - "@uniswap/v3-sdk": "3.14.0", + "@uniswap/v3-sdk": "3.17.0", + "@uniswap/v4-sdk": "1.10.0", "@vanilla-extract/css": "1.14.0", "@vanilla-extract/dynamic": "2.1.0", "@vanilla-extract/sprinkles": "1.6.1", diff --git a/apps/web/public/images/unichain/unichain_modal_desktop.png b/apps/web/public/images/unichain/unichain_modal_desktop.png new file mode 100644 index 00000000000..c4b2c5765bf Binary files /dev/null and b/apps/web/public/images/unichain/unichain_modal_desktop.png differ diff --git a/apps/web/public/images/unichain/unichain_modal_mobile.png b/apps/web/public/images/unichain/unichain_modal_mobile.png new file mode 100644 index 00000000000..620f6fdf06c Binary files /dev/null and b/apps/web/public/images/unichain/unichain_modal_mobile.png differ diff --git a/apps/web/public/pools-sitemap.xml b/apps/web/public/pools-sitemap.xml index a5c68fedf2c..b92c2358057 100644 --- a/apps/web/public/pools-sitemap.xml +++ b/apps/web/public/pools-sitemap.xml @@ -6462,47 +6462,47 @@ https://app.uniswap.org/explore/pools/arbitrum/0xb736330326cf379ecd918dba10614bd63c2713da - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0xe3d4faff3179f0a664a3a84c3e1da3b90e27f186 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/pools/arbitrum/0x50e7b9293aef80c304234e86c84a01be8401c530 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x87dddd2e152bf1955e7e03d9f23a9dcc163eebf6 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/pools/optimism/0xd9dd34576c7034beb0b11a99afffc49e91011235 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/pools/optimism/0x394a9fcbab8599437d9ec4e5a4a0eb7cb1fd2f69 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/pools/base/0xb3adde966b8a1a6f22a04914ee9fe0798e71fc5b - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/pools/base/0xa2d4a8e00daad32acace1a0dd0905f6aaf57e84e - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/pools/celo/0x2392ae4ba6daf181ce7343d237b695cdf525e233 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 @@ -6620,4 +6620,19 @@ 2024-10-03T00:43:01.506Z 0.8 + + https://app.uniswap.org/explore/pools/bnb/0xb0bb2c1d32c7b27f21eec4402c6d1c38795c090a + 2024-10-07T22:51:22.436Z + 0.8 + + + https://app.uniswap.org/explore/pools/bnb/0x534d3930edba2c0b90a7973549a0287141c987ef + 2024-10-07T22:51:22.436Z + 0.8 + + + https://app.uniswap.org/explore/pools/celo/0xf27d0dac09460b236d4d9e0da316fe9c3a99b4a2 + 2024-10-07T22:51:22.436Z + 0.8 + \ No newline at end of file diff --git a/apps/web/public/tokens-sitemap.xml b/apps/web/public/tokens-sitemap.xml index 2d73ccac554..d55fc40031c 100644 --- a/apps/web/public/tokens-sitemap.xml +++ b/apps/web/public/tokens-sitemap.xml @@ -6657,187 +6657,187 @@ https://app.uniswap.org/explore/tokens/ethereum/0xd9fcd98c322942075a5c3860693e9f4f03aae07b - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x240cd7b53d364a208ed41f8ced4965d11f571b7a - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xb8d6196d71cdd7d90a053a7769a077772aaac464 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xcbde0453d4e7d748077c1b0ac2216c011dd2f406 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x786f112c9a6bc840cdc07cfd840105efd6ef2d4b - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x0bffdd787c83235f6f0afa0faed42061a4619b7a - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x1c43cd666f22878ee902769fccda61f401814efb - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x1b54a6fa1360bd71a0f28f77a1d6fba215d498c3 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xb528edbef013aff855ac3c50b381f253af13b997 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x888888ae2c4a298efd66d162ffc53b3f2a869888 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x4cd27e18757baa3a4fe7b0ab7db083002637a6c5 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x240d6faf8c3b1a7394e371792a3bf9d28dd65515 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x41b1f9dcd5923c9542b6957b9b72169595acbc5c - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xd1f2586790a5bd6da1e443441df53af6ec213d83 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x8de5b80a0c1b02fe4976851d030b36122dbb8624 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x391cf4b21f557c935c7f670218ef42c21bd8d686 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0x8bd35250918ed056304fa8641e083be2c42308bb - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/ethereum/0xc3960227e41c3f54e9b399ce216149dea5315c34 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x59062301fb510f4ea2417b67404cb16d31e604ba - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x75ec618a817eb0a4a7e44ac3dfc64c963daf921a - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x7e7a7c916c19a45769f6bdaf91087f93c6c12f78 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/arbitrum/0x21ccbc5e7f353ec43b2f5b1fb12c3e9d89d30dca - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/optimism/0x87eee96d50fb761ad85b1c982d28a042169d61b1 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x3c720206bfacb2d16fa3ac0ed87d2048dbc401fc - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/polygon/0x8d60fb5886497851aac8c5195006ecf07647ba0d - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/base/0xcb327b99ff831bf8223cced12b1338ff3aa322ff - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/base/0xf544251d25f3d243a36b07e7e7962a678f952691 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/base/0xa7296cefae8477a81e23230ca5d3a3d6f49d3764 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/base/0x051fb509e4a775fabd257611eea1efaed8f91359 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/base/0xae2bddbcc932c2d2cf286bad0028c6f5074c77b5 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/base/0x1dd2d631c92b1acdfcdd51a0f7145a50130050c4 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/base/0xd3c68968137317a57a9babeacc7707ec433548b4 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/base/0x7f6f6720a73c0f54f95ab343d7efeb1fa991f4f7 - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xf3527ef8de265eaa3716fb312c12847bfba66cef - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0x8888888888f004100c0353d657be6300587a6ccd - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xe2a59d5e33c6540e18aaa46bf98917ac3158db0d - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 https://app.uniswap.org/explore/tokens/bnb/0xfa2ad87e35fc8d3c9f57d73c4667a4651ce6ad2f - 2024-09-27T19:51:14.628Z + 2024-10-07T22:51:22.436Z 0.8 @@ -6960,4 +6960,94 @@ 2024-10-03T01:24:22.631Z 0.8 + + https://app.uniswap.org/explore/tokens/ethereum/0x888c1a341ce9d9ae9c2d2a75a72a7f0d2551a2dc + 2024-10-07T22:51:22.436Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x465dbc39f46f9d43c581a5d90a43e4a0f2a6ff2d + 2024-10-07T22:51:22.436Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x44e18207b6e98f4a786957954e462ed46b8c95be + 2024-10-07T22:51:22.436Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x70c29e99ca32592c0e88bb571b87444bb0e08e33 + 2024-10-07T22:51:22.436Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x8c7ac134ed985367eadc6f727d79e8295e11435c + 2024-10-07T22:51:22.436Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x6aa56e1d98b3805921c170eb4b3fe7d4fda6d89b + 2024-10-07T22:51:22.436Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x81db1949d0e888557bc632f7c0f6698b1f8c9106 + 2024-10-07T22:51:22.436Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x2de1218c31a04e1040fc5501b89e3a58793b3ddf + 2024-10-07T22:51:22.436Z + 0.8 + + + https://app.uniswap.org/explore/tokens/ethereum/0x30ae41d5f9988d359c733232c6c693c0e645c77e + 2024-10-07T22:51:22.436Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x1fc01117e196800f416a577350cb1938d10501c2 + 2024-10-07T22:51:22.436Z + 0.8 + + + https://app.uniswap.org/explore/tokens/arbitrum/0x3212dc0f8c834e4de893532d27cc9b6001684db0 + 2024-10-07T22:51:22.436Z + 0.8 + + + https://app.uniswap.org/explore/tokens/optimism/0xd0cf4de352ac8dcce00bd6b93ee73d3cb272edc3 + 2024-10-07T22:51:22.436Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x75e6b648c91d222b2f6318e8ceeed4b691d5323f + 2024-10-07T22:51:22.436Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x2a06a17cbc6d0032cac2c6696da90f29d39a1a29 + 2024-10-07T22:51:22.436Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x6668d4a6605a27e5ee51eda040581155eddc6666 + 2024-10-07T22:51:22.436Z + 0.8 + + + https://app.uniswap.org/explore/tokens/base/0x2dc90fa3a0f178ba4bee16cac5d6c9a5a7b4c6cb + 2024-10-07T22:51:22.436Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x9c7beba8f6ef6643abd725e45a4e8387ef260649 + 2024-10-07T22:51:22.436Z + 0.8 + + + https://app.uniswap.org/explore/tokens/bnb/0x0cf8e180350253271f4b917ccfb0accc4862f262 + 2024-10-07T22:51:22.436Z + 0.8 + \ No newline at end of file diff --git a/apps/web/src/components/AccountDetails/TransactionSummary.tsx b/apps/web/src/components/AccountDetails/TransactionSummary.tsx index affc2a7a8fe..3e1ae63eda6 100644 --- a/apps/web/src/components/AccountDetails/TransactionSummary.tsx +++ b/apps/web/src/components/AccountDetails/TransactionSummary.tsx @@ -11,6 +11,7 @@ import { ClaimTransactionInfo, CollectFeesTransactionInfo, CreateV3PoolTransactionInfo, + DecreaseLiquidityTransactionInfo, DelegateTransactionInfo, ExactInputSwapTransactionInfo, ExactOutputSwapTransactionInfo, @@ -394,6 +395,32 @@ function IncreaseLiquiditySummary({ info }: { info: IncreaseLiquidityTransaction ) } +function DecreaseLiquiditySummary({ info }: { info: DecreaseLiquidityTransactionInfo }) { + const { token0CurrencyId, token1CurrencyId, token0CurrencyAmountRaw, token1CurrencyAmountRaw } = info + + return ( + + ), + quote: ( + + ), + }} + /> + ) +} + export function TransactionSummary({ info }: { info: TransactionInfo }) { switch (info.type) { case TransactionType.ADD_LIQUIDITY_V3_POOL: @@ -452,5 +479,8 @@ export function TransactionSummary({ info }: { info: TransactionInfo }) { case TransactionType.INCREASE_LIQUIDITY: return + + case TransactionType.DECREASE_LIQUIDITY: + return } } diff --git a/apps/web/src/components/AccountDrawer/AuthenticatedHeader.tsx b/apps/web/src/components/AccountDrawer/AuthenticatedHeader.tsx index 9f941292461..7a61f63dc23 100644 --- a/apps/web/src/components/AccountDrawer/AuthenticatedHeader.tsx +++ b/apps/web/src/components/AccountDrawer/AuthenticatedHeader.tsx @@ -26,14 +26,18 @@ import styled from 'lib/styled-components' import { useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks' import { ProfilePageStateType } from 'nft/types' import { useCallback, useState } from 'react' +import { useDispatch } from 'react-redux' import { useNavigate } from 'react-router-dom' import { useCloseModal, useFiatOnrampAvailability, useOpenModal, useToggleModal } from 'state/application/hooks' import { ApplicationModal } from 'state/application/reducer' import { useUserHasAvailableClaim, useUserUnclaimedAmount } from 'state/claim/hooks' import { ThemedText } from 'theme/components' import { ArrowDownCircleFilled } from 'ui/src/components/icons/ArrowDownCircleFilled' +import { TestnetModeBanner } from 'uniswap/src/components/banners/TestnetModeBanner' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' +import { setIsTestnetModeEnabled } from 'uniswap/src/features/settings/slice' import Trace from 'uniswap/src/features/telemetry/Trace' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { useUnitagByAddress } from 'uniswap/src/features/unitags/hooks' @@ -111,6 +115,7 @@ export default function AuthenticatedHeader({ account, openSettings }: { account const shouldShowBuyFiatButton = !isPathBlocked('/buy') const { formatNumber, formatDelta } = useFormatter() const isUniExtensionAvailable = useIsUniExtensionAvailable() + const { isTestnetModeEnabled } = useEnabledChains() const forAggregatorEnabled = useFeatureFlag(FeatureFlags.ForAggregator) const shouldDisableNFTRoutes = useDisableNFTRoutes() @@ -120,6 +125,12 @@ export default function AuthenticatedHeader({ account, openSettings }: { account const openClaimModal = useToggleModal(ApplicationModal.ADDRESS_CLAIM) const accountDrawer = useAccountDrawer() + const dispatch = useDispatch() + + const handleDisconnect = useCallback(() => { + dispatch(setIsTestnetModeEnabled(false)) + disconnect() + }, [disconnect, dispatch]) const navigateToProfile = useCallback(() => { accountDrawer.close() @@ -172,7 +183,9 @@ export default function AuthenticatedHeader({ account, openSettings }: { account const { data: portfolioBalances } = useTokenBalancesQuery({ cacheOnly: !accountDrawer.isOpen }) const portfolio = portfolioBalances?.portfolios?.[0] const totalBalance = portfolio?.tokensTotalDenominatedValue?.value - const isEmptyWallet = totalBalance === 0 && forAggregatorEnabled + // denominated portfolio balance on testnet is always 0 + const isPortfolioZero = !isTestnetModeEnabled && totalBalance === 0 + const isEmptyWallet = isPortfolioZero && forAggregatorEnabled const absoluteChange = portfolio?.tokensTotalDenominatedValueChange?.absolute?.value const percentChange = portfolio?.tokensTotalDenominatedValueChange?.percentage?.value const [showDisconnectConfirm, setShowDisconnectConfirm] = useState(false) @@ -182,6 +195,7 @@ export default function AuthenticatedHeader({ account, openSettings }: { account return ( + @@ -194,7 +208,7 @@ export default function AuthenticatedHeader({ account, openSettings }: { account {totalBalance !== undefined ? ( - + {formatNumber({ - input: totalBalance, + input: isTestnetModeEnabled ? null : totalBalance, + placeholder: '--', + forceShowCurrencySymbol: true, type: NumberType.PortfolioBalance, })} - {absoluteChange !== 0 && percentChange && ( + {absoluteChange !== 0 && percentChange && !isTestnetModeEnabled && ( <> diff --git a/apps/web/src/components/AccountDrawer/DefaultMenu.tsx b/apps/web/src/components/AccountDrawer/DefaultMenu.tsx index 7294b36719e..faebda2fcd5 100644 --- a/apps/web/src/components/AccountDrawer/DefaultMenu.tsx +++ b/apps/web/src/components/AccountDrawer/DefaultMenu.tsx @@ -1,3 +1,4 @@ +import { MenuState, miniPortfolioMenuStateAtom } from 'components/AccountDrawer' import AuthenticatedHeader from 'components/AccountDrawer/AuthenticatedHeader' import LanguageMenu from 'components/AccountDrawer/LanguageMenu' import LocalCurrencyMenu from 'components/AccountDrawer/LocalCurrencyMenu' @@ -7,7 +8,7 @@ import SettingsMenu from 'components/AccountDrawer/SettingsMenu' import Column from 'components/deprecated/Column' import WalletModal from 'components/WalletModal' import { useAccount } from 'hooks/useAccount' -import { atom, useAtom } from 'jotai' +import { useAtom } from 'jotai' import styled from 'lib/styled-components' import { useCallback, useEffect, useMemo } from 'react' import { InterfaceEventNameLocal } from 'uniswap/src/features/telemetry/constants' @@ -18,17 +19,6 @@ const DefaultMenuWrap = styled(Column)` height: 100%; ` -export enum MenuState { - DEFAULT = 'default', - SETTINGS = 'settings', - LANGUAGE_SETTINGS = 'language_settings', - LOCAL_CURRENCY_SETTINGS = 'local_currency_settings', - LIMITS = 'limits', - POOLS = 'pools', -} - -export const miniPortfolioMenuStateAtom = atom(MenuState.DEFAULT) - function DefaultMenu({ drawerOpen }: { drawerOpen: boolean }) { const account = useAccount() diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/ActivityTab.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/ActivityTab.tsx index 7a5a95c23ec..a25c446cc0b 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/ActivityTab.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/ActivityTab.tsx @@ -1,4 +1,4 @@ -import { MenuState, miniPortfolioMenuStateAtom } from 'components/AccountDrawer/DefaultMenu' +import { MenuState, miniPortfolioMenuStateAtom } from 'components/AccountDrawer' import { ActivityRow } from 'components/AccountDrawer/MiniPortfolio/Activity/ActivityRow' import { useAllActivities } from 'components/AccountDrawer/MiniPortfolio/Activity/hooks' import { createGroups } from 'components/AccountDrawer/MiniPortfolio/Activity/utils' diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/CancelOrdersDialog.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/CancelOrdersDialog.tsx index 617f206c888..252862b6007 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/CancelOrdersDialog.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/CancelOrdersDialog.tsx @@ -15,7 +15,7 @@ import { SignatureType, UniswapXOrderDetails } from 'state/signatures/types' import { ExternalLink, ThemedText } from 'theme/components' import { nativeOnChain } from 'uniswap/src/constants/tokens' import { Plural, Trans, t } from 'uniswap/src/i18n' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking' import { NumberType, useFormatter } from 'utils/formatNumbers' @@ -162,7 +162,7 @@ export function CancelOrdersDialog( } } -function GasEstimateDisplay({ gasEstimateValue, chainId }: { gasEstimateValue?: string; chainId: InterfaceChainId }) { +function GasEstimateDisplay({ gasEstimateValue, chainId }: { gasEstimateValue?: string; chainId: UniverseChainId }) { const gasFeeCurrencyAmount = CurrencyAmount.fromRawAmount(nativeOnChain(chainId), gasEstimateValue ?? '0') const gasFeeUSD = useStablecoinValue(gasFeeCurrencyAmount) const { formatCurrencyAmount } = useFormatter() diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/__snapshots__/OffchainActivityModal.test.tsx.snap b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/__snapshots__/OffchainActivityModal.test.tsx.snap index 9064f7878f2..3b133c99b9d 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/__snapshots__/OffchainActivityModal.test.tsx.snap +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/__snapshots__/OffchainActivityModal.test.tsx.snap @@ -43,7 +43,7 @@ exports[`OrderContent should render without error, filled order 1`] = ` gap: 12px; } -.c18 { +.c15 { width: 100%; display: -webkit-box; display: -webkit-flex; @@ -60,7 +60,7 @@ exports[`OrderContent should render without error, filled order 1`] = ` justify-content: flex-start; } -.c19 { +.c16 { -webkit-box-pack: justify; -webkit-justify-content: space-between; -ms-flex-pack: justify; @@ -90,7 +90,7 @@ exports[`OrderContent should render without error, filled order 1`] = ` letter-spacing: -0.01em; } -.c23 { +.c20 { -webkit-text-decoration: none; text-decoration: none; cursor: pointer; @@ -101,11 +101,11 @@ exports[`OrderContent should render without error, filled order 1`] = ` font-weight: 500; } -.c23:hover { +.c20:hover { opacity: 0.6; } -.c23:active { +.c20:active { opacity: 0.4; } @@ -161,7 +161,7 @@ exports[`OrderContent should render without error, filled order 1`] = ` gap: 4px; } -.c17 { +.c14 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -176,17 +176,17 @@ exports[`OrderContent should render without error, filled order 1`] = ` gap: 8px; } -.c20 { +.c17 { cursor: auto; color: #7D7D7D; } -.c21 { +.c18 { text-align: right; overflow-wrap: break-word; } -.c22 { +.c19 { background-color: transparent; border: none; cursor: pointer; @@ -218,23 +218,6 @@ exports[`OrderContent should render without error, filled order 1`] = ` user-select: text; } -.c15 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - gap: 2px; - position: relative; - top: 0; - left: 0; -} - -.c15 img { - width: 36px; - height: 36px; - border-radius: 50%; -} - .c4 { display: -webkit-box; display: -webkit-flex; @@ -268,12 +251,6 @@ exports[`OrderContent should render without error, filled order 1`] = ` border-radius: 50%; } -.c16 { - width: 18px; - height: 36px; - border-radius: 50%; -} - .c3 { display: -webkit-box; display: -webkit-flex; @@ -288,14 +265,6 @@ exports[`OrderContent should render without error, filled order 1`] = ` left: 0; } -.c14 { - position: relative; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; -} - .c9 { margin: 28px 0; } @@ -385,30 +354,6 @@ exports[`OrderContent should render without error, filled order 1`] = `
-
-
-
-
- - -
-
-
-
-
-
-
-
- - -
-
-
-
Rate
-
-
-
-
- - -
-
-
-
Rate
diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/getCurrency.ts b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/getCurrency.ts index 92741c46a1e..fe490fc4972 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/getCurrency.ts +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/getCurrency.ts @@ -1,5 +1,5 @@ import { Currency } from '@uniswap/sdk-core' -import { SupportedInterfaceChainId, chainIdToBackendChain } from 'constants/chains' +import { chainIdToBackendChain } from 'constants/chains' import { NATIVE_CHAIN_ID } from 'constants/tokens' import { apolloClient } from 'graphql/data/apollo/client' import { gqlTokenToCurrencyInfo } from 'graphql/data/types' @@ -10,12 +10,10 @@ import { TokenDocument, TokenQuery, } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' +import { UniverseChainId } from 'uniswap/src/types/chains' import { isSameAddress } from 'utilities/src/addresses' -export async function getCurrency( - currencyId: string, - chainId: SupportedInterfaceChainId, -): Promise { +export async function getCurrency(currencyId: string, chainId: UniverseChainId): Promise { const isNative = currencyId === NATIVE_CHAIN_ID || currencyId?.toLowerCase() === 'native' || currencyId?.toLowerCase() === 'eth' if (isNative) { diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts index 466386aa6da..1dc8141e0ea 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts @@ -10,7 +10,6 @@ import { OrderTextTable, getActivityTitle, } from 'components/AccountDrawer/MiniPortfolio/constants' -import { SupportedInterfaceChainId } from 'constants/chains' import { isOnChainOrder, useAllSignatures } from 'state/signatures/hooks' import { SignatureDetails, SignatureType } from 'state/signatures/types' import { useMultichainTransactions } from 'state/transactions/hooks' @@ -32,8 +31,9 @@ import { import { isConfirmedTx } from 'state/transactions/utils' import { nativeOnChain } from 'uniswap/src/constants/tokens' import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { t } from 'uniswap/src/i18n' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { isAddress } from 'utilities/src/addresses' import { logger } from 'utilities/src/logger/logger' import { NumberType, useFormatter } from 'utils/formatNumbers' @@ -79,7 +79,7 @@ function buildCurrencyDescriptor( async function parseSwap( swap: ExactInputSwapTransactionInfo | ExactOutputSwapTransactionInfo, - chainId: SupportedInterfaceChainId, + chainId: UniverseChainId, formatNumber: FormatNumberFunctionType, ): Promise> { const [tokenIn, tokenOut] = await Promise.all([ @@ -100,7 +100,7 @@ async function parseSwap( function parseWrap( wrap: WrapTransactionInfo, - chainId: InterfaceChainId, + chainId: UniverseChainId, status: TransactionStatus, formatNumber: FormatNumberFunctionType, ): Partial { @@ -124,7 +124,7 @@ function parseWrap( async function parseApproval( approval: ApproveTransactionInfo, - chainId: SupportedInterfaceChainId, + chainId: UniverseChainId, status: TransactionStatus, ): Promise> { const currency = await getCurrency(approval.tokenAddress, chainId) @@ -146,7 +146,7 @@ type GenericLPInfo = Omit< > async function parseLP( lp: GenericLPInfo, - chainId: SupportedInterfaceChainId, + chainId: UniverseChainId, formatNumber: FormatNumberFunctionType, ): Promise> { const [baseCurrency, quoteCurrency] = await Promise.all([ @@ -161,7 +161,7 @@ async function parseLP( async function parseCollectFees( collect: CollectFeesTransactionInfo, - chainId: SupportedInterfaceChainId, + chainId: UniverseChainId, formatNumber: FormatNumberFunctionType, ): Promise> { // Adapts CollectFeesTransactionInfo to generic LP type @@ -180,7 +180,7 @@ async function parseCollectFees( async function parseMigrateCreateV3( lp: MigrateV2LiquidityToV3TransactionInfo | CreateV3PoolTransactionInfo, - chainId: SupportedInterfaceChainId, + chainId: UniverseChainId, ): Promise> { const [baseCurrency, quoteCurrency] = await Promise.all([ getCurrency(lp.baseCurrencyId, chainId), @@ -198,7 +198,7 @@ async function parseMigrateCreateV3( async function parseSend( send: SendTransactionInfo, - chainId: SupportedInterfaceChainId, + chainId: UniverseChainId, formatNumber: FormatNumberFunctionType, ): Promise> { const { currencyId, amount, recipient } = send @@ -223,7 +223,7 @@ async function parseSend( export async function transactionToActivity( details: TransactionDetails | undefined, - chainId: SupportedInterfaceChainId, + chainId: UniverseChainId, formatNumber: FormatNumberFunctionType, ): Promise { if (!details) { @@ -279,7 +279,7 @@ export async function transactionToActivity( export function getTransactionToActivityQueryOptions( transaction: TransactionDetails | undefined, - chainId: SupportedInterfaceChainId, + chainId: UniverseChainId, formatNumber: FormatNumberFunctionType, ) { return queryOptions({ @@ -352,12 +352,14 @@ export function useLocalActivities(account: string): ActivityMap { const allTransactions = useMultichainTransactions() const allSignatures = useAllSignatures() const { formatNumber } = useFormatter() + const { chains } = useEnabledChains() const { data } = useQuery({ queryKey: ['localActivities', account, allTransactions, allSignatures], queryFn: async () => { const transactions = Object.values(allTransactions) .filter(([transaction]) => transaction.from === account) + .filter(([, chainId]) => chains.includes(chainId)) .map(([transaction, chainId]) => transactionToActivity(transaction, chainId, formatNumber)) const signatures = Object.values(allSignatures) .filter((signature) => signature.offerer === account) diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/types.ts b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/types.ts index 60d8ec7aa32..c63b33d9dd6 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/types.ts +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/types.ts @@ -1,7 +1,7 @@ import { Currency } from '@uniswap/sdk-core' import { UniswapXOrderDetails } from 'state/signatures/types' import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' /** * TODO: refactor parsing / Activity so that all Activity Types can have a detail sheet. @@ -9,7 +9,7 @@ import { InterfaceChainId } from 'uniswap/src/types/chains' export type Activity = { hash: string - chainId: InterfaceChainId + chainId: UniverseChainId status: TransactionStatus offchainOrderDetails?: UniswapXOrderDetails statusMessage?: string diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/utils.ts b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/utils.ts index a953f6bd2c0..7636ec49de7 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/utils.ts +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Activity/utils.ts @@ -18,7 +18,7 @@ import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__g import { InterfaceEventNameLocal } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { t } from 'uniswap/src/i18n' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { logger } from 'utilities/src/logger/logger' import { useAsyncData } from 'utilities/src/react/hooks' import { didUserReject } from 'utils/swapErrorToUserReadableMessage' @@ -94,7 +94,7 @@ export const createGroups = (activities: Array = [], hideSpam = false) function getCancelMultipleUniswapXOrdersParams( orders: Array<{ encodedOrder: string; type: SignatureType }>, - chainId: InterfaceChainId, + chainId: UniverseChainId, ) { const nonces = orders .map(({ encodedOrder, type }) => @@ -149,7 +149,7 @@ async function cancelMultipleUniswapXOrders({ provider, }: { orders: Array<{ encodedOrder: string; type: SignatureType }> - chainId: InterfaceChainId + chainId: UniverseChainId permit2: Permit2 | null provider?: Web3Provider }) { @@ -174,7 +174,7 @@ async function cancelMultipleUniswapXOrders({ async function getCancelMultipleUniswapXOrdersTransaction( orders: Array<{ encodedOrder: string; type: SignatureType }>, - chainId: InterfaceChainId, + chainId: UniverseChainId, permit2: Permit2, ): Promise { const cancelParams = getCancelMultipleUniswapXOrdersParams(orders, chainId) @@ -201,7 +201,7 @@ export function useCreateCancelTransactionRequest( params: | { orders: Array<{ encodedOrder: string; type: SignatureType }> - chainId: InterfaceChainId + chainId: UniverseChainId } | undefined, ): TransactionRequest | undefined { diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/ExtensionDeeplinks.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/ExtensionDeeplinks.tsx index 0f972d81d18..9bfd096b10e 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/ExtensionDeeplinks.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/ExtensionDeeplinks.tsx @@ -1,4 +1,4 @@ -import { MenuState, miniPortfolioMenuStateAtom } from 'components/AccountDrawer/DefaultMenu' +import { MenuState, miniPortfolioMenuStateAtom } from 'components/AccountDrawer' import { useOpenLimitOrders, usePendingActivity } from 'components/AccountDrawer/MiniPortfolio/Activity/hooks' import { useFilterPossiblyMaliciousPositionInfo } from 'components/AccountDrawer/MiniPortfolio/Pools/PoolsTab' import useMultiChainPositions from 'components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions' diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/NFTs/NFTItem.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/NFTs/NFTItem.tsx index ed37516a383..6661808bd0f 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/NFTs/NFTItem.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/NFTs/NFTItem.tsx @@ -13,8 +13,10 @@ import { useNavigate } from 'react-router-dom' import { ThemedText } from 'theme/components' import { capitalize } from 'tsafe' import { Chain } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { t } from 'uniswap/src/i18n' +import { InterfaceGqlChain } from 'uniswap/src/types/chains' import { useTrace } from 'utilities/src/telemetry/trace/TraceContext' import { NumberType, useFormatter } from 'utils/formatNumbers' @@ -51,12 +53,18 @@ export function NFT({ mediaShouldBePlaying: boolean setCurrentTokenPlayingMedia: (tokenId: string | undefined) => void }) { + const { isTestnetModeEnabled, gqlChains } = useEnabledChains() const accountDrawer = useAccountDrawer() const navigate = useNavigate() const trace = useTrace() + const enabled = + asset.chain && isTestnetModeEnabled + ? gqlChains.includes(asset.chain as InterfaceGqlChain) + : asset.chain === Chain.Ethereum + const navigateToNFTDetails = () => { - if (asset.chain === Chain.Ethereum) { + if (enabled) { accountDrawer.close() navigate(detailsHref(asset)) } @@ -67,7 +75,7 @@ export function NFT({ sendAnalyticsEvent(SharedEventName.ELEMENT_CLICKED, { diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/NFTs/NFTTab.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/NFTs/NFTTab.tsx index 67e74a32277..bb95a09c48e 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/NFTs/NFTTab.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/NFTs/NFTTab.tsx @@ -15,6 +15,7 @@ import { Gallery } from 'ui/src/components/icons/Gallery' import { Chain } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { t } from 'uniswap/src/i18n' const StyledTabButton = styled(TabButton)` @@ -29,13 +30,14 @@ export default function NFTs({ account }: { account: string }) { const setSellPageState = useProfilePageState((state) => state.setProfilePageState) const resetSellAssets = useSellAsset((state) => state.reset) const clearCollectionFilters = useWalletCollections((state) => state.clearCollectionFilters) + const { gqlChains, isTestnetModeEnabled } = useEnabledChains() const isL2NFTsEnabled = useFeatureFlag(FeatureFlags.L2NFTs) const { walletAssets, loading, hasNext, loadMore } = useNftBalance({ ownerAddress: account, first: DEFAULT_NFT_QUERY_AMOUNT, skip: !accountDrawer.isOpen, - chains: isL2NFTsEnabled ? [Chain.Ethereum, Chain.Zora] : undefined, + chains: isTestnetModeEnabled ? gqlChains : isL2NFTsEnabled ? [Chain.Ethereum, Chain.Zora] : undefined, }) const [currentTokenPlayingMedia, setCurrentTokenPlayingMedia] = useState() diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/cache.ts b/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/cache.ts index e48e6f74f3f..b92f8be40ad 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/cache.ts +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/cache.ts @@ -8,13 +8,13 @@ import ms from 'ms' import { useCallback } from 'react' import { PositionDetails } from 'types/position' import { SerializedToken } from 'uniswap/src/features/tokens/slice/types' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { deserializeToken, serializeToken } from 'uniswap/src/utils/currency' import { buildCurrencyKey, currencyKey } from 'utils/currencyKey' export type PositionInfo = { owner: string - chainId: InterfaceChainId + chainId: UniverseChainId position: Position pool: Pool details: PositionDetails @@ -57,7 +57,7 @@ export function useCachedPositions(account: string): UseCachedPositionsReturnTyp return [cachedPositions[account], setPositionsAndStaleTimeout] } -const poolAddressKey = (details: PositionDetails, chainId: InterfaceChainId) => +const poolAddressKey = (details: PositionDetails, chainId: UniverseChainId) => `${chainId}-${details.token0}-${details.token1}-${details.fee}` type PoolAddressMap = { [key: string]: string | undefined } @@ -69,11 +69,11 @@ const poolAddressCacheAtom = atomWithStorage('poolCache', {}) export function usePoolAddressCache() { const [cache, updateCache] = useAtom(poolAddressCacheAtom) const get = useCallback( - (details: PositionDetails, chainId: InterfaceChainId) => cache[poolAddressKey(details, chainId)], + (details: PositionDetails, chainId: UniverseChainId) => cache[poolAddressKey(details, chainId)], [cache], ) const set = useCallback( - (details: PositionDetails, chainId: InterfaceChainId, address: string) => + (details: PositionDetails, chainId: UniverseChainId, address: string) => updateCache((c) => ({ ...c, [poolAddressKey(details, chainId)]: address })), [updateCache], ) @@ -102,8 +102,8 @@ function useTokenCache() { return { get, set } } -type TokenGetterFn = (addresses: string[], chainId: InterfaceChainId) => Promise<{ [key: string]: Token | undefined }> -export function useGetCachedTokens(chains: InterfaceChainId[]): TokenGetterFn { +type TokenGetterFn = (addresses: string[], chainId: UniverseChainId) => Promise<{ [key: string]: Token | undefined }> +export function useGetCachedTokens(chains: UniverseChainId[]): TokenGetterFn { const multicallContracts = useInterfaceMulticallContracts(chains) const tokenCache = useTokenCache() diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/getTokensAsync.ts b/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/getTokensAsync.ts index ab807df7b58..c141bf98669 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/getTokensAsync.ts +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/getTokensAsync.ts @@ -4,7 +4,7 @@ import ERC20_ABI from 'uniswap/src/abis/erc20.json' import { Erc20Interface } from 'uniswap/src/abis/types/Erc20' import { Erc20Bytes32Interface } from 'uniswap/src/abis/types/Erc20Bytes32' import { UniswapInterfaceMulticall } from 'uniswap/src/abis/types/v3' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { isAddress } from 'utilities/src/addresses' import { logger } from 'utilities/src/logger/logger' import { DEFAULT_ERC20_DECIMALS } from 'utilities/src/tokens/constants' @@ -39,7 +39,7 @@ async function fetchChunk(multicall: UniswapInterfaceMulticall, chunk: Call[]): } } -function tryParseToken(address: string, chainId: InterfaceChainId, data: CallResult[]) { +function tryParseToken(address: string, chainId: UniverseChainId, data: CallResult[]) { try { const [nameData, symbolData, decimalsData, nameDataBytes32, symbolDataBytes32] = data @@ -62,7 +62,7 @@ function tryParseToken(address: string, chainId: InterfaceChainId, data: CallRes } } -function parseTokens(addresses: string[], chainId: InterfaceChainId, returnData: CallResult[]) { +function parseTokens(addresses: string[], chainId: UniverseChainId, returnData: CallResult[]) { const tokenDataSlices = arrayToSlices(returnData, 5) return tokenDataSlices.reduce((acc: TokenMap, slice, index) => { @@ -93,7 +93,7 @@ const TokenPromiseCache: { [key: CurrencyKey]: Promise | unde // Returns tokens using a single RPC call to the multicall contract export async function getTokensAsync( addresses: string[], - chainId: InterfaceChainId, + chainId: UniverseChainId, multicall: UniswapInterfaceMulticall, ): Promise { if (addresses.length === 0) { diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/hooks.ts b/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/hooks.ts index e909758c2c8..b08d4cb607b 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/hooks.ts +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/hooks.ts @@ -20,7 +20,7 @@ import { ContractInput, useUniswapPricesQuery, } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { getContract } from 'utilities/src/contracts/getContract' import { CurrencyKey, currencyKey, currencyKeyFromGraphQL } from 'utils/currencyKey' @@ -30,7 +30,7 @@ type ContractMap = { [key: number]: T } export function useContractMultichain( addressMap: AddressMap, ABI: any, - chainIds?: InterfaceChainId[], + chainIds?: UniverseChainId[], ): ContractMap { const account = useAccount() const { provider: walletProvider } = useWeb3React() @@ -58,11 +58,11 @@ export function useContractMultichain( }, [ABI, addressMap, chainIds, isSupportedChain, account.chainId, walletProvider]) } -export function useV3ManagerContracts(chainIds: InterfaceChainId[]): ContractMap { +export function useV3ManagerContracts(chainIds: UniverseChainId[]): ContractMap { return useContractMultichain(V3NFT_ADDRESSES, NFTPositionManagerJSON.abi, chainIds) } -export function useInterfaceMulticallContracts(chainIds: InterfaceChainId[]): ContractMap { +export function useInterfaceMulticallContracts(chainIds: UniverseChainId[]): ContractMap { return useContractMultichain(MULTICALL_ADDRESSES, MulticallJSON.abi, chainIds) } diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions.tsx index 3ca93e7d6da..57a59cc0e12 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions.tsx @@ -13,7 +13,6 @@ import { usePoolPriceMap, useV3ManagerContracts, } from 'components/AccountDrawer/MiniPortfolio/Pools/hooks' -import { PRODUCTION_CHAIN_IDS } from 'constants/chains' import { BigNumber } from 'ethers/lib/ethers' import { Interface } from 'ethers/lib/utils' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' @@ -21,14 +20,15 @@ import { PositionDetails } from 'types/position' import { NonfungiblePositionManager, UniswapInterfaceMulticall } from 'uniswap/src/abis/types/v3' import { UniswapV3PoolInterface } from 'uniswap/src/abis/types/v3/UniswapV3Pool' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' +import { UniverseChainId } from 'uniswap/src/types/chains' import { logger } from 'utilities/src/logger/logger' import { DEFAULT_ERC20_DECIMALS } from 'utilities/src/tokens/constants' import { currencyKey } from 'utils/currencyKey' function createPositionInfo( owner: string, - chainId: InterfaceChainId, + chainId: UniverseChainId, details: PositionDetails, slot0: any, tokenA: Token, @@ -62,10 +62,9 @@ type UseMultiChainPositionsData = { positions?: PositionInfo[]; loading: boolean * @param chains - chains to fetch positions from * @returns positions, fees */ -export default function useMultiChainPositions( - account: string, - chains = PRODUCTION_CHAIN_IDS, -): UseMultiChainPositionsData { +export default function useMultiChainPositions(account: string): UseMultiChainPositionsData { + const { chains } = useEnabledChains() + const pms = useV3ManagerContracts(chains) const multicalls = useInterfaceMulticallContracts(chains) @@ -125,7 +124,7 @@ export default function useMultiChainPositions( // Combines PositionDetails with Pool data to build our return type const fetchPositionInfo = useCallback( - async (positionDetails: PositionDetails[], chainId: InterfaceChainId, multicall: UniswapInterfaceMulticall) => { + async (positionDetails: PositionDetails[], chainId: UniverseChainId, multicall: UniswapInterfaceMulticall) => { const poolInterface = new Interface(IUniswapV3PoolStateJSON.abi) as UniswapV3PoolInterface const tokens = await getTokens( positionDetails.flatMap((details) => [details.token0, details.token1]), @@ -172,7 +171,7 @@ export default function useMultiChainPositions( ) const fetchPositionsForChain = useCallback( - async (chainId: InterfaceChainId): Promise => { + async (chainId: UniverseChainId): Promise => { if (!account || account.length === 0) { return [] } diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/PortfolioLogo.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/PortfolioLogo.tsx index 1c26c13f634..f7686e4d650 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/PortfolioLogo.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/PortfolioLogo.tsx @@ -3,6 +3,7 @@ import blankTokenUrl from 'assets/svg/blank_token.svg' import { ReactComponent as UnknownStatus } from 'assets/svg/contract-interaction.svg' import Identicon from 'components/Identicon' import { ChainLogo } from 'components/Logo/ChainLogo' +import CurrencyLogo from 'components/Logo/CurrencyLogo' import { CircleLogoImage, DoubleCurrencyLogo, @@ -10,10 +11,11 @@ import { L2LogoContainer, SingleLogoContainer, } from 'components/Logo/DoubleLogo' +import { TESTNET_CHAIN_IDS } from 'constants/chains' import styled from 'lib/styled-components' import React, { memo } from 'react' import { Flex, SpinningLoader, styled as TamaguiStyled } from 'ui/src' -import { InterfaceChainId, UniverseChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' const UnknownContract = styled(UnknownStatus)` color: ${({ theme }) => theme.neutral2}; @@ -28,7 +30,7 @@ const LogoContainer = styled.div` ` interface PortfolioLogoProps { - chainId: InterfaceChainId + chainId: UniverseChainId accountAddress?: string currencies?: Array images?: Array @@ -37,7 +39,7 @@ interface PortfolioLogoProps { loading?: boolean } -function SquareL2Logo({ chainId, size }: { chainId: InterfaceChainId; size: number }) { +function SquareL2Logo({ chainId, size }: { chainId: UniverseChainId; size: number }) { if (chainId === UniverseChainId.Mainnet) { return null } @@ -59,11 +61,15 @@ const AbsoluteCenteredElement = TamaguiStyled(Flex, { top: -4.5, }) -// TODO(WEB-2983) +// TODO(WEB-5111): Replace currency logos on web with uniswap currency logos /** * Renders an image by prioritizing a list of sources, and then eventually a fallback contract icon */ export const PortfolioLogo = memo(function PortfolioLogo(props: PortfolioLogoProps) { + if (TESTNET_CHAIN_IDS.includes(props.chainId)) { + return + } + return ( diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/Tokens/TokensTab.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/Tokens/TokensTab.tsx index 24bd3116b7b..fd888f331fd 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/Tokens/TokensTab.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/Tokens/TokensTab.tsx @@ -16,8 +16,14 @@ import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletConten import { useCallback, useMemo, useState } from 'react' import { useNavigate } from 'react-router-dom' import { EllipsisStyle, ThemedText } from 'theme/components' -import { useHideSmallBalancesSetting, useHideSpamTokensSetting } from 'uniswap/src/features/settings/hooks' +import { Text, Tooltip } from 'ui/src' +import { + useEnabledChains, + useHideSmallBalancesSetting, + useHideSpamTokensSetting, +} from 'uniswap/src/features/settings/hooks' import Trace from 'uniswap/src/features/telemetry/Trace' +import { useTranslation } from 'uniswap/src/i18n' import { logger } from 'utilities/src/logger/logger' import { NumberType, useFormatter } from 'utils/formatNumbers' import { splitHiddenTokens } from 'utils/splitHiddenTokens' @@ -77,16 +83,22 @@ function TokenRow({ denominatedValue, tokenProjectMarket, }: PortfolioBalance & { token: PortfolioToken }) { + const { t } = useTranslation() const { formatDelta } = useFormatter() + const { isTestnetModeEnabled } = useEnabledChains() const percentChange = tokenProjectMarket?.relativeChange24?.value ?? 0 const navigate = useNavigate() const accountDrawer = useAccountDrawer() const navigateToTokenDetails = useCallback(async () => { + if (isTestnetModeEnabled) { + return + } + navigate(getTokenDetailsURL({ ...token })) accountDrawer.close() - }, [navigate, token, accountDrawer]) + }, [navigate, token, accountDrawer, isTestnetModeEnabled]) const { formatNumber } = useFormatter() const currency = gqlToCurrency(token) @@ -100,6 +112,40 @@ function TokenRow({ }) return null } + + const portfolioRow = ( + } + title={{token?.name ?? token?.project?.name}} + descriptor={ + + {formatNumber({ + input: quantity, + type: NumberType.TokenNonTx, + })}{' '} + {token?.symbol} + + } + onClick={navigateToTokenDetails} + right={ + denominatedValue && ( + <> + + {formatNumber({ + input: denominatedValue?.value, + type: NumberType.PortfolioBalance, + })} + + + + {formatDelta(percentChange)} + + + ) + } + /> + ) + return ( - } - title={{token?.name ?? token?.project?.name}} - descriptor={ - - {formatNumber({ - input: quantity, - type: NumberType.TokenNonTx, - })}{' '} - {token?.symbol} - - } - onClick={navigateToTokenDetails} - right={ - denominatedValue && ( - <> - - {formatNumber({ - input: denominatedValue?.value, - type: NumberType.PortfolioBalance, - })} - - - - {formatDelta(percentChange)} - - - ) - } - /> + {isTestnetModeEnabled ? ( + + + {t('token.details.testnet.unsupported')} + + + {portfolioRow} + + ) : ( + portfolioRow + )} ) } diff --git a/apps/web/src/components/AccountDrawer/MiniPortfolio/constants.tsx b/apps/web/src/components/AccountDrawer/MiniPortfolio/constants.tsx index f359282ce7f..cb02a949947 100644 --- a/apps/web/src/components/AccountDrawer/MiniPortfolio/constants.tsx +++ b/apps/web/src/components/AccountDrawer/MiniPortfolio/constants.tsx @@ -147,6 +147,11 @@ const TransactionTitleTable: { [key in TransactionType]: { [state in Transaction [TransactionStatus.Confirmed]: t('common.added.liquidity'), [TransactionStatus.Failed]: t('common.add.liquidity.failed'), }, + [TransactionType.DECREASE_LIQUIDITY]: { + [TransactionStatus.Pending]: t('common.removing.liquidity'), + [TransactionStatus.Confirmed]: t('common.removed.liquidity'), + [TransactionStatus.Failed]: t('common.remove.liquidity.failed'), + }, } export const CancelledTransactionTitleTable: { [key in TransactionType]: string } = { @@ -178,6 +183,7 @@ export const CancelledTransactionTitleTable: { [key in TransactionType]: string [TransactionType.SUBMIT_PROPOSAL]: t('common.submit.proposal.cancelled'), [TransactionType.LIMIT]: t('common.limit.cancelled'), [TransactionType.INCREASE_LIQUIDITY]: t('common.add.liquidity.cancelled'), + [TransactionType.DECREASE_LIQUIDITY]: t('common.remove.liquidity.cancelled'), } const AlternateTransactionTitleTable: { [key in TransactionType]?: { [state in TransactionStatus]: string } } = { diff --git a/apps/web/src/components/AccountDrawer/SettingsMenu.tsx b/apps/web/src/components/AccountDrawer/SettingsMenu.tsx index f5344aeb7fe..aa3c2ddcb9d 100644 --- a/apps/web/src/components/AccountDrawer/SettingsMenu.tsx +++ b/apps/web/src/components/AccountDrawer/SettingsMenu.tsx @@ -13,6 +13,8 @@ import { ReactNode } from 'react' import { ChevronRight } from 'react-feather' import { ClickableStyle, ThemedText } from 'theme/components' import ThemeToggle from 'theme/components/ThemeToggle' +import { FeatureFlags } from 'uniswap/src/features/gating/flags' +import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { useLanguageInfo } from 'uniswap/src/features/language/hooks' import { Trans } from 'uniswap/src/i18n' @@ -73,6 +75,7 @@ export default function SettingsMenu({ const activeLanguage = useActiveLanguage() const activeLocalCurrency = useActiveLocalCurrency() const languageInfo = useLanguageInfo(activeLanguage) + const isTestnetFeatureFlagOn = useFeatureFlag(FeatureFlags.TestnetMode) return ( } onClose={onClose}> @@ -83,7 +86,7 @@ export default function SettingsMenu({ - + {isTestnetFeatureFlagOn && } diff --git a/apps/web/src/components/AccountDrawer/SettingsToggle.tsx b/apps/web/src/components/AccountDrawer/SettingsToggle.tsx index d00d991f821..fb659d5874b 100644 --- a/apps/web/src/components/AccountDrawer/SettingsToggle.tsx +++ b/apps/web/src/components/AccountDrawer/SettingsToggle.tsx @@ -14,11 +14,12 @@ interface SettingsToggleProps { title: ReactNode description?: string dataid?: string + disabled?: boolean isActive: boolean toggle: () => void } -export function SettingsToggle({ title, description, dataid, isActive, toggle }: SettingsToggleProps) { +export function SettingsToggle({ title, description, dataid, isActive, toggle, disabled }: SettingsToggleProps) { return ( @@ -33,7 +34,7 @@ export function SettingsToggle({ title, description, dataid, isActive, toggle }: )} - + ) } diff --git a/apps/web/src/components/AccountDrawer/SmallBalanceToggle.tsx b/apps/web/src/components/AccountDrawer/SmallBalanceToggle.tsx index 5867cf6bdd8..3bf664c6075 100644 --- a/apps/web/src/components/AccountDrawer/SmallBalanceToggle.tsx +++ b/apps/web/src/components/AccountDrawer/SmallBalanceToggle.tsx @@ -1,16 +1,24 @@ import { SettingsToggle } from 'components/AccountDrawer/SettingsToggle' import { useDispatch } from 'react-redux' -import { useHideSmallBalancesSetting } from 'uniswap/src/features/settings/hooks' +import { useEnabledChains, useHideSmallBalancesSetting } from 'uniswap/src/features/settings/hooks' import { setHideSmallBalances } from 'uniswap/src/features/settings/slice' import { t } from 'uniswap/src/i18n' export function SmallBalanceToggle() { const hideSmallBalances = useHideSmallBalancesSetting() const dispatch = useDispatch() + const { isTestnetModeEnabled } = useEnabledChains() const onToggle = () => { dispatch(setHideSmallBalances(!hideSmallBalances)) } - return + return ( + + ) } diff --git a/apps/web/src/components/AccountDrawer/SpamToggle.tsx b/apps/web/src/components/AccountDrawer/SpamToggle.tsx index 951d06e5c8a..9b516cd9709 100644 --- a/apps/web/src/components/AccountDrawer/SpamToggle.tsx +++ b/apps/web/src/components/AccountDrawer/SpamToggle.tsx @@ -1,18 +1,24 @@ import { SettingsToggle } from 'components/AccountDrawer/SettingsToggle' import { useDispatch } from 'react-redux' -import { useHideSpamTokensSetting } from 'uniswap/src/features/settings/hooks' +import { useEnabledChains, useHideSpamTokensSetting } from 'uniswap/src/features/settings/hooks' import { setHideSpamTokens } from 'uniswap/src/features/settings/slice' import { Trans } from 'uniswap/src/i18n' export function SpamToggle() { const hideSpamTokens = useHideSpamTokensSetting() const dispatch = useDispatch() + const { isTestnetModeEnabled } = useEnabledChains() const onToggle = () => { dispatch(setHideSpamTokens(!hideSpamTokens)) } return ( - } isActive={hideSpamTokens} toggle={onToggle} /> + } + isActive={hideSpamTokens && !isTestnetModeEnabled} + toggle={onToggle} + disabled={isTestnetModeEnabled} + /> ) } diff --git a/apps/web/src/components/AccountDrawer/TestnetsToggle.tsx b/apps/web/src/components/AccountDrawer/TestnetsToggle.tsx index 41b07a57608..3a5d6196f70 100644 --- a/apps/web/src/components/AccountDrawer/TestnetsToggle.tsx +++ b/apps/web/src/components/AccountDrawer/TestnetsToggle.tsx @@ -1,19 +1,28 @@ import { SettingsToggle } from 'components/AccountDrawer/SettingsToggle' -import { useAtom } from 'jotai' -import { atomWithStorage } from 'jotai/utils' +import { useDispatch } from 'react-redux' +import { useOpenModal } from 'state/application/hooks' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' +import { setIsTestnetModeEnabled } from 'uniswap/src/features/settings/slice' +import { ModalName } from 'uniswap/src/features/telemetry/constants' import { t } from 'uniswap/src/i18n' -export const showTestnetsAtom = atomWithStorage('showTestnets', false) - export function TestnetsToggle() { - const [showTestnets, updateShowTestnets] = useAtom(showTestnetsAtom) + const dispatch = useDispatch() + const { isTestnetModeEnabled } = useEnabledChains() + const openTestnetModal = useOpenModal({ name: ModalName.TestnetMode }) return ( void updateShowTestnets((value) => !value)} + isActive={isTestnetModeEnabled} + toggle={() => { + const nextIsTestnetModeEnabled = !isTestnetModeEnabled + if (nextIsTestnetModeEnabled) { + openTestnetModal() + } + dispatch(setIsTestnetModeEnabled(nextIsTestnetModeEnabled)) + }} /> ) } diff --git a/apps/web/src/components/AccountDrawer/UniwalletModal.tsx b/apps/web/src/components/AccountDrawer/UniwalletModal.tsx index d75b87771f1..eb6e12c6b01 100644 --- a/apps/web/src/components/AccountDrawer/UniwalletModal.tsx +++ b/apps/web/src/components/AccountDrawer/UniwalletModal.tsx @@ -2,11 +2,11 @@ import { InterfaceElementName, InterfaceEventName } from '@uniswap/analytics-eve import MobileAppLogo from 'assets/svg/uniswap_app_logo.svg' import Modal from 'components/Modal' import { useConnectorWithId } from 'components/WalletModal/useOrderedConnections' -import { CONNECTION } from 'components/Web3Provider/constants' import { useConnect } from 'hooks/useConnect' import { useCallback, useEffect, useState } from 'react' import { CloseIcon } from 'theme/components' import { Button, Flex, Image, QRCodeDisplay, Separator, Text, useSporeColors } from 'ui/src' +import { CONNECTION_PROVIDER_IDS } from 'uniswap/src/constants/web3' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { useTranslation } from 'uniswap/src/i18n' import { isWebAndroid, isWebIOS } from 'utilities/src/platform' @@ -21,9 +21,12 @@ export default function UniwalletModal() { const onLaunchedMobilePlatform = isWebIOS || isWebAndroid const open = !onLaunchedMobilePlatform && !!uri && connection.isPending - const uniswapWalletConnectConnector = useConnectorWithId(CONNECTION.UNISWAP_WALLET_CONNECT_CONNECTOR_ID, { - shouldThrow: true, - }) + const uniswapWalletConnectConnector = useConnectorWithId( + CONNECTION_PROVIDER_IDS.UNISWAP_WALLET_CONNECT_CONNECTOR_ID, + { + shouldThrow: true, + }, + ) useEffect(() => { function listener({ type, data }: { type: string; data?: unknown }) { diff --git a/apps/web/src/components/AccountDrawer/index.tsx b/apps/web/src/components/AccountDrawer/index.tsx index a1aeb5a9adf..31c3d7e5e33 100644 --- a/apps/web/src/components/AccountDrawer/index.tsx +++ b/apps/web/src/components/AccountDrawer/index.tsx @@ -8,7 +8,7 @@ import useDisableScrolling from 'hooks/useDisableScrolling' import { useOnClickOutside } from 'hooks/useOnClickOutside' import usePrevious from 'hooks/usePrevious' import { useIsUniExtensionAvailable } from 'hooks/useUniswapWalletOptions' -import { useAtom } from 'jotai' +import { atom, useAtom } from 'jotai' import styled, { css } from 'lib/styled-components' import { useEffect, useRef, useState } from 'react' import { ChevronsRight } from 'react-feather' @@ -27,6 +27,17 @@ const DRAWER_OFFSET = '10px' export const MODAL_WIDTH = '368px' +export enum MenuState { + DEFAULT = 'default', + SETTINGS = 'settings', + LANGUAGE_SETTINGS = 'language_settings', + LOCAL_CURRENCY_SETTINGS = 'local_currency_settings', + LIMITS = 'limits', + POOLS = 'pools', +} + +export const miniPortfolioMenuStateAtom = atom(MenuState.DEFAULT) + const ScrimBackground = styled.div<{ $open: boolean; $maxWidth?: number; $zIndex?: number }>` z-index: ${({ $zIndex }) => $zIndex ?? Z_INDEX.modalBackdrop}; overflow: hidden; diff --git a/apps/web/src/components/AddressQRModal.tsx b/apps/web/src/components/AddressQRModal.tsx index 9a9be9ee7fd..6e0232f7e70 100644 --- a/apps/web/src/components/AddressQRModal.tsx +++ b/apps/web/src/components/AddressQRModal.tsx @@ -4,22 +4,16 @@ import Identicon from 'components/Identicon' import { GetHelpHeader } from 'components/Modal/GetHelpHeader' import { PRODUCTION_CHAIN_IDS } from 'constants/chains' import useENSName from 'hooks/useENSName' -import styled from 'lib/styled-components' import { useCallback } from 'react' import { useModalIsOpen, useOpenModal, useToggleModal } from 'state/application/hooks' import { ApplicationModal } from 'state/application/reducer' -import { ExternalLink, ThemedText } from 'theme/components' +import { ThemedText } from 'theme/components' import { AdaptiveWebModal, Flex, QRCodeDisplay, Text, useSporeColors } from 'ui/src' import { NetworkLogos } from 'uniswap/src/components/network/NetworkLogos' import { useAddressColorProps } from 'uniswap/src/features/address/color' import { useUnitagByAddress } from 'uniswap/src/features/unitags/hooks' import { Trans } from 'uniswap/src/i18n' -const HelpCenterLink = styled(ExternalLink)` - font-size: 14px; - margin: 4px auto 0 auto; -` - const UNICON_SIZE = 50 const QR_CODE_SIZE = 240 @@ -44,6 +38,9 @@ export function AddressQRModal({ accountAddress }: { accountAddress: Address }) + + + {hasSecondaryIdentifier && ( )} - - - - + - - - - + diff --git a/apps/web/src/components/Charts/LiquidityChart/index.tsx b/apps/web/src/components/Charts/LiquidityChart/index.tsx index 9e7369b18ba..8ee96b70ed9 100644 --- a/apps/web/src/components/Charts/LiquidityChart/index.tsx +++ b/apps/web/src/components/Charts/LiquidityChart/index.tsx @@ -12,7 +12,7 @@ import { TickProcessed, usePoolActiveLiquidity } from 'hooks/usePoolTickData' import JSBI from 'jsbi' import { ISeriesApi, UTCTimestamp } from 'lightweight-charts' import { useEffect, useState } from 'react' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { NumberType, useFormatter } from 'utils/formatNumbers' interface LiquidityBarChartModelParams extends ChartModelParams, LiquidityBarProps {} @@ -231,7 +231,7 @@ export function useLiquidityBarData({ tokenB: Token feeTier: FeeAmount isReversed: boolean - chainId: InterfaceChainId + chainId: UniverseChainId }) { const { formatNumber, formatPrice } = useFormatter() const activePoolData = usePoolActiveLiquidity(tokenA, tokenB, feeTier, chainId) diff --git a/apps/web/src/components/ConfirmSwapModal/__snapshots__/Error.test.tsx.snap b/apps/web/src/components/ConfirmSwapModal/__snapshots__/Error.test.tsx.snap index 4af650fb7a3..1a85ee73eee 100644 --- a/apps/web/src/components/ConfirmSwapModal/__snapshots__/Error.test.tsx.snap +++ b/apps/web/src/components/ConfirmSwapModal/__snapshots__/Error.test.tsx.snap @@ -2,62 +2,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 0 1`] = ` - .c4 { - height: 24px; - width: 24px; -} - -.c4 path { - background: #7D7D7D; - fill: #7D7D7D; -} - -.c14 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - gap: 2px; - position: relative; - top: 0; - left: 0; -} - -.c14 img { - width: 16px; - height: 16px; - border-radius: 50%; -} - -.c15 { - width: 8px; - height: 16px; - border-radius: 50%; -} - -.c13 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - position: relative; - top: 0; - left: 0; -} - -.c12 { - position: relative; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; -} - -.c10 { + .c10 { box-sizing: border-box; margin: 0; min-width: 0; @@ -81,7 +26,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 0 gap: 8px; } -.c17 { +.c13 { width: 100%; display: -webkit-box; display: -webkit-flex; @@ -115,7 +60,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 0 letter-spacing: -0.01em; } -.c16 { +.c12 { -webkit-text-decoration: none; text-decoration: none; cursor: pointer; @@ -126,15 +71,15 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 0 font-weight: 500; } -.c16:hover { +.c12:hover { opacity: 0.6; } -.c16:active { +.c12:active { opacity: 0.4; } -.c21 { +.c17 { background-color: transparent; bottom: 0; border-radius: inherit; @@ -148,7 +93,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 0 width: 100%; } -.c18 { +.c14 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -183,26 +128,26 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 0 user-select: none; } -.c18:active .c20 { +.c14:active .c16 { background-color: #B8C0DC3d; } -.c18:focus .c20 { +.c14:focus .c16 { background-color: #B8C0DC3d; } -.c18:hover .c20 { +.c14:hover .c16 { background-color: #98A1C014; } -.c18:disabled { +.c14:disabled { cursor: default; opacity: 0.6; } -.c18:disabled:active .c20, -.c18:disabled:focus .c20, -.c18:disabled:hover .c20 { +.c14:disabled:active .c16, +.c14:disabled:focus .c16, +.c14:disabled:hover .c16 { background-color: transparent; } @@ -296,7 +241,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 0 text-align: center; } -.c19 { +.c15 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -309,6 +254,16 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 0 border-radius: 12px; } +.c4 { + height: 24px; + width: 24px; +} + +.c4 path { + background: #7D7D7D; + fill: #7D7D7D; +} + -
-
-
-
- - -
-
-
-
@@ -410,30 +341,6 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 0 points="12 5 19 12 12 19" /> -
-
-
-
- - -
-
-
-
@@ -441,7 +348,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 0
@@ -472,62 +379,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 0 exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 1 1`] = ` - .c4 { - height: 24px; - width: 24px; -} - -.c4 path { - background: #7D7D7D; - fill: #7D7D7D; -} - -.c14 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - gap: 2px; - position: relative; - top: 0; - left: 0; -} - -.c14 img { - width: 16px; - height: 16px; - border-radius: 50%; -} - -.c15 { - width: 8px; - height: 16px; - border-radius: 50%; -} - -.c13 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - position: relative; - top: 0; - left: 0; -} - -.c12 { - position: relative; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; -} - -.c10 { + .c10 { box-sizing: border-box; margin: 0; min-width: 0; @@ -551,7 +403,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 1 gap: 8px; } -.c17 { +.c13 { width: 100%; display: -webkit-box; display: -webkit-flex; @@ -585,7 +437,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 1 letter-spacing: -0.01em; } -.c16 { +.c12 { -webkit-text-decoration: none; text-decoration: none; cursor: pointer; @@ -596,15 +448,15 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 1 font-weight: 500; } -.c16:hover { +.c12:hover { opacity: 0.6; } -.c16:active { +.c12:active { opacity: 0.4; } -.c21 { +.c17 { background-color: transparent; bottom: 0; border-radius: inherit; @@ -618,7 +470,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 1 width: 100%; } -.c18 { +.c14 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -653,26 +505,26 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 1 user-select: none; } -.c18:active .c20 { +.c14:active .c16 { background-color: #B8C0DC3d; } -.c18:focus .c20 { +.c14:focus .c16 { background-color: #B8C0DC3d; } -.c18:hover .c20 { +.c14:hover .c16 { background-color: #98A1C014; } -.c18:disabled { +.c14:disabled { cursor: default; opacity: 0.6; } -.c18:disabled:active .c20, -.c18:disabled:focus .c20, -.c18:disabled:hover .c20 { +.c14:disabled:active .c16, +.c14:disabled:focus .c16, +.c14:disabled:hover .c16 { background-color: transparent; } @@ -766,7 +618,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 1 text-align: center; } -.c19 { +.c15 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -779,6 +631,16 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 1 border-radius: 12px; } +.c4 { + height: 24px; + width: 24px; +} + +.c4 path { + background: #7D7D7D; + fill: #7D7D7D; +} + -
-
-
-
- - -
-
-
-
@@ -880,30 +718,6 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 1 points="12 5 19 12 12 19" /> -
-
-
-
- - -
-
-
-
@@ -911,7 +725,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 1
@@ -942,62 +756,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 1 exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 3 1`] = ` - .c4 { - height: 24px; - width: 24px; -} - -.c4 path { - background: #7D7D7D; - fill: #7D7D7D; -} - -.c14 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - gap: 2px; - position: relative; - top: 0; - left: 0; -} - -.c14 img { - width: 16px; - height: 16px; - border-radius: 50%; -} - -.c15 { - width: 8px; - height: 16px; - border-radius: 50%; -} - -.c13 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - position: relative; - top: 0; - left: 0; -} - -.c12 { - position: relative; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; -} - -.c10 { + .c10 { box-sizing: border-box; margin: 0; min-width: 0; @@ -1021,7 +780,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 3 gap: 8px; } -.c17 { +.c13 { width: 100%; display: -webkit-box; display: -webkit-flex; @@ -1055,7 +814,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 3 letter-spacing: -0.01em; } -.c16 { +.c12 { -webkit-text-decoration: none; text-decoration: none; cursor: pointer; @@ -1066,15 +825,15 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 3 font-weight: 500; } -.c16:hover { +.c12:hover { opacity: 0.6; } -.c16:active { +.c12:active { opacity: 0.4; } -.c21 { +.c17 { background-color: transparent; bottom: 0; border-radius: inherit; @@ -1088,7 +847,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 3 width: 100%; } -.c18 { +.c14 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -1123,26 +882,26 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 3 user-select: none; } -.c18:active .c20 { +.c14:active .c16 { background-color: #B8C0DC3d; } -.c18:focus .c20 { +.c14:focus .c16 { background-color: #B8C0DC3d; } -.c18:hover .c20 { +.c14:hover .c16 { background-color: #98A1C014; } -.c18:disabled { +.c14:disabled { cursor: default; opacity: 0.6; } -.c18:disabled:active .c20, -.c18:disabled:focus .c20, -.c18:disabled:hover .c20 { +.c14:disabled:active .c16, +.c14:disabled:focus .c16, +.c14:disabled:hover .c16 { background-color: transparent; } @@ -1236,7 +995,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 3 text-align: center; } -.c19 { +.c15 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -1249,6 +1008,16 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 3 border-radius: 12px; } +.c4 { + height: 24px; + width: 24px; +} + +.c4 path { + background: #7D7D7D; + fill: #7D7D7D; +} + -
-
-
-
- - -
-
-
-
@@ -1350,30 +1095,6 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 3 points="12 5 19 12 12 19" /> -
-
-
-
- - -
-
-
-
@@ -1381,7 +1102,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 3
@@ -1412,62 +1133,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 3 exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 4 1`] = ` - .c4 { - height: 24px; - width: 24px; -} - -.c4 path { - background: #7D7D7D; - fill: #7D7D7D; -} - -.c14 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - gap: 2px; - position: relative; - top: 0; - left: 0; -} - -.c14 img { - width: 16px; - height: 16px; - border-radius: 50%; -} - -.c15 { - width: 8px; - height: 16px; - border-radius: 50%; -} - -.c13 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - position: relative; - top: 0; - left: 0; -} - -.c12 { - position: relative; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; -} - -.c10 { + .c10 { box-sizing: border-box; margin: 0; min-width: 0; @@ -1491,7 +1157,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 4 gap: 8px; } -.c17 { +.c13 { width: 100%; display: -webkit-box; display: -webkit-flex; @@ -1525,7 +1191,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 4 letter-spacing: -0.01em; } -.c16 { +.c12 { -webkit-text-decoration: none; text-decoration: none; cursor: pointer; @@ -1536,15 +1202,15 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 4 font-weight: 500; } -.c16:hover { +.c12:hover { opacity: 0.6; } -.c16:active { +.c12:active { opacity: 0.4; } -.c21 { +.c17 { background-color: transparent; bottom: 0; border-radius: inherit; @@ -1558,7 +1224,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 4 width: 100%; } -.c18 { +.c14 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -1593,26 +1259,26 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 4 user-select: none; } -.c18:active .c20 { +.c14:active .c16 { background-color: #B8C0DC3d; } -.c18:focus .c20 { +.c14:focus .c16 { background-color: #B8C0DC3d; } -.c18:hover .c20 { +.c14:hover .c16 { background-color: #98A1C014; } -.c18:disabled { +.c14:disabled { cursor: default; opacity: 0.6; } -.c18:disabled:active .c20, -.c18:disabled:focus .c20, -.c18:disabled:hover .c20 { +.c14:disabled:active .c16, +.c14:disabled:focus .c16, +.c14:disabled:hover .c16 { background-color: transparent; } @@ -1706,7 +1372,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 4 text-align: center; } -.c19 { +.c15 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -1719,6 +1385,16 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 4 border-radius: 12px; } +.c4 { + height: 24px; + width: 24px; +} + +.c4 path { + background: #7D7D7D; + fill: #7D7D7D; +} + -
-
-
-
- - -
-
-
-
@@ -1820,30 +1472,6 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 4 points="12 5 19 12 12 19" /> -
-
-
-
- - -
-
-
-
@@ -1851,7 +1479,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 4
@@ -1882,62 +1510,7 @@ exports[`ConfirmSwapModal/Error renders "classic trade" correctly, with error= 4 exports[`ConfirmSwapModal/Error renders "limit order" correctly, with error= 3 1`] = ` - .c4 { - height: 24px; - width: 24px; -} - -.c4 path { - background: #7D7D7D; - fill: #7D7D7D; -} - -.c14 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - gap: 2px; - position: relative; - top: 0; - left: 0; -} - -.c14 img { - width: 16px; - height: 16px; - border-radius: 50%; -} - -.c15 { - width: 8px; - height: 16px; - border-radius: 50%; -} - -.c13 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - position: relative; - top: 0; - left: 0; -} - -.c12 { - position: relative; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; -} - -.c10 { + .c10 { box-sizing: border-box; margin: 0; min-width: 0; @@ -1961,7 +1534,7 @@ exports[`ConfirmSwapModal/Error renders "limit order" correctly, with error= 3 1 gap: 8px; } -.c17 { +.c13 { width: 100%; display: -webkit-box; display: -webkit-flex; @@ -1995,7 +1568,7 @@ exports[`ConfirmSwapModal/Error renders "limit order" correctly, with error= 3 1 letter-spacing: -0.01em; } -.c16 { +.c12 { -webkit-text-decoration: none; text-decoration: none; cursor: pointer; @@ -2006,15 +1579,15 @@ exports[`ConfirmSwapModal/Error renders "limit order" correctly, with error= 3 1 font-weight: 500; } -.c16:hover { +.c12:hover { opacity: 0.6; } -.c16:active { +.c12:active { opacity: 0.4; } -.c21 { +.c17 { background-color: transparent; bottom: 0; border-radius: inherit; @@ -2028,7 +1601,7 @@ exports[`ConfirmSwapModal/Error renders "limit order" correctly, with error= 3 1 width: 100%; } -.c18 { +.c14 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -2063,26 +1636,26 @@ exports[`ConfirmSwapModal/Error renders "limit order" correctly, with error= 3 1 user-select: none; } -.c18:active .c20 { +.c14:active .c16 { background-color: #B8C0DC3d; } -.c18:focus .c20 { +.c14:focus .c16 { background-color: #B8C0DC3d; } -.c18:hover .c20 { +.c14:hover .c16 { background-color: #98A1C014; } -.c18:disabled { +.c14:disabled { cursor: default; opacity: 0.6; } -.c18:disabled:active .c20, -.c18:disabled:focus .c20, -.c18:disabled:hover .c20 { +.c14:disabled:active .c16, +.c14:disabled:focus .c16, +.c14:disabled:hover .c16 { background-color: transparent; } @@ -2176,7 +1749,7 @@ exports[`ConfirmSwapModal/Error renders "limit order" correctly, with error= 3 1 text-align: center; } -.c19 { +.c15 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -2189,6 +1762,16 @@ exports[`ConfirmSwapModal/Error renders "limit order" correctly, with error= 3 1 border-radius: 12px; } +.c4 { + height: 24px; + width: 24px; +} + +.c4 path { + background: #7D7D7D; + fill: #7D7D7D; +} + -
-
-
-
- - -
-
-
-
@@ -2288,30 +1847,6 @@ exports[`ConfirmSwapModal/Error renders "limit order" correctly, with error= 3 1 points="12 5 19 12 12 19" /> -
-
-
-
- - -
-
-
-
@@ -2319,7 +1854,7 @@ exports[`ConfirmSwapModal/Error renders "limit order" correctly, with error= 3 1
@@ -2350,62 +1885,7 @@ exports[`ConfirmSwapModal/Error renders "limit order" correctly, with error= 3 1 exports[`ConfirmSwapModal/Error renders "limit order" correctly, with error= 4 1`] = ` - .c4 { - height: 24px; - width: 24px; -} - -.c4 path { - background: #7D7D7D; - fill: #7D7D7D; -} - -.c14 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - gap: 2px; - position: relative; - top: 0; - left: 0; -} - -.c14 img { - width: 16px; - height: 16px; - border-radius: 50%; -} - -.c15 { - width: 8px; - height: 16px; - border-radius: 50%; -} - -.c13 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - position: relative; - top: 0; - left: 0; -} - -.c12 { - position: relative; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; -} - -.c10 { + .c10 { box-sizing: border-box; margin: 0; min-width: 0; @@ -2429,7 +1909,7 @@ exports[`ConfirmSwapModal/Error renders "limit order" correctly, with error= 4 1 gap: 8px; } -.c17 { +.c13 { width: 100%; display: -webkit-box; display: -webkit-flex; @@ -2463,7 +1943,7 @@ exports[`ConfirmSwapModal/Error renders "limit order" correctly, with error= 4 1 letter-spacing: -0.01em; } -.c16 { +.c12 { -webkit-text-decoration: none; text-decoration: none; cursor: pointer; @@ -2474,15 +1954,15 @@ exports[`ConfirmSwapModal/Error renders "limit order" correctly, with error= 4 1 font-weight: 500; } -.c16:hover { +.c12:hover { opacity: 0.6; } -.c16:active { +.c12:active { opacity: 0.4; } -.c21 { +.c17 { background-color: transparent; bottom: 0; border-radius: inherit; @@ -2496,7 +1976,7 @@ exports[`ConfirmSwapModal/Error renders "limit order" correctly, with error= 4 1 width: 100%; } -.c18 { +.c14 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -2531,26 +2011,26 @@ exports[`ConfirmSwapModal/Error renders "limit order" correctly, with error= 4 1 user-select: none; } -.c18:active .c20 { +.c14:active .c16 { background-color: #B8C0DC3d; } -.c18:focus .c20 { +.c14:focus .c16 { background-color: #B8C0DC3d; } -.c18:hover .c20 { +.c14:hover .c16 { background-color: #98A1C014; } -.c18:disabled { +.c14:disabled { cursor: default; opacity: 0.6; } -.c18:disabled:active .c20, -.c18:disabled:focus .c20, -.c18:disabled:hover .c20 { +.c14:disabled:active .c16, +.c14:disabled:focus .c16, +.c14:disabled:hover .c16 { background-color: transparent; } @@ -2644,7 +2124,7 @@ exports[`ConfirmSwapModal/Error renders "limit order" correctly, with error= 4 1 text-align: center; } -.c19 { +.c15 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -2657,6 +2137,16 @@ exports[`ConfirmSwapModal/Error renders "limit order" correctly, with error= 4 1 border-radius: 12px; } +.c4 { + height: 24px; + width: 24px; +} + +.c4 path { + background: #7D7D7D; + fill: #7D7D7D; +} + -
-
-
-
- - -
-
-
-
@@ -2758,30 +2224,6 @@ exports[`ConfirmSwapModal/Error renders "limit order" correctly, with error= 4 1 points="12 5 19 12 12 19" /> -
-
-
-
- - -
-
-
-
@@ -2789,7 +2231,7 @@ exports[`ConfirmSwapModal/Error renders "limit order" correctly, with error= 4 1
diff --git a/apps/web/src/components/ConfirmSwapModal/__snapshots__/Pending.test.tsx.snap b/apps/web/src/components/ConfirmSwapModal/__snapshots__/Pending.test.tsx.snap index 5d5b5e32b46..b463ae2cfa8 100644 --- a/apps/web/src/components/ConfirmSwapModal/__snapshots__/Pending.test.tsx.snap +++ b/apps/web/src/components/ConfirmSwapModal/__snapshots__/Pending.test.tsx.snap @@ -42,7 +42,7 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap min-width: 0; } -.c18 { +.c14 { box-sizing: border-box; margin: 0; min-width: 0; @@ -68,7 +68,7 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap gap: 8px; } -.c19 { +.c15 { width: 100%; display: -webkit-box; display: -webkit-flex; @@ -101,7 +101,7 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap letter-spacing: -0.01em; } -.c20 { +.c16 { -webkit-text-decoration: none; text-decoration: none; cursor: pointer; @@ -112,11 +112,11 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap font-weight: 500; } -.c20:hover { +.c16:hover { opacity: 0.6; } -.c20:active { +.c16:active { opacity: 0.4; } @@ -158,51 +158,6 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap align-items: center; } -.c16 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - gap: 2px; - position: relative; - top: 0; - left: 0; -} - -.c16 img { - width: 16px; - height: 16px; - border-radius: 50%; -} - -.c17 { - width: 8px; - height: 16px; - border-radius: 50%; -} - -.c15 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - position: relative; - top: 0; - left: 0; -} - -.c14 { - position: relative; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; -} - .c2 { margin: 48px 0 8px; } @@ -307,30 +262,6 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap
-
-
-
-
- - -
-
-
-
@@ -357,30 +288,6 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap points="12 5 19 12 12 19" /> -
-
-
-
- - -
-
-
-
@@ -391,13 +298,13 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap
-
-
-
-
- - -
-
-
-
@@ -771,30 +609,6 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap points="12 5 19 12 12 19" /> -
-
-
-
- - -
-
-
-
@@ -805,13 +619,13 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap
-
-
-
-
- - -
-
-
-
@@ -1190,30 +935,6 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap points="12 5 19 12 12 19" /> -
-
-
-
- - -
-
-
-
@@ -1224,7 +945,7 @@ exports[`Pending - classic trade titles renders classic trade correctly, with ap
-
-
-
-
- - -
-
-
-
@@ -1553,30 +1205,6 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app points="12 5 19 12 12 19" /> -
-
-
-
- - -
-
-
-
@@ -1636,7 +1264,7 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app min-width: 0; } -.c18 { +.c14 { box-sizing: border-box; margin: 0; min-width: 0; @@ -1662,7 +1290,7 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app gap: 8px; } -.c19 { +.c15 { width: 100%; display: -webkit-box; display: -webkit-flex; @@ -1695,7 +1323,7 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app letter-spacing: -0.01em; } -.c20 { +.c16 { -webkit-text-decoration: none; text-decoration: none; cursor: pointer; @@ -1706,11 +1334,11 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app font-weight: 500; } -.c20:hover { +.c16:hover { opacity: 0.6; } -.c20:active { +.c16:active { opacity: 0.4; } @@ -1752,51 +1380,6 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app align-items: center; } -.c16 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - gap: 2px; - position: relative; - top: 0; - left: 0; -} - -.c16 img { - width: 16px; - height: 16px; - border-radius: 50%; -} - -.c17 { - width: 8px; - height: 16px; - border-radius: 50%; -} - -.c15 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - position: relative; - top: 0; - left: 0; -} - -.c14 { - position: relative; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; -} - .c2 { margin: 48px 0 8px; } @@ -1900,30 +1483,6 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app
-
-
-
-
- - -
-
-
-
@@ -1950,30 +1509,6 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app points="12 5 19 12 12 19" /> -
-
-
-
- - -
-
-
-
@@ -1984,13 +1519,13 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app
-
-
-
-
- - -
-
-
-
@@ -2369,30 +1835,6 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app points="12 5 19 12 12 19" /> -
-
-
-
- - -
-
-
-
@@ -2403,7 +1845,7 @@ exports[`Pending - uniswapX trade titles renders limit order correctly, with app
+ @@ -224,6 +225,7 @@ export default function FeatureFlagModal() { flag={FeatureFlags.IndicativeSwapQuotes} label="[Universal Swap Flow Only] Enable Quick Routes" /> + { const { supportedChains } = FEE_AMOUNT_DETAIL[_feeAmount] - if ((supportedChains as unknown as InterfaceChainId[]).includes(chainId)) { + if ((supportedChains as unknown as UniverseChainId[]).includes(chainId)) { return ( = { [FeeAmount.LOWEST]: { label: '0.01', description: , - supportedChains: WEB_SUPPORTED_CHAIN_IDS, + supportedChains: SUPPORTED_CHAIN_IDS, }, [FeeAmount.LOW_200]: { label: '0.02', @@ -27,16 +27,16 @@ export const FEE_AMOUNT_DETAIL: Record< [FeeAmount.LOW]: { label: '0.05', description: , - supportedChains: WEB_SUPPORTED_CHAIN_IDS, + supportedChains: SUPPORTED_CHAIN_IDS, }, [FeeAmount.MEDIUM]: { label: '0.3', description: , - supportedChains: WEB_SUPPORTED_CHAIN_IDS, + supportedChains: SUPPORTED_CHAIN_IDS, }, [FeeAmount.HIGH]: { label: '1', description: , - supportedChains: WEB_SUPPORTED_CHAIN_IDS, + supportedChains: SUPPORTED_CHAIN_IDS, }, } diff --git a/apps/web/src/components/FiatOnrampModal/utils.ts b/apps/web/src/components/FiatOnrampModal/utils.ts index e045927009f..21cfd71d1d1 100644 --- a/apps/web/src/components/FiatOnrampModal/utils.ts +++ b/apps/web/src/components/FiatOnrampModal/utils.ts @@ -1,6 +1,6 @@ import { WETH9 } from '@uniswap/sdk-core' import { MoonpaySupportedCurrencyCode } from 'components/FiatOnrampModal/constants' -import { InterfaceGqlChain, getChainFromChainUrlParam, getChainUrlParam } from 'constants/chains' +import { getChainFromChainUrlParam, getChainUrlParam, InterfaceGqlChain } from 'constants/chains' import { MATIC_MAINNET, USDC_ARBITRUM, diff --git a/apps/web/src/components/IncreaseLiquidity/IncreaseLiquidityContext.tsx b/apps/web/src/components/IncreaseLiquidity/IncreaseLiquidityContext.tsx index 9a25326abe2..fe4b0d5a9d4 100644 --- a/apps/web/src/components/IncreaseLiquidity/IncreaseLiquidityContext.tsx +++ b/apps/web/src/components/IncreaseLiquidity/IncreaseLiquidityContext.tsx @@ -1,16 +1,8 @@ -import { Currency, CurrencyAmount } from '@uniswap/sdk-core' import { useDerivedIncreaseLiquidityInfo } from 'components/IncreaseLiquidity/hooks' -import { PositionInfo, getProtocolItems, useModalLiquidityPositionInfo } from 'components/Liquidity/utils' -import { ZERO_ADDRESS } from 'constants/misc' -import { usePool } from 'pages/Pool/Positions/create/hooks' +import { DepositInfo, PositionInfo } from 'components/Liquidity/types' +import { useModalLiquidityPositionInfo } from 'components/Liquidity/utils' import { Dispatch, PropsWithChildren, SetStateAction, createContext, useContext, useMemo, useState } from 'react' import { PositionField } from 'types/position' -import { useCheckLpApprovalQuery } from 'uniswap/src/data/apiClients/tradingApi/useCheckLpApprovalQuery' -import { useIncreaseLpPositionCalldataQuery } from 'uniswap/src/data/apiClients/tradingApi/useIncreaseLpPositionCalldataQuery' -import { CheckApprovalLPRequest, IncreaseLPPositionRequest } from 'uniswap/src/data/tradingApi/__generated__' -import { UniverseChainId } from 'uniswap/src/types/chains' -import { ONE_SECOND_MS } from 'utilities/src/time/time' -import { useAccount } from 'wagmi' export enum IncreaseLiquidityStep { Input, @@ -27,18 +19,11 @@ const DEFAULT_INCREASE_LIQUIDITY_STATE = { exactField: PositionField.TOKEN0, } -export interface IncreaseLiquidityInfo { - formattedAmounts?: { [field in PositionField]?: string } - currencyBalances?: { [field in PositionField]?: CurrencyAmount } - currencyAmounts?: { [field in PositionField]?: CurrencyAmount } - currencyAmountsUSDValue?: { [field in PositionField]?: CurrencyAmount } -} - interface IncreaseLiquidityContextType { step: IncreaseLiquidityStep setStep: Dispatch> increaseLiquidityState: IncreaseLiquidityState - derivedIncreaseLiquidityInfo: IncreaseLiquidityInfo + derivedIncreaseLiquidityInfo: DepositInfo setIncreaseLiquidityState: Dispatch> } @@ -65,86 +50,6 @@ export function IncreaseLiquidityContextProvider({ children }: PropsWithChildren }) const derivedIncreaseLiquidityInfo = useDerivedIncreaseLiquidityInfo(increaseLiquidityState) - const pool = usePool( - positionInfo?.currency0Amount.currency, - positionInfo?.currency1Amount.currency, - positionInfo?.feeTier ? Number(positionInfo.feeTier) : undefined, - positionInfo?.currency0Amount.currency.chainId ?? UniverseChainId.Mainnet, - positionInfo?.version, - ) - - const account = useAccount() - - const increaseLiquidityApprovalParams: CheckApprovalLPRequest | undefined = useMemo(() => { - if ( - !positionInfo || - !account.address || - !derivedIncreaseLiquidityInfo.currencyBalances?.TOKEN0 || - !derivedIncreaseLiquidityInfo.currencyBalances?.TOKEN1 - ) { - return undefined - } - return { - simulateTransaction: true, - walletAddress: account.address, - chainId: positionInfo.currency0Amount.currency.chainId, - protocol: getProtocolItems(positionInfo.version), - token0: positionInfo.currency0Amount.currency.isNative - ? ZERO_ADDRESS - : positionInfo.currency0Amount.currency.address, - token1: positionInfo.currency1Amount.currency.isNative - ? ZERO_ADDRESS - : positionInfo.currency1Amount.currency.address, - amount0: derivedIncreaseLiquidityInfo.currencyBalances?.TOKEN0?.quotient.toString(), - amount1: derivedIncreaseLiquidityInfo.currencyBalances?.TOKEN1?.quotient.toString(), - } - }, [positionInfo, account.address, derivedIncreaseLiquidityInfo]) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { data: increaseLiquidityTokenApprovals, isLoading: approvalLoading } = useCheckLpApprovalQuery({ - params: increaseLiquidityApprovalParams, - staleTime: 5 * ONE_SECOND_MS, - }) - - const increaseCalldataQueryParams: IncreaseLPPositionRequest | undefined = useMemo(() => { - const apiProtocolItems = getProtocolItems(positionInfo?.version) - const amount0 = derivedIncreaseLiquidityInfo.currencyAmounts?.TOKEN0?.quotient.toString() - const amount1 = derivedIncreaseLiquidityInfo.currencyAmounts?.TOKEN1?.quotient.toString() - if (!positionInfo || !account.address || !apiProtocolItems || !amount0 || !amount1) { - return undefined - } - return { - simulateTransaction: false, - protocol: apiProtocolItems, - tokenId: positionInfo.tokenId ? Number(positionInfo.tokenId) : undefined, - walletAddress: account.address, - chainId: positionInfo.currency0Amount.currency.chainId, - amount0, - amount1, - poolLiquidity: pool?.liquidity, - currentTick: pool?.tick, - sqrtRatioX96: pool?.sqrtPriceX96, - position: { - tickLower: positionInfo.tickLower ? Number(positionInfo.tickLower) : undefined, - tickUpper: positionInfo.tickUpper ? Number(positionInfo.tickUpper) : undefined, - pool: { - token0: positionInfo.currency0Amount.currency.isNative - ? ZERO_ADDRESS - : positionInfo.currency0Amount.currency.address, - token1: positionInfo.currency1Amount.currency.isNative - ? ZERO_ADDRESS - : positionInfo.currency1Amount.currency.address, - fee: positionInfo.feeTier ? Number(positionInfo.feeTier) : undefined, - tickSpacing: positionInfo.tickSpacing ? Number(positionInfo.tickSpacing) : undefined, - hooks: positionInfo.v4hook, - }, - }, - } - }, [account, positionInfo, derivedIncreaseLiquidityInfo, pool]) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { data: increaseCalldata } = useIncreaseLpPositionCalldataQuery({ - params: increaseCalldataQueryParams, - staleTime: 5 * ONE_SECOND_MS, - }) const value = useMemo( () => ({ diff --git a/apps/web/src/components/IncreaseLiquidity/IncreaseLiquidityReview.tsx b/apps/web/src/components/IncreaseLiquidity/IncreaseLiquidityReview.tsx index e7c0cb54f1d..f5fe5e5c08c 100644 --- a/apps/web/src/components/IncreaseLiquidity/IncreaseLiquidityReview.tsx +++ b/apps/web/src/components/IncreaseLiquidity/IncreaseLiquidityReview.tsx @@ -1,62 +1,63 @@ -import { Currency, CurrencyAmount } from '@uniswap/sdk-core' +// eslint-disable-next-line no-restricted-imports +import { ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' import { useIncreaseLiquidityContext } from 'components/IncreaseLiquidity/IncreaseLiquidityContext' -import { useV2PositionDerivedInfo } from 'components/Liquidity/utils' -import CurrencyLogo from 'components/Logo/CurrencyLogo' +import { useIncreaseLiquidityTxContext } from 'components/IncreaseLiquidity/IncreaseLiquidityTxContext' +import { TokenInfo } from 'components/Liquidity/TokenInfo' +import { useGetPoolTokenPercentage } from 'components/Liquidity/utils' import { DetailLineItem } from 'components/swap/DetailLineItem' -import { useMemo } from 'react' +import { useAccount } from 'hooks/useAccount' +import useSelectChain from 'hooks/useSelectChain' +import { useMemo, useState } from 'react' +import { useDispatch } from 'react-redux' +import { liquiditySaga } from 'state/sagas/liquidity/liquiditySaga' import { Button, Flex, Separator, Text } from 'ui/src' -import { iconSizes } from 'ui/src/theme' +import { ProgressIndicator } from 'uniswap/src/components/ConfirmSwapModal/ProgressIndicator' +import { useAccountMeta } from 'uniswap/src/contexts/UniswapContext' +import { AccountType } from 'uniswap/src/features/accounts/types' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' +import { isValidLiquidityTxContext } from 'uniswap/src/features/transactions/liquidity/types' import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice' +import { TransactionStep } from 'uniswap/src/features/transactions/swap/utils/generateTransactionSteps' import { Trans, useTranslation } from 'uniswap/src/i18n' import { getSymbolDisplayText } from 'uniswap/src/utils/currency' import { NumberType } from 'utilities/src/format/types' -function TokenInfo({ - currencyAmount, - currencyUSDAmount, -}: { - currencyAmount?: CurrencyAmount - currencyUSDAmount?: CurrencyAmount -}) { - const { formatCurrencyAmount } = useLocalizationContext() - - return ( - - - - {formatCurrencyAmount({ - value: currencyAmount, - type: NumberType.TokenNonTx, - })}{' '} - {getSymbolDisplayText(currencyAmount?.currency.symbol)} - - - {formatCurrencyAmount({ - value: currencyUSDAmount, - type: NumberType.FiatStandard, - })} - - - - - ) -} - -export function IncreaseLiquidityReview() { +export function IncreaseLiquidityReview({ onClose }: { onClose: () => void }) { const { t } = useTranslation() + const dispatch = useDispatch() + const selectChain = useSelectChain() + const startChainId = useAccount().chainId + const account = useAccountMeta() + const { formatCurrencyAmount, formatPercent } = useLocalizationContext() - const { derivedIncreaseLiquidityInfo: derivedAddLiquidityInfo, increaseLiquidityState: addLiquidityState } = - useIncreaseLiquidityContext() - const { currencyAmounts, currencyAmountsUSDValue } = derivedAddLiquidityInfo + const { derivedIncreaseLiquidityInfo, increaseLiquidityState } = useIncreaseLiquidityContext() + const { txInfo, gasFeeEstimateUSD } = useIncreaseLiquidityTxContext() + + const { currencyAmounts, currencyAmountsUSDValue } = derivedIncreaseLiquidityInfo - if (!addLiquidityState.position) { + const [steps, setSteps] = useState([]) + const [currentStep, setCurrentStep] = useState<{ step: TransactionStep; accepted: boolean } | undefined>() + + if (!increaseLiquidityState.position) { throw new Error('a position must be defined') } - const { currency0Amount, currency1Amount } = addLiquidityState.position - const { poolTokenPercentage } = useV2PositionDerivedInfo(addLiquidityState.position) + const { currency0Amount, currency1Amount } = increaseLiquidityState.position + + const currentPrice = useMemo(() => { + if (increaseLiquidityState.position?.version === ProtocolVersion.V2) { + return increaseLiquidityState.position.pair?.token1Price + } + + if (increaseLiquidityState.position?.version === ProtocolVersion.V3) { + return increaseLiquidityState.position.pool?.token1Price + } + + return undefined + }, [increaseLiquidityState.position]) + + const poolTokenPercentage = useGetPoolTokenPercentage(increaseLiquidityState.position) const newToken0Amount = useMemo(() => { return currencyAmounts?.TOKEN0?.add(currency0Amount) @@ -68,6 +69,30 @@ export function IncreaseLiquidityReview() { }, [currency1Amount, currencyAmounts?.TOKEN1]) const newToken1AmountUSD = useUSDCValue(newToken1Amount) + const onFailure = () => { + setCurrentStep(undefined) + } + + const onIncreaseLiquidity = () => { + const isValidTx = isValidLiquidityTxContext(txInfo) + if (!account || account?.type !== AccountType.SignerMnemonic || !isValidTx) { + return + } + + dispatch( + liquiditySaga.actions.trigger({ + selectChain, + startChainId, + account, + liquidityTxContext: txInfo, + setCurrentStep, + setSteps, + onSuccess: onClose, + onFailure, + }), + ) + } + return ( @@ -77,93 +102,100 @@ export function IncreaseLiquidityReview() { - - - ( - - {t('common.rate')} - - ), - // TODO(WEB-4976): update with the actual rate. This comes from sqrtPrice and is the - // same as Current Price in v3 current code. Get from Jack - Value: () => 1 ETH = 1,607.58 DAI ($1,610.73), - }} - /> + {currentStep ? ( + + ) : ( <> - ( - - - - ), - Value: () => ( - - - {formatCurrencyAmount({ value: newToken0Amount, type: NumberType.TokenNonTx })}{' '} - {getSymbolDisplayText(newToken0Amount?.currency.symbol)} + + + ( + + {t('common.rate')} + ), + Value: () => ( + {`1 ${currentPrice?.baseCurrency.symbol} = ${currentPrice?.toFixed()} ${currentPrice?.quoteCurrency.symbol}`} + ), + }} + /> + ( - {`(${formatCurrencyAmount({ value: newToken0AmountUSD, type: NumberType.FiatStandard })})`} + - - ), - }} - /> - ( - - - - ), - Value: () => ( - - - {formatCurrencyAmount({ value: newToken1Amount, type: NumberType.TokenNonTx })}{' '} - {getSymbolDisplayText(newToken1Amount?.currency.symbol)} + ), + Value: () => ( + + + {formatCurrencyAmount({ value: newToken0Amount, type: NumberType.TokenNonTx })}{' '} + {getSymbolDisplayText(newToken0Amount?.currency.symbol)} + + + {`(${formatCurrencyAmount({ value: newToken0AmountUSD, type: NumberType.FiatStandard })})`} + + + ), + }} + /> + ( + + + ), + Value: () => ( + + + {formatCurrencyAmount({ value: newToken1Amount, type: NumberType.TokenNonTx })}{' '} + {getSymbolDisplayText(newToken1Amount?.currency.symbol)} + + + {`(${formatCurrencyAmount({ value: newToken1AmountUSD, type: NumberType.FiatStandard })})`} + + + ), + }} + /> + {poolTokenPercentage ? ( + ( + + {t('addLiquidity.shareOfPool')} + + ), + Value: () => {formatPercent(poolTokenPercentage.toFixed())}, + }} + /> + ) : null} + ( - {`(${formatCurrencyAmount({ value: newToken1AmountUSD, type: NumberType.FiatStandard })})`} + {t('common.networkCost')} - - ), - }} - /> + ), + Value: () => ( + + {formatCurrencyAmount({ value: gasFeeEstimateUSD, type: NumberType.FiatGasPrice })} + + ), + }} + /> + + - {poolTokenPercentage ? ( - ( - - {t('addLiquidity.shareOfPool')} - - ), - Value: () => {formatPercent(poolTokenPercentage.toFixed())}, - }} - /> - ) : null} - ( - - {t('common.networkCost')} - - ), - // TODO(WEB-4978): calculate this value from the trading API - Value: () => $0, - }} - /> - - + )} ) } diff --git a/apps/web/src/components/IncreaseLiquidity/IncreaseLiquidityTxContext.tsx b/apps/web/src/components/IncreaseLiquidity/IncreaseLiquidityTxContext.tsx new file mode 100644 index 00000000000..ab0afd75282 --- /dev/null +++ b/apps/web/src/components/IncreaseLiquidity/IncreaseLiquidityTxContext.tsx @@ -0,0 +1,206 @@ +// eslint-disable-next-line no-restricted-imports +import { ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' +import { Currency, CurrencyAmount } from '@uniswap/sdk-core' +import { useIncreaseLiquidityContext } from 'components/IncreaseLiquidity/IncreaseLiquidityContext' +import { getProtocolItems, useModalLiquidityPositionInfo } from 'components/Liquidity/utils' +import { ZERO_ADDRESS } from 'constants/misc' +import { PropsWithChildren, createContext, useContext, useMemo } from 'react' +import { useCheckLpApprovalQuery } from 'uniswap/src/data/apiClients/tradingApi/useCheckLpApprovalQuery' +import { useIncreaseLpPositionCalldataQuery } from 'uniswap/src/data/apiClients/tradingApi/useIncreaseLpPositionCalldataQuery' +import { CheckApprovalLPRequest, IncreaseLPPositionRequest } from 'uniswap/src/data/tradingApi/__generated__' +import { useTransactionGasFee, useUSDCurrencyAmountOfGasFee } from 'uniswap/src/features/gas/hooks' +import { IncreasePositionTxAndGasInfo } from 'uniswap/src/features/transactions/liquidity/types' +import { validatePermit, validateTransactionRequest } from 'uniswap/src/features/transactions/swap/utils/trade' +import { ONE_SECOND_MS } from 'utilities/src/time/time' +import { useAccount } from 'wagmi' + +interface IncreasePositionContextType { + txInfo?: IncreasePositionTxAndGasInfo + gasFeeEstimateUSD?: CurrencyAmount +} + +const IncreaseLiquidityTxContext = createContext(undefined) + +export function IncreaseLiquidityTxContextProvider({ children }: PropsWithChildren): JSX.Element { + const positionInfo = useModalLiquidityPositionInfo() + const { derivedIncreaseLiquidityInfo } = useIncreaseLiquidityContext() + + const { currencyAmounts } = derivedIncreaseLiquidityInfo + + const pool = positionInfo?.version === ProtocolVersion.V3 ? positionInfo.pool : undefined + + const account = useAccount() + + const increaseLiquidityApprovalParams: CheckApprovalLPRequest | undefined = useMemo(() => { + if (!positionInfo || !account.address || !currencyAmounts?.TOKEN0 || !currencyAmounts?.TOKEN1) { + return undefined + } + return { + simulateTransaction: true, + walletAddress: account.address, + chainId: positionInfo.currency0Amount.currency.chainId, + protocol: getProtocolItems(positionInfo.version), + token0: positionInfo.currency0Amount.currency.isNative + ? ZERO_ADDRESS + : positionInfo.currency0Amount.currency.address, + token1: positionInfo.currency1Amount.currency.isNative + ? ZERO_ADDRESS + : positionInfo.currency1Amount.currency.address, + amount0: currencyAmounts?.TOKEN0?.quotient.toString(), + amount1: currencyAmounts?.TOKEN1?.quotient.toString(), + } + }, [positionInfo, account.address, currencyAmounts]) + + const { data: increaseLiquidityTokenApprovals, isLoading: approvalLoading } = useCheckLpApprovalQuery({ + params: increaseLiquidityApprovalParams, + staleTime: 5 * ONE_SECOND_MS, + }) + const { + token0Approval, + token1Approval, + positionTokenApproval, + permitData, + gasFeeToken0Approval, + gasFeeToken1Approval, + gasFeePositionTokenApproval, + } = increaseLiquidityTokenApprovals || {} + const gasFeeToken0USD = useUSDCurrencyAmountOfGasFee( + positionInfo?.currency0Amount.currency.chainId, + gasFeeToken0Approval, + ) + const gasFeeToken1USD = useUSDCurrencyAmountOfGasFee( + positionInfo?.currency1Amount.currency.chainId, + gasFeeToken1Approval, + ) + const gasFeeLiquidityTokenUSD = useUSDCurrencyAmountOfGasFee( + positionInfo?.liquidityToken?.chainId, + gasFeePositionTokenApproval, + ) + + const approvalsNeeded = Boolean(permitData || token0Approval || token1Approval || positionTokenApproval) + + const increaseCalldataQueryParams: IncreaseLPPositionRequest | undefined = useMemo(() => { + const apiProtocolItems = getProtocolItems(positionInfo?.version) + const amount0 = currencyAmounts?.TOKEN0?.quotient.toString() + const amount1 = currencyAmounts?.TOKEN1?.quotient.toString() + if (!positionInfo || !account.address || !apiProtocolItems || !amount0 || !amount1) { + return undefined + } + return { + simulateTransaction: !approvalsNeeded, + protocol: apiProtocolItems, + tokenId: positionInfo.tokenId ? Number(positionInfo.tokenId) : undefined, + walletAddress: account.address, + chainId: positionInfo.currency0Amount.currency.chainId, + amount0, + amount1, + poolLiquidity: pool?.liquidity.toString(), + currentTick: pool?.tickCurrent, + sqrtRatioX96: pool?.sqrtRatioX96.toString(), + position: { + tickLower: positionInfo.tickLower ? Number(positionInfo.tickLower) : undefined, + tickUpper: positionInfo.tickUpper ? Number(positionInfo.tickUpper) : undefined, + pool: { + token0: positionInfo.currency0Amount.currency.isNative + ? ZERO_ADDRESS + : positionInfo.currency0Amount.currency.address, + token1: positionInfo.currency1Amount.currency.isNative + ? ZERO_ADDRESS + : positionInfo.currency1Amount.currency.address, + fee: positionInfo.feeTier ? Number(positionInfo.feeTier) : undefined, + tickSpacing: positionInfo.tickSpacing ? Number(positionInfo.tickSpacing) : undefined, + hooks: positionInfo.v4hook, + }, + }, + } + }, [account, positionInfo, pool, currencyAmounts, approvalsNeeded]) + + const { data: increaseCalldata, isLoading: isCalldataLoading } = useIncreaseLpPositionCalldataQuery({ + params: increaseCalldataQueryParams, + staleTime: 5 * ONE_SECOND_MS, + }) + const { increase, gasFee: actualGasFee } = increaseCalldata || {} + + const { value: calculatedGasFee } = useTransactionGasFee(increase, !!actualGasFee) + const increaseGasFeeUsd = useUSDCurrencyAmountOfGasFee( + increaseCalldata?.increase?.chainId, + actualGasFee || calculatedGasFee, + ) + + const increaseLiquidityTxContext = useMemo((): IncreasePositionTxAndGasInfo | undefined => { + if ( + !positionInfo || + approvalLoading || + isCalldataLoading || + !increaseCalldata || + !currencyAmounts?.TOKEN0 || + !currencyAmounts?.TOKEN1 + ) { + return undefined + } + + const approveToken0Request = validateTransactionRequest(token0Approval) + const approveToken1Request = validateTransactionRequest(token1Approval) + const approvePositionTokenRequest = validateTransactionRequest(positionTokenApproval) + const permit = validatePermit(permitData) + const unsigned = Boolean(permitData) + const txRequest = validateTransactionRequest(increase) + + return { + type: 'increase', + protocolVersion: positionInfo?.version, + action: { + currency0Amount: currencyAmounts?.TOKEN0, + currency1Amount: currencyAmounts?.TOKEN1, + liquidityToken: positionInfo.liquidityToken, + }, + approveToken0Request, + approveToken1Request, + approvePositionTokenRequest, + revocationTxRequest: undefined, // TODO: add support for revokes + permit, + increasePositionRequestArgs: increaseCalldataQueryParams, + txRequest, + unsigned, + } + }, [ + approvalLoading, + isCalldataLoading, + increaseCalldata, + permitData, + positionInfo, + positionTokenApproval, + token0Approval, + token1Approval, + increaseCalldataQueryParams, + increase, + currencyAmounts, + ]) + + const totalGasFee = useMemo(() => { + const fees = [gasFeeToken0USD, gasFeeToken1USD, gasFeeLiquidityTokenUSD, increaseGasFeeUsd] + return fees.reduce((total, fee) => { + if (fee && total) { + return total.add(fee) + } + return total || fee + }) + }, [gasFeeToken0USD, gasFeeToken1USD, gasFeeLiquidityTokenUSD, increaseGasFeeUsd]) + + const value = { + txInfo: increaseLiquidityTxContext, + gasFeeEstimateUSD: totalGasFee ?? undefined, + } + + return {children} +} + +export const useIncreaseLiquidityTxContext = (): IncreasePositionContextType => { + const increaseContext = useContext(IncreaseLiquidityTxContext) + + if (!increaseContext) { + throw new Error('`useIncreaseLiquidityTxContext` must be used inside of `IncreaseLiquidityTxContextProvider`') + } + + return increaseContext +} diff --git a/apps/web/src/components/IncreaseLiquidity/hooks.tsx b/apps/web/src/components/IncreaseLiquidity/hooks.tsx index 38a2f24f528..a2a1ae72df5 100644 --- a/apps/web/src/components/IncreaseLiquidity/hooks.tsx +++ b/apps/web/src/components/IncreaseLiquidity/hooks.tsx @@ -1,19 +1,12 @@ // eslint-disable-next-line no-restricted-imports -import { PoolPosition } from '@uniswap/client-pools/dist/pools/v1/types_pb' -import { Currency, CurrencyAmount } from '@uniswap/sdk-core' -import { Position } from '@uniswap/v3-sdk' -import { IncreaseLiquidityInfo, IncreaseLiquidityState } from 'components/IncreaseLiquidity/IncreaseLiquidityContext' -import { parseV3FeeTier } from 'components/Liquidity/utils' +import { ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' +import { IncreaseLiquidityState } from 'components/IncreaseLiquidity/IncreaseLiquidityContext' +import { DepositInfo } from 'components/Liquidity/types' import { useAccount } from 'hooks/useAccount' -import { usePool } from 'hooks/usePools' -import { useV2Pair } from 'hooks/useV2Pairs' -import { useCurrencyBalances } from 'lib/hooks/useCurrencyBalance' -import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' +import { UseDepositInfoProps, useDepositInfo } from 'pages/Pool/Positions/create/hooks' import { useMemo } from 'react' -import { PositionField } from 'types/position' -import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice' -export function useDerivedIncreaseLiquidityInfo(state: IncreaseLiquidityState): IncreaseLiquidityInfo { +export function useDerivedIncreaseLiquidityInfo(state: IncreaseLiquidityState): DepositInfo { const account = useAccount() const { position: positionInfo, exactAmount, exactField } = state @@ -21,98 +14,48 @@ export function useDerivedIncreaseLiquidityInfo(state: IncreaseLiquidityState): throw new Error('no position available') } - const token0 = positionInfo.currency0Amount.currency - const token1 = positionInfo.currency1Amount.currency - - const [token0Balance, token1Balance] = useCurrencyBalances(account.address, [token0, token1]) - - const [independentToken, dependentToken] = exactField === PositionField.TOKEN0 ? [token0, token1] : [token1, token0] - const independentAmount = tryParseCurrencyAmount(exactAmount, independentToken) - - const [, pool] = usePool(token0, token1, parseV3FeeTier(positionInfo.feeTier)) - const [, pair] = useV2Pair(token0, token1) - - const dependentAmount: CurrencyAmount | undefined = useMemo(() => { - // we wrap the currencies just to get the price in terms of the other token - const wrappedIndependentAmount = independentAmount?.wrapped - - if (positionInfo.restPosition.position.case === 'v2Pair') { - const [token0Wrapped, token1Wrapped] = [token0?.wrapped, token1?.wrapped] - - if (token0Wrapped && token1Wrapped && wrappedIndependentAmount && pair) { - const dependentTokenAmount = - exactField === PositionField.TOKEN0 - ? pair.priceOf(token0Wrapped).quote(wrappedIndependentAmount) - : pair.priceOf(token1Wrapped).quote(wrappedIndependentAmount) - return dependentToken?.isNative - ? CurrencyAmount.fromRawAmount(dependentToken, dependentTokenAmount.quotient) - : dependentTokenAmount + const currency0 = positionInfo.currency0Amount.currency + const token0 = currency0.isNative ? currency0.wrapped : currency0 + const currency1 = positionInfo.currency1Amount.currency + const token1 = currency1.isNative ? currency1.wrapped : currency1 + + const depositInfoProps: UseDepositInfoProps = useMemo(() => { + if (positionInfo.version === ProtocolVersion.V2) { + return { + protocolVersion: ProtocolVersion.V2, + pair: positionInfo.pair, + address: account.address, + token0: currency0, + token1: currency1, + exactField, + exactAmount, } - return undefined } - if (positionInfo.restPosition.position.case === 'v3Position') { - const position: PoolPosition = positionInfo.restPosition.position.value - const { tickLower: tickLowerStr, tickUpper: tickUpperStr } = position - const tickLower = parseInt(tickLowerStr) - const tickUpper = parseInt(tickUpperStr) - - if ( - independentAmount && - wrappedIndependentAmount && - typeof tickLower === 'number' && - typeof tickUpper === 'number' && - pool - ) { - const position: Position | undefined = wrappedIndependentAmount.currency.equals(pool.token0) - ? Position.fromAmount0({ - pool, - tickLower, - tickUpper, - amount0: independentAmount.quotient, - useFullPrecision: true, // we want full precision for the theoretical position - }) - : Position.fromAmount1({ - pool, - tickLower, - tickUpper, - amount1: independentAmount.quotient, - }) - - const dependentTokenAmount = wrappedIndependentAmount.currency.equals(pool.token0) - ? position.amount1 - : position.amount0 - return dependentToken && CurrencyAmount.fromRawAmount(dependentToken, dependentTokenAmount.quotient) + if (positionInfo.version === ProtocolVersion.V3) { + const { tickLower: tickLowerStr, tickUpper: tickUpperStr } = positionInfo + const tickLower = tickLowerStr ? parseInt(tickLowerStr) : undefined + const tickUpper = tickUpperStr ? parseInt(tickUpperStr) : undefined + + return { + protocolVersion: ProtocolVersion.V3, + pool: positionInfo.pool, + address: account.address, + tickLower, + tickUpper, + token0, + token1, + exactField, + exactAmount, } - - return undefined } - if (positionInfo.restPosition.position.case === 'v4Position') { - // TODO: calculate for v4 - return undefined + // TODO: handle v4 case + return { + protocolVersion: ProtocolVersion.UNSPECIFIED, + exactField, } + }, [account.address, exactAmount, exactField, positionInfo, currency0, currency1, token0, token1]) - return undefined - }, [ - dependentToken, - independentAmount, - pool, - positionInfo.restPosition.position, - exactField, - pair, - token0.wrapped, - token1.wrapped, - ]) - - const independentTokenUSDValue = useUSDCValue(independentAmount) || undefined - const dependentTokenUSDValue = useUSDCValue(dependentAmount) || undefined - - const dependentField = exactField === PositionField.TOKEN0 ? PositionField.TOKEN1 : PositionField.TOKEN0 - return { - currencyBalances: { [PositionField.TOKEN0]: token0Balance, [PositionField.TOKEN1]: token1Balance }, - formattedAmounts: { [exactField]: exactAmount, [dependentField]: dependentAmount?.toExact() }, - currencyAmounts: { [exactField]: independentAmount, [dependentField]: dependentAmount }, - currencyAmountsUSDValue: { [exactField]: independentTokenUSDValue, [dependentField]: dependentTokenUSDValue }, - } + return useDepositInfo(depositInfoProps) } diff --git a/apps/web/src/components/Liquidity/DepositInputForm.tsx b/apps/web/src/components/Liquidity/DepositInputForm.tsx index 744ebcea55f..e2e64a9ead1 100644 --- a/apps/web/src/components/Liquidity/DepositInputForm.tsx +++ b/apps/web/src/components/Liquidity/DepositInputForm.tsx @@ -1,5 +1,5 @@ import { Currency } from '@uniswap/sdk-core' -import { IncreaseLiquidityInfo } from 'components/IncreaseLiquidity/IncreaseLiquidityContext' +import { DepositInfo } from 'components/Liquidity/types' import { useCurrencyInfo } from 'hooks/Tokens' import { useState } from 'react' import { PositionField } from 'types/position' @@ -12,7 +12,7 @@ type InputFormProps = { token1: Currency onUserInput: (field: PositionField, newValue: string) => void onSetMax: (field: PositionField, amount: string) => void -} & IncreaseLiquidityInfo +} & DepositInfo export function DepositInputForm({ token0, diff --git a/apps/web/src/components/Liquidity/LiquidityPositionAmountsTile.tsx b/apps/web/src/components/Liquidity/LiquidityPositionAmountsTile.tsx index 7594171e12c..6de4df1c1d1 100644 --- a/apps/web/src/components/Liquidity/LiquidityPositionAmountsTile.tsx +++ b/apps/web/src/components/Liquidity/LiquidityPositionAmountsTile.tsx @@ -39,13 +39,13 @@ export function LiquidityPositionAmountsTile({ ({formatCurrencyAmount({ value: fiatValue0, type: NumberType.FiatTokenPrice })}) )} - {totalFiatValue?.greaterThan(0) && fiatValue0 && ( - + + {totalFiatValue?.greaterThan(0) && fiatValue0 && ( {formatPercent(new Percent(fiatValue0.quotient, totalFiatValue.quotient).toFixed(6))} - - )} + )} + @@ -64,13 +64,13 @@ export function LiquidityPositionAmountsTile({ ({formatCurrencyAmount({ value: fiatValue1, type: NumberType.FiatTokenPrice })}) )} - {totalFiatValue?.greaterThan(0) && fiatValue1 && ( - + + {totalFiatValue?.greaterThan(0) && fiatValue1 && ( {formatPercent(new Percent(fiatValue1.quotient, totalFiatValue.quotient).toFixed(6))} - - )} + )} + diff --git a/apps/web/src/components/Liquidity/LiquidityPositionCard.tsx b/apps/web/src/components/Liquidity/LiquidityPositionCard.tsx index 96892291d02..536d6234af3 100644 --- a/apps/web/src/components/Liquidity/LiquidityPositionCard.tsx +++ b/apps/web/src/components/Liquidity/LiquidityPositionCard.tsx @@ -1,19 +1,51 @@ // eslint-disable-next-line no-restricted-imports -import { Position } from '@uniswap/client-pools/dist/pools/v1/types_pb' -import { Price } from '@uniswap/sdk-core' +import { ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' import { LiquidityPositionFeeStats } from 'components/Liquidity/LiquidityPositionFeeStats' import { LiquidityPositionInfo } from 'components/Liquidity/LiquidityPositionInfo' -import { parseRestPosition } from 'components/Liquidity/utils' -import { useMemo } from 'react' +import { PositionInfo } from 'components/Liquidity/types' +import { useV3PositionDerivedInfo } from 'components/Liquidity/utils' import { Flex, FlexProps } from 'ui/src' +import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' +import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice' +import { NumberType } from 'utilities/src/format/types' -export function LiquidityPositionCard({ liquidityPosition, ...rest }: { liquidityPosition: Position } & FlexProps) { - const positionInfo = useMemo(() => parseRestPosition(liquidityPosition), [liquidityPosition]) - if (!liquidityPosition || !positionInfo) { +export function LiquidityPositionCard({ liquidityPosition, ...rest }: { liquidityPosition: PositionInfo } & FlexProps) { + const { formatCurrencyAmount } = useLocalizationContext() + const { + fiatFeeValue0, + fiatFeeValue1, + fiatValue0, + fiatValue1, + priceOrdering: { priceLower, priceUpper }, + } = useV3PositionDerivedInfo(liquidityPosition) + + const token0USDValue = useUSDCValue(liquidityPosition.currency0Amount) + const token1USDValue = useUSDCValue(liquidityPosition.currency1Amount) + + const v3FormattedUsdValue = + fiatValue0 && fiatValue1 + ? formatCurrencyAmount({ + value: fiatValue0.add(fiatValue1), + type: NumberType.FiatTokenPrice, + }) + : '-' + const v2FormattedUsdValue = + token0USDValue && token1USDValue + ? formatCurrencyAmount({ value: token0USDValue.add(token1USDValue), type: NumberType.FiatStandard }) + : '-' + + const v3FormattedFeesValue = + fiatFeeValue0 && fiatFeeValue1 + ? formatCurrencyAmount({ + value: fiatFeeValue0.add(fiatFeeValue1), + type: NumberType.FiatTokenPrice, + }) + : '-' + + if (!liquidityPosition) { return null } - const { currency0Amount, currency1Amount } = positionInfo return ( - - {/* TODO: add the range chart */} + + {/* TODO (WEB-4920): add the range chart */} - {/* TODO: calculate the real fee stats here: */} ) diff --git a/apps/web/src/components/Liquidity/LiquidityPositionFeeStats.tsx b/apps/web/src/components/Liquidity/LiquidityPositionFeeStats.tsx index d25435ed977..2b6d160f2a9 100644 --- a/apps/web/src/components/Liquidity/LiquidityPositionFeeStats.tsx +++ b/apps/web/src/components/Liquidity/LiquidityPositionFeeStats.tsx @@ -6,12 +6,12 @@ import { ReverseArrows } from 'ui/src/components/icons/ReverseArrows' import { Trans } from 'uniswap/src/i18n' interface LiquidityPositionFeeStatsProps { - formattedUsdValue: string - formattedUsdFees: string - totalApr: string - feeApr: string - lowPrice: Price - highPrice: Price + formattedUsdValue?: string + formattedUsdFees?: string + totalApr?: string + feeApr?: string + lowPrice?: Price + highPrice?: Price } const PrimaryText = styled(Text, { @@ -33,47 +33,55 @@ export function LiquidityPositionFeeStats({ highPrice, }: LiquidityPositionFeeStatsProps) { const [pricesInverted, setPricesInverted] = useState(false) - const lowDisplayPrice = pricesInverted ? lowPrice.invert() : lowPrice - const highDisplayPrice = pricesInverted ? highPrice.invert() : highPrice + const lowDisplayPrice = pricesInverted ? lowPrice?.invert() : lowPrice + const highDisplayPrice = pricesInverted ? highPrice?.invert() : highPrice return ( - - {formattedUsdValue} - +{formattedUsdFees} fees - - - {totalApr} - - - - - - {feeApr} - - - - - - + {formattedUsdValue && ( + + {formattedUsdValue} + {formattedUsdFees && +{formattedUsdFees} fees} + + )} + {totalApr && ( + + {totalApr} - + - - {lowDisplayPrice.toSignificant(6)} {lowDisplayPrice.quoteCurrency.symbol} /{' '} - {lowDisplayPrice.baseCurrency.symbol} - - + )} + {feeApr && ( + + {feeApr} - + - - {highDisplayPrice.toSignificant(6)} {highDisplayPrice.quoteCurrency.symbol} /{' '} - {highDisplayPrice.baseCurrency.symbol} - - + )} + {lowDisplayPrice && highDisplayPrice && ( + + + + + + + {lowDisplayPrice.toSignificant(6)} {lowDisplayPrice.quoteCurrency.symbol} /{' '} + {lowDisplayPrice.baseCurrency.symbol} + + + + + + + + {highDisplayPrice.toSignificant(6)} {highDisplayPrice.quoteCurrency.symbol} /{' '} + {highDisplayPrice.baseCurrency.symbol} + + + + )} setPricesInverted((prevInverted) => !prevInverted)}> diff --git a/apps/web/src/components/Liquidity/LiquidityPositionInfo.test.tsx b/apps/web/src/components/Liquidity/LiquidityPositionInfo.test.tsx index ddeacff2f5d..8fd4a9b43dc 100644 --- a/apps/web/src/components/Liquidity/LiquidityPositionInfo.test.tsx +++ b/apps/web/src/components/Liquidity/LiquidityPositionInfo.test.tsx @@ -1,51 +1,49 @@ // eslint-disable-next-line no-restricted-imports import { PositionStatus, ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' import { LiquidityPositionInfo } from 'components/Liquidity/LiquidityPositionInfo' -import { getProtocolVersionLabel, parseRestPosition } from 'components/Liquidity/utils' +import { PositionInfo } from 'components/Liquidity/types' import { TEST_TOKEN_1, TEST_TOKEN_2, toCurrencyAmount } from 'test-utils/constants' -import { mocked } from 'test-utils/mocked' import { render } from 'test-utils/render' jest.mock('components/Liquidity/utils') describe('LiquidityPositionInfo', () => { - beforeEach(() => { - mocked(getProtocolVersionLabel).mockReturnValue('V3') - }) - it('should render in range', () => { - mocked(parseRestPosition).mockReturnValue({ + const positionInfo: PositionInfo = { currency0Amount: toCurrencyAmount(TEST_TOKEN_1, 1), currency1Amount: toCurrencyAmount(TEST_TOKEN_2, 1), status: PositionStatus.IN_RANGE, - restPosition: {} as any, version: ProtocolVersion.V3, - }) - const { getByText } = render() + tokenId: '1', + v4hook: undefined, + } + const { getByText } = render() expect(getByText('In range')).toBeInTheDocument() }) it('should render out of range', () => { - mocked(parseRestPosition).mockReturnValue({ + const positionInfo: PositionInfo = { currency0Amount: toCurrencyAmount(TEST_TOKEN_1, 1), currency1Amount: toCurrencyAmount(TEST_TOKEN_2, 1), status: PositionStatus.OUT_OF_RANGE, - restPosition: {} as any, version: ProtocolVersion.V3, - }) - const { getByText } = render() + tokenId: '4', + v4hook: undefined, + } + const { getByText } = render() expect(getByText('Out of range')).toBeInTheDocument() }) it('should render closed', () => { - mocked(parseRestPosition).mockReturnValue({ + const positionInfo: PositionInfo = { currency0Amount: toCurrencyAmount(TEST_TOKEN_1, 1), currency1Amount: toCurrencyAmount(TEST_TOKEN_2, 1), status: PositionStatus.CLOSED, - restPosition: {} as any, version: ProtocolVersion.V3, - }) - const { getByText } = render() + tokenId: '1', + v4hook: undefined, + } + const { getByText } = render() expect(getByText('Closed')).toBeInTheDocument() }) }) diff --git a/apps/web/src/components/Liquidity/LiquidityPositionInfo.tsx b/apps/web/src/components/Liquidity/LiquidityPositionInfo.tsx index df3e36bbcbf..989664d5ae2 100644 --- a/apps/web/src/components/Liquidity/LiquidityPositionInfo.tsx +++ b/apps/web/src/components/Liquidity/LiquidityPositionInfo.tsx @@ -1,22 +1,16 @@ -// eslint-disable-next-line no-restricted-imports -import { Position } from '@uniswap/client-pools/dist/pools/v1/types_pb' import { BadgeData, LiquidityPositionInfoBadges } from 'components/Liquidity/LiquidityPositionInfoBadges' import { LiquidityPositionStatusIndicator } from 'components/Liquidity/LiquidityPositionStatusIndicator' -import { getProtocolVersionLabel, parseRestPosition } from 'components/Liquidity/utils' +import { PositionInfo } from 'components/Liquidity/types' +import { getProtocolVersionLabel } from 'components/Liquidity/utils' import { DoubleCurrencyAndChainLogo } from 'components/Logo/DoubleLogo' -import { useMemo } from 'react' import { Flex, Text } from 'ui/src' import { DocumentList } from 'ui/src/components/icons/DocumentList' interface LiquidityPositionInfoProps { - position: Position + positionInfo: PositionInfo } -export function LiquidityPositionInfo({ position }: LiquidityPositionInfoProps) { - const positionInfo = useMemo(() => parseRestPosition(position), [position]) - if (!positionInfo) { - return null - } +export function LiquidityPositionInfo({ positionInfo }: LiquidityPositionInfoProps) { const { currency0Amount, currency1Amount, status, feeTier, v4hook, version } = positionInfo const versionLabel = getProtocolVersionLabel(version) return ( diff --git a/apps/web/src/components/Liquidity/LiquidityPositionPriceRangeTile.tsx b/apps/web/src/components/Liquidity/LiquidityPositionPriceRangeTile.tsx index 7ff0d572fb4..15d65e57533 100644 --- a/apps/web/src/components/Liquidity/LiquidityPositionPriceRangeTile.tsx +++ b/apps/web/src/components/Liquidity/LiquidityPositionPriceRangeTile.tsx @@ -1,10 +1,12 @@ // eslint-disable-next-line no-restricted-imports import { PositionStatus } from '@uniswap/client-pools/dist/pools/v1/types_pb' -import { Currency, Price } from '@uniswap/sdk-core' +import { Price, Token } from '@uniswap/sdk-core' import { LiquidityPositionStatusIndicator } from 'components/Liquidity/LiquidityPositionStatusIndicator' import { useMemo, useState } from 'react' +import { Bound } from 'state/mint/v3/actions' import { Flex, SegmentedControl, SegmentedControlOption, Text, styled } from 'ui/src' import { Trans } from 'uniswap/src/i18n' +import { NumberType, useFormatter } from 'utils/formatNumbers' const InnerTile = styled(Flex, { grow: true, @@ -17,20 +19,64 @@ const InnerTile = styled(Flex, { interface LiquidityPositionPriceRangeTileProps { status?: PositionStatus - minPrice: Price - maxPrice: Price - currentPrice: Price + priceOrdering: { + priceLower?: Price + priceUpper?: Price + quote?: Token + base?: Token + } + isTickAtLimit: { + [Bound.LOWER]?: boolean + [Bound.UPPER]?: boolean + } + token0CurrentPrice: Price + token1CurrentPrice: Price +} + +const getValues = ({ + token0CurrentPrice, + token1CurrentPrice, + priceLower, + priceUpper, + quote, + base, + invert, +}: { + token0CurrentPrice?: Price + token1CurrentPrice?: Price + priceLower?: Price + priceUpper?: Price + quote?: Token + base?: Token + invert?: boolean +}): { + currentPrice?: Price + priceLower?: Price + priceUpper?: Price + quote?: Token + base?: Token +} => { + return { + currentPrice: invert ? token1CurrentPrice : token0CurrentPrice, + priceUpper: invert ? priceLower?.invert() : priceUpper, + priceLower: invert ? priceUpper?.invert() : priceLower, + quote: invert ? base : quote, + base: invert ? quote : base, + } } export function LiquidityPositionPriceRangeTile({ status, - minPrice, - maxPrice, - currentPrice, + priceOrdering, + isTickAtLimit, + token0CurrentPrice, + token1CurrentPrice, }: LiquidityPositionPriceRangeTileProps) { + const { formatTickPrice, formatPrice } = useFormatter() const [pricesInverted, setPricesInverted] = useState(false) - const currencyASymbol = currentPrice.baseCurrency.symbol - const currencyBSymbol = currentPrice.quoteCurrency.symbol + + const currencyASymbol = token0CurrentPrice.baseCurrency.symbol + const currencyBSymbol = token0CurrentPrice.quoteCurrency.symbol const controlOptions: SegmentedControlOption[] = useMemo(() => { return [ @@ -49,11 +95,33 @@ export function LiquidityPositionPriceRangeTile({ throw new Error('LiquidityPositionPriceRangeTile: Currency symbols are required') } - const displayMinPrice = pricesInverted ? minPrice.invert() : minPrice - const displayMaxPrice = pricesInverted ? maxPrice.invert() : maxPrice - const displayCurrentPrice = pricesInverted ? currentPrice.invert() : currentPrice - const displayASymbol = pricesInverted ? currencyBSymbol : currencyASymbol - const displayBSymbol = pricesInverted ? currencyASymbol : currencyBSymbol + const { + currentPrice: displayCurrentPrice, + priceLower, + priceUpper, + base, + quote, + } = getValues({ + token0CurrentPrice, + token1CurrentPrice, + ...priceOrdering, + invert: pricesInverted, + }) + + const displayMinPrice = formatTickPrice({ + price: priceLower, + atLimit: isTickAtLimit, + direction: Bound.LOWER, + numberType: NumberType.TokenTx, + }) + const displayMaxPrice = formatTickPrice({ + price: priceUpper, + atLimit: isTickAtLimit, + direction: Bound.UPPER, + numberType: NumberType.TokenTx, + }) + const displayASymbol = quote?.symbol + const displayBSymbol = base?.symbol return ( @@ -78,7 +146,7 @@ export function LiquidityPositionPriceRangeTile({ - {displayMinPrice.toFixed()} + {displayMinPrice} - {displayMaxPrice.toFixed()} + {displayMaxPrice} - {displayCurrentPrice.toFixed()} + {formatPrice({ price: displayCurrentPrice, type: NumberType.TokenTx })} + currencyUSDAmount?: CurrencyAmount +}) { + const { formatCurrencyAmount } = useLocalizationContext() + + return ( + + + + {formatCurrencyAmount({ + value: currencyAmount, + type: NumberType.TokenNonTx, + })}{' '} + {getSymbolDisplayText(currencyAmount?.currency.symbol)} + + + {formatCurrencyAmount({ + value: currencyUSDAmount, + type: NumberType.FiatStandard, + })} + + + + + ) +} diff --git a/apps/web/src/components/Liquidity/types.ts b/apps/web/src/components/Liquidity/types.ts new file mode 100644 index 00000000000..e8ec507c578 --- /dev/null +++ b/apps/web/src/components/Liquidity/types.ts @@ -0,0 +1,69 @@ +// eslint-disable-next-line no-restricted-imports +import { PositionStatus, ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' +import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core' +import { Pair } from '@uniswap/v2-sdk' +import { FeeAmount, Pool, Position } from '@uniswap/v3-sdk' +import { Dispatch, SetStateAction } from 'react' +import { PositionField } from 'types/position' + +export interface DepositState { + exactField: PositionField + exactAmount?: string +} + +export type DepositContextType = { + depositState: DepositState + setDepositState: Dispatch> + derivedDepositInfo: DepositInfo +} + +export interface DepositInfo { + formattedAmounts?: { [field in PositionField]?: string } + currencyBalances?: { [field in PositionField]?: CurrencyAmount } + currencyAmounts?: { [field in PositionField]?: CurrencyAmount } + currencyAmountsUSDValue?: { [field in PositionField]?: CurrencyAmount } +} + +interface BasePositionInfo { + status: PositionStatus + version: ProtocolVersion + currency0Amount: CurrencyAmount + currency1Amount: CurrencyAmount + tokenId?: string + tickLower?: string + tickUpper?: string + tickSpacing?: number + liquidity?: string + liquidityToken?: Token + totalSupply?: CurrencyAmount + liquidityAmount?: CurrencyAmount + token0UncollectedFees?: string + token1UncollectedFees?: string +} + +type V2PairInfo = BasePositionInfo & { + version: ProtocolVersion.V2 + pair?: Pair + liquidityToken: Token + feeTier: undefined + v4hook: undefined +} + +type V3PositionInfo = BasePositionInfo & { + version: ProtocolVersion.V3 + tokenId: string + pool?: Pool + feeTier?: FeeAmount + position?: Position + v4hook: undefined +} + +type V4PositionInfo = BasePositionInfo & { + version: ProtocolVersion.V4 + tokenId: string + position?: Position + feeTier?: string + v4hook?: string +} + +export type PositionInfo = V2PairInfo | V3PositionInfo | V4PositionInfo diff --git a/apps/web/src/components/Liquidity/utils.tsx b/apps/web/src/components/Liquidity/utils.tsx index 0ef2f76b5a5..41d967a71af 100644 --- a/apps/web/src/components/Liquidity/utils.tsx +++ b/apps/web/src/components/Liquidity/utils.tsx @@ -1,23 +1,25 @@ -import { BigNumber } from '@ethersproject/bignumber' // eslint-disable-next-line no-restricted-imports import { - Position, + PairPosition, + PoolPosition, PositionStatus, ProtocolVersion, + Pair as RestPair, Pool as RestPool, + Position as RestPosition, Token as RestToken, } from '@uniswap/client-pools/dist/pools/v1/types_pb' -import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core' -import { FeeAmount, Pool, Position as V3SDKPosition } from '@uniswap/v3-sdk' +import { CurrencyAmount, Percent, Token } from '@uniswap/sdk-core' +import { Pair } from '@uniswap/v2-sdk' +import { FeeAmount, Pool, Position } from '@uniswap/v3-sdk' +import { PositionInfo } from 'components/Liquidity/types' import { getPriceOrderingFromPositionForUI } from 'components/PositionListItem' -import { usePool } from 'hooks/usePools' -import { useV3PositionFees } from 'hooks/useV3PositionFees' import JSBI from 'jsbi' import { useMemo } from 'react' import { useAppSelector } from 'state/hooks' import { AppTFunction } from 'ui/src/i18n/types' import { ProtocolItems } from 'uniswap/src/data/tradingApi/__generated__' -import { useUSDCPrice, useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice' +import { useUSDCPrice } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice' export function getProtocolVersionLabel(version: ProtocolVersion): string | undefined { switch (version) { @@ -79,7 +81,7 @@ export function getPoolFromRest({ token0, token1, }: { - pool?: RestPool + pool?: RestPool | PoolPosition token0?: Token token1?: Token }): Pool | undefined { @@ -87,7 +89,18 @@ export function getPoolFromRest({ return undefined } - return new Pool(token0, token1, pool.fee, pool.sqrtPriceX96, pool.liquidity, pool.tick) + if (pool instanceof RestPool) { + return new Pool(token0, token1, pool.fee, pool.sqrtPriceX96, pool.liquidity, pool.tick) + } + + if (pool instanceof PoolPosition) { + const feeTier = parseV3FeeTier(pool.feeTier) + if (feeTier) { + return new Pool(token0, token1, feeTier, pool.currentPrice, pool.liquidity, parseInt(pool.currentTick)) + } + } + + return undefined } function parseRestToken(token?: RestToken): Token | undefined { @@ -97,34 +110,31 @@ function parseRestToken(token?: RestToken): Token | undefined { return new Token(token.chainId, token.address, token.decimals, token.symbol) } -export type PositionInfo = { - restPosition: Position - status: PositionStatus - version: ProtocolVersion - currency0Amount: CurrencyAmount - currency1Amount: CurrencyAmount - feeTier?: string - v4hook?: string - tokenId?: string - tickLower?: string - tickUpper?: string - tickSpacing?: number - liquidity?: string - liquidityToken?: Token - totalSupply?: CurrencyAmount - liquidityAmount?: CurrencyAmount - token0UncollectedFees?: string - token1UncollectedFees?: string +export function getPairFromRest({ + pair, + token0, + token1, +}: { + pair?: PairPosition | RestPair + token0: Token + token1: Token +}): Pair | undefined { + if (!pair) { + return undefined + } + + return new Pair( + CurrencyAmount.fromRawAmount(token0, pair.reserve0), + CurrencyAmount.fromRawAmount(token1, pair.reserve1), + ) } /** * @param position REST position with unknown version / fields. * @returns PositionInfo with the available fields parsed. */ -export function parseRestPosition(position?: Position): PositionInfo | undefined { - if (!position?.position) { - return undefined - } else if (position.position.case === 'v2Pair') { +export function parseRestPosition(position?: RestPosition): PositionInfo | undefined { + if (position?.position.case === 'v2Pair') { const v2PairPosition = position.position.value const token0 = parseRestToken(v2PairPosition.token0) const token1 = parseRestToken(v2PairPosition.token1) @@ -133,17 +143,21 @@ export function parseRestPosition(position?: Position): PositionInfo | undefined return undefined } + const pair = getPairFromRest({ pair: position.position.value, token0, token1 }) + return { status: position.status, - version: position.protocolVersion, - restPosition: position, + version: ProtocolVersion.V2, + pair, liquidityToken, currency0Amount: CurrencyAmount.fromRawAmount(token0, v2PairPosition.liquidity0), currency1Amount: CurrencyAmount.fromRawAmount(token1, v2PairPosition.liquidity1), totalSupply: CurrencyAmount.fromRawAmount(liquidityToken, v2PairPosition.totalSupply), liquidityAmount: CurrencyAmount.fromRawAmount(liquidityToken, v2PairPosition.liquidity), + v4hook: undefined, + feeTier: undefined, } - } else if (position.position.case === 'v3Position') { + } else if (position?.position.case === 'v3Position') { const v3Position = position.position.value const token0 = parseRestToken(v3Position.token0) @@ -151,11 +165,23 @@ export function parseRestPosition(position?: Position): PositionInfo | undefined if (!token0 || !token1) { return undefined } + + const pool = getPoolFromRest({ pool: position.position.value, token0, token1 }) + const sdkPosition = pool + ? new Position({ + pool, + liquidity: v3Position.liquidity, + tickLower: Number(v3Position.tickLower), + tickUpper: Number(v3Position.tickUpper), + }) + : undefined + return { status: position.status, - feeTier: v3Position.feeTier, - version: position.protocolVersion, - restPosition: position, + feeTier: parseV3FeeTier(v3Position.feeTier), + version: ProtocolVersion.V3, + pool, + position: sdkPosition, tickLower: v3Position.tickLower, tickUpper: v3Position.tickUpper, tickSpacing: Number(v3Position.tickSpacing), @@ -165,25 +191,26 @@ export function parseRestPosition(position?: Position): PositionInfo | undefined token1UncollectedFees: v3Position.token1UncollectedFees, currency0Amount: CurrencyAmount.fromRawAmount(token0, v3Position.amount0), currency1Amount: CurrencyAmount.fromRawAmount(token1, v3Position.amount1), + v4hook: undefined, } - } else if (position.position.case === 'v4Position' && position.position.value?.poolPosition) { + } else if (position?.position.case === 'v4Position') { const v4Position = position.position.value.poolPosition const token0 = parseRestToken(v4Position?.token0) const token1 = parseRestToken(v4Position?.token1) - if (!token0 || !token1) { + if (!v4Position || !token0 || !token1) { return undefined } return { status: position.status, - feeTier: v4Position.feeTier, - version: position.protocolVersion, + feeTier: v4Position?.feeTier, + version: ProtocolVersion.V4, + position: undefined, // add support for this v4hook: position.position.value.hooks[0]?.address, - tokenId: v4Position?.tokenId, + tokenId: v4Position.tokenId, tickLower: v4Position?.tickLower, tickUpper: v4Position?.tickUpper, tickSpacing: Number(v4Position?.tickSpacing), - restPosition: position, currency0Amount: CurrencyAmount.fromRawAmount(token0, v4Position?.amount0 ?? 0), currency1Amount: CurrencyAmount.fromRawAmount(token1, v4Position?.amount1 ?? 0), } @@ -197,15 +224,11 @@ export function parseRestPosition(position?: Position): PositionInfo | undefined */ export function useModalLiquidityPositionInfo(): PositionInfo | undefined { const modalState = useAppSelector((state) => state.application.openModal) - const position = modalState?.initialState - return useMemo(() => parseRestPosition(position), [position]) + return modalState?.initialState } -/** - * V2-specific hooks for a position parsed using parseRestPosition. - */ -export function useV2PositionDerivedInfo(positionInfo?: PositionInfo) { - const { currency0Amount, currency1Amount, totalSupply, liquidityAmount } = positionInfo ?? {} +export function useGetPoolTokenPercentage(positionInfo?: PositionInfo) { + const { totalSupply, liquidityAmount } = positionInfo ?? {} const poolTokenPercentage = useMemo(() => { return !!liquidityAmount && !!totalSupply && JSBI.greaterThanOrEqual(totalSupply.quotient, liquidityAmount.quotient) @@ -213,50 +236,52 @@ export function useV2PositionDerivedInfo(positionInfo?: PositionInfo) { : undefined }, [liquidityAmount, totalSupply]) - const token0USDValue = useUSDCValue(currency0Amount) - const token1USDValue = useUSDCValue(currency1Amount) - - return useMemo( - () => ({ - poolTokenPercentage, - token0USDValue, - token1USDValue, - }), - [poolTokenPercentage, token0USDValue, token1USDValue], - ) + return poolTokenPercentage } /** * V3-specific hooks for a position parsed using parseRestPosition. */ -export function useV3PositionDerivedInfo(positionInfo?: PositionInfo, tokenId?: string, collectAsWeth?: boolean) { - const { currency0Amount, currency1Amount, feeTier, liquidity, tickLower, tickUpper } = positionInfo ?? {} +export function useV3PositionDerivedInfo(positionInfo?: PositionInfo) { + const { + token0UncollectedFees, + token1UncollectedFees, + currency0Amount, + currency1Amount, + liquidity, + tickLower, + tickUpper, + } = positionInfo ?? {} // TODO(WEB-4920): construct Pool object from backend data rather than using multicall - const [, pool] = usePool(currency0Amount?.currency, currency1Amount?.currency, parseV3FeeTier(feeTier)) - const price0 = useUSDCPrice(currency0Amount?.currency) - const price1 = useUSDCPrice(currency1Amount?.currency) + const { price: price0 } = useUSDCPrice(currency0Amount?.currency) + const { price: price1 } = useUSDCPrice(currency1Amount?.currency) - // TODO(WEB-4920): use fees from REST response instead once they are included. - const [feeValue0, feeValue1] = useV3PositionFees(pool ?? undefined, BigNumber.from(tokenId), collectAsWeth) - const { fiatFeeValue0, fiatFeeValue1 } = useMemo(() => { - if (!price0 || !price1 || !feeValue0 || !feeValue1) { + const { feeValue0, feeValue1 } = useMemo(() => { + if (!currency0Amount || !currency1Amount) { return {} } + return { + feeValue0: token0UncollectedFees + ? CurrencyAmount.fromRawAmount(currency0Amount.currency, token0UncollectedFees) + : undefined, + feeValue1: token1UncollectedFees + ? CurrencyAmount.fromRawAmount(currency1Amount.currency, token1UncollectedFees) + : undefined, + } + }, [currency0Amount, currency1Amount, token0UncollectedFees, token1UncollectedFees]) - const feeValue0Wrapped = feeValue0?.wrapped - const feeValue1Wrapped = feeValue1?.wrapped - - if (!feeValue0Wrapped || !feeValue1Wrapped) { + const { fiatFeeValue0, fiatFeeValue1 } = useMemo(() => { + if (!price0 || !price1 || !currency0Amount || !currency1Amount || !feeValue0 || !feeValue1) { return {} } - const amount0 = price0.quote(feeValue0Wrapped) - const amount1 = price1.quote(feeValue1Wrapped) + const amount0 = price0.quote(feeValue0.wrapped) + const amount1 = price1.quote(feeValue1.wrapped) return { fiatFeeValue0: amount0, fiatFeeValue1: amount1, } - }, [price0, price1, feeValue0, feeValue1]) + }, [price0, price1, currency0Amount, currency1Amount, feeValue0, feeValue1]) const { fiatValue0, fiatValue1 } = useMemo(() => { if (!price0 || !price1 || !currency0Amount || !currency1Amount) { @@ -269,30 +294,32 @@ export function useV3PositionDerivedInfo(positionInfo?: PositionInfo, tokenId?: fiatValue1: amount1, } }, [price0, price1, currency0Amount, currency1Amount]) - const { priceLower, priceUpper } = useMemo(() => { - if (!pool || !liquidity || !tickLower || !tickUpper) { + + const priceOrdering = useMemo(() => { + if ( + positionInfo?.version !== ProtocolVersion.V3 || + !positionInfo.position || + !liquidity || + !tickLower || + !tickUpper + ) { return {} } - const sdkPosition = new V3SDKPosition({ - pool, - liquidity, - tickLower: Number(tickLower), - tickUpper: Number(tickUpper), - }) - return getPriceOrderingFromPositionForUI(sdkPosition) - }, [liquidity, pool, tickLower, tickUpper]) + return getPriceOrderingFromPositionForUI(positionInfo.position) + }, [liquidity, tickLower, tickUpper, positionInfo]) + return useMemo( () => ({ fiatFeeValue0, fiatFeeValue1, fiatValue0, fiatValue1, - priceLower, - priceUpper, + priceOrdering, feeValue0, feeValue1, - currentPrice: pool?.token1Price, + token0CurrentPrice: positionInfo?.version === ProtocolVersion.V3 ? positionInfo.pool?.token0Price : undefined, + token1CurrentPrice: positionInfo?.version === ProtocolVersion.V3 ? positionInfo.pool?.token0Price : undefined, }), - [feeValue0, feeValue1, fiatFeeValue0, fiatFeeValue1, fiatValue0, fiatValue1, priceLower, priceUpper, pool], + [fiatFeeValue0, fiatFeeValue1, fiatValue0, fiatValue1, priceOrdering, feeValue0, feeValue1, positionInfo], ) } diff --git a/apps/web/src/components/Logo/AssetLogo.tsx b/apps/web/src/components/Logo/AssetLogo.tsx index 079644f982c..747d8c5832f 100644 --- a/apps/web/src/components/Logo/AssetLogo.tsx +++ b/apps/web/src/components/Logo/AssetLogo.tsx @@ -1,8 +1,5 @@ import { Currency } from '@uniswap/sdk-core' -import { PortfolioLogo } from 'components/AccountDrawer/MiniPortfolio/PortfolioLogo' -import styled from 'lib/styled-components' import React from 'react' -import { UniverseChainId } from 'uniswap/src/types/chains' export type AssetLogoBaseProps = { symbol?: string | null @@ -12,26 +9,3 @@ export type AssetLogoBaseProps = { currency?: Currency | null loading?: boolean } -type AssetLogoProps = AssetLogoBaseProps & { isNative?: boolean; address?: string | null; chainId?: number } - -const LogoContainer = styled.div` - position: relative; - display: flex; -` - -/** - * Renders an image by prioritizing a list of sources, and then eventually a fallback triangle alert - */ -export default function AssetLogo({ - currency, - chainId = UniverseChainId.Mainnet, - size = 24, - style, - loading, -}: AssetLogoProps) { - return ( - - - - ) -} diff --git a/apps/web/src/components/Logo/ChainLogo.tsx b/apps/web/src/components/Logo/ChainLogo.tsx index 57e6e6610a5..b2afc33cbc3 100644 --- a/apps/web/src/components/Logo/ChainLogo.tsx +++ b/apps/web/src/components/Logo/ChainLogo.tsx @@ -1,8 +1,9 @@ -import { SupportedInterfaceChainId, getChain, useIsSupportedChainId } from 'constants/chains' +import { getChain, useIsSupportedChainId } from 'constants/chains' import { CSSProperties } from 'react' import { useIsDarkMode } from 'theme/components/ThemeToggle' import { ARBITRUM_LOGO, + ASTROCHAIN_SEPOLIA_LOGO, AVALANCHE_LOGO, BASE_LOGO, BLAST_LOGO, @@ -11,18 +12,18 @@ import { ETHEREUM_LOGO, OPTIMISM_LOGO, POLYGON_LOGO, + WORLD_CHAIN_LOGO, ZKSYNC_LOGO, ZORA_LOGO, } from 'ui/src/assets' -import { InterfaceChainId, UniverseChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' type ChainUI = { symbol: string; bgColor: string; textColor: string } -export function getChainUI(chainId: SupportedInterfaceChainId, darkMode: boolean): ChainUI -export function getChainUI(chainId: InterfaceChainId, darkMode: boolean): ChainUI | undefined { +export function getChainUI(chainId: UniverseChainId, darkMode: boolean): ChainUI +export function getChainUI(chainId: UniverseChainId, darkMode: boolean): ChainUI | undefined { switch (chainId) { case UniverseChainId.Mainnet: - case UniverseChainId.Goerli: case UniverseChainId.Sepolia: return { symbol: ETHEREUM_LOGO, @@ -30,28 +31,24 @@ export function getChainUI(chainId: InterfaceChainId, darkMode: boolean): ChainU textColor: '#6B8AFF', } case UniverseChainId.Polygon: - case UniverseChainId.PolygonMumbai: return { symbol: POLYGON_LOGO, bgColor: '#9558FF33', textColor: '#9558FF', } case UniverseChainId.ArbitrumOne: - case UniverseChainId.ArbitrumGoerli: return { symbol: ARBITRUM_LOGO, bgColor: '#00A3FF33', textColor: '#00A3FF', } case UniverseChainId.Optimism: - case UniverseChainId.OptimismGoerli: return { symbol: OPTIMISM_LOGO, bgColor: '#FF042033', textColor: '#FF0420', } case UniverseChainId.Celo: - case UniverseChainId.CeloAlfajores: return darkMode ? { symbol: CELO_LOGO, @@ -117,6 +114,18 @@ export function getChainUI(chainId: InterfaceChainId, darkMode: boolean): ChainU bgColor: 'rgba(54, 103, 246, 0.12)', textColor: '#3667F6', } + case UniverseChainId.WorldChain: + return { + symbol: WORLD_CHAIN_LOGO, + bgColor: darkMode ? '#FFFFFF' : '#222222', + textColor: darkMode ? '#FFFFFF' : '#222222', + } + case UniverseChainId.AstrochainSepolia: + return { + symbol: ASTROCHAIN_SEPOLIA_LOGO, + bgColor: '#fc0fa4', + textColor: '#fc0fa4', + } default: return undefined } @@ -125,7 +134,7 @@ export function getChainUI(chainId: InterfaceChainId, darkMode: boolean): ChainU const getDefaultBorderRadius = (size: number) => size / 2 - 4 type ChainLogoProps = { - chainId: InterfaceChainId + chainId: UniverseChainId className?: string size?: number borderRadius?: number diff --git a/apps/web/src/components/Logo/CurrencyLogo.tsx b/apps/web/src/components/Logo/CurrencyLogo.tsx index a47730d3aa3..6099e901992 100644 --- a/apps/web/src/components/Logo/CurrencyLogo.tsx +++ b/apps/web/src/components/Logo/CurrencyLogo.tsx @@ -1,21 +1,29 @@ import { Currency } from '@uniswap/sdk-core' -import { TokenInfo } from '@uniswap/token-lists' -import AssetLogo, { AssetLogoBaseProps } from 'components/Logo/AssetLogo' +import { AssetLogoBaseProps } from 'components/Logo/AssetLogo' +import { CurrencyLogo as UniverseCurrencyLogo } from 'uniswap/src/components/CurrencyLogo/CurrencyLogo' +import { useCurrencyInfo } from 'uniswap/src/features/tokens/useCurrencyInfo' +import { buildCurrencyId, currencyAddress } from 'uniswap/src/utils/currencyId' export default function CurrencyLogo( props: AssetLogoBaseProps & { currency?: Currency | null }, ) { - return ( - - ) + const { currency, ...rest } = props + + if (!currency) { + return null + } + + return <_CurrencyLogo currency={currency} {...rest} /> +} + +const _CurrencyLogo = ( + props: AssetLogoBaseProps & { + currency: Currency + }, +) => { + const currencyId = buildCurrencyId(props.currency.chainId, currencyAddress(props.currency)) + const currencyInfo = useCurrencyInfo(currencyId) + return } diff --git a/apps/web/src/components/Logo/DoubleLogo.tsx b/apps/web/src/components/Logo/DoubleLogo.tsx index c4200ff040e..db542ebecf9 100644 --- a/apps/web/src/components/Logo/DoubleLogo.tsx +++ b/apps/web/src/components/Logo/DoubleLogo.tsx @@ -5,7 +5,7 @@ import { useCurrencyInfo } from 'hooks/Tokens' import styled, { css } from 'lib/styled-components' import { memo, useState } from 'react' import { useColorSchemeFromSeed } from 'ui/src' -import { InterfaceChainId, UniverseChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' const MissingImageLogo = styled.div<{ $size?: string; $textColor: string; $backgroundColor: string }>` --size: ${({ $size }) => $size}; @@ -148,7 +148,7 @@ export const L2LogoContainer = styled.div<{ $size: number }>` justify-content: center; ` -function SquareL2Logo({ chainId, size }: { chainId: InterfaceChainId; size: number }) { +function SquareL2Logo({ chainId, size }: { chainId: UniverseChainId; size: number }) { if (chainId === UniverseChainId.Mainnet) { return null } diff --git a/apps/web/src/components/NavBar/ChainSelector/ChainSelectorRow.test.tsx b/apps/web/src/components/NavBar/ChainSelector/ChainSelectorRow.test.tsx index 0c33647d288..6b293d814c9 100644 --- a/apps/web/src/components/NavBar/ChainSelector/ChainSelectorRow.test.tsx +++ b/apps/web/src/components/NavBar/ChainSelector/ChainSelectorRow.test.tsx @@ -1,9 +1,9 @@ import ChainSelectorRow from 'components/NavBar/ChainSelector/ChainSelectorRow' import { render } from 'test-utils/render' -import { UniverseChainId, WEB_SUPPORTED_CHAIN_IDS } from 'uniswap/src/types/chains' +import { SUPPORTED_CHAIN_IDS, UniverseChainId } from 'uniswap/src/types/chains' describe('ChainSelectorRow', () => { - WEB_SUPPORTED_CHAIN_IDS.forEach((chainId) => { + SUPPORTED_CHAIN_IDS.forEach((chainId) => { it(`should match snapshot for chainId ${chainId}`, () => { const { container } = render( , diff --git a/apps/web/src/components/NavBar/ChainSelector/ChainSelectorRow.tsx b/apps/web/src/components/NavBar/ChainSelector/ChainSelectorRow.tsx index f28422703a1..da5c0021d11 100644 --- a/apps/web/src/components/NavBar/ChainSelector/ChainSelectorRow.tsx +++ b/apps/web/src/components/NavBar/ChainSelector/ChainSelectorRow.tsx @@ -7,7 +7,7 @@ import { useSwapAndLimitContext } from 'state/swap/useSwapContext' import Trace from 'uniswap/src/features/telemetry/Trace' import { SectionName } from 'uniswap/src/features/telemetry/constants' import { Trans } from 'uniswap/src/i18n' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' const LOGO_SIZE = 20 @@ -56,7 +56,7 @@ const CaptionText = styled.div` interface ChainSelectorRowProps { disabled?: boolean - targetChain: InterfaceChainId + targetChain: UniverseChainId onSelectChain?: (targetChain: number) => void isPending: boolean } diff --git a/apps/web/src/components/NavBar/ChainSelector/__snapshots__/ChainSelectorRow.test.tsx.snap b/apps/web/src/components/NavBar/ChainSelector/__snapshots__/ChainSelectorRow.test.tsx.snap index 67720363bbd..d6427200e1c 100644 --- a/apps/web/src/components/NavBar/ChainSelector/__snapshots__/ChainSelectorRow.test.tsx.snap +++ b/apps/web/src/components/NavBar/ChainSelector/__snapshots__/ChainSelectorRow.test.tsx.snap @@ -106,96 +106,6 @@ exports[`ChainSelectorRow should match snapshot for chainId 1 1`] = `
`; -exports[`ChainSelectorRow should match snapshot for chainId 5 1`] = ` -.c0 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - background: none; - border: none; - border-radius: 12px; - color: #222222; - cursor: pointer; - display: grid; - grid-template-columns: min-content 1fr min-content; - -webkit-box-pack: justify; - -webkit-justify-content: space-between; - -ms-flex-pack: justify; - justify-content: space-between; - line-height: 20px; - opacity: 1; - padding: 10px 8px; - text-align: left; - outline: none; - -webkit-transition: 250ms ease background-color; - transition: 250ms ease background-color; - width: 100%; -} - -.c0:hover { - background-color: #22222212; -} - -.c1 { - grid-column: 2; - grid-row: 1; - font-size: 16px; - font-weight: 485; -} - -.c2 { - grid-column: 3; - grid-row: 1; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - width: 20px; -} - -
- - - - - - - -
-`; - exports[`ChainSelectorRow should match snapshot for chainId 10 1`] = ` .c0 { -webkit-align-items: center; @@ -556,7 +466,7 @@ exports[`ChainSelectorRow should match snapshot for chainId 324 1`] = `
`; -exports[`ChainSelectorRow should match snapshot for chainId 420 1`] = ` +exports[`ChainSelectorRow should match snapshot for chainId 480 1`] = ` .c0 { -webkit-align-items: center; -webkit-box-align: center; @@ -588,13 +498,6 @@ exports[`ChainSelectorRow should match snapshot for chainId 420 1`] = ` } .c1 { - grid-column: 2; - grid-row: 1; - font-size: 16px; - font-weight: 485; -} - -.c2 { grid-column: 3; grid-row: 1; display: -webkit-box; @@ -621,23 +524,10 @@ exports[`ChainSelectorRow should match snapshot for chainId 420 1`] = ` > @@ -1006,186 +896,6 @@ exports[`ChainSelectorRow should match snapshot for chainId 43114 1`] = `
`; -exports[`ChainSelectorRow should match snapshot for chainId 44787 1`] = ` -.c0 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - background: none; - border: none; - border-radius: 12px; - color: #222222; - cursor: pointer; - display: grid; - grid-template-columns: min-content 1fr min-content; - -webkit-box-pack: justify; - -webkit-justify-content: space-between; - -ms-flex-pack: justify; - justify-content: space-between; - line-height: 20px; - opacity: 1; - padding: 10px 8px; - text-align: left; - outline: none; - -webkit-transition: 250ms ease background-color; - transition: 250ms ease background-color; - width: 100%; -} - -.c0:hover { - background-color: #22222212; -} - -.c1 { - grid-column: 2; - grid-row: 1; - font-size: 16px; - font-weight: 485; -} - -.c2 { - grid-column: 3; - grid-row: 1; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - width: 20px; -} - -
- - - - - - - -
-`; - -exports[`ChainSelectorRow should match snapshot for chainId 80001 1`] = ` -.c0 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - background: none; - border: none; - border-radius: 12px; - color: #222222; - cursor: pointer; - display: grid; - grid-template-columns: min-content 1fr min-content; - -webkit-box-pack: justify; - -webkit-justify-content: space-between; - -ms-flex-pack: justify; - justify-content: space-between; - line-height: 20px; - opacity: 1; - padding: 10px 8px; - text-align: left; - outline: none; - -webkit-transition: 250ms ease background-color; - transition: 250ms ease background-color; - width: 100%; -} - -.c0:hover { - background-color: #22222212; -} - -.c1 { - grid-column: 2; - grid-row: 1; - font-size: 16px; - font-weight: 485; -} - -.c2 { - grid-column: 3; - grid-row: 1; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - width: 20px; -} - -
- - - - - - - -
-`; - exports[`ChainSelectorRow should match snapshot for chainId 81457 1`] = ` .c0 { -webkit-align-items: center; @@ -1276,96 +986,6 @@ exports[`ChainSelectorRow should match snapshot for chainId 81457 1`] = `
`; -exports[`ChainSelectorRow should match snapshot for chainId 421613 1`] = ` -.c0 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - background: none; - border: none; - border-radius: 12px; - color: #222222; - cursor: pointer; - display: grid; - grid-template-columns: min-content 1fr min-content; - -webkit-box-pack: justify; - -webkit-justify-content: space-between; - -ms-flex-pack: justify; - justify-content: space-between; - line-height: 20px; - opacity: 1; - padding: 10px 8px; - text-align: left; - outline: none; - -webkit-transition: 250ms ease background-color; - transition: 250ms ease background-color; - width: 100%; -} - -.c0:hover { - background-color: #22222212; -} - -.c1 { - grid-column: 2; - grid-row: 1; - font-size: 16px; - font-weight: 485; -} - -.c2 { - grid-column: 3; - grid-row: 1; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - width: 20px; -} - -
- - - - - - - -
-`; - exports[`ChainSelectorRow should match snapshot for chainId 7777777 1`] = ` .c0 { -webkit-align-items: center; @@ -1397,76 +1017,6 @@ exports[`ChainSelectorRow should match snapshot for chainId 7777777 1`] = ` background-color: #22222212; } -.c1 { - grid-column: 3; - grid-row: 1; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - width: 20px; -} - -
- - - - - - - -
-`; - -exports[`ChainSelectorRow should match snapshot for chainId 11155111 1`] = ` -.c0 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - background: none; - border: none; - border-radius: 12px; - color: #222222; - cursor: pointer; - display: grid; - grid-template-columns: min-content 1fr min-content; - -webkit-box-pack: justify; - -webkit-justify-content: space-between; - -ms-flex-pack: justify; - justify-content: space-between; - line-height: 20px; - opacity: 1; - padding: 10px 8px; - text-align: left; - outline: none; - -webkit-transition: 250ms ease background-color; - transition: 250ms ease background-color; - width: 100%; -} - -.c0:hover { - background-color: #22222212; -} - .c1 { grid-column: 2; grid-row: 1; @@ -1501,20 +1051,20 @@ exports[`ChainSelectorRow should match snapshot for chainId 11155111 1`] = ` >
diff --git a/apps/web/src/components/Pools/PoolTable/PoolTable.tsx b/apps/web/src/components/Pools/PoolTable/PoolTable.tsx index dba449ba241..e71193e25f6 100644 --- a/apps/web/src/components/Pools/PoolTable/PoolTable.tsx +++ b/apps/web/src/components/Pools/PoolTable/PoolTable.tsx @@ -36,7 +36,7 @@ import { Chain, ProtocolVersion, Token } from 'uniswap/src/data/graphql/uniswap- import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { Trans } from 'uniswap/src/i18n' -import { InterfaceChainId, UniverseChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { NumberType, useFormatter } from 'utils/formatNumbers' const HEADER_DESCRIPTIONS: Record = { @@ -90,7 +90,7 @@ function PoolDescription({ token0?: Token | TokenStats token1?: Token | TokenStats feeTier?: number - chainId: InterfaceChainId + chainId: UniverseChainId protocolVersion?: ProtocolVersion | string }) { const isRestExploreEnabled = useFeatureFlag(FeatureFlags.RestExplore) diff --git a/apps/web/src/components/Popups/PopupContent.tsx b/apps/web/src/components/Popups/PopupContent.tsx index 9d5f11c2e21..67c26556180 100644 --- a/apps/web/src/components/Popups/PopupContent.tsx +++ b/apps/web/src/components/Popups/PopupContent.tsx @@ -11,7 +11,7 @@ import AlertTriangleFilled from 'components/Icons/AlertTriangleFilled' import { LoaderV3 } from 'components/Icons/LoadingSpinner' import Column, { AutoColumn } from 'components/deprecated/Column' import { AutoRow } from 'components/deprecated/Row' -import { SupportedInterfaceChainId, useIsSupportedChainId } from 'constants/chains' +import { useIsSupportedChainId } from 'constants/chains' import styled from 'lib/styled-components' import { X } from 'react-feather' import { useOrder } from 'state/signatures/hooks' @@ -21,7 +21,7 @@ import { Flex, useSporeColors } from 'ui/src' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { Trans } from 'uniswap/src/i18n' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking' import { useFormatter } from 'utils/formatNumbers' @@ -70,7 +70,7 @@ const PopupAlertTriangle = styled(AlertTriangleFilled)` height: 32px; ` -export function FailedNetworkSwitchPopup({ chainId, onClose }: { chainId: InterfaceChainId; onClose: () => void }) { +export function FailedNetworkSwitchPopup({ chainId, onClose }: { chainId: UniverseChainId; onClose: () => void }) { const isSupportedChain = useIsSupportedChainId(chainId) const chainInfo = isSupportedChain ? UNIVERSE_CHAIN_INFO[chainId] : undefined @@ -146,7 +146,7 @@ export function TransactionPopupContent({ hash, onClose, }: { - chainId: SupportedInterfaceChainId + chainId: UniverseChainId hash: string onClose: () => void }) { diff --git a/apps/web/src/components/SearchModal/CurrencySearch.tsx b/apps/web/src/components/SearchModal/CurrencySearch.tsx index fdfa411e380..a0a6ac7da80 100644 --- a/apps/web/src/components/SearchModal/CurrencySearch.tsx +++ b/apps/web/src/components/SearchModal/CurrencySearch.tsx @@ -3,12 +3,12 @@ import { Currency } from '@uniswap/sdk-core' import { useAccount } from 'hooks/useAccount' import useSelectChain from 'hooks/useSelectChain' import { useShowSwapNetworkNotification } from 'hooks/useShowSwapNetworkNotification' -import { useSupportedChainIds } from 'hooks/useSupportedChainIds' import { useCallback, useEffect } from 'react' import { useSwapAndLimitContext } from 'state/swap/useSwapContext' import { Flex } from 'ui/src' import { TokenSelectorContent, TokenSelectorVariation } from 'uniswap/src/components/TokenSelector/TokenSelector' import { TokenSelectorFlow } from 'uniswap/src/components/TokenSelector/types' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import Trace from 'uniswap/src/features/telemetry/Trace' import { CurrencyField } from 'uniswap/src/types/currency' import { SwapTab } from 'uniswap/src/types/screens/interface' @@ -29,7 +29,7 @@ export function CurrencySearch({ currencyField, onCurrencySelect, onDismiss }: C const showSwapNetworkNotification = useShowSwapNetworkNotification() const selectChain = useSelectChain() - const { supported: supportedChains } = useSupportedChainIds() + const { chains } = useEnabledChains() const handleCurrencySelectTokenSelectorCallback = useCallback( async (currency: Currency) => { @@ -67,7 +67,7 @@ export function CurrencySearch({ currencyField, onCurrencySelect, onDismiss }: C activeAccountAddress={account.address!} isLimits={currentTab === SwapTab.Limit} chainId={!multichainUXEnabled || isUserSelectedToken ? chainId : undefined} - chainIds={supportedChains} + chainIds={chains} currencyField={currencyField} flow={TokenSelectorFlow.Swap} isSurfaceReady={true} diff --git a/apps/web/src/components/Tokens/TokenDetails/BalanceSummary.tsx b/apps/web/src/components/Tokens/TokenDetails/BalanceSummary.tsx index d5e59aca191..64e2242aa28 100644 --- a/apps/web/src/components/Tokens/TokenDetails/BalanceSummary.tsx +++ b/apps/web/src/components/Tokens/TokenDetails/BalanceSummary.tsx @@ -10,7 +10,7 @@ import { useNavigate } from 'react-router-dom' import { ThemedText } from 'theme/components' import { Chain } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { Trans } from 'uniswap/src/i18n' -import { InterfaceChainId, UniverseChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { NumberType, useFormatter } from 'utils/formatNumbers' const BalancesCard = styled.div` @@ -60,7 +60,7 @@ const BalanceAmountsContainer = styled.div<{ $alignLeft: boolean }>` interface BalanceProps { currency?: Currency - chainId?: InterfaceChainId + chainId?: UniverseChainId gqlBalance?: PortfolioBalance alignLeft?: boolean onClick?: () => void diff --git a/apps/web/src/components/Tokens/TokenDetails/InvalidTokenDetails.tsx b/apps/web/src/components/Tokens/TokenDetails/InvalidTokenDetails.tsx index bc06d522348..57b05bf3ec3 100644 --- a/apps/web/src/components/Tokens/TokenDetails/InvalidTokenDetails.tsx +++ b/apps/web/src/components/Tokens/TokenDetails/InvalidTokenDetails.tsx @@ -8,7 +8,7 @@ import { useNavigate } from 'react-router-dom' import { ThemedText } from 'theme/components' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { Trans } from 'uniswap/src/i18n' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' const InvalidDetailsContainer = styled.div` padding-top: 128px; @@ -43,7 +43,7 @@ export default function InvalidTokenDetails({ pageChainId, isInvalidAddress, }: { - pageChainId: InterfaceChainId + pageChainId: UniverseChainId isInvalidAddress?: boolean }) { const { chainId } = useAccount() diff --git a/apps/web/src/components/Tokens/TokenDetails/StatsSection.tsx b/apps/web/src/components/Tokens/TokenDetails/StatsSection.tsx index 7b094601109..2b750967050 100644 --- a/apps/web/src/components/Tokens/TokenDetails/StatsSection.tsx +++ b/apps/web/src/components/Tokens/TokenDetails/StatsSection.tsx @@ -10,7 +10,7 @@ import { ExternalLink, ThemedText } from 'theme/components' import { textFadeIn } from 'theme/styles' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { Trans } from 'uniswap/src/i18n' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { NumberType, useFormatter } from 'utils/formatNumbers' export const StatWrapper = styled.div` @@ -85,7 +85,7 @@ function Stat({ } type StatsSectionProps = { - chainId: InterfaceChainId + chainId: UniverseChainId address: string tokenQueryData: TokenQueryData } diff --git a/apps/web/src/components/Tokens/TokenDetails/tables/TokenDetailsPoolsTable.tsx b/apps/web/src/components/Tokens/TokenDetails/tables/TokenDetailsPoolsTable.tsx index 502ae37d6e8..de2790a1fce 100644 --- a/apps/web/src/components/Tokens/TokenDetails/tables/TokenDetailsPoolsTable.tsx +++ b/apps/web/src/components/Tokens/TokenDetails/tables/TokenDetailsPoolsTable.tsx @@ -1,13 +1,13 @@ import { ApolloError } from '@apollo/client' import { Token } from '@uniswap/sdk-core' import { PoolsTable, sortAscendingAtom, sortMethodAtom } from 'components/Pools/PoolTable/PoolTable' -import { SupportedInterfaceChainId } from 'constants/chains' import { useUpdateManualOutage } from 'featureFlags/flags/outageBanner' import { usePoolsFromTokenAddress } from 'graphql/data/pools/usePoolsFromTokenAddress' import { PoolSortFields } from 'graphql/data/pools/useTopPools' import { OrderDirection } from 'graphql/data/util' import { useAtomValue, useResetAtom } from 'jotai/utils' import { useEffect, useMemo } from 'react' +import { UniverseChainId } from 'uniswap/src/types/chains' const HIDDEN_COLUMNS = [PoolSortFields.VolOverTvl] @@ -15,7 +15,7 @@ export function TokenDetailsPoolsTable({ chainId, referenceToken, }: { - chainId: SupportedInterfaceChainId + chainId: UniverseChainId referenceToken: Token }) { const sortMethod = useAtomValue(sortMethodAtom) diff --git a/apps/web/src/components/Tokens/TokenDetails/tables/TransactionsTable.tsx b/apps/web/src/components/Tokens/TokenDetails/tables/TransactionsTable.tsx index 8526abb47ee..47577dfd613 100644 --- a/apps/web/src/components/Tokens/TokenDetails/tables/TransactionsTable.tsx +++ b/apps/web/src/components/Tokens/TokenDetails/tables/TransactionsTable.tsx @@ -12,7 +12,6 @@ import { TimestampCell, TokenLinkCell, } from 'components/Table/styled' -import { SupportedInterfaceChainId } from 'constants/chains' import { useUpdateManualOutage } from 'featureFlags/flags/outageBanner' import { TokenTransactionType, useTokenTransactions } from 'graphql/data/useTokenTransactions' import { OrderDirection, unwrapToken } from 'graphql/data/util' @@ -22,6 +21,7 @@ import { EllipsisTamaguiStyle } from 'theme/components' import { Flex, Text, styled } from 'ui/src' import { Token as GQLToken } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { Trans } from 'uniswap/src/i18n' +import { UniverseChainId } from 'uniswap/src/types/chains' import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking' import { shortenAddress } from 'utilities/src/addresses' import { useFormatter } from 'utils/formatNumbers' @@ -55,13 +55,7 @@ interface SwapLeg { token: GQLToken } -export function TransactionsTable({ - chainId, - referenceToken, -}: { - chainId: SupportedInterfaceChainId - referenceToken: Token -}) { +export function TransactionsTable({ chainId, referenceToken }: { chainId: UniverseChainId; referenceToken: Token }) { const activeLocalCurrency = useActiveLocalCurrency() const { formatNumber, formatFiatPrice } = useFormatter() const [filterModalIsOpen, toggleFilterModal] = useReducer((s) => !s, false) diff --git a/apps/web/src/components/TopLevelModals/ExtensionLaunchModal.tsx b/apps/web/src/components/TopLevelModals/ExtensionLaunchModal.tsx index 38df6b631f2..b07e981c369 100644 --- a/apps/web/src/components/TopLevelModals/ExtensionLaunchModal.tsx +++ b/apps/web/src/components/TopLevelModals/ExtensionLaunchModal.tsx @@ -1,8 +1,13 @@ import { InterfaceElementName, InterfaceModalName } from '@uniswap/analytics-events' import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button/buttons' import Modal from 'components/Modal' +import { + LAUNCH_MODAL_DESKTOP_MAX_HEIGHT, + LAUNCH_MODAL_DESKTOP_MAX_WIDTH, + LAUNCH_MODAL_MOBILE_MAX_HEIGHT, + LAUNCH_MODAL_MOBILE_MAX_IMAGE_HEIGHT, +} from 'components/TopLevelModals/constants' import { useConnectorWithId } from 'components/WalletModal/useOrderedConnections' -import { CONNECTION } from 'components/Web3Provider/constants' import Column from 'components/deprecated/Column' import Row from 'components/deprecated/Row' import { useIsMobile } from 'hooks/screenSize/useIsMobile' @@ -17,6 +22,7 @@ import { Image } from 'ui/src' import { UNISWAP_LOGO } from 'ui/src/assets' import { iconSizes } from 'ui/src/theme' import { uniswapUrls } from 'uniswap/src/constants/urls' +import { CONNECTION_PROVIDER_IDS } from 'uniswap/src/constants/web3' import Trace from 'uniswap/src/features/telemetry/Trace' import { Trans } from 'uniswap/src/i18n' @@ -48,7 +54,7 @@ const PromoImage = styled.img` background: url('/images/extension_promo/announcement_modal_mobile.png'); background-size: cover; background-position: 50%; - height: 392px; + height: ${LAUNCH_MODAL_MOBILE_MAX_IMAGE_HEIGHT}px; width: 100%; flex: unset; } @@ -105,14 +111,14 @@ const showExtensionLaunchAtom = atomWithStorage('showUniswapExtensionLaunchAtom' export function ExtensionLaunchModal() { const [showExtensionLaunch, setShowExtensionLaunch] = useAtom(showExtensionLaunchAtom) const isOnLandingPage = useIsLandingPage() - const uniswapExtensionConnector = useConnectorWithId(CONNECTION.UNISWAP_EXTENSION_RDNS) + const uniswapExtensionConnector = useConnectorWithId(CONNECTION_PROVIDER_IDS.UNISWAP_EXTENSION_RDNS) const isMobile = useIsMobile() return ( setShowExtensionLaunch(false)} diff --git a/apps/web/src/components/TopLevelModals/LaunchModal.tsx b/apps/web/src/components/TopLevelModals/LaunchModal.tsx new file mode 100644 index 00000000000..177a6e9f067 --- /dev/null +++ b/apps/web/src/components/TopLevelModals/LaunchModal.tsx @@ -0,0 +1,99 @@ +import { InterfaceElementName } from '@uniswap/analytics-events' +import Modal from 'components/Modal' +import { + LAUNCH_MODAL_DESKTOP_MAX_HEIGHT, + LAUNCH_MODAL_DESKTOP_MAX_WIDTH, + LAUNCH_MODAL_MOBILE_MAX_HEIGHT, + LAUNCH_MODAL_MOBILE_MAX_IMAGE_HEIGHT, +} from 'components/TopLevelModals/constants' +import { useIsLandingPage } from 'hooks/useIsLandingPage' +import { useAtom } from 'jotai' +import { atomWithStorage } from 'jotai/utils' +import { useMemo } from 'react' +import { Button, Flex, Image, ImageProps, Text, TouchableArea, useMedia } from 'ui/src' +import { X } from 'ui/src/components/icons/X' +import { iconSizes } from 'ui/src/theme' +import Trace from 'uniswap/src/features/telemetry/Trace' +import { ModalNameType } from 'uniswap/src/features/telemetry/constants' +import { useTranslation } from 'uniswap/src/i18n' +import { openUri } from 'uniswap/src/utils/linking' + +type Props = { + interfaceModalName: ModalNameType + learnMoreUrl: string + desktopImage: string + mobileImage: string + logo: ImageProps['source'] + title: string + description: string +} + +export function LaunchModal({ + interfaceModalName, + learnMoreUrl, + desktopImage, + mobileImage, + logo, + title, + description, +}: Props) { + const showModalAtom = useMemo(() => atomWithStorage(`showModal.${interfaceModalName}`, true), [interfaceModalName]) + const [showModal, setShowModal] = useAtom(showModalAtom) + const isOnLandingPage = useIsLandingPage() + const media = useMedia() + const { t } = useTranslation() + + return ( + + setShowModal(false)} + hideBorder + > + + + + {!media.md && ( + + + + setShowModal(false)}> + + + + + )} + + {title} + + {description} + + + + + + + + + + + + + + + ) +} diff --git a/apps/web/src/components/TopLevelModals/UnichainLaunchModal.tsx b/apps/web/src/components/TopLevelModals/UnichainLaunchModal.tsx new file mode 100644 index 00000000000..d821b1c1a2a --- /dev/null +++ b/apps/web/src/components/TopLevelModals/UnichainLaunchModal.tsx @@ -0,0 +1,26 @@ +import { InterfaceModalName } from '@uniswap/analytics-events' +import { LaunchModal } from 'components/TopLevelModals/LaunchModal' +import { UNICHAIN_LOGO } from 'ui/src/assets' +import { AstroChainConfigKey, DynamicConfigs } from 'uniswap/src/features/gating/configs' +import { useDynamicConfigValue } from 'uniswap/src/features/gating/hooks' +import { useTranslation } from 'uniswap/src/i18n' + +const DESKTOP_IMAGE = '/images/unichain/unichain_modal_desktop.png' +const MOBILE_IMAGE = '/images/unichain/unichain_modal_mobile.png' + +export function UnichainLaunchModal() { + const { t } = useTranslation() + const url = useDynamicConfigValue(DynamicConfigs.AstroChain, AstroChainConfigKey.Url, '' as string) + + return ( + + ) +} diff --git a/apps/web/src/components/TopLevelModals/constants.ts b/apps/web/src/components/TopLevelModals/constants.ts new file mode 100644 index 00000000000..799ce2cd7da --- /dev/null +++ b/apps/web/src/components/TopLevelModals/constants.ts @@ -0,0 +1,4 @@ +export const LAUNCH_MODAL_DESKTOP_MAX_WIDTH = 520 +export const LAUNCH_MODAL_DESKTOP_MAX_HEIGHT = 320 +export const LAUNCH_MODAL_MOBILE_MAX_HEIGHT = 564 +export const LAUNCH_MODAL_MOBILE_MAX_IMAGE_HEIGHT = 392 diff --git a/apps/web/src/components/TopLevelModals/index.tsx b/apps/web/src/components/TopLevelModals/index.tsx index 1250f069ca0..9d7c6ccc1d0 100644 --- a/apps/web/src/components/TopLevelModals/index.tsx +++ b/apps/web/src/components/TopLevelModals/index.tsx @@ -10,6 +10,7 @@ import { PrivacyPolicyModal } from 'components/PrivacyPolicy' import { ReceiveCryptoModal } from 'components/ReceiveCryptoModal' import { ExtensionLaunchModal } from 'components/TopLevelModals/ExtensionLaunchModal' import { UkDisclaimerModal } from 'components/TopLevelModals/UkDisclaimerModal' +import { UnichainLaunchModal } from 'components/TopLevelModals/UnichainLaunchModal' import AddressClaimModal from 'components/claim/AddressClaimModal' import DevFlagsBox from 'dev/DevFlagsBox' import { useAccount } from 'hooks/useAccount' @@ -18,9 +19,12 @@ import Bag from 'nft/components/bag/Bag' import TransactionCompleteModal from 'nft/components/collection/TransactionCompleteModal' import { IncreaseLiquidityModal } from 'pages/IncreaseLiquidity/IncreaseLiquidityModal' import { RemoveLiquidityModal } from 'pages/RemoveLiquidity/RemoveLiquidityModal' -import { useModalIsOpen, useToggleModal } from 'state/application/hooks' +import { useCloseModal, useModalIsOpen, useToggleModal } from 'state/application/hooks' import { ApplicationModal } from 'state/application/reducer' +import { FeatureFlags } from 'uniswap/src/features/gating/flags' +import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { ModalName } from 'uniswap/src/features/telemetry/constants' +import { TestnetModeModal } from 'uniswap/src/features/testnets/TestnetModeModal' import { isBetaEnv, isDevEnv } from 'utilities/src/environment/env' export default function TopLevelModals() { @@ -29,11 +33,14 @@ export default function TopLevelModals() { const blockedAccountModalOpen = useModalIsOpen(ApplicationModal.BLOCKED_ACCOUNT) const isAddLiquidityModalOpen = useModalIsOpen(ModalName.AddLiquidity) const isRemoveLiquidityModalOpen = useModalIsOpen(ModalName.RemoveLiquidity) + const isTestnetModeModalOpen = useModalIsOpen(ModalName.TestnetMode) + const closeTestnetModeModal = useCloseModal(ModalName.TestnetMode) const account = useAccount() useAccountRiskCheck(account.address) const accountBlocked = Boolean(blockedAccountModalOpen && account.isConnected) const shouldShowDevFlags = isDevEnv() || isBetaEnv() + const showUnichainLaunchModal = useFeatureFlag(FeatureFlags.AstroChainLaunchModal) return ( <> @@ -50,11 +57,13 @@ export default function TopLevelModals() { {account.address && } {account.address && } + {shouldShowDevFlags && } + {showUnichainLaunchModal && } {isAddLiquidityModalOpen && } {isRemoveLiquidityModalOpen && } diff --git a/apps/web/src/components/WalletModal/UniswapWalletOptions.test.tsx b/apps/web/src/components/WalletModal/UniswapWalletOptions.test.tsx index b320a086b13..1c5fa471824 100644 --- a/apps/web/src/components/WalletModal/UniswapWalletOptions.test.tsx +++ b/apps/web/src/components/WalletModal/UniswapWalletOptions.test.tsx @@ -1,9 +1,9 @@ import { UniswapWalletOptions } from 'components/WalletModal/UniswapWalletOptions' import { useConnectorWithId } from 'components/WalletModal/useOrderedConnections' -import { CONNECTION } from 'components/Web3Provider/constants' import { mocked } from 'test-utils/mocked' import { render, screen } from 'test-utils/render' import { UNISWAP_EXTENSION_CONNECTOR, WALLET_CONNECT_CONNECTOR } from 'test-utils/wagmi/fixtures' +import { CONNECTION_PROVIDER_IDS } from 'uniswap/src/constants/web3' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' jest.mock('components/WalletModal/useOrderedConnections', () => ({ @@ -17,12 +17,12 @@ jest.mock('uniswap/src/features/gating/hooks', () => ({ describe('UniswapWalletOptions Test', () => { beforeEach(() => { mocked(useConnectorWithId).mockImplementation((id) => - id === CONNECTION.WALLET_CONNECT_CONNECTOR_ID ? WALLET_CONNECT_CONNECTOR : undefined, + id === CONNECTION_PROVIDER_IDS.WALLET_CONNECT_CONNECTOR_ID ? WALLET_CONNECT_CONNECTOR : undefined, ) }) it('Download wallet option should be visible if extension is not detected', () => { mocked(useConnectorWithId).mockImplementation((id) => - id === CONNECTION.UNISWAP_EXTENSION_RDNS ? undefined : WALLET_CONNECT_CONNECTOR, + id === CONNECTION_PROVIDER_IDS.UNISWAP_EXTENSION_RDNS ? undefined : WALLET_CONNECT_CONNECTOR, ) mocked(useFeatureFlag).mockReturnValue(true) const { asFragment } = render() @@ -32,7 +32,7 @@ describe('UniswapWalletOptions Test', () => { }) it('Extension connecter should be shown if detected', () => { mocked(useConnectorWithId).mockImplementation((id) => - id === CONNECTION.UNISWAP_EXTENSION_RDNS ? UNISWAP_EXTENSION_CONNECTOR : WALLET_CONNECT_CONNECTOR, + id === CONNECTION_PROVIDER_IDS.UNISWAP_EXTENSION_RDNS ? UNISWAP_EXTENSION_CONNECTOR : WALLET_CONNECT_CONNECTOR, ) mocked(useFeatureFlag).mockReturnValue(false) const { asFragment } = render() diff --git a/apps/web/src/components/WalletModal/UniswapWalletOptions.tsx b/apps/web/src/components/WalletModal/UniswapWalletOptions.tsx index d89036de75b..93030d6be97 100644 --- a/apps/web/src/components/WalletModal/UniswapWalletOptions.tsx +++ b/apps/web/src/components/WalletModal/UniswapWalletOptions.tsx @@ -3,7 +3,6 @@ import { GooglePlayStoreLogo } from 'components/Icons/GooglePlayStoreLogo' import { DownloadWalletOption } from 'components/WalletModal/DownloadWalletOption' import { DetectedBadge } from 'components/WalletModal/shared' import { useConnectorWithId } from 'components/WalletModal/useOrderedConnections' -import { CONNECTION } from 'components/Web3Provider/constants' import Column from 'components/deprecated/Column' import Row from 'components/deprecated/Row' import { useConnect } from 'hooks/useConnect' @@ -15,6 +14,7 @@ import { AppStoreLogo } from 'ui/src/components/icons/AppStoreLogo' import { PhoneDownload } from 'ui/src/components/icons/PhoneDownload' import { ScanQr } from 'ui/src/components/icons/ScanQr' import { iconSizes } from 'ui/src/theme' +import { CONNECTION_PROVIDER_IDS } from 'uniswap/src/constants/web3' import { Trans } from 'uniswap/src/i18n' import { isMobileWeb, isWebIOS } from 'utilities/src/platform' import { openDownloadApp } from 'utils/openDownloadApp' @@ -44,10 +44,13 @@ export const AppIcon = styled.img` ` export function UniswapWalletOptions() { - const uniswapExtensionConnector = useConnectorWithId(CONNECTION.UNISWAP_EXTENSION_RDNS) - const uniswapWalletConnectConnector = useConnectorWithId(CONNECTION.UNISWAP_WALLET_CONNECT_CONNECTOR_ID, { - shouldThrow: true, - }) + const uniswapExtensionConnector = useConnectorWithId(CONNECTION_PROVIDER_IDS.UNISWAP_EXTENSION_RDNS) + const uniswapWalletConnectConnector = useConnectorWithId( + CONNECTION_PROVIDER_IDS.UNISWAP_WALLET_CONNECT_CONNECTOR_ID, + { + shouldThrow: true, + }, + ) const { connect } = useConnect() diff --git a/apps/web/src/components/WalletModal/useOrderedConnections.test.tsx b/apps/web/src/components/WalletModal/useOrderedConnections.test.tsx index 72c610ef648..0013af1d794 100644 --- a/apps/web/src/components/WalletModal/useOrderedConnections.test.tsx +++ b/apps/web/src/components/WalletModal/useOrderedConnections.test.tsx @@ -1,5 +1,5 @@ import { useOrderedConnections } from 'components/WalletModal/useOrderedConnections' -import { CONNECTION, useRecentConnectorId } from 'components/Web3Provider/constants' +import { useRecentConnectorId } from 'components/Web3Provider/constants' import { mocked } from 'test-utils/mocked' import { renderHook } from 'test-utils/render' import { @@ -11,6 +11,7 @@ import { UNISWAP_MOBILE_CONNECTOR, WALLET_CONNECT_CONNECTOR, } from 'test-utils/wagmi/fixtures' +import { CONNECTION_PROVIDER_IDS } from 'uniswap/src/constants/web3' // eslint-disable-next-line @typescript-eslint/no-restricted-imports import { useConnect } from 'wagmi' @@ -49,10 +50,10 @@ describe('useOrderedConnections', () => { const { result } = renderHook(() => useOrderedConnections()) const expectedConnectors = [ - { id: CONNECTION.UNISWAP_WALLET_CONNECT_CONNECTOR_ID }, - { id: CONNECTION.METAMASK_RDNS }, - { id: CONNECTION.WALLET_CONNECT_CONNECTOR_ID }, - { id: CONNECTION.COINBASE_SDK_CONNECTOR_ID }, + { id: CONNECTION_PROVIDER_IDS.UNISWAP_WALLET_CONNECT_CONNECTOR_ID }, + { id: CONNECTION_PROVIDER_IDS.METAMASK_RDNS }, + { id: CONNECTION_PROVIDER_IDS.WALLET_CONNECT_CONNECTOR_ID }, + { id: CONNECTION_PROVIDER_IDS.COINBASE_SDK_CONNECTOR_ID }, ] result.current.forEach((connector, index) => { @@ -71,14 +72,14 @@ describe('useOrderedConnections', () => { }) it('should place the most recent connector at the top of the list', () => { - mocked(useRecentConnectorId).mockReturnValue(CONNECTION.WALLET_CONNECT_CONNECTOR_ID) + mocked(useRecentConnectorId).mockReturnValue(CONNECTION_PROVIDER_IDS.WALLET_CONNECT_CONNECTOR_ID) const { result } = renderHook(() => useOrderedConnections()) const expectedConnectors = [ - { id: CONNECTION.WALLET_CONNECT_CONNECTOR_ID }, - { id: CONNECTION.UNISWAP_WALLET_CONNECT_CONNECTOR_ID }, - { id: CONNECTION.METAMASK_RDNS }, - { id: CONNECTION.COINBASE_SDK_CONNECTOR_ID }, + { id: CONNECTION_PROVIDER_IDS.WALLET_CONNECT_CONNECTOR_ID }, + { id: CONNECTION_PROVIDER_IDS.UNISWAP_WALLET_CONNECT_CONNECTOR_ID }, + { id: CONNECTION_PROVIDER_IDS.METAMASK_RDNS }, + { id: CONNECTION_PROVIDER_IDS.COINBASE_SDK_CONNECTOR_ID }, ] result.current.forEach((connector, index) => { @@ -91,7 +92,7 @@ describe('useOrderedConnections', () => { UserAgentMock.isMobileWeb = true const { result } = renderHook(() => useOrderedConnections()) expect(result.current.length).toEqual(1) - expect(result.current[0].id).toEqual(CONNECTION.METAMASK_RDNS) + expect(result.current[0].id).toEqual(CONNECTION_PROVIDER_IDS.METAMASK_RDNS) }) it('should return only the Coinbase injected connector in the Coinbase Wallet', async () => { @@ -107,7 +108,7 @@ describe('useOrderedConnections', () => { } as unknown as ReturnType) const { result } = renderHook(() => useOrderedConnections()) expect(result.current.length).toEqual(1) - expect(result.current[0].id).toEqual(CONNECTION.COINBASE_SDK_CONNECTOR_ID) + expect(result.current[0].id).toEqual(CONNECTION_PROVIDER_IDS.COINBASE_SDK_CONNECTOR_ID) }) it('should not return uniswap connections when excludeUniswapConnections is true', () => { @@ -117,9 +118,9 @@ describe('useOrderedConnections', () => { const { result } = renderHook(() => useOrderedConnections(true)) const expectedConnectors = [ - { id: CONNECTION.METAMASK_RDNS }, - { id: CONNECTION.WALLET_CONNECT_CONNECTOR_ID }, - { id: CONNECTION.COINBASE_SDK_CONNECTOR_ID }, + { id: CONNECTION_PROVIDER_IDS.METAMASK_RDNS }, + { id: CONNECTION_PROVIDER_IDS.WALLET_CONNECT_CONNECTOR_ID }, + { id: CONNECTION_PROVIDER_IDS.COINBASE_SDK_CONNECTOR_ID }, ] result.current.forEach((connector, index) => { @@ -135,10 +136,10 @@ describe('useOrderedConnections', () => { } as unknown as ReturnType) const { result } = renderHook(() => useOrderedConnections()) const expectedConnectors = [ - { id: CONNECTION.UNISWAP_WALLET_CONNECT_CONNECTOR_ID }, - { id: CONNECTION.INJECTED_CONNECTOR_ID }, - { id: CONNECTION.WALLET_CONNECT_CONNECTOR_ID }, - { id: CONNECTION.COINBASE_SDK_CONNECTOR_ID }, + { id: CONNECTION_PROVIDER_IDS.UNISWAP_WALLET_CONNECT_CONNECTOR_ID }, + { id: CONNECTION_PROVIDER_IDS.INJECTED_CONNECTOR_ID }, + { id: CONNECTION_PROVIDER_IDS.WALLET_CONNECT_CONNECTOR_ID }, + { id: CONNECTION_PROVIDER_IDS.COINBASE_SDK_CONNECTOR_ID }, ] result.current.forEach((connector, index) => { expect(connector.id).toEqual(expectedConnectors[index].id) @@ -153,7 +154,7 @@ describe('useOrderedConnections', () => { connectors: [UNISWAP_MOBILE_CONNECTOR, INJECTED_CONNECTOR, WALLET_CONNECT_CONNECTOR, COINBASE_SDK_CONNECTOR], } as unknown as ReturnType) const { result } = renderHook(() => useOrderedConnections()) - const expectedConnectors = [{ id: CONNECTION.INJECTED_CONNECTOR_ID }] + const expectedConnectors = [{ id: CONNECTION_PROVIDER_IDS.INJECTED_CONNECTOR_ID }] result.current.forEach((connector, index) => { expect(connector.id).toEqual(expectedConnectors[index].id) }) diff --git a/apps/web/src/components/WalletModal/useOrderedConnections.tsx b/apps/web/src/components/WalletModal/useOrderedConnections.tsx index 773af15ddc8..05ad7358030 100644 --- a/apps/web/src/components/WalletModal/useOrderedConnections.tsx +++ b/apps/web/src/components/WalletModal/useOrderedConnections.tsx @@ -1,10 +1,11 @@ -import { CONNECTION, useRecentConnectorId } from 'components/Web3Provider/constants' +import { useRecentConnectorId } from 'components/Web3Provider/constants' import { useConnect } from 'hooks/useConnect' import { useCallback, useMemo } from 'react' +import { CONNECTION_PROVIDER_IDS } from 'uniswap/src/constants/web3' import { isMobileWeb, isTouchable, isWebAndroid, isWebIOS } from 'utilities/src/platform' import { Connector } from 'wagmi' -type ConnectorID = (typeof CONNECTION)[keyof typeof CONNECTION] +type ConnectorID = (typeof CONNECTION_PROVIDER_IDS)[keyof typeof CONNECTION_PROVIDER_IDS] const SHOULD_THROW = { shouldThrow: true } as const @@ -41,7 +42,7 @@ function getInjectedConnectors(connectors: readonly Connector[], excludeUniswapC let isCoinbaseWalletBrowser = false const injectedConnectors = connectors.filter((c) => { // Special-case: Ignore coinbase eip6963-injected connector; coinbase connection is handled via the SDK connector. - if (c.id === CONNECTION.COINBASE_RDNS) { + if (c.id === CONNECTION_PROVIDER_IDS.COINBASE_RDNS) { if (isMobileWeb) { isCoinbaseWalletBrowser = true } @@ -49,15 +50,20 @@ function getInjectedConnectors(connectors: readonly Connector[], excludeUniswapC } // Special-case: Ignore the Uniswap Extension injection here if it's being displayed separately. - if (c.id === CONNECTION.UNISWAP_EXTENSION_RDNS && excludeUniswapConnections) { + if (c.id === CONNECTION_PROVIDER_IDS.UNISWAP_EXTENSION_RDNS && excludeUniswapConnections) { return false } - return c.type === CONNECTION.INJECTED_CONNECTOR_TYPE && c.id !== CONNECTION.INJECTED_CONNECTOR_ID + return ( + c.type === CONNECTION_PROVIDER_IDS.INJECTED_CONNECTOR_TYPE && + c.id !== CONNECTION_PROVIDER_IDS.INJECTED_CONNECTOR_ID + ) }) // Special-case: Return deprecated window.ethereum connector when no eip6963 injectors are present. - const fallbackInjector = getConnectorWithId(connectors, CONNECTION.INJECTED_CONNECTOR_ID, { shouldThrow: true }) + const fallbackInjector = getConnectorWithId(connectors, CONNECTION_PROVIDER_IDS.INJECTED_CONNECTOR_ID, { + shouldThrow: true, + }) if (!injectedConnectors.length && Boolean(window.ethereum)) { return { injectedConnectors: [fallbackInjector], isCoinbaseWalletBrowser } } @@ -90,11 +96,19 @@ export function useOrderedConnections(excludeUniswapConnections?: boolean): Inje ) const injectedConnectors = injectedConnectorsBase.map((c) => ({ ...c, isInjected: true })) - const coinbaseSdkConnector = getConnectorWithId(connectors, CONNECTION.COINBASE_SDK_CONNECTOR_ID, SHOULD_THROW) - const walletConnectConnector = getConnectorWithId(connectors, CONNECTION.WALLET_CONNECT_CONNECTOR_ID, SHOULD_THROW) + const coinbaseSdkConnector = getConnectorWithId( + connectors, + CONNECTION_PROVIDER_IDS.COINBASE_SDK_CONNECTOR_ID, + SHOULD_THROW, + ) + const walletConnectConnector = getConnectorWithId( + connectors, + CONNECTION_PROVIDER_IDS.WALLET_CONNECT_CONNECTOR_ID, + SHOULD_THROW, + ) const uniswapWalletConnectConnector = getConnectorWithId( connectors, - CONNECTION.UNISWAP_WALLET_CONNECT_CONNECTOR_ID, + CONNECTION_PROVIDER_IDS.UNISWAP_WALLET_CONNECT_CONNECTOR_ID, SHOULD_THROW, ) if (!coinbaseSdkConnector || !walletConnectConnector || !uniswapWalletConnectConnector) { @@ -141,7 +155,7 @@ const ExtensionRequestArguments = { } as const export function useUniswapExtensionConnector() { - const connector = useConnectorWithId(CONNECTION.UNISWAP_EXTENSION_RDNS) + const connector = useConnectorWithId(CONNECTION_PROVIDER_IDS.UNISWAP_EXTENSION_RDNS) const extensionRequest = useCallback( async < Type extends keyof typeof ExtensionRequestArguments, diff --git a/apps/web/src/components/Web3Provider/WebUniswapContext.tsx b/apps/web/src/components/Web3Provider/WebUniswapContext.tsx index 6f9d22d9162..a0b1cdab3a1 100644 --- a/apps/web/src/components/Web3Provider/WebUniswapContext.tsx +++ b/apps/web/src/components/Web3Provider/WebUniswapContext.tsx @@ -32,6 +32,7 @@ function useWagmiAccount(): AccountMeta | undefined { export function WebUniswapProvider({ children }: PropsWithChildren) { const account = useWagmiAccount() const signer = useEthersSigner() + const { connector } = useAccount() const showSwapNetworkNotification = useShowSwapNetworkNotification() const navigate = useNavigate() const navigateToFiatOnRamp = useCallback(() => navigate(`/buy`, { replace: true }), [navigate]) @@ -41,6 +42,7 @@ export function WebUniswapProvider({ children }: PropsWithChildren) { UNIVERSE_CHAIN_INFO[chainId]), + ...COMBINED_CHAIN_IDS.map((chainId) => UNIVERSE_CHAIN_INFO[chainId]), ], connectors: [ injectedWithFallback(), diff --git a/apps/web/src/components/Web3Provider/walletConnect.ts b/apps/web/src/components/Web3Provider/walletConnect.ts index 450767bd5bf..68bf6f92a02 100644 --- a/apps/web/src/components/Web3Provider/walletConnect.ts +++ b/apps/web/src/components/Web3Provider/walletConnect.ts @@ -1,8 +1,6 @@ -import { CONNECTION } from 'components/Web3Provider/constants' import { Z_INDEX } from 'theme/zIndex' -import { InterfaceChainId } from 'uniswap/src/types/chains' import { isWebAndroid, isWebIOS } from 'utilities/src/platform' -import { Connector, createConnector } from 'wagmi' +import { createConnector } from 'wagmi' import { walletConnect } from 'wagmi/connectors' if (process.env.REACT_APP_WALLET_CONNECT_PROJECT_ID === undefined) { @@ -30,12 +28,6 @@ export const walletTypeToAmplitudeWalletType = (connectionType?: string) => { } } -export interface WalletConnectConnector extends Connector { - type: typeof CONNECTION.UNISWAP_WALLET_CONNECT_CONNECTOR_ID - getNamespaceChainsIds: () => InterfaceChainId[] - getProvider(): Promise<{ modal: { setTheme: ({ themeMode }: { themeMode: 'dark' | 'light' }) => void } }> -} - export const WC_PARAMS = { projectId: WALLET_CONNECT_PROJECT_ID, metadata: { diff --git a/apps/web/src/components/swap/SwapHeader.test.tsx b/apps/web/src/components/swap/SwapHeader.test.tsx index 2a7d8eb2bc1..19ce3d9769e 100644 --- a/apps/web/src/components/swap/SwapHeader.test.tsx +++ b/apps/web/src/components/swap/SwapHeader.test.tsx @@ -2,14 +2,14 @@ import SwapHeader from 'components/swap/SwapHeader' import { Dispatch, PropsWithChildren, SetStateAction } from 'react' import { CurrencyState, EMPTY_DERIVED_SWAP_INFO, SwapAndLimitContext, SwapContext } from 'state/swap/types' import { act, render, screen } from 'test-utils/render' -import { InterfaceChainId, UniverseChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { SwapTab } from 'uniswap/src/types/screens/interface' interface WrapperProps { setCurrentTab?: Dispatch> setCurrencyState?: Dispatch> - chainId?: InterfaceChainId + chainId?: UniverseChainId } function Wrapper(props: PropsWithChildren) { diff --git a/apps/web/src/components/swap/UnsupportedCurrencyFooter.tsx b/apps/web/src/components/swap/UnsupportedCurrencyFooter.tsx index 9745b9be43d..f28f8d8b0a9 100644 --- a/apps/web/src/components/swap/UnsupportedCurrencyFooter.tsx +++ b/apps/web/src/components/swap/UnsupportedCurrencyFooter.tsx @@ -14,7 +14,7 @@ import { Z_INDEX } from 'theme/zIndex' import { Text } from 'ui/src' import { SafetyLevel } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { Trans } from 'uniswap/src/i18n' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { ExplorerDataType, getExplorerLink } from 'uniswap/src/utils/linking' const DetailsFooter = styled.div<{ show: boolean }>` @@ -99,7 +99,7 @@ export default function UnsupportedCurrencyFooter({ ) } -function UnsupportedTokenCard({ token, chainId }: { token?: Token; chainId?: InterfaceChainId }) { +function UnsupportedTokenCard({ token, chainId }: { token?: Token; chainId?: UniverseChainId }) { const currencyInfo = useCurrencyInfo(token) if (!token || (!currencyInfo?.isSpam && currencyInfo?.safetyLevel === SafetyLevel.Verified)) { diff --git a/apps/web/src/components/swap/__snapshots__/SwapPreview.test.tsx.snap b/apps/web/src/components/swap/__snapshots__/SwapPreview.test.tsx.snap index 52834c5e8b4..817fe3f2296 100644 --- a/apps/web/src/components/swap/__snapshots__/SwapPreview.test.tsx.snap +++ b/apps/web/src/components/swap/__snapshots__/SwapPreview.test.tsx.snap @@ -38,51 +38,6 @@ exports[`SwapPreview.tsx matches base snapshot, test trade exact input 1`] = ` grid-row-gap: 8px; } -.c13 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - gap: 2px; - position: relative; - top: 0; - left: 0; -} - -.c13 img { - width: 36px; - height: 36px; - border-radius: 50%; -} - -.c14 { - width: 18px; - height: 36px; - border-radius: 50%; -} - -.c12 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - position: relative; - top: 0; - left: 0; -} - -.c11 { - position: relative; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; -} - .c7 { display: inline-block; height: inherit; @@ -140,7 +95,7 @@ exports[`SwapPreview.tsx matches base snapshot, test trade exact input 1`] = ` margin-right: 8px; } -.c15 { +.c11 { cursor: help; color: #7D7D7D; margin-right: 8px; @@ -203,30 +158,6 @@ exports[`SwapPreview.tsx matches base snapshot, test trade exact input 1`] = `
-
-
-
-
- - -
-
-
-
Buy @@ -266,30 +197,6 @@ exports[`SwapPreview.tsx matches base snapshot, test trade exact input 1`] = `
-
-
-
-
- - -
-
-
-
@@ -337,51 +244,6 @@ exports[`SwapPreview.tsx renders ETH input token for an ETH input UniswapX swap grid-row-gap: 8px; } -.c13 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - gap: 2px; - position: relative; - top: 0; - left: 0; -} - -.c13 img { - width: 36px; - height: 36px; - border-radius: 50%; -} - -.c14 { - width: 18px; - height: 36px; - border-radius: 50%; -} - -.c12 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - position: relative; - top: 0; - left: 0; -} - -.c11 { - position: relative; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; -} - .c7 { display: inline-block; height: inherit; @@ -439,7 +301,7 @@ exports[`SwapPreview.tsx renders ETH input token for an ETH input UniswapX swap margin-right: 8px; } -.c15 { +.c11 { cursor: help; color: #7D7D7D; margin-right: 8px; @@ -502,30 +364,6 @@ exports[`SwapPreview.tsx renders ETH input token for an ETH input UniswapX swap
-
-
-
-
- - -
-
-
-
Buy @@ -565,30 +403,6 @@ exports[`SwapPreview.tsx renders ETH input token for an ETH input UniswapX swap
-
-
-
-
- - -
-
-
-
@@ -636,51 +450,6 @@ exports[`SwapPreview.tsx renders ETH input token for an ETH input UniswapX v2 sw grid-row-gap: 8px; } -.c13 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - gap: 2px; - position: relative; - top: 0; - left: 0; -} - -.c13 img { - width: 36px; - height: 36px; - border-radius: 50%; -} - -.c14 { - width: 18px; - height: 36px; - border-radius: 50%; -} - -.c12 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - position: relative; - top: 0; - left: 0; -} - -.c11 { - position: relative; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; -} - .c7 { display: inline-block; height: inherit; @@ -738,7 +507,7 @@ exports[`SwapPreview.tsx renders ETH input token for an ETH input UniswapX v2 sw margin-right: 8px; } -.c15 { +.c11 { cursor: help; color: #7D7D7D; margin-right: 8px; @@ -801,30 +570,6 @@ exports[`SwapPreview.tsx renders ETH input token for an ETH input UniswapX v2 sw
-
-
-
-
- - -
-
-
-
Buy @@ -864,30 +609,6 @@ exports[`SwapPreview.tsx renders ETH input token for an ETH input UniswapX v2 sw
-
-
-
-
- - -
-
-
-
@@ -935,51 +656,6 @@ exports[`SwapPreview.tsx renders preview trades with loading states 1`] = ` grid-row-gap: 8px; } -.c13 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - gap: 2px; - position: relative; - top: 0; - left: 0; -} - -.c13 img { - width: 36px; - height: 36px; - border-radius: 50%; -} - -.c14 { - width: 18px; - height: 36px; - border-radius: 50%; -} - -.c12 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - position: relative; - top: 0; - left: 0; -} - -.c11 { - position: relative; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; -} - .c7 { display: inline-block; height: inherit; @@ -1037,7 +713,7 @@ exports[`SwapPreview.tsx renders preview trades with loading states 1`] = ` margin-right: 8px; } -.c15 { +.c11 { cursor: help; color: #7D7D7D; margin-right: 8px; @@ -1100,30 +776,6 @@ exports[`SwapPreview.tsx renders preview trades with loading states 1`] = `
-
-
-
-
- - -
-
-
-
Buy @@ -1163,30 +815,6 @@ exports[`SwapPreview.tsx renders preview trades with loading states 1`] = `
-
-
-
-
- - -
-
-
-
@@ -1234,51 +862,6 @@ exports[`SwapPreview.tsx test trade exact output, no recipient 1`] = ` grid-row-gap: 8px; } -.c13 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - gap: 2px; - position: relative; - top: 0; - left: 0; -} - -.c13 img { - width: 36px; - height: 36px; - border-radius: 50%; -} - -.c14 { - width: 18px; - height: 36px; - border-radius: 50%; -} - -.c12 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - position: relative; - top: 0; - left: 0; -} - -.c11 { - position: relative; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; -} - .c7 { display: inline-block; height: inherit; @@ -1336,7 +919,7 @@ exports[`SwapPreview.tsx test trade exact output, no recipient 1`] = ` margin-right: 8px; } -.c15 { +.c11 { cursor: help; color: #7D7D7D; margin-right: 8px; @@ -1399,30 +982,6 @@ exports[`SwapPreview.tsx test trade exact output, no recipient 1`] = `
-
-
-
-
- - -
-
-
-
Buy @@ -1462,30 +1021,6 @@ exports[`SwapPreview.tsx test trade exact output, no recipient 1`] = `
-
-
-
-
- - -
-
-
-
diff --git a/apps/web/src/constants/chains.test.ts b/apps/web/src/constants/chains.test.ts index dd942a5af50..4a8a8befdaa 100644 --- a/apps/web/src/constants/chains.test.ts +++ b/apps/web/src/constants/chains.test.ts @@ -8,32 +8,26 @@ import { INFURA_PREFIX_TO_CHAIN_ID, InterfaceGqlChain, SUPPORTED_GAS_ESTIMATE_CHAIN_IDS, - SupportedInterfaceChainId, TESTNET_CHAIN_IDS, UX_SUPPORTED_GQL_CHAINS, getChainFromChainUrlParam, getChainPriority, } from 'constants/chains' -import { GQL_MAINNET_CHAINS, UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' +import { GQL_MAINNET_CHAINS, GQL_TESTNET_CHAINS, UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { Chain } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { InterfaceChainId, UniverseChainId, WEB_SUPPORTED_CHAIN_IDS } from 'uniswap/src/types/chains' +import { SUPPORTED_CHAIN_IDS, UniverseChainId } from 'uniswap/src/types/chains' // Define an array of test cases with chainId and expected priority -const chainPriorityTestCases: [InterfaceChainId, number][] = [ +const chainPriorityTestCases: [UniverseChainId, number][] = [ [UniverseChainId.Mainnet, 0], - [UniverseChainId.Goerli, 0], [UniverseChainId.Sepolia, 0], [UniverseChainId.ArbitrumOne, 1], - [UniverseChainId.ArbitrumGoerli, 1], [UniverseChainId.Optimism, 2], - [UniverseChainId.OptimismGoerli, 2], [UniverseChainId.Polygon, 3], - [UniverseChainId.PolygonMumbai, 3], [UniverseChainId.Base, 4], [UniverseChainId.Bnb, 5], [UniverseChainId.Avalanche, 6], [UniverseChainId.Celo, 7], - [UniverseChainId.CeloAlfajores, 7], [UniverseChainId.Blast, 8], [UniverseChainId.Zora, 9], [UniverseChainId.Zksync, 10], @@ -41,35 +35,32 @@ const chainPriorityTestCases: [InterfaceChainId, number][] = [ test.each(chainPriorityTestCases)( 'getChainPriority returns expected priority for a given ChainId %O', - (chainId: InterfaceChainId, expectedPriority: number) => { + (chainId: UniverseChainId, expectedPriority: number) => { const priority = getChainPriority(chainId) expect(priority).toBe(expectedPriority) }, ) -const chainIdNames: { [chainId in SupportedInterfaceChainId]: string } = { +const chainIdNames: { [chainId in UniverseChainId]: string } = { [UniverseChainId.Mainnet]: 'mainnet', - [UniverseChainId.Goerli]: 'goerli', [UniverseChainId.Sepolia]: 'sepolia', [UniverseChainId.Polygon]: 'polygon', - [UniverseChainId.PolygonMumbai]: 'polygon_mumbai', [UniverseChainId.Celo]: 'celo', - [UniverseChainId.CeloAlfajores]: 'celo_alfajores', [UniverseChainId.ArbitrumOne]: 'arbitrum', - [UniverseChainId.ArbitrumGoerli]: 'arbitrum_goerli', [UniverseChainId.Optimism]: 'optimism', - [UniverseChainId.OptimismGoerli]: 'optimism_goerli', [UniverseChainId.Bnb]: 'bnb', [UniverseChainId.Avalanche]: 'avalanche', [UniverseChainId.Base]: 'base', [UniverseChainId.Blast]: 'blast', + [UniverseChainId.WorldChain]: 'worldchain', [UniverseChainId.Zora]: 'zora', [UniverseChainId.Zksync]: 'zksync', + [UniverseChainId.AstrochainSepolia]: 'astrochain', } as const -test.each(Object.keys(chainIdNames).map((key) => parseInt(key) as SupportedInterfaceChainId))( +test.each(Object.keys(chainIdNames).map((key) => parseInt(key) as UniverseChainId))( 'CHAIN_IDS_TO_NAMES generates the correct chainIds', - (chainId: SupportedInterfaceChainId) => { + (chainId: UniverseChainId) => { const name = CHAIN_IDS_TO_NAMES[chainId] expect(name).toBe(chainIdNames[chainId]) }, @@ -90,46 +81,24 @@ const supportedGasEstimateChains = [ test.each(supportedGasEstimateChains)( 'SUPPORTED_GAS_ESTIMATE_CHAIN_IDS generates the correct chainIds', - (chainId: SupportedInterfaceChainId) => { + (chainId: UniverseChainId) => { expect(SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId)).toBe(true) expect(SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.length).toEqual(supportedGasEstimateChains.length) }, ) -const testnetChainIds = [ - UniverseChainId.Goerli, - UniverseChainId.Sepolia, - UniverseChainId.PolygonMumbai, - UniverseChainId.ArbitrumGoerli, - UniverseChainId.OptimismGoerli, - UniverseChainId.CeloAlfajores, -] as const +const testnetChainIds = [UniverseChainId.Sepolia, UniverseChainId.AstrochainSepolia] as const -test.each(testnetChainIds)('TESTNET_CHAIN_IDS generates the correct chainIds', (chainId: SupportedInterfaceChainId) => { +test.each(testnetChainIds)('TESTNET_CHAIN_IDS generates the correct chainIds', (chainId: UniverseChainId) => { expect(TESTNET_CHAIN_IDS.includes(chainId)).toBe(true) expect(TESTNET_CHAIN_IDS.length).toEqual(testnetChainIds.length) }) -const GQLProductionChains = [ - Chain.Ethereum, - Chain.Polygon, - Chain.Celo, - Chain.Optimism, - Chain.Arbitrum, - Chain.Bnb, - Chain.Avalanche, - Chain.Base, - Chain.Blast, - Chain.Zora, - Chain.Zksync, -] as const - -const GQL_TESTNET_CHAINS = [Chain.EthereumGoerli, Chain.EthereumSepolia] as const -const uxSupportedGQLChains = [...GQLProductionChains, ...GQL_TESTNET_CHAINS] as const +const uxSupportedGQLChains = [...GQL_MAINNET_CHAINS, ...GQL_TESTNET_CHAINS] as const -test.each(GQLProductionChains)('GQL_MAINNET_CHAINS generates the correct chains', (chain: InterfaceGqlChain) => { +test.each(GQL_MAINNET_CHAINS)('GQL_MAINNET_CHAINS generates the correct chains', (chain: InterfaceGqlChain) => { expect(GQL_MAINNET_CHAINS.includes(chain)).toBe(true) - expect(GQL_MAINNET_CHAINS.length).toEqual(GQLProductionChains.length) + expect(GQL_MAINNET_CHAINS.length).toEqual(GQL_MAINNET_CHAINS.length) }) test.each(uxSupportedGQLChains)('UX_SUPPORTED_GQL_CHAINS generates the correct chains', (chain: InterfaceGqlChain) => { @@ -139,16 +108,11 @@ test.each(uxSupportedGQLChains)('UX_SUPPORTED_GQL_CHAINS generates the correct c const chainIdToBackendName: { [key: number]: InterfaceGqlChain } = { [UniverseChainId.Mainnet]: Chain.Ethereum, - [UniverseChainId.Goerli]: Chain.EthereumGoerli, [UniverseChainId.Sepolia]: Chain.EthereumSepolia, [UniverseChainId.Polygon]: Chain.Polygon, - [UniverseChainId.PolygonMumbai]: Chain.Polygon, [UniverseChainId.Celo]: Chain.Celo, - [UniverseChainId.CeloAlfajores]: Chain.Celo, [UniverseChainId.ArbitrumOne]: Chain.Arbitrum, - [UniverseChainId.ArbitrumGoerli]: Chain.Arbitrum, [UniverseChainId.Optimism]: Chain.Optimism, - [UniverseChainId.OptimismGoerli]: Chain.Optimism, [UniverseChainId.Bnb]: Chain.Bnb, [UniverseChainId.Avalanche]: Chain.Avalanche, [UniverseChainId.Base]: Chain.Base, @@ -156,9 +120,9 @@ const chainIdToBackendName: { [key: number]: InterfaceGqlChain } = { [UniverseChainId.Zora]: Chain.Zora, } -test.each(Object.keys(chainIdToBackendName).map((key) => parseInt(key) as SupportedInterfaceChainId))( +test.each(Object.keys(chainIdToBackendName).map((key) => parseInt(key) as UniverseChainId))( 'CHAIN_IDS_TO_BACKEND_NAME generates the correct chains', - (chainId: SupportedInterfaceChainId) => { + (chainId: UniverseChainId) => { const name = CHAIN_ID_TO_BACKEND_NAME[chainId] expect(name).toBe(chainIdToBackendName[chainId]) }, @@ -166,7 +130,6 @@ test.each(Object.keys(chainIdToBackendName).map((key) => parseInt(key) as Suppor const chainToChainId = { [Chain.Ethereum]: UniverseChainId.Mainnet, - [Chain.EthereumGoerli]: UniverseChainId.Goerli, [Chain.EthereumSepolia]: UniverseChainId.Sepolia, [Chain.Polygon]: UniverseChainId.Polygon, [Chain.Celo]: UniverseChainId.Celo, @@ -176,6 +139,7 @@ const chainToChainId = { [Chain.Avalanche]: UniverseChainId.Avalanche, [Chain.Base]: UniverseChainId.Base, [Chain.Blast]: UniverseChainId.Blast, + [Chain.Worldchain]: UniverseChainId.WorldChain, [Chain.Zora]: UniverseChainId.Zora, [Chain.Zksync]: UniverseChainId.Zksync, } as const @@ -184,7 +148,7 @@ test.each(Object.keys(chainToChainId).map((key) => key as InterfaceGqlChain))( 'CHAIN_NAME_TO_CHAIN_ID generates the correct chains', (chain) => { const chainId = CHAIN_NAME_TO_CHAIN_ID[chain] - expect(chainId).toBe(chainToChainId[chain]) + expect(chainId).toBe(chainToChainId[chain as Exclude]) // will remove this after AstrochainSepolia is added }, ) @@ -198,6 +162,7 @@ const backendSupportedChains = [ Chain.Celo, Chain.Blast, Chain.Avalanche, + Chain.Worldchain, Chain.Zksync, ] as const @@ -213,22 +178,18 @@ const backendNotyetSupportedChainIds = [UniverseChainId.Zora] as const test.each(backendNotyetSupportedChainIds)( 'BACKEND_SUPPORTED_CHAINS generates the correct chains', - (chainId: SupportedInterfaceChainId) => { + (chainId: UniverseChainId) => { expect(BACKEND_NOT_YET_SUPPORTED_CHAIN_IDS.includes(chainId)).toBe(true) expect(BACKEND_NOT_YET_SUPPORTED_CHAIN_IDS.length).toEqual(backendNotyetSupportedChainIds.length) }, ) -const infuraPrefixToChainId: { [prefix: string]: SupportedInterfaceChainId } = { +const infuraPrefixToChainId: { [prefix: string]: UniverseChainId } = { mainnet: UniverseChainId.Mainnet, - goerli: UniverseChainId.Goerli, sepolia: UniverseChainId.Sepolia, 'optimism-mainnet': UniverseChainId.Optimism, - 'optimism-goerli': UniverseChainId.OptimismGoerli, 'arbitrum-mainnet': UniverseChainId.ArbitrumOne, - 'arbitrum-goerli': UniverseChainId.ArbitrumGoerli, 'polygon-mainnet': UniverseChainId.Polygon, - 'polygon-mumbai': UniverseChainId.PolygonMumbai, 'avalanche-mainnet': UniverseChainId.Avalanche, 'base-mainnet': UniverseChainId.Base, 'blast-mainnet': UniverseChainId.Blast, @@ -266,9 +227,9 @@ function getBlocksPerMainnetEpochForChainId(chainId: number | undefined): number } } -test.each(WEB_SUPPORTED_CHAIN_IDS)( +test.each(SUPPORTED_CHAIN_IDS)( 'CHAIN_INFO maps the correct blocks per mainnet epoch for chainId', - (chainId: SupportedInterfaceChainId) => { + (chainId: UniverseChainId) => { const block = UNIVERSE_CHAIN_INFO[chainId].blockPerMainnetEpochForChainId expect(block).toEqual(getBlocksPerMainnetEpochForChainId(chainId)) }, diff --git a/apps/web/src/constants/chains.ts b/apps/web/src/constants/chains.ts index b3318d2fe27..5c263661f53 100644 --- a/apps/web/src/constants/chains.ts +++ b/apps/web/src/constants/chains.ts @@ -1,40 +1,26 @@ /* eslint-disable rulesdir/no-undefined-or */ import { Currency, V2_ROUTER_ADDRESSES } from '@uniswap/sdk-core' import ms from 'ms' -import { useCallback, useMemo } from 'react' +import { useCallback } from 'react' import { useParams } from 'react-router-dom' -import { GQL_MAINNET_CHAINS, UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' +import { GQL_MAINNET_CHAINS, GQL_TESTNET_CHAINS, UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { Chain as BackendChainId } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' +import { useFeatureFlaggedChainIds } from 'uniswap/src/features/chains/utils' import { ArbitrumXV2ExperimentGroup, Experiments } from 'uniswap/src/features/gating/experiments' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useExperimentGroupName, useFeatureFlag } from 'uniswap/src/features/gating/hooks' -import { InterfaceChainId, UniverseChainId, UniverseChainInfo, WEB_SUPPORTED_CHAIN_IDS } from 'uniswap/src/types/chains' +import { COMBINED_CHAIN_IDS, UniverseChainId, UniverseChainInfo } from 'uniswap/src/types/chains' export const AVERAGE_L1_BLOCK_TIME = ms(`12s`) -export function isSupportedChainId(chainId?: number | InterfaceChainId | null): chainId is SupportedInterfaceChainId { - return !!chainId && WEB_SUPPORTED_CHAIN_IDS.includes(chainId as SupportedInterfaceChainId) +export function isSupportedChainId(chainId?: number | UniverseChainId | null): chainId is UniverseChainId { + return !!chainId && COMBINED_CHAIN_IDS.includes(chainId as UniverseChainId) } -// Used to feature flag chains. If a chain is not included in the object, it is considered enabled by default. -// This is the reason why useSupportedChainId and useIsSupportedChainId is a hook instead of a function. -function useFeatureFlaggedChainIds(): Partial> { - // You can use the useFeatureFlag hook here to enable/disable chains based on feature flags. - // Example: [ChainId.BLAST]: useFeatureFlag(FeatureFlags.BLAST) - - const zoraEnabled = useFeatureFlag(FeatureFlags.Zora) - return useMemo( - () => ({ - [UniverseChainId.Zora]: zoraEnabled, - }), - [zoraEnabled], - ) -} - -export function useIsSupportedChainId(chainId?: number | InterfaceChainId): chainId is SupportedInterfaceChainId { +export function useIsSupportedChainId(chainId?: number | UniverseChainId): chainId is UniverseChainId { const featureFlaggedChains = useFeatureFlaggedChainIds() - const chainIsNotEnabled = featureFlaggedChains[chainId as SupportedInterfaceChainId] === false + const chainIsNotEnabled = !featureFlaggedChains.includes(chainId as UniverseChainId) return chainIsNotEnabled ? false : isSupportedChainId(chainId) } @@ -42,44 +28,39 @@ export function useIsSupportedChainIdCallback() { const featureFlaggedChains = useFeatureFlaggedChainIds() return useCallback( - (chainId?: number | InterfaceChainId): chainId is SupportedInterfaceChainId => { - const chainIsNotEnabled = featureFlaggedChains[chainId as SupportedInterfaceChainId] === false + (chainId?: number | UniverseChainId): chainId is UniverseChainId => { + const chainIsNotEnabled = !featureFlaggedChains.includes(chainId as UniverseChainId) return chainIsNotEnabled ? false : isSupportedChainId(chainId) }, [featureFlaggedChains], ) } -export function useSupportedChainId(chainId?: number): SupportedInterfaceChainId | undefined { +export function useSupportedChainId(chainId?: number): UniverseChainId | undefined { const featureFlaggedChains = useFeatureFlaggedChainIds() - if (!chainId || WEB_SUPPORTED_CHAIN_IDS.indexOf(chainId) === -1) { + if (!chainId || COMBINED_CHAIN_IDS.indexOf(chainId) === -1) { return undefined } - const chainDisabled = featureFlaggedChains[chainId as SupportedInterfaceChainId] === false - return chainDisabled ? undefined : (chainId as SupportedInterfaceChainId) + const chainDisabled = !featureFlaggedChains.includes(chainId as UniverseChainId) + return chainDisabled ? undefined : (chainId as UniverseChainId) } -export type InterfaceGqlChain = Exclude - -export type SupportedInterfaceChainId = InterfaceChainId +export type InterfaceGqlChain = Exclude export type ChainSlug = UniverseChainInfo['urlParam'] export const isChainUrlParam = (str?: string): str is ChainSlug => !!str && Object.values(UNIVERSE_CHAIN_INFO).some((chain) => chain.urlParam === str) export const getChainUrlParam = (str?: string): ChainSlug | undefined => (isChainUrlParam(str) ? str : undefined) -export function getChain(options: { chainId: SupportedInterfaceChainId }): UniverseChainInfo -export function getChain(options: { chainId?: SupportedInterfaceChainId; withFallback: true }): UniverseChainInfo -export function getChain(options: { - chainId?: SupportedInterfaceChainId - withFallback?: boolean -}): UniverseChainInfo | undefined +export function getChain(options: { chainId: UniverseChainId }): UniverseChainInfo +export function getChain(options: { chainId?: UniverseChainId; withFallback: true }): UniverseChainInfo +export function getChain(options: { chainId?: UniverseChainId; withFallback?: boolean }): UniverseChainInfo | undefined export function getChain({ chainId, withFallback, }: { - chainId?: SupportedInterfaceChainId + chainId?: UniverseChainId withFallback?: boolean }): UniverseChainInfo | undefined { return chainId @@ -91,17 +72,13 @@ export function getChain({ export const CHAIN_IDS_TO_NAMES = Object.fromEntries( Object.entries(UNIVERSE_CHAIN_INFO).map(([key, value]) => [key, value.interfaceName]), -) as { [chainId in SupportedInterfaceChainId]: string } - -const GQL_TESTNET_CHAINS = Object.values(UNIVERSE_CHAIN_INFO) - .filter((chain) => chain.testnet && !chain.backendChain.isSecondaryChain) - .map((chain) => chain.backendChain.chain) +) as { [chainId in UniverseChainId]: string } export const UX_SUPPORTED_GQL_CHAINS = [...GQL_MAINNET_CHAINS, ...GQL_TESTNET_CHAINS] export const CHAIN_ID_TO_BACKEND_NAME = Object.fromEntries( Object.entries(UNIVERSE_CHAIN_INFO).map(([key, value]) => [key, value.backendChain.chain]), -) as { [chainId in SupportedInterfaceChainId]: InterfaceGqlChain } +) as { [chainId in UniverseChainId]: InterfaceGqlChain } export function chainIdToBackendChain(options: { chainId: UniverseChainId }): InterfaceGqlChain export function chainIdToBackendChain(options: { chainId?: UniverseChainId; withFallback: true }): InterfaceGqlChain @@ -126,20 +103,20 @@ export function chainIdToBackendChain({ export const CHAIN_NAME_TO_CHAIN_ID = Object.fromEntries( Object.entries(UNIVERSE_CHAIN_INFO) .filter(([, value]) => !value.backendChain.isSecondaryChain) - .map(([key, value]) => [value.backendChain.chain, parseInt(key) as SupportedInterfaceChainId]), -) as { [chain in InterfaceGqlChain]: SupportedInterfaceChainId } + .map(([key, value]) => [value.backendChain.chain, parseInt(key) as UniverseChainId]), +) as { [chain in InterfaceGqlChain]: UniverseChainId } export const SUPPORTED_GAS_ESTIMATE_CHAIN_IDS = Object.keys(UNIVERSE_CHAIN_INFO) - .filter((key) => UNIVERSE_CHAIN_INFO[parseInt(key) as SupportedInterfaceChainId].supportsGasEstimates) - .map((key) => parseInt(key) as SupportedInterfaceChainId) + .filter((key) => UNIVERSE_CHAIN_INFO[parseInt(key) as UniverseChainId].supportsGasEstimates) + .map((key) => parseInt(key) as UniverseChainId) export const PRODUCTION_CHAIN_IDS: UniverseChainId[] = Object.values(UNIVERSE_CHAIN_INFO) .filter((chain) => !chain.testnet) .map((chain) => chain.id) export const TESTNET_CHAIN_IDS = Object.keys(UNIVERSE_CHAIN_INFO) - .filter((key) => UNIVERSE_CHAIN_INFO[parseInt(key) as SupportedInterfaceChainId].testnet) - .map((key) => parseInt(key) as SupportedInterfaceChainId) + .filter((key) => UNIVERSE_CHAIN_INFO[parseInt(key) as UniverseChainId].testnet) + .map((key) => parseInt(key) as UniverseChainId) /** * @deprecated when v2 pools are enabled on chains supported through sdk-core @@ -148,23 +125,23 @@ export const SUPPORTED_V2POOL_CHAIN_IDS = Object.keys(V2_ROUTER_ADDRESSES).map(( export const BACKEND_SUPPORTED_CHAINS = Object.keys(UNIVERSE_CHAIN_INFO) .filter((key) => { - const chainId = parseInt(key) as SupportedInterfaceChainId + const chainId = parseInt(key) as UniverseChainId return ( UNIVERSE_CHAIN_INFO[chainId].backendChain.backendSupported && !UNIVERSE_CHAIN_INFO[chainId].backendChain.isSecondaryChain && !UNIVERSE_CHAIN_INFO[chainId].testnet ) }) - .map((key) => UNIVERSE_CHAIN_INFO[parseInt(key) as SupportedInterfaceChainId].backendChain.chain as InterfaceGqlChain) + .map((key) => UNIVERSE_CHAIN_INFO[parseInt(key) as UniverseChainId].backendChain.chain as InterfaceGqlChain) export const BACKEND_NOT_YET_SUPPORTED_CHAIN_IDS = GQL_MAINNET_CHAINS.filter( (chain) => !BACKEND_SUPPORTED_CHAINS.includes(chain), -).map((chain) => CHAIN_NAME_TO_CHAIN_ID[chain]) as [SupportedInterfaceChainId] +).map((chain) => CHAIN_NAME_TO_CHAIN_ID[chain]) as [UniverseChainId] -export const INFURA_PREFIX_TO_CHAIN_ID: { [prefix: string]: SupportedInterfaceChainId } = Object.fromEntries( +export const INFURA_PREFIX_TO_CHAIN_ID: { [prefix: string]: UniverseChainId } = Object.fromEntries( Object.entries(UNIVERSE_CHAIN_INFO) .filter(([, value]) => !!value.infuraPrefix) - .map(([key, value]) => [value.infuraPrefix, parseInt(key) as SupportedInterfaceChainId]), + .map(([key, value]) => [value.infuraPrefix, parseInt(key) as UniverseChainId]), ) /** @@ -172,7 +149,7 @@ export const INFURA_PREFIX_TO_CHAIN_ID: { [prefix: string]: SupportedInterfaceCh * @param {ChainId} chainId - The chainId to determine the priority for. * @returns {number} The priority of the chainId, the lower the priority, the earlier it should be displayed, with base of MAINNET=0. */ -export function getChainPriority(chainId: InterfaceChainId): number { +export function getChainPriority(chainId: UniverseChainId): number { if (isSupportedChainId(chainId)) { return UNIVERSE_CHAIN_INFO[chainId].chainPriority } @@ -197,7 +174,7 @@ export function isStablecoin(currency?: Currency): boolean { return false } - return getChain({ chainId: currency.chainId as SupportedInterfaceChainId }).stablecoins.some((stablecoin) => + return getChain({ chainId: currency.chainId as UniverseChainId }).stablecoins.some((stablecoin) => stablecoin.equals(currency), ) } diff --git a/apps/web/src/constants/deprecatedTokenSafety.tsx b/apps/web/src/constants/deprecatedTokenSafety.tsx index ce0aa8ae525..3ea0eaa1d25 100644 --- a/apps/web/src/constants/deprecatedTokenSafety.tsx +++ b/apps/web/src/constants/deprecatedTokenSafety.tsx @@ -6,7 +6,7 @@ import { useCurrencyInfo } from 'hooks/Tokens' import { SafetyLevel } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { Trans, t } from 'uniswap/src/i18n' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' export const TOKEN_SAFETY_ARTICLE = 'https://support.uniswap.org/hc/en-us/articles/8723118437133' @@ -88,7 +88,7 @@ export const BlockedWarning: Warning = { canProceed: false, } -export function useTokenWarning(tokenAddress?: string, chainId?: InterfaceChainId | number): Warning | undefined { +export function useTokenWarning(tokenAddress?: string, chainId?: UniverseChainId | number): Warning | undefined { const currencyInfo = useCurrencyInfo(tokenAddress, chainId) switch (currencyInfo?.safetyLevel) { case SafetyLevel.MediumWarning: diff --git a/apps/web/src/constants/providers.ts b/apps/web/src/constants/providers.ts index 4cba0f9fa13..8aeaa777396 100644 --- a/apps/web/src/constants/providers.ts +++ b/apps/web/src/constants/providers.ts @@ -1,10 +1,10 @@ -import { CHAIN_IDS_TO_NAMES, SupportedInterfaceChainId } from 'constants/chains' +import { CHAIN_IDS_TO_NAMES } from 'constants/chains' import AppJsonRpcProvider from 'rpc/AppJsonRpcProvider' import ConfiguredJsonRpcProvider from 'rpc/ConfiguredJsonRpcProvider' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' -import { WEB_SUPPORTED_CHAIN_IDS } from 'uniswap/src/types/chains' +import { SUPPORTED_CHAIN_IDS, UniverseChainId } from 'uniswap/src/types/chains' -function getAppProvider(chainId: SupportedInterfaceChainId) { +function getAppProvider(chainId: UniverseChainId) { const info = UNIVERSE_CHAIN_INFO[chainId] return new AppJsonRpcProvider( info.rpcUrls.appOnly.http.map( @@ -15,5 +15,5 @@ function getAppProvider(chainId: SupportedInterfaceChainId) { /** These are the only JsonRpcProviders used directly by the interface. */ export const RPC_PROVIDERS = Object.fromEntries( - WEB_SUPPORTED_CHAIN_IDS.map((chain) => [chain as SupportedInterfaceChainId, getAppProvider(chain)]), -) as Record + SUPPORTED_CHAIN_IDS.map((chain) => [chain as UniverseChainId, getAppProvider(chain)]), +) as Record diff --git a/apps/web/src/featureFlags/dynamicConfig/quickRouteChains.ts b/apps/web/src/featureFlags/dynamicConfig/quickRouteChains.ts index 0192c7b0fd1..4ee3118beea 100644 --- a/apps/web/src/featureFlags/dynamicConfig/quickRouteChains.ts +++ b/apps/web/src/featureFlags/dynamicConfig/quickRouteChains.ts @@ -1,16 +1,16 @@ import { DynamicConfigs, QuickRouteChainsConfigKey } from 'uniswap/src/features/gating/configs' import { useDynamicConfigValue } from 'uniswap/src/features/gating/hooks' -import { InterfaceChainId, WEB_SUPPORTED_CHAIN_IDS } from 'uniswap/src/types/chains' +import { SUPPORTED_CHAIN_IDS, UniverseChainId } from 'uniswap/src/types/chains' import { logger } from 'utilities/src/logger/logger' -export function useQuickRouteChains(): InterfaceChainId[] { +export function useQuickRouteChains(): UniverseChainId[] { const chains = useDynamicConfigValue( DynamicConfigs.QuickRouteChains, QuickRouteChainsConfigKey.Chains, - [] as InterfaceChainId[], + [] as UniverseChainId[], (x: unknown) => Array.isArray(x) && x.every((c: unknown) => typeof c === 'number'), ) - if (chains.every((c) => WEB_SUPPORTED_CHAIN_IDS.includes(c))) { + if (chains.every((c) => SUPPORTED_CHAIN_IDS.includes(c))) { return chains } else { logger.error(new Error('dynamic config chains contain invalid ChainId'), { diff --git a/apps/web/src/graphql/data/RecentTokenTransfers.ts b/apps/web/src/graphql/data/RecentTokenTransfers.ts index 6f6757c5d10..b2231c8de1c 100644 --- a/apps/web/src/graphql/data/RecentTokenTransfers.ts +++ b/apps/web/src/graphql/data/RecentTokenTransfers.ts @@ -5,10 +5,15 @@ import { TransactionType, useRecentTokenTransfersQuery, } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' export function useRecentTokenTransfers(address?: string) { + const { gqlChains } = useEnabledChains() const { data, loading } = useRecentTokenTransfersQuery({ - variables: { address: address ?? '' }, + variables: { + address: address ?? '', + chains: gqlChains, + }, skip: !address, }) diff --git a/apps/web/src/graphql/data/TrendingTokens.ts b/apps/web/src/graphql/data/TrendingTokens.ts index 435e1a87fe9..e5e28a858ff 100644 --- a/apps/web/src/graphql/data/TrendingTokens.ts +++ b/apps/web/src/graphql/data/TrendingTokens.ts @@ -1,9 +1,10 @@ -import { SupportedInterfaceChainId, chainIdToBackendChain } from 'constants/chains' +import { chainIdToBackendChain } from 'constants/chains' import { unwrapToken } from 'graphql/data/util' import { useMemo } from 'react' import { useTrendingTokensQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' +import { UniverseChainId } from 'uniswap/src/types/chains' -export default function useTrendingTokens(chainId?: SupportedInterfaceChainId) { +export default function useTrendingTokens(chainId?: UniverseChainId) { const chain = chainIdToBackendChain({ chainId, withFallback: true }) const { data, loading } = useTrendingTokensQuery({ variables: { chain } }) diff --git a/apps/web/src/graphql/data/apollo/AssetActivityProvider.tsx b/apps/web/src/graphql/data/apollo/AssetActivityProvider.tsx index 62dc7d0ef3a..6ab8a2d5a03 100644 --- a/apps/web/src/graphql/data/apollo/AssetActivityProvider.tsx +++ b/apps/web/src/graphql/data/apollo/AssetActivityProvider.tsx @@ -14,7 +14,6 @@ import { useState, } from 'react' import { useFiatOnRampTransactions } from 'state/fiatOnRampTransactions/hooks' -import { GQL_MAINNET_CHAINS_MUTABLE } from 'uniswap/src/constants/chains' import { ActivityWebQueryResult, AssetActivityPartsFragment, @@ -25,6 +24,7 @@ import { } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { logger } from 'utilities/src/logger/logger' import { useInterval } from 'utilities/src/time/timing' import { v4 as uuidV4 } from 'uuid' @@ -39,6 +39,8 @@ const SubscriptionContext = createContext< export function AssetActivityProvider({ children }: PropsWithChildren) { const account = useAccount() const previousAccount = usePrevious(account.address) + const { isTestnetModeEnabled, gqlChains } = useEnabledChains() + const previousIsTestnetModeEnabled = usePrevious(isTestnetModeEnabled) const isRealtimeEnabled = useFeatureFlag(FeatureFlags.Realtime) const [attempt, incrementAttempt] = useReducer((attempt) => attempt + 1, 1) @@ -65,7 +67,9 @@ export function AssetActivityProvider({ children }: PropsWithChildren) { lazyFetch({ variables: { account: account.address ?? '', - chains: GQL_MAINNET_CHAINS_MUTABLE, + chains: gqlChains, + // Backend will return off-chain activities even if gqlChains are all testnets. + includeOffChain: !isTestnetModeEnabled, // Include the externalsessionIDs of all fiat on-ramp transactions in the local store, // so that the backend can find the transactions without signature authentication. onRampTransactionIDs: Object.values(fiatOnRampTransactions).map( @@ -73,7 +77,7 @@ export function AssetActivityProvider({ children }: PropsWithChildren) { ), }, }), - [account.address, fiatOnRampTransactions, lazyFetch], + [account.address, fiatOnRampTransactions, lazyFetch, gqlChains, isTestnetModeEnabled], ) useInterval(async () => { @@ -88,7 +92,11 @@ export function AssetActivityProvider({ children }: PropsWithChildren) { return ( - + {children} diff --git a/apps/web/src/graphql/data/apollo/TokenBalancesProvider.tsx b/apps/web/src/graphql/data/apollo/TokenBalancesProvider.tsx index 985f22a63a3..2747a311fc6 100644 --- a/apps/web/src/graphql/data/apollo/TokenBalancesProvider.tsx +++ b/apps/web/src/graphql/data/apollo/TokenBalancesProvider.tsx @@ -3,7 +3,6 @@ import { AdaptiveTokenBalancesProvider } from 'graphql/data/apollo/AdaptiveToken import { useAssetActivitySubscription } from 'graphql/data/apollo/AssetActivityProvider' import { useAccount } from 'hooks/useAccount' import { PropsWithChildren, useCallback, useMemo } from 'react' -import { GQL_MAINNET_CHAINS_MUTABLE } from 'uniswap/src/constants/chains' import { OnAssetActivitySubscription, SwapOrderStatus, @@ -11,8 +10,12 @@ import { } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' -import { useHideSmallBalancesSetting, useHideSpamTokensSetting } from 'uniswap/src/features/settings/hooks' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { + useEnabledChains, + useHideSmallBalancesSetting, + useHideSpamTokensSetting, +} from 'uniswap/src/features/settings/hooks' +import { UniverseChainId } from 'uniswap/src/types/chains' import { SUBSCRIPTION_CHAINIDS } from 'utilities/src/apollo/constants' import { usePrevious } from 'utilities/src/react/hooks' @@ -33,7 +36,7 @@ function useIsRealtime() { const { chainId } = useAccount() const isRealtimeEnabled = useFeatureFlag(FeatureFlags.Realtime) - return isRealtimeEnabled && chainId && (SUBSCRIPTION_CHAINIDS as unknown as InterfaceChainId[]).includes(chainId) + return isRealtimeEnabled && chainId && (SUBSCRIPTION_CHAINIDS as unknown as UniverseChainId[]).includes(chainId) } function useHasAccountUpdate() { @@ -50,13 +53,26 @@ function useHasAccountUpdate() { const account = useAccount() const prevAccount = usePrevious(account.address) + const { isTestnetModeEnabled } = useEnabledChains() + const prevIsTestnetModeEnabled = usePrevious(isTestnetModeEnabled) + return useMemo(() => { const hasPolledTxUpdate = !isRealtime && hasLocalStateUpdate const hasSubscriptionTxUpdate = data !== prevData && mayAffectTokenBalances(data) const accountChanged = Boolean(prevAccount !== account.address && account.address) + const hasTestnetModeChanged = prevIsTestnetModeEnabled !== isTestnetModeEnabled - return hasPolledTxUpdate || hasSubscriptionTxUpdate || accountChanged - }, [account.address, data, hasLocalStateUpdate, isRealtime, prevAccount, prevData]) + return hasPolledTxUpdate || hasSubscriptionTxUpdate || accountChanged || hasTestnetModeChanged + }, [ + account.address, + data, + hasLocalStateUpdate, + isRealtime, + prevAccount, + prevData, + prevIsTestnetModeEnabled, + isTestnetModeEnabled, + ]) } function usePortfolioValueModifiers(): { @@ -80,6 +96,7 @@ export function TokenBalancesProvider({ children }: PropsWithChildren) { const hasAccountUpdate = useHasAccountUpdate() const valueModifiers = usePortfolioValueModifiers() const prevValueModifiers = usePrevious(valueModifiers) + const { gqlChains } = useEnabledChains() const fetch = useCallback(() => { if (!account.address) { @@ -88,7 +105,7 @@ export function TokenBalancesProvider({ children }: PropsWithChildren) { lazyFetch({ variables: { ownerAddress: account.address, - chains: GQL_MAINNET_CHAINS_MUTABLE, + chains: gqlChains, valueModifiers: [ { ownerAddress: account.address, @@ -100,7 +117,7 @@ export function TokenBalancesProvider({ children }: PropsWithChildren) { ], }, }) - }, [account.address, lazyFetch, valueModifiers]) + }, [account.address, lazyFetch, valueModifiers, gqlChains]) return ( { @@ -121,7 +122,7 @@ function useFilteredPools(pools: TablePool[]) { ) } -export function useTopPools(sortState: PoolTableSortState, chainId?: SupportedInterfaceChainId) { +export function useTopPools(sortState: PoolTableSortState, chainId?: UniverseChainId) { const isWindowVisible = useIsWindowVisible() const isRestExploreEnabled = useFeatureFlag(FeatureFlags.RestExplore) const { diff --git a/apps/web/src/graphql/data/types.ts b/apps/web/src/graphql/data/types.ts index 388a32f6bf5..923e14b8ef0 100644 --- a/apps/web/src/graphql/data/types.ts +++ b/apps/web/src/graphql/data/types.ts @@ -1,4 +1,4 @@ -import { SupportedInterfaceChainId, isSupportedChainId } from 'constants/chains' +import { isSupportedChainId } from 'constants/chains' import { fiatOnRampToCurrency, gqlToCurrency } from 'graphql/data/util' import { COMMON_BASES, buildCurrencyInfo } from 'uniswap/src/constants/routing' import { USDC_OPTIMISM } from 'uniswap/src/constants/tokens' @@ -10,6 +10,7 @@ import { import { CurrencyInfo, TokenList } from 'uniswap/src/features/dataApi/types' import { getCurrencySafetyInfo } from 'uniswap/src/features/dataApi/utils' import { FORSupportedToken } from 'uniswap/src/features/fiatOnRamp/types' +import { UniverseChainId } from 'uniswap/src/types/chains' import { isSameAddress } from 'utilities/src/addresses' import { currencyId } from 'utils/currencyId' @@ -43,7 +44,7 @@ export function meldSupportedCurrencyToCurrencyInfo(forCurrency: FORSupportedTok return undefined } - const supportedChainId = Number(forCurrency.chainId) as SupportedInterfaceChainId + const supportedChainId = Number(forCurrency.chainId) as UniverseChainId const commonBases = COMMON_BASES[supportedChainId] const currencyInfo = commonBases.find((base) => { diff --git a/apps/web/src/graphql/data/useTokenTransactions.ts b/apps/web/src/graphql/data/useTokenTransactions.ts index 388858a69ec..debe193563d 100644 --- a/apps/web/src/graphql/data/useTokenTransactions.ts +++ b/apps/web/src/graphql/data/useTokenTransactions.ts @@ -1,4 +1,4 @@ -import { SupportedInterfaceChainId, chainIdToBackendChain } from 'constants/chains' +import { chainIdToBackendChain } from 'constants/chains' import { useCallback, useMemo, useRef } from 'react' import { Chain, @@ -7,6 +7,7 @@ import { useV2TokenTransactionsQuery, useV3TokenTransactionsQuery, } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' +import { UniverseChainId } from 'uniswap/src/types/chains' export enum TokenTransactionType { BUY = 'Buy', @@ -17,7 +18,7 @@ const TokenTransactionDefaultQuerySize = 25 export function useTokenTransactions( address: string, - chainId: SupportedInterfaceChainId, + chainId: UniverseChainId, filter: TokenTransactionType[] = [TokenTransactionType.BUY, TokenTransactionType.SELL], ) { const { diff --git a/apps/web/src/graphql/data/util.tsx b/apps/web/src/graphql/data/util.tsx index 82f6823178b..1957b9fe1ed 100644 --- a/apps/web/src/graphql/data/util.tsx +++ b/apps/web/src/graphql/data/util.tsx @@ -7,13 +7,11 @@ import { BACKEND_SUPPORTED_CHAINS, CHAIN_NAME_TO_CHAIN_ID, InterfaceGqlChain, - SupportedInterfaceChainId, UX_SUPPORTED_GQL_CHAINS, chainIdToBackendChain, getChainFromChainUrlParam, isSupportedChainId, } from 'constants/chains' - import { NATIVE_CHAIN_ID } from 'constants/tokens' import { DefaultTheme } from 'lib/styled-components' import ms from 'ms' @@ -87,12 +85,12 @@ export function isPricePoint(p: PricePoint | undefined): p is PricePoint { return p !== undefined } -export function isGqlSupportedChain(chainId?: SupportedInterfaceChainId) { +export function isGqlSupportedChain(chainId?: UniverseChainId) { return !!chainId && GQL_MAINNET_CHAINS.includes(UNIVERSE_CHAIN_INFO[chainId].backendChain.chain) } export function toContractInput(currency: Currency): ContractInput { - const chain = chainIdToBackendChain({ chainId: currency.chainId as SupportedInterfaceChainId }) + const chain = chainIdToBackendChain({ chainId: currency.chainId as UniverseChainId }) return { chain, address: currency.isToken ? currency.address : getNativeTokenDBAddress(chain) } } @@ -121,7 +119,7 @@ export function fiatOnRampToCurrency(forCurrency: FORSupportedToken): Currency | if (!isSupportedChainId(Number(forCurrency.chainId))) { return undefined } - const supportedChainId = Number(forCurrency.chainId) as SupportedInterfaceChainId + const supportedChainId = Number(forCurrency.chainId) as UniverseChainId if (!forCurrency.address) { return nativeOnChain(supportedChainId) @@ -152,9 +150,9 @@ export function isSupportedGQLChain(chain: Chain): chain is InterfaceGqlChain { return chains.includes(chain) } -export function supportedChainIdFromGQLChain(chain: InterfaceGqlChain): SupportedInterfaceChainId -export function supportedChainIdFromGQLChain(chain: Chain): SupportedInterfaceChainId | undefined -export function supportedChainIdFromGQLChain(chain: Chain): SupportedInterfaceChainId | undefined { +export function supportedChainIdFromGQLChain(chain: InterfaceGqlChain): UniverseChainId +export function supportedChainIdFromGQLChain(chain: Chain): UniverseChainId | undefined +export function supportedChainIdFromGQLChain(chain: Chain): UniverseChainId | undefined { return isSupportedGQLChain(chain) ? CHAIN_NAME_TO_CHAIN_ID[chain] : undefined } diff --git a/apps/web/src/hooks/Tokens.ts b/apps/web/src/hooks/Tokens.ts index 73648a24abe..bef10d8cff9 100644 --- a/apps/web/src/hooks/Tokens.ts +++ b/apps/web/src/hooks/Tokens.ts @@ -1,20 +1,20 @@ import { Currency, Token } from '@uniswap/sdk-core' -import { SupportedInterfaceChainId, useSupportedChainId } from 'constants/chains' +import { useSupportedChainId } from 'constants/chains' import { NATIVE_CHAIN_ID } from 'constants/tokens' import { useAccount } from 'hooks/useAccount' import { useMemo } from 'react' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { COMMON_BASES } from 'uniswap/src/constants/routing' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { useCurrencyInfo as useUniswapCurrencyInfo } from 'uniswap/src/features/tokens/useCurrencyInfo' -import { InterfaceChainId, UniverseChainId } from 'uniswap/src/types/chains' import { buildCurrencyId } from 'uniswap/src/utils/currencyId' import { isAddress, isSameAddress } from 'utilities/src/addresses' type Maybe = T | undefined -export function useCurrency(address?: string, chainId?: InterfaceChainId, skip?: boolean): Maybe { +export function useCurrency(address?: string, chainId?: UniverseChainId, skip?: boolean): Maybe { const currencyInfo = useCurrencyInfo(address, chainId, skip) return currencyInfo?.currency } @@ -22,11 +22,11 @@ export function useCurrency(address?: string, chainId?: InterfaceChainId, skip?: /** * Returns a CurrencyInfo from the tokenAddress+chainId pair. */ -export function useCurrencyInfo(currency?: Currency, chainId?: InterfaceChainId, skip?: boolean): Maybe -export function useCurrencyInfo(address?: string, chainId?: InterfaceChainId, skip?: boolean): Maybe +export function useCurrencyInfo(currency?: Currency, chainId?: UniverseChainId, skip?: boolean): Maybe +export function useCurrencyInfo(address?: string, chainId?: UniverseChainId, skip?: boolean): Maybe export function useCurrencyInfo( addressOrCurrency?: string | Currency, - chainId?: InterfaceChainId, + chainId?: UniverseChainId, skip?: boolean, ): Maybe { const { chainId: connectedChainId } = useAccount() @@ -104,7 +104,7 @@ const getAddress = ( return undefined } -export function useToken(tokenAddress?: string, chainId?: SupportedInterfaceChainId): Maybe { +export function useToken(tokenAddress?: string, chainId?: UniverseChainId): Maybe { const formattedAddress = isAddress(tokenAddress) const { chainId: connectedChainId } = useAccount() const currency = useCurrency(formattedAddress ? formattedAddress : undefined, chainId ?? connectedChainId) diff --git a/apps/web/src/hooks/useAccount.ts b/apps/web/src/hooks/useAccount.ts index f072102d8d5..f89896332bf 100644 --- a/apps/web/src/hooks/useAccount.ts +++ b/apps/web/src/hooks/useAccount.ts @@ -1,13 +1,14 @@ /* eslint-disable rulesdir/no-undefined-or */ -import { SupportedInterfaceChainId, useSupportedChainId } from 'constants/chains' +import { useSupportedChainId } from 'constants/chains' import { useMemo } from 'react' +import { UniverseChainId } from 'uniswap/src/types/chains' // eslint-disable-next-line @typescript-eslint/no-restricted-imports import { UseAccountReturnType as UseAccountReturnTypeWagmi, useAccount as useAccountWagmi, useChainId } from 'wagmi' type ReplaceChainId = T extends { chainId: number } - ? Omit & { chainId: SupportedInterfaceChainId | undefined } + ? Omit & { chainId: UniverseChainId | undefined } : T extends { chainId: number | undefined } - ? Omit & { chainId: SupportedInterfaceChainId | undefined } + ? Omit & { chainId: UniverseChainId | undefined } : T type UseAccountReturnType = ReplaceChainId diff --git a/apps/web/src/hooks/useConnectedWalletSupportedChains.ts b/apps/web/src/hooks/useConnectedWalletSupportedChains.ts deleted file mode 100644 index 9e55c386b96..00000000000 --- a/apps/web/src/hooks/useConnectedWalletSupportedChains.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { CONNECTION } from 'components/Web3Provider/constants' -import { WalletConnectConnector } from 'components/Web3Provider/walletConnect' -import { useAccount } from 'hooks/useAccount' -import { ALL_CHAIN_IDS } from 'uniswap/src/constants/chains' -import { InterfaceChainId } from 'uniswap/src/types/chains' - -// Returns the chain ids supported by the user's connected wallet -export function useConnectedWalletSupportedChains(): InterfaceChainId[] { - const { connector } = useAccount() - - switch (connector?.type) { - case CONNECTION.UNISWAP_WALLET_CONNECT_CONNECTOR_ID: - case CONNECTION.WALLET_CONNECT_CONNECTOR_ID: - // Wagmi currently offers no way to discriminate a Connector as a WalletConnect connector providing access to getNamespaceChainsIds. - return (connector as WalletConnectConnector).getNamespaceChainsIds?.().length - ? (connector as WalletConnectConnector).getNamespaceChainsIds?.() - : ALL_CHAIN_IDS - default: - return ALL_CHAIN_IDS - } -} diff --git a/apps/web/src/hooks/useContract.ts b/apps/web/src/hooks/useContract.ts index a14e1a9c193..0e3a34ebcf0 100644 --- a/apps/web/src/hooks/useContract.ts +++ b/apps/web/src/hooks/useContract.ts @@ -38,7 +38,7 @@ import { V3Migrator } from 'uniswap/src/abis/types/v3/V3Migrator' import WETH_ABI from 'uniswap/src/abis/weth.json' import { WRAPPED_NATIVE_CURRENCY } from 'uniswap/src/constants/tokens' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' -import { InterfaceChainId, UniverseChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { getContract } from 'utilities/src/contracts/getContract' import { logger } from 'utilities/src/logger/logger' @@ -53,7 +53,7 @@ export function useContract( address: string | undefined, ABI: any, withSignerIfPossible = true, - chainId?: InterfaceChainId, + chainId?: UniverseChainId, ): T | null { const account = useAccount() const provider = useEthersProvider({ chainId: chainId ?? account.chainId }) @@ -108,11 +108,11 @@ export function useV2MigratorContract() { ) } -export function useTokenContract(tokenAddress?: string, withSignerIfPossible?: boolean, chainId?: InterfaceChainId) { +export function useTokenContract(tokenAddress?: string, withSignerIfPossible?: boolean, chainId?: UniverseChainId) { return useContract(tokenAddress, ERC20_ABI, withSignerIfPossible, chainId) } -export function useWETHContract(withSignerIfPossible?: boolean, chainId?: InterfaceChainId) { +export function useWETHContract(withSignerIfPossible?: boolean, chainId?: UniverseChainId) { return useContract( chainId ? WRAPPED_NATIVE_CURRENCY[chainId]?.address : undefined, WETH_ABI, @@ -159,7 +159,7 @@ export function useV2RouterContract(): Contract | null { return useContract(chainId ? V2_ROUTER_ADDRESSES[chainId] : undefined, IUniswapV2Router02ABI, true) } -export function useInterfaceMulticall(chainId?: InterfaceChainId) { +export function useInterfaceMulticall(chainId?: UniverseChainId) { const account = useAccount() const chain = chainId ?? account.chainId return useContract( diff --git a/apps/web/src/hooks/useERC20Permit.ts b/apps/web/src/hooks/useERC20Permit.ts index 688e8b0c960..e17057a1271 100644 --- a/apps/web/src/hooks/useERC20Permit.ts +++ b/apps/web/src/hooks/useERC20Permit.ts @@ -9,7 +9,7 @@ import JSBI from 'jsbi' import { useSingleCallResult } from 'lib/hooks/multicall' import { useMemo, useState } from 'react' import { DAI, UNI, USDC_MAINNET } from 'uniswap/src/constants/tokens' -import { InterfaceChainId, UniverseChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' export enum PermitType { AMOUNT = 1, @@ -37,9 +37,6 @@ const PERMITTABLE_TOKENS: { [DAI.address]: { type: PermitType.ALLOWED, name: 'Dai Stablecoin', version: '1' }, [UNI[UniverseChainId.Mainnet].address]: { type: PermitType.AMOUNT, name: 'Uniswap' }, }, - [UniverseChainId.Goerli]: { - [UNI[UniverseChainId.Goerli].address]: { type: PermitType.AMOUNT, name: 'Uniswap' }, - }, [UniverseChainId.Sepolia]: { [UNI[UniverseChainId.Sepolia].address]: { type: PermitType.AMOUNT, name: 'Uniswap' }, }, @@ -226,7 +223,7 @@ export function useERC20Permit( deadline: signatureDeadline, ...(allowed ? { allowed } : { amount: value }), nonce: nonceNumber, - chainId: account.chainId as InterfaceChainId, + chainId: account.chainId as UniverseChainId, owner: account.address as string, spender, tokenAddress, diff --git a/apps/web/src/hooks/useFilterPossiblyMaliciousPositions.ts b/apps/web/src/hooks/useFilterPossiblyMaliciousPositions.ts index a6e2dd85165..148c40d577a 100644 --- a/apps/web/src/hooks/useFilterPossiblyMaliciousPositions.ts +++ b/apps/web/src/hooks/useFilterPossiblyMaliciousPositions.ts @@ -1,5 +1,5 @@ import { useQueries } from '@tanstack/react-query' -import { SupportedInterfaceChainId, chainIdToBackendChain } from 'constants/chains' +import { chainIdToBackendChain } from 'constants/chains' import { apolloClient } from 'graphql/data/apollo/client' import { gqlTokenToCurrencyInfo } from 'graphql/data/types' import { apolloQueryOptions } from 'graphql/data/util' @@ -13,6 +13,7 @@ import { TokenDocument, TokenQuery, } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' +import { UniverseChainId } from 'uniswap/src/types/chains' import { hasURL } from 'utils/urlChecks' function getUniqueAddressesFromPositions(positions: PositionDetails[]): string[] { @@ -21,7 +22,7 @@ function getUniqueAddressesFromPositions(positions: PositionDetails[]): string[] ) } -function getPositionCurrencyInfosQueryOptions(position: PositionDetails, chainId?: SupportedInterfaceChainId) { +function getPositionCurrencyInfosQueryOptions(position: PositionDetails, chainId?: UniverseChainId) { return apolloQueryOptions({ queryKey: ['positionCurrencyInfo', position], queryFn: async () => { diff --git a/apps/web/src/hooks/usePoolTickData.ts b/apps/web/src/hooks/usePoolTickData.ts index 417f8d0be79..b48f4b73050 100644 --- a/apps/web/src/hooks/usePoolTickData.ts +++ b/apps/web/src/hooks/usePoolTickData.ts @@ -8,7 +8,7 @@ import JSBI from 'jsbi' import ms from 'ms' import { useEffect, useMemo, useState } from 'react' import { useAllV3TicksQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { InterfaceChainId, UniverseChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { logger } from 'utilities/src/logger/logger' import computeSurroundingTicks from 'utils/computeSurroundingTicks' @@ -32,7 +32,7 @@ function useTicksFromSubgraph( currencyB: Currency | undefined, feeAmount: FeeAmount | undefined, skip = 0, - chainId: InterfaceChainId, + chainId: UniverseChainId, ) { const poolAddress = currencyA && currencyB && feeAmount @@ -63,7 +63,7 @@ function useAllV3Ticks( currencyA: Currency | undefined, currencyB: Currency | undefined, feeAmount: FeeAmount | undefined, - chainId: InterfaceChainId, + chainId: UniverseChainId, ): { isLoading: boolean error: unknown @@ -94,7 +94,7 @@ export function usePoolActiveLiquidity( currencyA: Currency | undefined, currencyB: Currency | undefined, feeAmount: FeeAmount | undefined, - chainId?: InterfaceChainId, + chainId?: UniverseChainId, ): { isLoading: boolean error: any diff --git a/apps/web/src/hooks/usePools.ts b/apps/web/src/hooks/usePools.ts index 7ddf29e4b79..a1106824f9b 100644 --- a/apps/web/src/hooks/usePools.ts +++ b/apps/web/src/hooks/usePools.ts @@ -10,7 +10,7 @@ import { useEffect, useMemo, useRef } from 'react' import { IUniswapV3PoolStateInterface } from 'uniswap/src/abis/types/v3/IUniswapV3PoolState' import { UniswapV3Pool } from 'uniswap/src/abis/types/v3/UniswapV3Pool' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { logger } from 'utilities/src/logger/logger' const POOL_STATE_INTERFACE = new Interface(IUniswapV3PoolStateJSON.abi) as IUniswapV3PoolStateInterface @@ -30,7 +30,7 @@ export class PoolCache { tokenA: Token, tokenB: Token, fee: FeeAmount, - chainId: InterfaceChainId, + chainId: UniverseChainId, ): string { if (this.addresses.length > this.MAX_ENTRIES) { this.addresses = this.addresses.slice(0, this.MAX_ENTRIES / 2) @@ -202,7 +202,7 @@ export function usePoolMultichain( tokenA: Token | undefined, tokenB: Token | undefined, fee: number | undefined, - chainId: InterfaceChainId, + chainId: UniverseChainId, ): [PoolState, Pool | null] { const poolData = useRef<[PoolState, Pool | null]>([PoolState.LOADING, null]) const poolAddress = diff --git a/apps/web/src/hooks/useSelectChain.ts b/apps/web/src/hooks/useSelectChain.ts index 3f97f2ad9b0..488ec4567c5 100644 --- a/apps/web/src/hooks/useSelectChain.ts +++ b/apps/web/src/hooks/useSelectChain.ts @@ -2,7 +2,7 @@ import { useSwitchChain } from 'hooks/useSwitchChain' import { useCallback } from 'react' import { useDispatch } from 'react-redux' import { PopupType, addPopup, removePopup } from 'state/application/reducer' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { logger } from 'utilities/src/logger/logger' import { UserRejectedRequestError } from 'viem' @@ -11,7 +11,7 @@ export default function useSelectChain() { const switchChain = useSwitchChain() return useCallback( - async (targetChain: InterfaceChainId) => { + async (targetChain: UniverseChainId) => { try { await switchChain(targetChain) dispatch( diff --git a/apps/web/src/hooks/useSupportedChainIds.ts b/apps/web/src/hooks/useSupportedChainIds.ts deleted file mode 100644 index 6322a2e153b..00000000000 --- a/apps/web/src/hooks/useSupportedChainIds.ts +++ /dev/null @@ -1,35 +0,0 @@ -// Returns supported and unsupported chainIds based on the connected wallet - -import { showTestnetsAtom } from 'components/AccountDrawer/TestnetsToggle' -import { TESTNET_CHAIN_IDS, getChainPriority, useIsSupportedChainIdCallback } from 'constants/chains' -import { useConnectedWalletSupportedChains } from 'hooks/useConnectedWalletSupportedChains' -import { useAtomValue } from 'jotai/utils' -import { useMemo } from 'react' -import { ALL_CHAIN_IDS } from 'uniswap/src/constants/chains' -import { InterfaceChainId } from 'uniswap/src/types/chains' - -// Returns testnets if testnets are enabled -export function useSupportedChainIds(): { supported: InterfaceChainId[]; unsupported: InterfaceChainId[] } { - const walletSupportsChain = useConnectedWalletSupportedChains() - const isSupportedChain = useIsSupportedChainIdCallback() - const showTestnets = useAtomValue(showTestnetsAtom) - - return useMemo(() => { - const { supported, unsupported } = ALL_CHAIN_IDS.filter((chain: number) => { - return isSupportedChain(chain) && (showTestnets || !TESTNET_CHAIN_IDS.includes(chain)) - }) - .sort((a, b) => getChainPriority(a) - getChainPriority(b)) - .reduce( - (acc, chain) => { - if (walletSupportsChain.includes(chain)) { - acc.supported.push(chain) - } else { - acc.unsupported.push(chain) - } - return acc - }, - { supported: [], unsupported: [] } as Record, - ) - return { supported, unsupported } - }, [isSupportedChain, showTestnets, walletSupportsChain]) -} diff --git a/apps/web/src/hooks/useSwapCallback.tsx b/apps/web/src/hooks/useSwapCallback.tsx index 488a1195693..88224e9ac94 100644 --- a/apps/web/src/hooks/useSwapCallback.tsx +++ b/apps/web/src/hooks/useSwapCallback.tsx @@ -1,7 +1,7 @@ import { Percent, TradeType } from '@uniswap/sdk-core' import { FlatFeeOptions } from '@uniswap/universal-router-sdk' import { FeeOptions } from '@uniswap/v3-sdk' -import { SupportedInterfaceChainId, useSupportedChainId } from 'constants/chains' +import { useSupportedChainId } from 'constants/chains' import { BigNumber } from 'ethers/lib/ethers' import { useAccount } from 'hooks/useAccount' import { PermitSignature } from 'hooks/usePermitAllowance' @@ -20,6 +20,7 @@ import { ExactOutputSwapTransactionInfo, TransactionType, } from 'state/transactions/types' +import { UniverseChainId } from 'uniswap/src/types/chains' import { currencyId } from 'utils/currencyId' export type SwapResult = Awaited>> @@ -115,7 +116,7 @@ export function useSwapCallback( addOrder( account.address, result.response.orderHash, - supportedConnectedChainId as SupportedInterfaceChainId, // satisfies type-checker; already checked & switched chain above if !supportedConnectedChainId + supportedConnectedChainId as UniverseChainId, // satisfies type-checker; already checked & switched chain above if !supportedConnectedChainId result.response.deadline, swapInfo as UniswapXOrderDetails['swapInfo'], result.response.encodedOrder, diff --git a/apps/web/src/hooks/useSwapTaxes.ts b/apps/web/src/hooks/useSwapTaxes.ts index b1121c13ae8..29d4375b724 100644 --- a/apps/web/src/hooks/useSwapTaxes.ts +++ b/apps/web/src/hooks/useSwapTaxes.ts @@ -8,11 +8,11 @@ import { useEffect, useState } from 'react' import FOT_DETECTOR_ABI from 'uniswap/src/abis/fee-on-transfer-detector.json' import { FeeOnTransferDetector } from 'uniswap/src/abis/types' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' -import { InterfaceChainId, UniverseChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { logger } from 'utilities/src/logger/logger' // TODO(WEB-4058): Move all of these contract addresses into the top-level wagmi config -function getFeeOnTransferAddress(chainId?: InterfaceChainId) { +function getFeeOnTransferAddress(chainId?: UniverseChainId) { switch (chainId) { case UniverseChainId.Mainnet: return '0x19C97dc2a25845C7f9d1d519c8C2d4809c58b43f' @@ -35,7 +35,7 @@ function getFeeOnTransferAddress(chainId?: InterfaceChainId) { } } -function useFeeOnTransferDetectorContract(chainId?: InterfaceChainId): FeeOnTransferDetector | null { +function useFeeOnTransferDetectorContract(chainId?: UniverseChainId): FeeOnTransferDetector | null { const account = useAccount() const contract = useContract(getFeeOnTransferAddress(chainId), FOT_DETECTOR_ABI, true, chainId) @@ -61,7 +61,7 @@ async function getSwapTaxes( fotDetector: FeeOnTransferDetector, inputTokenAddress: string | undefined, outputTokenAddress: string | undefined, - chainId: InterfaceChainId, + chainId: UniverseChainId, ) { const addresses = [] if (inputTokenAddress && FEE_CACHE[inputTokenAddress] === undefined) { @@ -95,7 +95,7 @@ async function getSwapTaxes( } // Use the buyFeeBps/sellFeeBps fields from Token GQL query where possible instead of this hook -export function useSwapTaxes(inputTokenAddress?: string, outputTokenAddress?: string, tokenChainId?: InterfaceChainId) { +export function useSwapTaxes(inputTokenAddress?: string, outputTokenAddress?: string, tokenChainId?: UniverseChainId) { const account = useAccount() const chainId = tokenChainId ?? account.chainId const fotDetector = useFeeOnTransferDetectorContract(chainId) diff --git a/apps/web/src/hooks/useSwitchChain.ts b/apps/web/src/hooks/useSwitchChain.ts index 278a489bbeb..2b136ae7b12 100644 --- a/apps/web/src/hooks/useSwitchChain.ts +++ b/apps/web/src/hooks/useSwitchChain.ts @@ -4,7 +4,7 @@ import { useCallback } from 'react' import { useDispatch } from 'react-redux' import { endSwitchingChain, startSwitchingChain } from 'state/wallets/reducer' import { trace } from 'tracing/trace' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { useSwitchChain as useSwitchChainWagmi } from 'wagmi' export function useSwitchChain() { @@ -14,7 +14,7 @@ export function useSwitchChain() { const account = useAccount() return useCallback( - (chainId: InterfaceChainId) => { + (chainId: UniverseChainId) => { const isSupportedChain = isSupportedChainCallback(chainId) if (!isSupportedChain) { throw new Error(`Chain ${chainId} not supported for connector (${account.connector?.name})`) diff --git a/apps/web/src/hooks/useTokenBalances.ts b/apps/web/src/hooks/useTokenBalances.ts index bee4eed2761..cc244adf8a3 100644 --- a/apps/web/src/hooks/useTokenBalances.ts +++ b/apps/web/src/hooks/useTokenBalances.ts @@ -3,11 +3,11 @@ import { PortfolioBalance } from 'graphql/data/portfolios' import { useAccount } from 'hooks/useAccount' import { TokenBalances } from 'lib/hooks/useTokenList/sorting' import { useMemo } from 'react' -import { GQL_MAINNET_CHAINS_MUTABLE } from 'uniswap/src/constants/chains' import { QuickTokenBalancePartsFragment, useQuickTokenBalancesWebQuery, } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { currencyKeyFromGraphQL } from 'utils/currencyKey' /** @@ -19,12 +19,13 @@ export function useTokenBalances({ cacheOnly }: { cacheOnly?: boolean } = {}): { loading: boolean } { const account = useAccount() + const { gqlChains } = useEnabledChains() // Quick result is always available at pageload, but never refetched when stale const quickQueryResult = useQuickTokenBalancesWebQuery({ variables: { ownerAddress: account.address ?? '', - chains: GQL_MAINNET_CHAINS_MUTABLE, + chains: gqlChains, }, skip: !account.address, fetchPolicy: 'cache-first', diff --git a/apps/web/src/hooks/useUSDPrice.ts b/apps/web/src/hooks/useUSDPrice.ts index a17de716e2d..69e1dc5eddd 100644 --- a/apps/web/src/hooks/useUSDPrice.ts +++ b/apps/web/src/hooks/useUSDPrice.ts @@ -1,11 +1,6 @@ import { NetworkStatus } from '@apollo/client' import { Currency, CurrencyAmount, Price, TradeType } from '@uniswap/sdk-core' -import { - SupportedInterfaceChainId, - chainIdToBackendChain, - useIsSupportedChainId, - useSupportedChainId, -} from 'constants/chains' +import { chainIdToBackendChain, useIsSupportedChainId, useSupportedChainId } from 'constants/chains' import { PollingInterval } from 'graphql/data/util' import useIsWindowVisible from 'hooks/useIsWindowVisible' import useStablecoinPrice from 'hooks/useStablecoinPrice' @@ -19,7 +14,7 @@ import { getNativeTokenDBAddress } from 'utils/nativeTokens' // ETH amounts used when calculating spot price for a given currency. // The amount is large enough to filter low liquidity pairs. -function getEthAmountOut(chainId: SupportedInterfaceChainId): CurrencyAmount { +function getEthAmountOut(chainId: UniverseChainId): CurrencyAmount { return CurrencyAmount.fromRawAmount(nativeOnChain(chainId), chainId === UniverseChainId.Mainnet ? 50e18 : 10e18) } diff --git a/apps/web/src/hooks/useUniswapWalletOptions.ts b/apps/web/src/hooks/useUniswapWalletOptions.ts index b58e0709ba8..d023691eabd 100644 --- a/apps/web/src/hooks/useUniswapWalletOptions.ts +++ b/apps/web/src/hooks/useUniswapWalletOptions.ts @@ -1,5 +1,5 @@ -import { CONNECTION } from 'components/Web3Provider/constants' import { useAccount } from 'hooks/useAccount' +import { CONNECTION_PROVIDER_IDS } from 'uniswap/src/constants/web3' // // Checks if the user is connected to the uniswap extension. @@ -12,7 +12,8 @@ import { useAccount } from 'hooks/useAccount' // export function useIsUniExtensionAvailable() { const currentConnector = useAccount().connector - const currentConnectIsNotUniExtension = currentConnector && currentConnector.id !== CONNECTION.UNISWAP_EXTENSION_RDNS + const currentConnectIsNotUniExtension = + currentConnector && currentConnector.id !== CONNECTION_PROVIDER_IDS.UNISWAP_EXTENSION_RDNS return !currentConnectIsNotUniExtension } diff --git a/apps/web/src/hooks/useUniversalRouter.ts b/apps/web/src/hooks/useUniversalRouter.ts index 95a141510c0..d2034c66505 100644 --- a/apps/web/src/hooks/useUniversalRouter.ts +++ b/apps/web/src/hooks/useUniversalRouter.ts @@ -2,7 +2,12 @@ import { TransactionResponse } from '@ethersproject/abstract-provider' import { BigNumber } from '@ethersproject/bignumber' import { CustomUserProperties, SwapEventName } from '@uniswap/analytics-events' import { Percent } from '@uniswap/sdk-core' -import { FlatFeeOptions, SwapRouter, UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk' +import { + FlatFeeOptions, + SwapRouter, + UNIVERSAL_ROUTER_ADDRESS, + UniversalRouterVersion, +} from '@uniswap/universal-router-sdk' import { FeeOptions, toHex } from '@uniswap/v3-sdk' import { useTotalBalancesUsdForAnalytics } from 'graphql/data/apollo/useTotalBalancesUsdForAnalytics' import { useAccount } from 'hooks/useAccount' @@ -93,7 +98,7 @@ export function useUniversalRouterSwapCallback( const deadline = await getDeadline() trace.setData('slippageTolerance', options.slippageTolerance.toFixed(2)) - const { calldata: data, value } = SwapRouter.swapERC20CallParameters(trade, { + const { calldata: data, value } = SwapRouter.swapCallParameters(trade, { slippageTolerance: options.slippageTolerance, deadlineOrPreviousBlockhash: deadline?.toString(), inputTokenPermit: options.permit, @@ -102,7 +107,7 @@ export function useUniversalRouterSwapCallback( }) const tx = { from: account.address, - to: UNIVERSAL_ROUTER_ADDRESS(chainId), + to: UNIVERSAL_ROUTER_ADDRESS(UniversalRouterVersion.V1_2, chainId), data, // TODO(https://github.com/Uniswap/universal-router-sdk/issues/113): universal-router-sdk returns a non-hexlified value. ...(value && !isZero(value) ? { value: toHex(value) } : {}), diff --git a/apps/web/src/index.tsx b/apps/web/src/index.tsx index d4a50f490cc..9a8af4de3b2 100644 --- a/apps/web/src/index.tsx +++ b/apps/web/src/index.tsx @@ -113,11 +113,11 @@ createRoot(container).render( - - - - - + + + + + @@ -127,11 +127,11 @@ createRoot(container).render( - - - - - + + + + + diff --git a/apps/web/src/lib/hooks/routing/clientSideSmartOrderRouter.ts b/apps/web/src/lib/hooks/routing/clientSideSmartOrderRouter.ts index a2a716d82a1..6165603ab55 100644 --- a/apps/web/src/lib/hooks/routing/clientSideSmartOrderRouter.ts +++ b/apps/web/src/lib/hooks/routing/clientSideSmartOrderRouter.ts @@ -2,24 +2,24 @@ import { BigintIsh, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core' // This file is lazy-loaded, so the import of smart-order-router is intentional. // eslint-disable-next-line @typescript-eslint/no-restricted-imports import { AlphaRouter, AlphaRouterConfig } from '@uniswap/smart-order-router' -import { SupportedInterfaceChainId, getChain, isSupportedChainId } from 'constants/chains' +import { getChain, isSupportedChainId } from 'constants/chains' import { RPC_PROVIDERS } from 'constants/providers' import JSBI from 'jsbi' import { GetQuoteArgs, QuoteResult, QuoteState, SwapRouterNativeAssets } from 'state/routing/types' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { nativeOnChain } from 'uniswap/src/constants/tokens' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { transformSwapRouteToGetQuoteResult } from 'utils/transformSwapRouteToGetQuoteResult' -const routers = new Map() -export function getRouter(chainId: InterfaceChainId): AlphaRouter { +const routers = new Map() +export function getRouter(chainId: UniverseChainId): AlphaRouter { const router = routers.get(chainId) if (router) { return router } if (isSupportedChainId(chainId) && getChain({ chainId }).supportsClientSideRouting) { - const provider = RPC_PROVIDERS[chainId as SupportedInterfaceChainId] + const provider = RPC_PROVIDERS[chainId as UniverseChainId] const router = new AlphaRouter({ chainId: UNIVERSE_CHAIN_INFO[chainId].sdkId, provider }) routers.set(chainId, router) return router diff --git a/apps/web/src/lib/hooks/useNativeCurrency.ts b/apps/web/src/lib/hooks/useNativeCurrency.ts index 370ec2ca149..943b3d2c510 100644 --- a/apps/web/src/lib/hooks/useNativeCurrency.ts +++ b/apps/web/src/lib/hooks/useNativeCurrency.ts @@ -1,9 +1,9 @@ import { NativeCurrency, Token } from '@uniswap/sdk-core' import { useMemo } from 'react' import { nativeOnChain } from 'uniswap/src/constants/tokens' -import { InterfaceChainId, UniverseChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' -export default function useNativeCurrency(chainId: InterfaceChainId | null | undefined): NativeCurrency | Token { +export default function useNativeCurrency(chainId: UniverseChainId | null | undefined): NativeCurrency | Token { return useMemo( () => chainId diff --git a/apps/web/src/lib/hooks/useTokenList/sorting.ts b/apps/web/src/lib/hooks/useTokenList/sorting.ts index 772eb8abd55..0bc0d8523bf 100644 --- a/apps/web/src/lib/hooks/useTokenList/sorting.ts +++ b/apps/web/src/lib/hooks/useTokenList/sorting.ts @@ -2,7 +2,7 @@ import { Token } from '@uniswap/sdk-core' import { PortfolioBalance } from 'graphql/data/portfolios' import { supportedChainIdFromGQLChain } from 'graphql/data/util' import { nativeOnChain } from 'uniswap/src/constants/tokens' -import { InterfaceChainId, UniverseChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { currencyKey } from 'utils/currencyKey' import { SplitOptions, splitHiddenTokens } from 'utils/splitHiddenTokens' @@ -42,7 +42,7 @@ function tokenComparator(balances: TokenBalances, a: Token, b: Token) { export function getSortedPortfolioTokens( portfolioTokenBalances: readonly (PortfolioBalance | undefined)[] | undefined, balances: TokenBalances, - chainId: InterfaceChainId | undefined, + chainId: UniverseChainId | undefined, splitOptions?: SplitOptions, ): Token[] { const validVisiblePortfolioTokens = splitHiddenTokens(portfolioTokenBalances ?? [], splitOptions) diff --git a/apps/web/src/nft/components/bag/BagFooter.test.tsx b/apps/web/src/nft/components/bag/BagFooter.test.tsx index 493c871c746..cbcd22d0c67 100644 --- a/apps/web/src/nft/components/bag/BagFooter.test.tsx +++ b/apps/web/src/nft/components/bag/BagFooter.test.tsx @@ -2,7 +2,7 @@ import 'test-utils/tokens/mocks' import { BigNumber } from '@ethersproject/bignumber' import { CurrencyAmount, Percent } from '@uniswap/sdk-core' -import { UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk' +import { UNIVERSAL_ROUTER_ADDRESS, UniversalRouterVersion } from '@uniswap/universal-router-sdk' import { getURAddress, useNftUniversalRouterAddress } from 'graphql/data/nft/NftUniversalRouterAddress' import { useAccount } from 'hooks/useAccount' import usePermit2Allowance, { AllowanceState } from 'hooks/usePermit2Allowance' @@ -410,8 +410,12 @@ describe('BagFooter.tsx', () => { it('should use the correct UR address', () => { expect(getURAddress(undefined)).toBe(undefined) - expect(getURAddress(UniverseChainId.Mainnet)).toBe(UNIVERSAL_ROUTER_ADDRESS(UniverseChainId.Mainnet)) + expect(getURAddress(UniverseChainId.Mainnet)).toBe( + UNIVERSAL_ROUTER_ADDRESS(UniversalRouterVersion.V1_2, UniverseChainId.Mainnet), + ) expect(getURAddress(UniverseChainId.Mainnet, 'test_nft_ur_address')).toBe('test_nft_ur_address') - expect(getURAddress(UniverseChainId.Optimism)).toBe(UNIVERSAL_ROUTER_ADDRESS(UniverseChainId.Optimism)) + expect(getURAddress(UniverseChainId.Optimism)).toBe( + UNIVERSAL_ROUTER_ADDRESS(UniversalRouterVersion.V1_2, UniverseChainId.Optimism), + ) }) }) diff --git a/apps/web/src/nft/utils/tokenRoutes.ts b/apps/web/src/nft/utils/tokenRoutes.ts index 939bf5b72fd..e9a8b070575 100644 --- a/apps/web/src/nft/utils/tokenRoutes.ts +++ b/apps/web/src/nft/utils/tokenRoutes.ts @@ -1,8 +1,10 @@ import { IRoute, Protocol } from '@uniswap/router-sdk' import { Currency, CurrencyAmount } from '@uniswap/sdk-core' import { Pair } from '@uniswap/v2-sdk' -import { Pool } from '@uniswap/v3-sdk' +import { Pool as V3Pool } from '@uniswap/v3-sdk' +import { Pool as V4Pool } from '@uniswap/v4-sdk' import { ClassicTrade } from 'state/routing/types' +import { DEFAULT_NATIVE_ADDRESS, UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { TokenAmountInput, TokenTradeRouteInput, @@ -20,7 +22,7 @@ interface TradeTokenInputAmounts { } interface Swap { - route: IRoute + route: IRoute inputAmount: CurrencyAmount outputAmount: CurrencyAmount } @@ -32,7 +34,7 @@ function buildTradeRouteInputAmounts(swapAmounts: SwapAmounts): TradeTokenInputA token: { address: swapAmounts.inputAmount.currency.isToken ? swapAmounts.inputAmount.currency.address - : '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + : DEFAULT_NATIVE_ADDRESS, chainId: swapAmounts.inputAmount.currency.chainId, decimals: swapAmounts.inputAmount.currency.decimals, isNative: swapAmounts.inputAmount.currency.isNative, @@ -43,7 +45,7 @@ function buildTradeRouteInputAmounts(swapAmounts: SwapAmounts): TradeTokenInputA token: { address: swapAmounts.outputAmount.currency.isToken ? swapAmounts.outputAmount.currency.address - : '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + : DEFAULT_NATIVE_ADDRESS, chainId: swapAmounts.outputAmount.currency.chainId, decimals: swapAmounts.outputAmount.currency.decimals, isNative: swapAmounts.outputAmount.currency.isNative, @@ -52,8 +54,12 @@ function buildTradeRouteInputAmounts(swapAmounts: SwapAmounts): TradeTokenInputA } } -function buildPool(pool: Pair | Pool): TradePoolInput { +function buildPool(pool: Pair | V3Pool | V4Pool): TradePoolInput { const isPool = 'fee' in pool + const chainIdInUniverseChainInfo = pool.chainId in UNIVERSE_CHAIN_INFO + const nativeCurrencyAddress = chainIdInUniverseChainInfo + ? UNIVERSE_CHAIN_INFO[pool.chainId as keyof typeof UNIVERSE_CHAIN_INFO].nativeCurrency.address + : DEFAULT_NATIVE_ADDRESS return { pair: !isPool @@ -85,13 +91,13 @@ function buildPool(pool: Pair | Pool): TradePoolInput { sqrtRatioX96: pool.sqrtRatioX96.toString(), tickCurrent: pool.tickCurrent.toString(), tokenA: { - address: pool.token0.address, + address: 'address' in pool.token0 ? pool.token0.address : nativeCurrencyAddress, chainId: pool.token0.chainId, decimals: pool.token0.decimals, isNative: pool.token0.isNative, }, tokenB: { - address: pool.token1.address, + address: 'address' in pool.token1 ? pool.token1.address : nativeCurrencyAddress, chainId: pool.token1.chainId, decimals: pool.token1.decimals, isNative: pool.token1.isNative, @@ -101,7 +107,7 @@ function buildPool(pool: Pair | Pool): TradePoolInput { } } -function buildPools(pools: (Pair | Pool)[]): TradePoolInput[] { +function buildPools(pools: (Pair | V3Pool | V4Pool)[]): TradePoolInput[] { return pools.map((pool) => buildPool(pool)) } diff --git a/apps/web/src/pages/Explore/charts/ExploreChartsSection.tsx b/apps/web/src/pages/Explore/charts/ExploreChartsSection.tsx index 749181b410e..0a4756e2a62 100644 --- a/apps/web/src/pages/Explore/charts/ExploreChartsSection.tsx +++ b/apps/web/src/pages/Explore/charts/ExploreChartsSection.tsx @@ -9,7 +9,7 @@ import { getCumulativeSum, getCumulativeVolume, getVolumeProtocolInfo } from 'co import { ChartType } from 'components/Charts/utils' import { DataQuality } from 'components/Tokens/TokenDetails/ChartSection/util' import { MAX_WIDTH_MEDIA_BREAKPOINT } from 'components/Tokens/constants' -import { SupportedInterfaceChainId, chainIdToBackendChain, useChainFromUrlParam } from 'constants/chains' +import { chainIdToBackendChain, useChainFromUrlParam } from 'constants/chains' import { useDailyProtocolTVL, useHistoricalProtocolVolume } from 'graphql/data/protocolStats' import { TimePeriod, getProtocolColor, getProtocolGradient, getSupportedGraphQlChain } from 'graphql/data/util' import { useScreenSize } from 'hooks/screenSize/useScreenSize' @@ -26,6 +26,7 @@ import { HistoryDuration, PriceSource } from 'uniswap/src/data/graphql/uniswap-d import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag, useFeatureFlagWithLoading } from 'uniswap/src/features/gating/hooks' import { Trans } from 'uniswap/src/i18n' +import { UniverseChainId } from 'uniswap/src/types/chains' import { NumberType, useFormatter } from 'utils/formatNumbers' const EXPLORE_CHART_HEIGHT_PX = 368 @@ -69,7 +70,7 @@ const SectionTitle = styled(Text, { lineHeight: 24, }) -function VolumeChartSection({ chainId }: { chainId: SupportedInterfaceChainId }) { +function VolumeChartSection({ chainId }: { chainId: UniverseChainId }) { const [timePeriod, setTimePeriod] = useState(TimePeriod.DAY) const theme = useTheme() const isSmallScreen = !useScreenSize()['sm'] @@ -193,7 +194,7 @@ function VolumeChartSection({ chainId }: { chainId: SupportedInterfaceChainId }) ) } -function TVLChartSection({ chainId }: { chainId: SupportedInterfaceChainId }) { +function TVLChartSection({ chainId }: { chainId: UniverseChainId }) { const theme = useTheme() const isMultichainExploreEnabled = useFeatureFlag(FeatureFlags.MultichainExplore) const { diff --git a/apps/web/src/pages/IncreaseLiquidity/IncreaseLiquidityForm.tsx b/apps/web/src/pages/IncreaseLiquidity/IncreaseLiquidityForm.tsx index 97a16d2b8e6..5c3e67f5b7c 100644 --- a/apps/web/src/pages/IncreaseLiquidity/IncreaseLiquidityForm.tsx +++ b/apps/web/src/pages/IncreaseLiquidity/IncreaseLiquidityForm.tsx @@ -2,6 +2,7 @@ import { IncreaseLiquidityStep, useIncreaseLiquidityContext, } from 'components/IncreaseLiquidity/IncreaseLiquidityContext' +import { useIncreaseLiquidityTxContext } from 'components/IncreaseLiquidity/IncreaseLiquidityTxContext' import { DepositInputForm } from 'components/Liquidity/DepositInputForm' import { LiquidityModalDetailRows } from 'components/Liquidity/LiquidityModalDetailRows' import { LiquidityPositionInfo } from 'components/Liquidity/LiquidityPositionInfo' @@ -20,12 +21,13 @@ export function IncreaseLiquidityForm() { } = useIncreaseLiquidityContext() const { formattedAmounts, currencyAmounts, currencyAmountsUSDValue, currencyBalances } = derivedAddLiquidityInfo const { position } = addLiquidityState + const { gasFeeEstimateUSD } = useIncreaseLiquidityTxContext() if (!position) { throw new Error('AddLiquidityModal must have an initial state when opening') } - const { restPosition, currency0Amount, currency1Amount } = position + const { currency0Amount, currency1Amount } = position const token0 = currency0Amount.currency const token1 = currency1Amount.currency @@ -63,7 +65,7 @@ export function IncreaseLiquidityForm() { return ( <> - + - + diff --git a/apps/web/src/pages/IncreaseLiquidity/IncreaseLiquidityModal.tsx b/apps/web/src/pages/IncreaseLiquidity/IncreaseLiquidityModal.tsx index 6b6afe77ad3..615734f27df 100644 --- a/apps/web/src/pages/IncreaseLiquidity/IncreaseLiquidityModal.tsx +++ b/apps/web/src/pages/IncreaseLiquidity/IncreaseLiquidityModal.tsx @@ -4,10 +4,11 @@ import { useIncreaseLiquidityContext, } from 'components/IncreaseLiquidity/IncreaseLiquidityContext' import { IncreaseLiquidityReview } from 'components/IncreaseLiquidity/IncreaseLiquidityReview' +import { IncreaseLiquidityTxContextProvider } from 'components/IncreaseLiquidity/IncreaseLiquidityTxContext' import { LiquidityModalHeader } from 'components/Liquidity/LiquidityModalHeader' import { IncreaseLiquidityForm } from 'pages/IncreaseLiquidity/IncreaseLiquidityForm' import { useCloseModal } from 'state/application/hooks' -import { Flex } from 'ui/src' +import { Flex, HeightAnimator } from 'ui/src' import { Modal } from 'uniswap/src/components/modals/Modal' import { ModalName } from 'uniswap/src/features/telemetry/constants' import { useTranslation } from 'uniswap/src/i18n' @@ -24,7 +25,7 @@ function IncreaseLiquidityModalInner() { modalContent = break case IncreaseLiquidityStep.Review: - modalContent = + modalContent = break } @@ -37,7 +38,7 @@ function IncreaseLiquidityModalInner() { goBack={step === IncreaseLiquidityStep.Review ? () => setStep(IncreaseLiquidityStep.Input) : undefined} /> - {modalContent} + {modalContent} ) } @@ -45,7 +46,9 @@ function IncreaseLiquidityModalInner() { export function IncreaseLiquidityModal() { return ( - + + + ) } diff --git a/apps/web/src/pages/Landing/components/cards/WebappCard.tsx b/apps/web/src/pages/Landing/components/cards/WebappCard.tsx index a6cf6f8c59e..00e1e03dc41 100644 --- a/apps/web/src/pages/Landing/components/cards/WebappCard.tsx +++ b/apps/web/src/pages/Landing/components/cards/WebappCard.tsx @@ -1,6 +1,6 @@ import { PortfolioLogo } from 'components/AccountDrawer/MiniPortfolio/PortfolioLogo' import { DeltaArrow } from 'components/Tokens/TokenDetails/Delta' -import { SupportedInterfaceChainId, chainIdToBackendChain } from 'constants/chains' +import { chainIdToBackendChain } from 'constants/chains' import { NATIVE_CHAIN_ID } from 'constants/tokens' import { getTokenDetailsURL } from 'graphql/data/util' import { useCurrency } from 'hooks/Tokens' @@ -18,7 +18,7 @@ import { NumberType, useFormatter } from 'utils/formatNumbers' const primary = '#2ABDFF' -const tokens: { chainId: SupportedInterfaceChainId; address: string }[] = [ +const tokens: { chainId: UniverseChainId; address: string }[] = [ { chainId: UniverseChainId.Mainnet, address: 'ETH', @@ -37,7 +37,7 @@ const tokens: { chainId: SupportedInterfaceChainId; address: string }[] = [ }, ] -function Token({ chainId, address }: { chainId: SupportedInterfaceChainId; address: string }) { +function Token({ chainId, address }: { chainId: UniverseChainId; address: string }) { const screenIsSmall = useScreenSize()['sm'] const navigate = useNavigate() const { formatFiatPrice, formatDelta } = useFormatter() diff --git a/apps/web/src/pages/LegacyPool/PositionPage.tsx b/apps/web/src/pages/LegacyPool/PositionPage.tsx index a903037588c..55bc88d372a 100644 --- a/apps/web/src/pages/LegacyPool/PositionPage.tsx +++ b/apps/web/src/pages/LegacyPool/PositionPage.tsx @@ -18,12 +18,7 @@ import TransactionConfirmationModal, { ConfirmationModalContent } from 'componen import { AutoColumn } from 'components/deprecated/Column' import { RowBetween, RowFixed } from 'components/deprecated/Row' import { Dots } from 'components/swap/styled' -import { - SupportedInterfaceChainId, - chainIdToBackendChain, - useIsSupportedChainId, - useSupportedChainId, -} from 'constants/chains' +import { chainIdToBackendChain, useIsSupportedChainId, useSupportedChainId } from 'constants/chains' import { getPoolDetailsURL, getTokenDetailsURL, isGqlSupportedChain } from 'graphql/data/util' import { useToken } from 'hooks/Tokens' import { useAccount } from 'hooks/useAccount' @@ -208,7 +203,7 @@ const TokenLink = ({ children, chainId, address, -}: PropsWithChildren<{ chainId: SupportedInterfaceChainId; address: string }>) => { +}: PropsWithChildren<{ chainId: UniverseChainId; address: string }>) => { const tokenLink = getTokenDetailsURL({ address, chain: chainIdToBackendChain({ chainId }) }) return {children} } diff --git a/apps/web/src/pages/MigrateV3/index.tsx b/apps/web/src/pages/MigrateV3/index.tsx index a96d5f0353c..4d7729db2b0 100644 --- a/apps/web/src/pages/MigrateV3/index.tsx +++ b/apps/web/src/pages/MigrateV3/index.tsx @@ -2,7 +2,9 @@ import { ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' import { BreadcrumbNavContainer, BreadcrumbNavLink } from 'components/BreadcrumbNav' import { LiquidityPositionCard } from 'components/Liquidity/LiquidityPositionCard' -import { PositionInfo, parseRestPosition } from 'components/Liquidity/utils' +import { PositionInfo } from 'components/Liquidity/types' +import { parseRestPosition } from 'components/Liquidity/utils' +import { LoadingRows } from 'components/Loader/styled' import { PoolProgressIndicator } from 'components/PoolProgressIndicator/PoolProgressIndicator' import { CreatePositionContextProvider, PriceRangeContextProvider } from 'pages/Pool/Positions/create/ContextProviders' import { useCreatePositionContext } from 'pages/Pool/Positions/create/CreatePositionContext' @@ -10,6 +12,7 @@ import { EditSelectTokensStep } from 'pages/Pool/Positions/create/EditStep' import { SelectPriceRangeStep } from 'pages/Pool/Positions/create/RangeSelectionStep' import { SelectTokensStep } from 'pages/Pool/Positions/create/SelectTokenStep' import { PositionFlowStep } from 'pages/Pool/Positions/create/types' +import { LoadingRow } from 'pages/Pool/Positions/shared' import { useMemo } from 'react' import { ChevronRight } from 'react-feather' import { Navigate, useParams } from 'react-router-dom' @@ -18,7 +21,7 @@ import { PositionField } from 'types/position' import { Flex, Main, Text, styled } from 'ui/src' import { ArrowDown } from 'ui/src/components/icons/ArrowDown' import { RotateLeft } from 'ui/src/components/icons/RotateLeft' -import { useGetPositionsQuery } from 'uniswap/src/data/rest/getPositions' +import { useGetPositionQuery } from 'uniswap/src/data/rest/getPosition' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlagWithLoading } from 'uniswap/src/features/gating/hooks' import { Trans, useTranslation } from 'uniswap/src/i18n' @@ -102,7 +105,7 @@ function MigrateV3Inner({ positionInfo }: { positionInfo: PositionInfo }) { - + @@ -141,23 +144,39 @@ function MigrateV3Inner({ positionInfo }: { positionInfo: PositionInfo }) { export default function MigrateV3() { const { tokenId } = useParams<{ tokenId: string }>() const account = useAccount() - const { data } = useGetPositionsQuery( + const { data, isLoading: positionLoading } = useGetPositionQuery( account.address ? { - poolId: tokenId, - address: account.address, - protocolVersions: [ProtocolVersion.V3], + owner: account.address, + protocolVersion: ProtocolVersion.V3, + tokenId, + chainId: account.chainId, } : undefined, ) - // TODO(WEB-4920): select the right position from the list, or use an endpoint that returns one position - const position = data?.positions[0] + const position = data?.position const positionInfo = useMemo(() => parseRestPosition(position), [position]) - if (!position || !positionInfo) { - // TODO(WEB-4920): handle loading/error states (including if the position is for v2) - return null + if (positionLoading || !position || !positionInfo) { + return ( + + + + + + + + + + + + + + + + ) } + const { currency0Amount, currency1Amount } = positionInfo return ( void + token0Fees?: CurrencyAmount + token1Fees?: CurrencyAmount + token0FeesUsd?: CurrencyAmount + token1FeesUsd?: CurrencyAmount +} + +export function ClaimFeeModal({ + positionInfo, + onClose, + isOpen, + token0Fees, + token1Fees, + token0FeesUsd, + token1FeesUsd, +}: ClaimFeeModalProps) { + const { t } = useTranslation() + const { formatCurrencyAmount } = useLocalizationContext() + const currencyInfo0 = useCurrencyInfo(token0Fees?.currency) + const currencyInfo1 = useCurrencyInfo(token1Fees?.currency) + const account = useAccount() + + const claimLpFeesParams = useMemo(() => { + return { + params: { + protocol: getProtocolItems(positionInfo.version), + tokenId: positionInfo.tokenId ? Number(positionInfo.tokenId) : undefined, + walletAddress: account.address, + chainId: positionInfo.currency0Amount.currency.chainId, + position: { + pool: { + token0: positionInfo.currency0Amount.currency.isNative + ? ZERO_ADDRESS + : positionInfo.currency0Amount.currency.address, + token1: positionInfo.currency1Amount.currency.isNative + ? ZERO_ADDRESS + : positionInfo.currency1Amount.currency.address, + fee: positionInfo.feeTier ? Number(positionInfo.feeTier) : undefined, + tickSpacing: positionInfo?.tickSpacing ? Number(positionInfo?.tickSpacing) : undefined, + hooks: positionInfo.v4hook, + }, + tickLower: positionInfo.tickLower ? Number(positionInfo.tickLower) : undefined, + tickUpper: positionInfo.tickUpper ? Number(positionInfo.tickUpper) : undefined, + }, + expectedTokenOwed0RawAmount: token0Fees?.quotient.toString(), + expectedTokenOwed1RawAmount: token1Fees?.quotient.toString(), + }, + } + }, [account.address, positionInfo, token0Fees, token1Fees]) + + const { data } = useClaimLpFeesCalldataQuery(claimLpFeesParams) + + const signer = useEthersSigner() + + return ( + + + + {token0Fees && token1Fees && ( + + + + + + {token0Fees.currency.symbol} + + + + + {formatCurrencyAmount({ value: token0Fees })} + + {token0FeesUsd && ( + + ({formatCurrencyAmount({ value: token0FeesUsd, type: NumberType.FiatTokenPrice })}) + + )} + + + + + + + {token1Fees.currency.symbol} + + + + + {formatCurrencyAmount({ value: token1Fees })} + + {token1FeesUsd && ( + + ({formatCurrencyAmount({ value: token1FeesUsd, type: NumberType.FiatTokenPrice })}) + + )} + + + + )} + + + + ) +} diff --git a/apps/web/src/pages/Pool/Positions/PositionPage.tsx b/apps/web/src/pages/Pool/Positions/PositionPage.tsx index d10d21b5314..1ca06519284 100644 --- a/apps/web/src/pages/Pool/Positions/PositionPage.tsx +++ b/apps/web/src/pages/Pool/Positions/PositionPage.tsx @@ -6,17 +6,20 @@ import { LiquidityPositionAmountsTile } from 'components/Liquidity/LiquidityPosi import { LiquidityPositionInfo } from 'components/Liquidity/LiquidityPositionInfo' import { LiquidityPositionPriceRangeTile } from 'components/Liquidity/LiquidityPositionPriceRangeTile' import { PositionNFT } from 'components/Liquidity/PositionNFT' -import { parseRestPosition, useV3PositionDerivedInfo } from 'components/Liquidity/utils' -import { LoadingFullscreen } from 'components/Loader/styled' +import { parseRestPosition, parseV3FeeTier, useV3PositionDerivedInfo } from 'components/Liquidity/utils' +import { LoadingFullscreen, LoadingRows } from 'components/Loader/styled' +import useIsTickAtLimit from 'hooks/useIsTickAtLimit' import { usePositionTokenURI } from 'hooks/usePositionTokenURI' +import { ClaimFeeModal } from 'pages/Pool/Positions/ClaimFeeModal' +import { LoadingRow } from 'pages/Pool/Positions/shared' import { useMemo, useState } from 'react' import { ChevronRight } from 'react-feather' -import { Navigate, useLocation, useParams } from 'react-router-dom' +import { Navigate, useLocation, useNavigate, useParams } from 'react-router-dom' import { setOpenModal } from 'state/application/reducer' import { useAppDispatch } from 'state/hooks' import { ClickableTamaguiStyle } from 'theme/components' import { Flex, Main, Switch, Text, styled } from 'ui/src' -import { useGetPositionsQuery } from 'uniswap/src/data/rest/getPositions' +import { useGetPositionQuery } from 'uniswap/src/data/rest/getPosition' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlagWithLoading } from 'uniswap/src/features/gating/hooks' import { ModalName } from 'uniswap/src/features/telemetry/constants' @@ -62,30 +65,31 @@ export default function PositionPage() { const { tokenId } = useParams<{ tokenId: string }>() const account = useAccount() const { pathname } = useLocation() - const { data } = useGetPositionsQuery( + const { data, isLoading: positionLoading } = useGetPositionQuery( account.address ? { - address: account.address, - protocolVersions: [ - pathname.includes('v3') - ? ProtocolVersion.V3 - : pathname.includes('v4') - ? ProtocolVersion.V4 - : ProtocolVersion.UNSPECIFIED, - ], + owner: account.address, + protocolVersion: pathname.includes('v3') + ? ProtocolVersion.V3 + : pathname.includes('v4') + ? ProtocolVersion.V4 + : ProtocolVersion.UNSPECIFIED, + tokenId, + chainId: account.chainId, } : undefined, ) - // TODO(WEB-4920): select the right position from the list, or use an endpoint that returns one position - const position = data?.positions[0] + const position = data?.position const positionInfo = useMemo(() => parseRestPosition(position), [position]) const metadata = usePositionTokenURI(tokenId ? BigNumber.from(tokenId) : undefined) const dispatch = useAppDispatch() const [collectAsWeth, setCollectAsWeth] = useState(false) + const [claimFeeModalOpen, setClaimFeeModalOpen] = useState(false) const { value: v4Enabled, isLoading } = useFeatureFlagWithLoading(FeatureFlags.V4Everywhere) const { formatCurrencyAmount } = useFormatter() + const navigate = useNavigate() const { currency0Amount, currency1Amount, status } = positionInfo ?? {} const { @@ -93,20 +97,40 @@ export default function PositionPage() { feeValue1, fiatFeeValue0, fiatFeeValue1, - currentPrice, + token0CurrentPrice, + token1CurrentPrice, fiatValue0, fiatValue1, - priceLower, - priceUpper, - } = useV3PositionDerivedInfo(positionInfo, tokenId, collectAsWeth) + priceOrdering, + } = useV3PositionDerivedInfo(positionInfo) + const isTickAtLimit = useIsTickAtLimit( + parseV3FeeTier(positionInfo?.feeTier?.toString()), + Number(positionInfo?.tickLower), + Number(positionInfo?.tickUpper), + ) if (!isLoading && !v4Enabled) { return } - if (!position || !positionInfo || !currency0Amount || !currency1Amount) { - // TODO(WEB-4920): handle loading/error states - return null + if (positionLoading || !position || !positionInfo || !currency0Amount || !currency1Amount) { + return ( + + + + + + + + + + + + + + + + ) } return ( @@ -120,13 +144,23 @@ export default function PositionPage() { - + {status !== PositionStatus.CLOSED && ( { - dispatch(setOpenModal({ name: ModalName.AddLiquidity, initialState: position })) + navigate(`/migrate/v3/${tokenId}`) + }} + > + + + + + { + dispatch(setOpenModal({ name: ModalName.AddLiquidity, initialState: positionInfo })) }} > @@ -136,7 +170,7 @@ export default function PositionPage() { { - dispatch(setOpenModal({ name: ModalName.RemoveLiquidity, initialState: position })) + dispatch(setOpenModal({ name: ModalName.RemoveLiquidity, initialState: positionInfo })) }} > @@ -185,7 +219,7 @@ export default function PositionPage() { { - // TODO(WEB-4920): open claim fees modal + setClaimFeeModalOpen(true) }} > @@ -224,14 +258,24 @@ export default function PositionPage() { - {priceLower && priceUpper && currentPrice && ( + {priceOrdering && token0CurrentPrice && token1CurrentPrice && ( )} + setClaimFeeModalOpen(false)} + token0Fees={feeValue0} + token1Fees={feeValue1} + token0FeesUsd={fiatFeeValue0} + token1FeesUsd={fiatFeeValue1} + /> ) } diff --git a/apps/web/src/pages/Pool/Positions/PositionsHeader.tsx b/apps/web/src/pages/Pool/Positions/PositionsHeader.tsx index f73bdefac1d..71ad02b19ae 100644 --- a/apps/web/src/pages/Pool/Positions/PositionsHeader.tsx +++ b/apps/web/src/pages/Pool/Positions/PositionsHeader.tsx @@ -1,7 +1,6 @@ // eslint-disable-next-line no-restricted-imports import { PositionStatus, ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' import { getProtocolStatusLabel, getProtocolVersionLabel } from 'components/Liquidity/utils' -import { useSupportedChainIds } from 'hooks/useSupportedChainIds' import { useMemo } from 'react' import { useNavigate } from 'react-router-dom' import { ClickableTamaguiStyle } from 'theme/components' @@ -12,6 +11,7 @@ import { RotatableChevron } from 'ui/src/components/icons/RotatableChevron' import { SortHorizontalLines } from 'ui/src/components/icons/SortHorizontalLines' import { ActionSheetDropdown } from 'uniswap/src/components/dropdowns/ActionSheetDropdown' import { NetworkFilter } from 'uniswap/src/components/network/NetworkFilter' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { useTranslation } from 'uniswap/src/i18n' import { UniverseChainId } from 'uniswap/src/types/chains' @@ -33,7 +33,7 @@ export function PositionsHeader({ onStatusChange, }: PositionsHeaderProps) { const { t } = useTranslation() - const { supported: supportedChains } = useSupportedChainIds() + const { chains } = useEnabledChains() const navigate = useNavigate() const filterOptions = useMemo(() => { @@ -105,7 +105,7 @@ export function PositionsHeader({ render: () => ( - {t('nav.tabs.createV2Position')} + {t('nav.tabs.createV4Position')} @@ -210,7 +210,7 @@ export function PositionsHeader({ includeAllNetworks selectedChain={selectedChain} onPressChain={onChainChange} - chainIds={supportedChains} + chainIds={chains} /> diff --git a/apps/web/src/pages/Pool/Positions/V2PositionPage.tsx b/apps/web/src/pages/Pool/Positions/V2PositionPage.tsx index a42bb038504..fce9522a8a7 100644 --- a/apps/web/src/pages/Pool/Positions/V2PositionPage.tsx +++ b/apps/web/src/pages/Pool/Positions/V2PositionPage.tsx @@ -2,20 +2,23 @@ import { PositionStatus, ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' import { BreadcrumbNavContainer, BreadcrumbNavLink } from 'components/BreadcrumbNav' import { LiquidityPositionInfo } from 'components/Liquidity/LiquidityPositionInfo' -import { parseRestPosition, useV2PositionDerivedInfo } from 'components/Liquidity/utils' +import { parseRestPosition, useGetPoolTokenPercentage } from 'components/Liquidity/utils' +import { LoadingRows } from 'components/Loader/styled' import { DoubleCurrencyAndChainLogo } from 'components/Logo/DoubleLogo' import { HeaderButton } from 'pages/Pool/Positions/PositionPage' +import { LoadingRow } from 'pages/Pool/Positions/shared' import { useMemo } from 'react' import { ChevronRight } from 'react-feather' import { Navigate, useNavigate, useParams } from 'react-router-dom' import { setOpenModal } from 'state/application/reducer' import { useAppDispatch } from 'state/hooks' import { Flex, Main, Text, styled } from 'ui/src' -import { useGetPositionsQuery } from 'uniswap/src/data/rest/getPositions' +import { useGetPositionQuery } from 'uniswap/src/data/rest/getPosition' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlagWithLoading } from 'uniswap/src/features/gating/hooks' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { ModalName } from 'uniswap/src/features/telemetry/constants' +import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice' import { Trans } from 'uniswap/src/i18n' import { NumberType } from 'utilities/src/format/types' import { useAccount } from 'wagmi' @@ -32,21 +35,20 @@ const BodyWrapper = styled(Main, { }) export default function V2PositionPage() { - const { currencyIdA, currencyIdB } = useParams<{ currencyIdA: string; currencyIdB: string }>() + const { pairAddress } = useParams<{ pairAddress: string }>() const account = useAccount() - const { data } = useGetPositionsQuery( + const { data, isLoading: positionLoading } = useGetPositionQuery( account.address ? { - token0: currencyIdA, - token1: currencyIdB, - address: account.address, - protocolVersions: [ProtocolVersion.V2], + owner: account.address, + protocolVersion: ProtocolVersion.V2, + pairAddress, + chainId: account.chainId, } : undefined, ) - // TODO(WEB-4920): select the right position from the list, or use an endpoint that returns one position - const position = data?.positions[0] + const position = data?.position const positionInfo = useMemo(() => parseRestPosition(position), [position]) const dispatch = useAppDispatch() const navigate = useNavigate() @@ -55,15 +57,34 @@ export default function V2PositionPage() { const { value: v4Enabled, isLoading } = useFeatureFlagWithLoading(FeatureFlags.V4Everywhere) const { currency0Amount, currency1Amount, status, liquidityAmount } = positionInfo ?? {} - const { poolTokenPercentage, token0USDValue, token1USDValue } = useV2PositionDerivedInfo(positionInfo) + + const token0USDValue = useUSDCValue(currency0Amount) + const token1USDValue = useUSDCValue(currency1Amount) + const poolTokenPercentage = useGetPoolTokenPercentage(positionInfo) if (!isLoading && !v4Enabled) { return } - if (!position || !positionInfo || !liquidityAmount || !currency0Amount || !currency1Amount) { - // TODO(WEB-4920): handle loading/error states - return null + if (positionLoading || !positionInfo || !liquidityAmount || !currency0Amount || !currency1Amount) { + return ( + + + + + + + + + + + + + + + + + ) } return ( @@ -77,7 +98,7 @@ export default function V2PositionPage() { - + {status === PositionStatus.IN_RANGE && ( { - dispatch(setOpenModal({ name: ModalName.AddLiquidity, initialState: position })) + dispatch(setOpenModal({ name: ModalName.AddLiquidity, initialState: positionInfo })) }} > @@ -103,7 +124,7 @@ export default function V2PositionPage() { { - dispatch(setOpenModal({ name: ModalName.RemoveLiquidity, initialState: position })) + dispatch(setOpenModal({ name: ModalName.RemoveLiquidity, initialState: positionInfo })) }} > diff --git a/apps/web/src/pages/Pool/Positions/create/AddHook.tsx b/apps/web/src/pages/Pool/Positions/create/AddHook.tsx index 426aff323d8..6119ca59ca9 100644 --- a/apps/web/src/pages/Pool/Positions/create/AddHook.tsx +++ b/apps/web/src/pages/Pool/Positions/create/AddHook.tsx @@ -56,5 +56,12 @@ export function AddHook() { ) } - return + return ( + + ) } diff --git a/apps/web/src/pages/Pool/Positions/create/ContextProviders.tsx b/apps/web/src/pages/Pool/Positions/create/ContextProviders.tsx index a0cc4be3f98..f044623ce42 100644 --- a/apps/web/src/pages/Pool/Positions/create/ContextProviders.tsx +++ b/apps/web/src/pages/Pool/Positions/create/ContextProviders.tsx @@ -1,12 +1,26 @@ +// eslint-disable-next-line no-restricted-imports +import { ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' import { FeeTierSearchModal } from 'components/Liquidity/FeeTierSearchModal' +import { DepositState } from 'components/Liquidity/types' import { getProtocolItems } from 'components/Liquidity/utils' import { ZERO_ADDRESS } from 'constants/misc' +import { useAccount } from 'hooks/useAccount' import { CreatePositionContext, + CreateTxContext, + DEFAULT_DEPOSIT_STATE, DEFAULT_PRICE_RANGE_STATE, + DepositContext, PriceRangeContext, + useCreatePositionContext, + useDepositContext, + usePriceRangeContext, } from 'pages/Pool/Positions/create/CreatePositionContext' -import { useDerivedPositionInfo, useDerivedPriceRangeInfo } from 'pages/Pool/Positions/create/hooks' +import { + useDerivedDepositInfo, + useDerivedPositionInfo, + useDerivedPriceRangeInfo, +} from 'pages/Pool/Positions/create/hooks' import { DEFAULT_POSITION_STATE, PositionFlowStep, @@ -17,8 +31,9 @@ import { useMemo, useState } from 'react' import { useCheckLpApprovalQuery } from 'uniswap/src/data/apiClients/tradingApi/useCheckLpApprovalQuery' import { useCreateLpPositionCalldataQuery } from 'uniswap/src/data/apiClients/tradingApi/useCreateLpPositionCalldataQuery' import { CheckApprovalLPRequest, CreateLPPositionRequest } from 'uniswap/src/data/tradingApi/__generated__' +import { CreatePositionTxAndGasInfo } from 'uniswap/src/features/transactions/liquidity/types' +import { validatePermit, validateTransactionRequest } from 'uniswap/src/features/transactions/swap/utils/trade' import { ONE_SECOND_MS } from 'utilities/src/time/time' -import { useAccount } from 'wagmi' export function CreatePositionContextProvider({ children, @@ -31,124 +46,210 @@ export function CreatePositionContextProvider({ const [step, setStep] = useState(PositionFlowStep.SELECT_TOKENS_AND_FEE_TIER) const derivedPositionInfo = useDerivedPositionInfo(positionState) const [feeTierSearchModalOpen, setFeeTierSearchModalOpen] = useState(false) + + return ( + + {children} + + + ) +} + +export function PriceRangeContextProvider({ children }: { children: React.ReactNode }) { + const [priceRangeState, setPriceRangeState] = useState(DEFAULT_PRICE_RANGE_STATE) + const derivedPriceRangeInfo = useDerivedPriceRangeInfo(priceRangeState) + + return ( + + {children} + + ) +} + +export function DepositContextProvider({ children }: { children: React.ReactNode }) { + const [depositState, setDepositState] = useState(DEFAULT_DEPOSIT_STATE) + const derivedDepositInfo = useDerivedDepositInfo(depositState) + + return ( + + {children} + + ) +} + +export function CreateTxContextProvider({ children }: { children: React.ReactNode }) { const account = useAccount() + const { + priceRangeState: { fullRange }, + derivedPriceRangeInfo: { tickSpaceLimits, ticks }, + } = usePriceRangeContext() + + const { + derivedDepositInfo: { currencyAmounts }, + } = useDepositContext() + + const { derivedPositionInfo, positionState } = useCreatePositionContext() const addLiquidityApprovalParams: CheckApprovalLPRequest | undefined = useMemo(() => { const apiProtocolItems = getProtocolItems(positionState.protocolVersion) - if (!derivedPositionInfo.pool || !account.address || !apiProtocolItems) { + if (!account.address || !apiProtocolItems || !currencyAmounts?.TOKEN0 || !currencyAmounts?.TOKEN1) { return undefined } return { walletAddress: account.address, - chainId: positionState.currencyInputs.TOKEN0?.chainId, + chainId: derivedPositionInfo.currencies.TOKEN0?.chainId, protocol: apiProtocolItems, - token0: positionState.currencyInputs.TOKEN0?.isNative + token0: derivedPositionInfo.currencies.TOKEN0?.isNative ? ZERO_ADDRESS - : positionState.currencyInputs.TOKEN0?.address, - token1: positionState.currencyInputs.TOKEN1?.isNative + : derivedPositionInfo.currencies.TOKEN0?.address, + token1: derivedPositionInfo.currencies.TOKEN1?.isNative ? ZERO_ADDRESS - : positionState.currencyInputs.TOKEN1?.address, - // todo: get these from input state - // amount0: positionInfo.currency0Amount.quotient.toString(), - // amount1: positionInfo.currency1Amount.quotient.toString(), + : derivedPositionInfo.currencies.TOKEN1?.address, + amount0: currencyAmounts?.TOKEN0?.quotient.toString(), + amount1: currencyAmounts?.TOKEN1?.quotient.toString(), } - }, [ - account.address, - derivedPositionInfo.pool, - positionState.currencyInputs.TOKEN0, - positionState.currencyInputs.TOKEN1, - positionState.protocolVersion, - ]) - const { data: addLiquidityTokenApprovals, isLoading: approvalLoading } = useCheckLpApprovalQuery({ + }, [account.address, positionState.protocolVersion, derivedPositionInfo.currencies, currencyAmounts]) + const { data: approvalCalldata } = useCheckLpApprovalQuery({ params: addLiquidityApprovalParams, staleTime: 5 * ONE_SECOND_MS, }) const createCalldataQueryParams: CreateLPPositionRequest | undefined = useMemo(() => { const apiProtocolItems = getProtocolItems(positionState.protocolVersion) + const tickLower = fullRange ? tickSpaceLimits[0] : ticks?.[0] + const tickUpper = fullRange ? tickSpaceLimits[1] : ticks?.[1] if ( !account.address || !apiProtocolItems || - !positionState.currencyInputs.TOKEN0 || - !positionState.currencyInputs.TOKEN1 + !derivedPositionInfo.currencies.TOKEN0 || + !derivedPositionInfo.currencies.TOKEN1 || + !currencyAmounts?.TOKEN0 || + !currencyAmounts?.TOKEN1 || + !tickLower || + !tickUpper ) { return undefined } - const needsApproval = Boolean( - addLiquidityTokenApprovals?.token0Approval || addLiquidityTokenApprovals?.token1Approval, - ) - // todo: don't skip this query, we actually want to load both to get gas estimates early - if (needsApproval || !addLiquidityApprovalParams || approvalLoading) { + let poolLiquidity: string | undefined + let currentTick: number | undefined + let sqrtRatioX96: string | undefined + let tickSpacing: number | undefined + if ( + derivedPositionInfo.protocolVersion === ProtocolVersion.V3 || + derivedPositionInfo.protocolVersion === ProtocolVersion.V4 + ) { + if (!derivedPositionInfo.pool) { + return undefined + } else { + poolLiquidity = derivedPositionInfo.pool.liquidity.toString() + currentTick = derivedPositionInfo.pool.tickCurrent + sqrtRatioX96 = derivedPositionInfo.pool.sqrtRatioX96.toString() + tickSpacing = derivedPositionInfo.pool.tickSpacing + } + } + if (derivedPositionInfo.protocolVersion === ProtocolVersion.V2 && !derivedPositionInfo.pair) { return undefined } + const { token0Approval, token1Approval, positionTokenApproval, permitData } = approvalCalldata ?? {} return { + simulateTransaction: !(permitData || token0Approval || token1Approval || positionTokenApproval), protocol: apiProtocolItems, walletAddress: account.address, - chainId: positionState.currencyInputs.TOKEN0?.chainId, - // todo: get these from input state - // amount0: derivedAddLiquidityInfo.currencyAmounts?.TOKEN0?.quotient.toString(), - // amount1: derivedAddLiquidityInfo.currencyAmounts?.TOKEN1?.quotient.toString(), - poolLiquidity: derivedPositionInfo.pool?.liquidity.toString(), - currentTick: derivedPositionInfo.pool?.tickCurrent, - sqrtRatioX96: derivedPositionInfo.pool?.sqrtRatioX96.toString(), + chainId: derivedPositionInfo.currencies.TOKEN0?.chainId, + amount0: currencyAmounts?.TOKEN0?.quotient.toString(), + amount1: currencyAmounts?.TOKEN1?.quotient.toString(), + poolLiquidity, + currentTick, + sqrtRatioX96, // todo: set the initial price if the pool doesn't already exist // initialPrice: derivedPositionInfo.pool ? undefined : 100 position: { - // todo: get these from input state - // tickLower: Number(positionInfo.tickLower), - // tickUpper: Number(positionInfo.tickUpper), - // tickSpacing: Number(positionInfo.tickSpacing), + tickLower, + tickUpper, pool: { - token0: positionState.currencyInputs.TOKEN0.isNative + tickSpacing, + token0: derivedPositionInfo.currencies.TOKEN0.isNative ? ZERO_ADDRESS - : positionState.currencyInputs.TOKEN0.address, - token1: positionState.currencyInputs.TOKEN1.isNative + : derivedPositionInfo.currencies.TOKEN0.address, + token1: derivedPositionInfo.currencies.TOKEN1.isNative ? ZERO_ADDRESS - : positionState.currencyInputs.TOKEN1.address, + : derivedPositionInfo.currencies.TOKEN1.address, fee: positionState.fee, hooks: positionState.hook, }, }, } }, [ - addLiquidityApprovalParams, - addLiquidityTokenApprovals, - approvalLoading, account, positionState, - derivedPositionInfo.pool, + derivedPositionInfo, + currencyAmounts, + fullRange, + ticks, + tickSpaceLimits, + approvalCalldata, ]) - // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { data: createCalldata } = useCreateLpPositionCalldataQuery({ params: createCalldataQueryParams, staleTime: 5 * ONE_SECOND_MS, }) - return ( - - {children} - - - ) -} + const validatedValue = useMemo(() => { + if (!createCalldata || !currencyAmounts?.TOKEN0 || !currencyAmounts?.TOKEN1) { + return undefined + } + const validatedApprove0Request = validateTransactionRequest(approvalCalldata?.token0Approval) + if (approvalCalldata?.token0Approval && !validatedApprove0Request) { + return undefined + } -export function PriceRangeContextProvider({ children }: { children: React.ReactNode }) { - const [priceRangeState, setPriceRangeState] = useState(DEFAULT_PRICE_RANGE_STATE) - const derivedPriceRangeInfo = useDerivedPriceRangeInfo(priceRangeState) + const validatedApprove1Request = validateTransactionRequest(approvalCalldata?.token1Approval) + if (approvalCalldata?.token1Approval && !validatedApprove1Request) { + return undefined + } - return ( - - {children} - - ) + const validatedPermitRequest = validatePermit(approvalCalldata?.permitData) + if (approvalCalldata?.permitData && !validatedPermitRequest) { + return undefined + } + + const txRequest = validateTransactionRequest(createCalldata.create) + if (!txRequest) { + return undefined + } + + return { + type: 'create', + unsigned: Boolean(approvalCalldata?.permitData), + protocolVersion: derivedPositionInfo.protocolVersion, + createPositionRequestArgs: createCalldataQueryParams, + action: { + currency0Amount: currencyAmounts.TOKEN0, + currency1Amount: currencyAmounts.TOKEN1, + liquidityToken: + derivedPositionInfo.protocolVersion === ProtocolVersion.V2 + ? derivedPositionInfo.pair?.liquidityToken + : undefined, + }, + approveToken0Request: validatedApprove0Request, + approveToken1Request: validatedApprove1Request, + txRequest, + approvePositionTokenRequest: undefined, + revocationTxRequest: undefined, + permit: validatedPermitRequest, + } satisfies CreatePositionTxAndGasInfo + }, [approvalCalldata, createCalldata, createCalldataQueryParams, derivedPositionInfo, currencyAmounts]) + + return {children} } diff --git a/apps/web/src/pages/Pool/Positions/create/CreatePosition.tsx b/apps/web/src/pages/Pool/Positions/create/CreatePosition.tsx index 6fd052107f4..3cc7682a960 100644 --- a/apps/web/src/pages/Pool/Positions/create/CreatePosition.tsx +++ b/apps/web/src/pages/Pool/Positions/create/CreatePosition.tsx @@ -3,12 +3,20 @@ import { ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' import { BreadcrumbNavContainer, BreadcrumbNavLink } from 'components/BreadcrumbNav' import { getProtocolVersionLabel, parseProtocolVersion } from 'components/Liquidity/utils' import { PoolProgressIndicator } from 'components/PoolProgressIndicator/PoolProgressIndicator' -import { CreatePositionContextProvider, PriceRangeContextProvider } from 'pages/Pool/Positions/create/ContextProviders' import { + CreatePositionContextProvider, + CreateTxContextProvider, + DepositContextProvider, + PriceRangeContextProvider, +} from 'pages/Pool/Positions/create/ContextProviders' +import { + DEFAULT_DEPOSIT_STATE, DEFAULT_PRICE_RANGE_STATE, useCreatePositionContext, + useDepositContext, usePriceRangeContext, } from 'pages/Pool/Positions/create/CreatePositionContext' +import { DepositStep } from 'pages/Pool/Positions/create/Deposit' import { EditRangeSelectionStep, EditSelectTokensStep } from 'pages/Pool/Positions/create/EditStep' import { SelectPriceRangeStep } from 'pages/Pool/Positions/create/RangeSelectionStep' import { SelectTokensStep } from 'pages/Pool/Positions/create/SelectTokenStep' @@ -55,6 +63,7 @@ function CreatePositionInner() { <> {!v2Selected && } + )} @@ -107,12 +116,14 @@ const Toolbar = () => { setStep, } = useCreatePositionContext() const { setPriceRangeState } = usePriceRangeContext() + const { setDepositState } = useDepositContext() const handleReset = useCallback(() => { setPositionState(DEFAULT_POSITION_STATE) setPriceRangeState(DEFAULT_PRICE_RANGE_STATE) + setDepositState(DEFAULT_DEPOSIT_STATE) setStep(PositionFlowStep.SELECT_TOKENS_AND_FEE_TIER) - }, [setPositionState, setPriceRangeState, setStep]) + }, [setDepositState, setPositionState, setPriceRangeState, setStep]) const handleVersionChange = useCallback( (version: ProtocolVersion) => { @@ -225,17 +236,21 @@ export function CreatePosition() { return ( - - - - - - - + + + + + + + + + + + ) diff --git a/apps/web/src/pages/Pool/Positions/create/CreatePositionContext.tsx b/apps/web/src/pages/Pool/Positions/create/CreatePositionContext.tsx index 6091d163fe8..dc7ae34b7aa 100644 --- a/apps/web/src/pages/Pool/Positions/create/CreatePositionContext.tsx +++ b/apps/web/src/pages/Pool/Positions/create/CreatePositionContext.tsx @@ -1,4 +1,6 @@ /* eslint-disable-next-line no-restricted-imports */ +import { ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' +import { DepositContextType, DepositState } from 'components/Liquidity/types' import { CreatePositionContextType, DEFAULT_POSITION_STATE, @@ -7,6 +9,8 @@ import { PriceRangeState, } from 'pages/Pool/Positions/create/types' import React, { useContext } from 'react' +import { PositionField } from 'types/position' +import { CreatePositionTxAndGasInfo } from 'uniswap/src/features/transactions/liquidity/types' export const CreatePositionContext = React.createContext({ step: PositionFlowStep.SELECT_TOKENS_AND_FEE_TIER, @@ -16,7 +20,8 @@ export const CreatePositionContext = React.createContext undefined, derivedPositionInfo: { - pool: undefined, + protocolVersion: ProtocolVersion.UNSPECIFIED, + currencies: {}, }, }) @@ -27,8 +32,8 @@ export const useCreatePositionContext = () => { export const DEFAULT_PRICE_RANGE_STATE: PriceRangeState = { priceInverted: false, fullRange: true, - minPrice: '0', - maxPrice: 'INF', + minPrice: '', + maxPrice: '', } export const PriceRangeContext = React.createContext({ @@ -37,9 +42,31 @@ export const PriceRangeContext = React.createContext({ derivedPriceRangeInfo: { isSorted: false, ticksAtLimit: [true, true], + invertPrice: false, + tickSpaceLimits: [0, 0], }, }) export const usePriceRangeContext = () => { return useContext(PriceRangeContext) } + +export const DEFAULT_DEPOSIT_STATE: DepositState = { + exactField: PositionField.TOKEN0, +} + +export const DepositContext = React.createContext({ + depositState: DEFAULT_DEPOSIT_STATE, + setDepositState: () => undefined, + derivedDepositInfo: {}, +}) + +export const useDepositContext = () => { + return useContext(DepositContext) +} + +export const CreateTxContext = React.createContext(undefined) + +export const useCreateTxContext = () => { + return useContext(CreateTxContext) +} diff --git a/apps/web/src/pages/Pool/Positions/create/CreatePositionModal.tsx b/apps/web/src/pages/Pool/Positions/create/CreatePositionModal.tsx new file mode 100644 index 00000000000..2b3872932ec --- /dev/null +++ b/apps/web/src/pages/Pool/Positions/create/CreatePositionModal.tsx @@ -0,0 +1,194 @@ +// eslint-disable-next-line no-restricted-imports +import { ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' +import { DoubleCurrencyLogo } from 'components/Logo/DoubleLogo' +import { GetHelpHeader } from 'components/Modal/GetHelpHeader' +import { useCurrencyInfo } from 'hooks/Tokens' +import useSelectChain from 'hooks/useSelectChain' +import { + useCreatePositionContext, + useCreateTxContext, + useDepositContext, + usePriceRangeContext, +} from 'pages/Pool/Positions/create/CreatePositionContext' +import { useCallback, useMemo, useState } from 'react' +import { useDispatch } from 'react-redux' +import { useNavigate } from 'react-router-dom' +import { liquiditySaga } from 'state/sagas/liquidity/liquiditySaga' +import { Button, Flex, Text } from 'ui/src' +import { iconSizes } from 'ui/src/theme' +import { ProgressIndicator } from 'uniswap/src/components/ConfirmSwapModal/ProgressIndicator' +import { TokenLogo } from 'uniswap/src/components/CurrencyLogo/TokenLogo' +import { Modal } from 'uniswap/src/components/modals/Modal' +import { useAccountMeta } from 'uniswap/src/contexts/UniswapContext' +import { AccountType } from 'uniswap/src/features/accounts/types' +import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' +import { ModalName } from 'uniswap/src/features/telemetry/constants' +import { isValidLiquidityTxContext } from 'uniswap/src/features/transactions/liquidity/types' +import { TransactionStep } from 'uniswap/src/features/transactions/swap/utils/generateTransactionSteps' +import { Trans } from 'uniswap/src/i18n' +import { NumberType } from 'utilities/src/format/types' +import { useAccount } from 'wagmi' + +export function CreatePositionModal({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) { + const { + positionState: { protocolVersion }, + derivedPositionInfo: { currencies }, + } = useCreatePositionContext() + const { + derivedPriceRangeInfo: { baseAndQuoteTokens, prices, ticksAtLimit, isSorted }, + } = usePriceRangeContext() + const { + derivedDepositInfo: { formattedAmounts, currencyAmounts, currencyAmountsUSDValue }, + } = useDepositContext() + + const { TOKEN0: token0, TOKEN1: token1 } = currencies + + const token0CurrencyInfo = useCurrencyInfo(token0) + const token1CurrencyInfo = useCurrencyInfo(token1) + + const { formatNumberOrString, formatCurrencyAmount } = useLocalizationContext() + const [baseCurrency, quoteCurrency] = baseAndQuoteTokens ?? [undefined, undefined] + + const formattedPrices = useMemo(() => { + const lowerPriceFormatted = ticksAtLimit[isSorted ? 0 : 1] + ? '0' + : formatNumberOrString({ value: prices?.[0]?.toSignificant(), type: NumberType.TokenTx }) + const upperPriceFormatted = ticksAtLimit[isSorted ? 1 : 0] + ? '∞' + : formatNumberOrString({ value: prices?.[1]?.toSignificant(), type: NumberType.TokenTx }) + + return [lowerPriceFormatted, upperPriceFormatted] + }, [formatNumberOrString, isSorted, prices, ticksAtLimit]) + + const [steps, setSteps] = useState([]) + const [currentStep, setCurrentStep] = useState<{ step: TransactionStep; accepted: boolean } | undefined>() + const dispatch = useDispatch() + const createTxContext = useCreateTxContext() + const account = useAccountMeta() + const selectChain = useSelectChain() + const navigate = useNavigate() + const startChainId = useAccount().chainId + + const onFailure = () => { + setCurrentStep(undefined) + } + + const onSuccess = useCallback(() => { + setSteps([]) + setCurrentStep(undefined) + onClose() + navigate('/positions') + }, [navigate, onClose]) + + const handleCreate = useCallback(() => { + const isValidTx = isValidLiquidityTxContext(createTxContext) + if (!account || account?.type !== AccountType.SignerMnemonic || !isValidTx) { + return + } + dispatch( + liquiditySaga.actions.trigger({ + selectChain, + startChainId, + account, + liquidityTxContext: createTxContext, + setCurrentStep, + setSteps, + onSuccess, + onFailure, + }), + ) + }, [account, createTxContext, dispatch, selectChain, startChainId, onSuccess]) + + return ( + + + + + + + } + closeModal={() => onClose()} + /> + + + + {token0?.symbol} + / + {token1?.symbol} + + + + {(protocolVersion === ProtocolVersion.V3 || protocolVersion === ProtocolVersion.V4) && ( + + + + + + {`${formattedPrices[0]} ${quoteCurrency?.symbol + '/' + baseCurrency?.symbol}`} + + + + + + {`${formattedPrices[1]} ${quoteCurrency?.symbol + '/' + baseCurrency?.symbol}`} + + + )} + + + + + + + + + {formattedAmounts?.TOKEN0} + {currencyAmounts?.TOKEN0?.currency.symbol} + + + {formatCurrencyAmount({ value: currencyAmountsUSDValue?.TOKEN0, type: NumberType.FiatTokenPrice })} + + + + + + + + {formattedAmounts?.TOKEN1} + {currencyAmounts?.TOKEN1?.currency.symbol} + + + {formatCurrencyAmount({ value: currencyAmountsUSDValue?.TOKEN1, type: NumberType.FiatTokenPrice })} + + + + + + + {currentStep ? ( + + ) : ( + + )} + + + ) +} diff --git a/apps/web/src/pages/Pool/Positions/create/Deposit.tsx b/apps/web/src/pages/Pool/Positions/create/Deposit.tsx new file mode 100644 index 00000000000..7dae1ba69ff --- /dev/null +++ b/apps/web/src/pages/Pool/Positions/create/Deposit.tsx @@ -0,0 +1,86 @@ +import { DepositInputForm } from 'components/Liquidity/DepositInputForm' +import { useCreatePositionContext, useDepositContext } from 'pages/Pool/Positions/create/CreatePositionContext' +import { CreatePositionModal } from 'pages/Pool/Positions/create/CreatePositionModal' +import { Container } from 'pages/Pool/Positions/create/shared' +import { useCallback, useState } from 'react' +import { PositionField } from 'types/position' +import { Button, Flex, FlexProps, Text } from 'ui/src' +import { Trans } from 'uniswap/src/i18n' + +export const DepositStep = ({ ...rest }: FlexProps) => { + const { + derivedPositionInfo: { sortedTokens }, + } = useCreatePositionContext() + const { + setDepositState, + derivedDepositInfo: { formattedAmounts, currencyAmounts, currencyAmountsUSDValue, currencyBalances }, + } = useDepositContext() + const [isReviewModalOpen, setIsReviewModalOpen] = useState(false) + + const handleUserInput = (field: PositionField, newValue: string) => { + setDepositState((prev) => ({ + ...prev, + exactField: field, + exactAmount: newValue, + })) + } + + const handleOnSetMax = (field: PositionField, amount: string) => { + setDepositState((prev) => ({ + ...prev, + exactField: field, + exactAmount: amount, + })) + } + + const handleReview = useCallback(() => { + setIsReviewModalOpen(true) + }, []) + + const [token0, token1] = sortedTokens ?? [undefined, undefined] + + if (!token0 || !token1) { + return null + } + + return ( + <> + + + + + + + + + + + + + + + + + setIsReviewModalOpen(false)} /> + + ) +} diff --git a/apps/web/src/pages/Pool/Positions/create/EditStep.tsx b/apps/web/src/pages/Pool/Positions/create/EditStep.tsx index 3df9ce8188e..0167c961d6f 100644 --- a/apps/web/src/pages/Pool/Positions/create/EditStep.tsx +++ b/apps/web/src/pages/Pool/Positions/create/EditStep.tsx @@ -2,7 +2,7 @@ import { DoubleCurrencyLogo } from 'components/Logo/DoubleLogo' import { useCreatePositionContext, usePriceRangeContext } from 'pages/Pool/Positions/create/CreatePositionContext' import { Container } from 'pages/Pool/Positions/create/shared' import { PositionFlowStep } from 'pages/Pool/Positions/create/types' -import { useCallback } from 'react' +import { useCallback, useMemo } from 'react' import { Button, Flex, FlexProps, Text } from 'ui/src' import { Edit } from 'ui/src/components/icons/Edit' import { iconSizes } from 'ui/src/theme' @@ -26,12 +26,10 @@ const EditStep = ({ children, onClick, ...rest }: { children: JSX.Element; onCli export const EditSelectTokensStep = (props?: FlexProps) => { const { - positionState: { - currencyInputs: { TOKEN0: token0, TOKEN1: token1 }, - }, setStep, + derivedPositionInfo: { currencies }, } = useCreatePositionContext() - const currencies = [token0, token1] + const { TOKEN0: token0, TOKEN1: token1 } = currencies const handleEdit = useCallback(() => { setStep(PositionFlowStep.SELECT_TOKENS_AND_FEE_TIER) @@ -40,7 +38,7 @@ export const EditSelectTokensStep = (props?: FlexProps) => { return ( - + {token0?.symbol} / @@ -54,7 +52,7 @@ export const EditSelectTokensStep = (props?: FlexProps) => { export const EditRangeSelectionStep = (props?: FlexProps) => { const { setStep } = useCreatePositionContext() const { - derivedPriceRangeInfo: { baseAndQuoteTokens, prices }, + derivedPriceRangeInfo: { baseAndQuoteTokens, prices, ticksAtLimit, isSorted }, } = usePriceRangeContext() const { formatNumberOrString } = useLocalizationContext() @@ -64,6 +62,17 @@ export const EditRangeSelectionStep = (props?: FlexProps) => { setStep(PositionFlowStep.PRICE_RANGE) }, [setStep]) + const formattedPrices = useMemo(() => { + const lowerPriceFormatted = ticksAtLimit[isSorted ? 0 : 1] + ? '0' + : formatNumberOrString({ value: prices?.[0]?.toSignificant(), type: NumberType.TokenTx }) + const upperPriceFormatted = ticksAtLimit[isSorted ? 1 : 0] + ? '∞' + : formatNumberOrString({ value: prices?.[1]?.toSignificant(), type: NumberType.TokenTx }) + + return [lowerPriceFormatted, upperPriceFormatted] + }, [formatNumberOrString, isSorted, prices, ticksAtLimit]) + return ( @@ -75,13 +84,13 @@ export const EditRangeSelectionStep = (props?: FlexProps) => { - {`${formatNumberOrString({ value: prices?.[0]?.toSignificant(), type: NumberType.TokenTx })} ${quoteCurrency?.symbol + '/' + baseCurrency?.symbol}`} + {`${formattedPrices[0]} ${quoteCurrency?.symbol + '/' + baseCurrency?.symbol}`} - {`${formatNumberOrString({ value: prices?.[1]?.toSignificant(), type: NumberType.TokenTx })} ${quoteCurrency?.symbol + '/' + baseCurrency?.symbol}`} + {`${formattedPrices[1]} ${quoteCurrency?.symbol + '/' + baseCurrency?.symbol}`} diff --git a/apps/web/src/pages/Pool/Positions/create/RangeSelectionStep.tsx b/apps/web/src/pages/Pool/Positions/create/RangeSelectionStep.tsx index 7cb16f1845d..02f37bf5b4e 100644 --- a/apps/web/src/pages/Pool/Positions/create/RangeSelectionStep.tsx +++ b/apps/web/src/pages/Pool/Positions/create/RangeSelectionStep.tsx @@ -1,3 +1,6 @@ +// eslint-disable-next-line no-restricted-imports +import { ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' +import LiquidityChartRangeInput from 'components/LiquidityChartRangeInput' import { useCreatePositionContext, usePriceRangeContext } from 'pages/Pool/Positions/create/CreatePositionContext' import { Container } from 'pages/Pool/Positions/create/shared' import { useCallback, useMemo, useState } from 'react' @@ -136,18 +139,16 @@ export const SelectPriceRangeStep = ({ onContinue, ...rest }: { onContinue: () = const { formatPrice } = useFormatter() const { - positionState: { - currencyInputs: { TOKEN0: token0, TOKEN1: token1 }, - fee, - }, - derivedPositionInfo: { pool }, + positionState: { fee }, + derivedPositionInfo, } = useCreatePositionContext() const { priceRangeState: { fullRange }, setPriceRangeState, - derivedPriceRangeInfo: { baseAndQuoteTokens, price, prices, ticks, isSorted, ticksAtLimit }, + derivedPriceRangeInfo: { baseAndQuoteTokens, price, prices, pricesAtTicks, ticks, isSorted, ticksAtLimit }, } = usePriceRangeContext() + const { TOKEN0: token0, TOKEN1: token1 } = derivedPositionInfo.currencies const [baseToken, quoteToken] = baseAndQuoteTokens ?? [undefined, undefined] const controlOptions = useMemo(() => { @@ -165,6 +166,7 @@ export const SelectPriceRangeStep = ({ onContinue, ...rest }: { onContinue: () = [token0?.symbol, setPriceRangeState], ) + const pool = derivedPositionInfo.protocolVersion === ProtocolVersion.V3 ? derivedPositionInfo.pool : undefined const { getDecrementLower, getIncrementLower, getDecrementUpper, getIncrementUpper } = useRangeHopCallbacks( baseToken ?? undefined, quoteToken ?? undefined, @@ -200,6 +202,17 @@ export const SelectPriceRangeStep = ({ onContinue, ...rest }: { onContinue: () = ] }, [isSorted, prices, ticksAtLimit]) + const handleChartRangeInput = useCallback( + (input: RangeSelectionInput, value: string) => { + if (input === RangeSelectionInput.MIN) { + setPriceRangeState((prev) => ({ ...prev, minPrice: value, fullRange: false })) + } else { + setPriceRangeState((prev) => ({ ...prev, maxPrice: value, fullRange: false })) + } + }, + [setPriceRangeState], + ) + return ( @@ -247,6 +260,21 @@ export const SelectPriceRangeStep = ({ onContinue, ...rest }: { onContinue: () = + handleChartRangeInput(RangeSelectionInput.MIN, text)} + onRightRangeInput={(text) => handleChartRangeInput(RangeSelectionInput.MAX, text)} + interactive={true} + /> void }) => { @@ -92,10 +93,12 @@ const FeeTier = ({ selected: boolean onSelect: (value: number) => void }) => { + const { formatPercent } = useFormatter() + return ( onSelect(feeTier.value)} background={selected ? '$surface3' : '$surface1'}> - {feeTier.value / 10000}% + {formatPercent(new Percent(feeTier.value, 1000000))} {selected && } {feeTier.title} @@ -115,22 +118,46 @@ export function SelectTokensStep({ const { t } = useTranslation() const { - positionState: { - currencyInputs: { TOKEN0: token0, TOKEN1: token1 }, - fee, - protocolVersion, - }, + positionState: { currencyInputs, fee, protocolVersion }, setPositionState, derivedPositionInfo, setFeeTierSearchModalOpen, } = useCreatePositionContext() + const { TOKEN0: token0, TOKEN1: token1 } = derivedPositionInfo.currencies const [currencySearchInputState, setCurrencySearchInputState] = useState(undefined) const [isShowMoreFeeTiersEnabled, toggleShowMoreFeeTiersEnabled] = useReducer((state) => !state, false) - const continueButtonEnabled = !!derivedPositionInfo.pool + const continueButtonEnabled = + derivedPositionInfo.protocolVersion !== ProtocolVersion.UNSPECIFIED && + ((derivedPositionInfo.protocolVersion === ProtocolVersion.V2 && derivedPositionInfo.pair) || + ((derivedPositionInfo.protocolVersion === ProtocolVersion.V3 || + derivedPositionInfo.protocolVersion === ProtocolVersion.V4) && + derivedPositionInfo.pool)) const handleCurrencySelect = useCallback( (currency: Currency) => { + if (currencySearchInputState === undefined) { + return + } + + const otherInputState = + currencySearchInputState === PositionField.TOKEN0 ? PositionField.TOKEN1 : PositionField.TOKEN0 + const otherCurrency = currencyInputs[otherInputState] + const wrappedCurrencyNew = currency.isNative ? currency.wrapped : currency + const wrappedCurrencyOther = otherCurrency?.isNative ? otherCurrency.wrapped : otherCurrency + + if (areCurrenciesEqual(currency, otherCurrency) || areCurrenciesEqual(wrappedCurrencyNew, wrappedCurrencyOther)) { + setPositionState((prevState) => ({ + ...prevState, + currencyInputs: { + ...prevState.currencyInputs, + [otherInputState]: undefined, + [currencySearchInputState]: currency, + }, + })) + return + } + switch (currencySearchInputState) { case PositionField.TOKEN0: case PositionField.TOKEN1: @@ -143,7 +170,7 @@ export function SelectTokensStep({ break } }, - [currencySearchInputState, setPositionState], + [currencyInputs, currencySearchInputState, setPositionState], ) const handleFeeTierSelect = useCallback( @@ -214,7 +241,7 @@ export function SelectTokensStep({ - + {fee === FeeAmount.MEDIUM ? ( @@ -260,7 +287,6 @@ export function SelectTokensStep({ )} {protocolVersion === ProtocolVersion.V4 && ( { diff --git a/apps/web/src/pages/Pool/Positions/create/hooks.tsx b/apps/web/src/pages/Pool/Positions/create/hooks.tsx index 9a0f0dddb00..a51be45da22 100644 --- a/apps/web/src/pages/Pool/Positions/create/hooks.tsx +++ b/apps/web/src/pages/Pool/Positions/create/hooks.tsx @@ -1,14 +1,22 @@ // eslint-disable-next-line no-restricted-imports import { ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' -import { Currency } from '@uniswap/sdk-core' -import { TICK_SPACINGS, TickMath, nearestUsableTick } from '@uniswap/v3-sdk' -import { getPoolFromRest, parseV3FeeTier } from 'components/Liquidity/utils' +import { Currency, CurrencyAmount, V2_FACTORY_ADDRESSES } from '@uniswap/sdk-core' +import { Pair, computePairAddress } from '@uniswap/v2-sdk' +import { Pool, Position, TICK_SPACINGS, TickMath, nearestUsableTick } from '@uniswap/v3-sdk' +import { DepositInfo, DepositState } from 'components/Liquidity/types' +import { getPairFromRest, getPoolFromRest, parseV3FeeTier } from 'components/Liquidity/utils' +import { useCurrencyInfo } from 'hooks/Tokens' import { useAccount } from 'hooks/useAccount' -import { useCreatePositionContext } from 'pages/Pool/Positions/create/CreatePositionContext' -import { PositionInfo, PositionState, PriceRangeInfo, PriceRangeState } from 'pages/Pool/Positions/create/types' +import { useCurrencyBalances } from 'lib/hooks/useCurrencyBalance' +import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' +import { useCreatePositionContext, usePriceRangeContext } from 'pages/Pool/Positions/create/CreatePositionContext' +import { CreatePositionInfo, PositionState, PriceRangeInfo, PriceRangeState } from 'pages/Pool/Positions/create/types' import { useMemo } from 'react' import { tryParseTick } from 'state/mint/v3/utils' +import { PositionField } from 'types/position' +import { useGetPair } from 'uniswap/src/data/rest/getPair' import { useGetPoolsByTokens } from 'uniswap/src/data/rest/getPools' +import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice' import { UniverseChainId } from 'uniswap/src/types/chains' import { getTickToPrice } from 'utils/getTickToPrice' @@ -16,11 +24,21 @@ import { getTickToPrice } from 'utils/getTickToPrice' * @param state user-defined state for a position being created or migrated * @returns derived position information such as existing Pools */ -export function useDerivedPositionInfo(state: PositionState): PositionInfo { +export function useDerivedPositionInfo(state: PositionState): CreatePositionInfo { const { chainId } = useAccount() + const { + currencyInputs: { TOKEN0: token0Input, TOKEN1: token1Input }, + protocolVersion, + } = state - const { TOKEN0, TOKEN1 } = state.currencyInputs + const inputCurrencyInfo = useCurrencyInfo(token0Input) + const outputCurrencyInfo = useCurrencyInfo(token1Input) + const currencies = useMemo( + () => ({ TOKEN0: inputCurrencyInfo?.currency, TOKEN1: outputCurrencyInfo?.currency }), + [inputCurrencyInfo, outputCurrencyInfo], + ) + const { TOKEN0, TOKEN1 } = currencies const tokens = useMemo( () => TOKEN0 && TOKEN1 @@ -30,32 +48,89 @@ export function useDerivedPositionInfo(state: PositionState): PositionInfo { ) const sortedTokens = tokens && tokens.toSorted((a, b) => (!b ? -1 : a?.sortsBefore(b) ? -1 : 1)) - const data = usePool( - sortedTokens?.[0], - sortedTokens?.[1], - state.fee, - chainId ?? (UniverseChainId.Mainnet as number), - state.protocolVersion, + const poolsQueryEnabled = protocolVersion === ProtocolVersion.V3 || protocolVersion === ProtocolVersion.V4 + const { data: poolData } = useGetPoolsByTokens( + { + fee: state.fee, + chainId, + protocolVersions: [protocolVersion], + token0: sortedTokens?.[0].address, + token1: sortedTokens?.[1].address, + }, + poolsQueryEnabled, ) - const pool = getPoolFromRest({ pool: data, token0: sortedTokens?.[0], token1: sortedTokens?.[1] }) + const pool = useMemo(() => { + return getPoolFromRest({ pool: poolData?.pools?.[0], token0: sortedTokens?.[0], token1: sortedTokens?.[1] }) + }, [poolData?.pools, sortedTokens]) - return useMemo( - () => ({ + const pairsQueryEnabled = protocolVersion === ProtocolVersion.V2 + const pairAddress = useMemo(() => { + return sortedTokens && sortedTokens[0] && sortedTokens[1] + ? computePairAddress({ + factoryAddress: V2_FACTORY_ADDRESSES[sortedTokens[0].chainId], + tokenA: sortedTokens[0], + tokenB: sortedTokens[1], + }) + : undefined + }, [sortedTokens]) + + const { data: pairData } = useGetPair( + { + chainId: chainId ?? (UniverseChainId.Mainnet as number), + pairAddress, + }, + pairsQueryEnabled, + ) + + const pair = useMemo(() => { + if (!sortedTokens || !sortedTokens[0] || !sortedTokens[1]) { + return undefined + } + + return getPairFromRest({ pair: pairData?.pair, token0: sortedTokens[0], token1: sortedTokens[1] }) + }, [pairData, sortedTokens]) + + return useMemo(() => { + if (protocolVersion === ProtocolVersion.V2) { + return { + currencies, + protocolVersion, + pair: pair ?? undefined, + tokens, + sortedTokens, + } + } + + if (protocolVersion === ProtocolVersion.UNSPECIFIED) { + return { + currencies, + protocolVersion: ProtocolVersion.UNSPECIFIED, + } + } + + return { + currencies, + protocolVersion, pool, tokens, sortedTokens, - }), - [pool, tokens, sortedTokens], - ) + } + }, [protocolVersion, currencies, pool, tokens, sortedTokens, pair]) } export function useDerivedPriceRangeInfo(state: PriceRangeState): PriceRangeInfo { const { positionState: { fee }, - derivedPositionInfo: { tokens, sortedTokens, pool }, + derivedPositionInfo, } = useCreatePositionContext() + const { sortedTokens, tokens, protocolVersion } = derivedPositionInfo + const pool = + protocolVersion === ProtocolVersion.V3 || protocolVersion === ProtocolVersion.V4 + ? derivedPositionInfo.pool + : undefined + const [sortedToken0, sortedToken1] = sortedTokens ?? [undefined, undefined] const [baseToken, quoteToken] = tokens ? state.priceInverted @@ -153,32 +228,183 @@ export function useDerivedPriceRangeInfo(state: PriceRangeState): PriceRangeInfo prices, pricesAtTicks, pricesAtLimit, + tickSpaceLimits, baseAndQuoteTokens, + invertPrice, }), - [baseAndQuoteTokens, isSorted, price, prices, pricesAtLimit, pricesAtTicks, ticks, ticksAtLimit], + [ + baseAndQuoteTokens, + isSorted, + price, + prices, + pricesAtLimit, + pricesAtTicks, + ticks, + ticksAtLimit, + invertPrice, + tickSpaceLimits, + ], ) } -export function usePool( - token0: Currency | undefined, - token1: Currency | undefined, - fee: number | undefined, - chainId: number | undefined, - protocolVersion: ProtocolVersion | undefined, -) { - const tokens = [token0?.isNative ? token0.wrapped : token0, token1?.isNative ? token1.wrapped : token1] - const sortedTokens = tokens.sort((a, b) => (!b ? -1 : a?.sortsBefore(b) ? -1 : 1)) - const { data } = useGetPoolsByTokens( - token0 && token1 && chainId && protocolVersion - ? { - fee, - chainId, - protocolVersions: [protocolVersion], - token0: sortedTokens[0]?.address, - token1: sortedTokens[1]?.address, - } - : undefined, - ) +export type UseDepositInfoProps = { + protocolVersion: ProtocolVersion + address?: string + token0?: Currency + token1?: Currency + exactField: PositionField + exactAmount?: string +} & ( + | { + protocolVersion: ProtocolVersion.V3 | ProtocolVersion.V4 + pool?: Pool + tickLower?: number + tickUpper?: number + } + | { + protocolVersion: ProtocolVersion.V2 + pair?: Pair + } + | { + protocolVersion: ProtocolVersion.UNSPECIFIED + } +) + +export function useDerivedDepositInfo(state: DepositState): DepositInfo { + const account = useAccount() + const { derivedPositionInfo } = useCreatePositionContext() + const { + derivedPriceRangeInfo: { ticks }, + } = usePriceRangeContext() + const { exactAmount, exactField } = state + const { protocolVersion, sortedTokens } = derivedPositionInfo + + const pool = + protocolVersion === ProtocolVersion.V3 || protocolVersion === ProtocolVersion.V4 + ? derivedPositionInfo.pool + : undefined - return data?.pools?.[0] + const pair = protocolVersion === ProtocolVersion.V2 ? derivedPositionInfo.pair : undefined + + const [token0, token1] = sortedTokens ?? [undefined, undefined] + const tickLower = ticks?.[0] + const tickUpper = ticks?.[1] + + const depositInfoProps: UseDepositInfoProps = useMemo(() => { + if (protocolVersion === ProtocolVersion.UNSPECIFIED) { + return { + protocolVersion, + exactField, + } + } + + if (protocolVersion === ProtocolVersion.V2) { + return { + protocolVersion, + pair, + address: account.address, + token0, + token1, + exactField, + exactAmount, + } + } + + return { + protocolVersion, + pool, + address: account.address, + tickLower, + tickUpper, + token0, + token1, + exactField, + exactAmount, + } + }, [account.address, exactAmount, exactField, pair, pool, protocolVersion, tickLower, tickUpper, token0, token1]) + + return useDepositInfo(depositInfoProps) +} + +export function useDepositInfo(state: UseDepositInfoProps): DepositInfo { + const { protocolVersion, address, token0, token1, exactField, exactAmount } = state + const [token0Balance, token1Balance] = useCurrencyBalances(address, [token0, token1]) + + const [independentToken, dependentToken] = exactField === PositionField.TOKEN0 ? [token0, token1] : [token1, token0] + const independentAmount = tryParseCurrencyAmount(exactAmount, independentToken) + + const dependentAmount: CurrencyAmount | undefined = useMemo(() => { + const wrappedIndependentAmount = independentAmount?.wrapped + + if (protocolVersion === ProtocolVersion.UNSPECIFIED) { + return undefined + } + + if (protocolVersion === ProtocolVersion.V2) { + const pair = state.pair + const [token0Wrapped, token1Wrapped] = [token0?.wrapped, token1?.wrapped] + + if (token0Wrapped && token1Wrapped && wrappedIndependentAmount && pair) { + const dependentTokenAmount = + exactField === PositionField.TOKEN0 + ? pair.priceOf(token0Wrapped).quote(wrappedIndependentAmount) + : pair.priceOf(token1Wrapped).quote(wrappedIndependentAmount) + return dependentToken?.isNative + ? CurrencyAmount.fromRawAmount(dependentToken, dependentTokenAmount.quotient) + : dependentTokenAmount + } + + return undefined + } + + const { tickLower, tickUpper, pool } = state + + if (!tickLower || !tickUpper || !pool || !independentAmount || !wrappedIndependentAmount) { + return undefined + } + + const position: Position | undefined = wrappedIndependentAmount.currency.equals(pool.token0) + ? Position.fromAmount0({ + pool, + tickLower, + tickUpper, + amount0: independentAmount.quotient, + useFullPrecision: true, + }) + : Position.fromAmount1({ + pool, + tickLower, + tickUpper, + amount1: independentAmount.quotient, + }) + + const dependentTokenAmount = wrappedIndependentAmount.currency.equals(pool.token0) + ? position.amount1 + : position.amount0 + return dependentToken && CurrencyAmount.fromRawAmount(dependentToken, dependentTokenAmount.quotient) + }, [independentAmount, protocolVersion, state, dependentToken, token0?.wrapped, token1?.wrapped, exactField]) + + const independentTokenUSDValue = useUSDCValue(independentAmount) || undefined + const dependentTokenUSDValue = useUSDCValue(dependentAmount) || undefined + + const dependentField = exactField === PositionField.TOKEN0 ? PositionField.TOKEN1 : PositionField.TOKEN0 + return useMemo( + () => ({ + currencyBalances: { [PositionField.TOKEN0]: token0Balance, [PositionField.TOKEN1]: token1Balance }, + formattedAmounts: { [exactField]: exactAmount, [dependentField]: dependentAmount?.toExact() }, + currencyAmounts: { [exactField]: independentAmount, [dependentField]: dependentAmount }, + currencyAmountsUSDValue: { [exactField]: independentTokenUSDValue, [dependentField]: dependentTokenUSDValue }, + }), + [ + token0Balance, + token1Balance, + exactAmount, + dependentAmount, + independentAmount, + independentTokenUSDValue, + dependentTokenUSDValue, + exactField, + dependentField, + ], + ) } diff --git a/apps/web/src/pages/Pool/Positions/create/shared.tsx b/apps/web/src/pages/Pool/Positions/create/shared.tsx index a2ef7a86030..0bef97edad8 100644 --- a/apps/web/src/pages/Pool/Positions/create/shared.tsx +++ b/apps/web/src/pages/Pool/Positions/create/shared.tsx @@ -1,3 +1,4 @@ +import { MouseoverTooltip } from 'components/Tooltip' import { Flex, GeneratedIcon, Text, styled } from 'ui/src' import { InfoCircleFilled } from 'ui/src/components/icons/InfoCircleFilled' import { iconSizes } from 'ui/src/theme' @@ -15,12 +16,12 @@ export const Container = styled(Flex, { export function AdvancedButton({ title, - tooltip = true, + tooltipText, Icon, onPress, }: { title: string - tooltip?: boolean + tooltipText?: string Icon: GeneratedIcon onPress: () => void }) { @@ -43,8 +44,11 @@ export function AdvancedButton({ ({t('common.advanced')}) - {/* TODO(WEB-4920): implement tooltip text here */} - {tooltip && } + {tooltipText && ( + + + + )} ) } diff --git a/apps/web/src/pages/Pool/Positions/create/types.ts b/apps/web/src/pages/Pool/Positions/create/types.ts index 1bbf3c8ba98..e63edf8ae08 100644 --- a/apps/web/src/pages/Pool/Positions/create/types.ts +++ b/apps/web/src/pages/Pool/Positions/create/types.ts @@ -1,7 +1,8 @@ // eslint-disable-next-line no-restricted-imports import { ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' import { Currency, Price, Token } from '@uniswap/sdk-core' -import { Pool } from '@uniswap/v3-sdk' +import { Pair } from '@uniswap/v2-sdk' +import { FeeAmount, Pool } from '@uniswap/v3-sdk' import { Dispatch, SetStateAction } from 'react' import { PositionField } from 'types/position' @@ -14,29 +15,44 @@ export enum PositionFlowStep { export interface PositionState { protocolVersion: ProtocolVersion currencyInputs: { [field in PositionField]?: Currency } - fee: number + fee: number // Denoted in hundredths of bips hook?: string } export const DEFAULT_POSITION_STATE: PositionState = { currencyInputs: {}, - fee: 3000, + fee: FeeAmount.MEDIUM, hook: undefined, protocolVersion: ProtocolVersion.V4, } -export interface PositionInfo { - pool?: Pool +export type CreatePositionInfo = { + protocolVersion: ProtocolVersion + currencies: { [field in PositionField]?: Currency } tokens?: Token[] sortedTokens?: Token[] -} +} & ( + | { + protocolVersion: ProtocolVersion.V3 | ProtocolVersion.V4 + pool?: Pool + tokens?: Token[] + sortedTokens?: Token[] + } + | { + protocolVersion: ProtocolVersion.V2 + pair?: Pair + } + | { + protocolVersion: ProtocolVersion.UNSPECIFIED + } +) export type CreatePositionContextType = { step: PositionFlowStep setStep: Dispatch> positionState: PositionState setPositionState: Dispatch> - derivedPositionInfo: PositionInfo + derivedPositionInfo: CreatePositionInfo feeTierSearchModalOpen: boolean setFeeTierSearchModalOpen: Dispatch> } @@ -51,12 +67,14 @@ export interface PriceRangeState { export interface PriceRangeInfo { ticks?: (number | undefined)[] ticksAtLimit: boolean[] + tickSpaceLimits: (number | undefined)[] isSorted: boolean price?: Price prices?: (Price | undefined)[] pricesAtLimit?: (Price | undefined)[] pricesAtTicks?: (Price | undefined)[] baseAndQuoteTokens?: Token[] + invertPrice: boolean } export type PriceRangeContextType = { diff --git a/apps/web/src/pages/Pool/Positions/index.tsx b/apps/web/src/pages/Pool/Positions/index.tsx index 5bedad18f83..df25ab23dd4 100644 --- a/apps/web/src/pages/Pool/Positions/index.tsx +++ b/apps/web/src/pages/Pool/Positions/index.tsx @@ -1,16 +1,21 @@ /* eslint-disable-next-line no-restricted-imports */ -import { Position, PositionStatus, ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' +import { PositionStatus, ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' import { LiquidityPositionCard } from 'components/Liquidity/LiquidityPositionCard' +import { PositionInfo } from 'components/Liquidity/types' +import { parseRestPosition } from 'components/Liquidity/utils' import { useAccount } from 'hooks/useAccount' import { PositionsHeader } from 'pages/Pool/Positions/PositionsHeader' -import { useCallback, useState } from 'react' +import { useCallback, useMemo, useState } from 'react' +import { ChevronLeft, ChevronRight } from 'react-feather' import { useNavigate } from 'react-router-dom' import { ClickableTamaguiStyle } from 'theme/components' -import { Flex } from 'ui/src' +import { Flex, Text } from 'ui/src' import { useGetPositionsQuery } from 'uniswap/src/data/rest/getPositions' import { UniverseChainId } from 'uniswap/src/types/chains' import { logger } from 'utilities/src/logger/logger' +const PAGE_SIZE = 8 + export default function Positions() { const [chainFilter, setChainFilter] = useState(null) const [versionFilter, setVersionFilter] = useState([ @@ -27,19 +32,20 @@ export default function Positions() { const navigate = useNavigate() const account = useAccount() const { address } = account + const [currentPage, setCurrentPage] = useState(0) const { data } = useGetPositionsQuery({ address, }) const onNavigateToPosition = useCallback( - (position: Position) => { - if (position.position.case === 'v2Pair' && position.position.value.token0 && position.position.value.token1) { - navigate(`/positions/v2/${position.position.value.token0.address}/${position.position.value.token1.address}`) - } else if (position.position.case === 'v3Position') { - navigate(`/positions/v3/${position.position.value.tokenId}`) - } else if (position.position.case === 'v4Position') { - navigate(`/positions/v4/${position.position.value.poolPosition?.tokenId}`) + (position: PositionInfo) => { + if (position.version === ProtocolVersion.V2) { + navigate(`/positions/v2/${position.liquidityToken.address}`) + } else if (position.version === ProtocolVersion.V3) { + navigate(`/positions/v3/${position.tokenId}`) + } else if (position.version === ProtocolVersion.V4) { + navigate(`/positions/v4/${position.tokenId}`) } else { logger.error('Invalid position', { tags: { file: 'Positions/index.tsx', function: 'onPress' }, @@ -49,7 +55,11 @@ export default function Positions() { [navigate], ) - // TODO(WEB-4920): implement pagination w/ max 8 positions per page. + const currentPageItems = useMemo(() => { + const start = currentPage * PAGE_SIZE + return (data?.positions.slice(start, start + PAGE_SIZE) ?? []).map((position) => parseRestPosition(position)) + }, [currentPage, data?.positions]) + const pageCount = data?.positions ? Math.ceil(data?.positions.length / PAGE_SIZE) : undefined return ( @@ -75,18 +85,77 @@ export default function Positions() { } }} /> - - {data?.positions.map((position, index) => { + + {currentPageItems.map((position, index) => { return ( - onNavigateToPosition(position)} - /> + position && ( + onNavigateToPosition(position)} + /> + ) ) })} + {pageCount && pageCount > 1 && data?.positions && ( + + { + setCurrentPage(Math.max(currentPage - 1, 0)) + }} + /> + {Array.from({ length: pageCount > 5 ? 3 : pageCount }).map((_, index) => { + const isSelected = currentPage === index + return ( + setCurrentPage(index)} + > + {index + 1} + + ) + })} + {pageCount > 5 && ( + + ... + + )} + {pageCount > 5 && ( + setCurrentPage(pageCount - 1)} + > + {pageCount} + + )} + { + setCurrentPage(Math.min(currentPage + 1, pageCount - 1)) + }} + /> + + )} ) } diff --git a/apps/web/src/pages/Pool/Positions/shared.tsx b/apps/web/src/pages/Pool/Positions/shared.tsx new file mode 100644 index 00000000000..92e7f875a73 --- /dev/null +++ b/apps/web/src/pages/Pool/Positions/shared.tsx @@ -0,0 +1,5 @@ +import { Flex, styled } from 'ui/src' + +export const LoadingRow = styled(Flex, { + my: '$spacing16', +}) diff --git a/apps/web/src/pages/RemoveLiquidity/RemoveLiquidityModal.tsx b/apps/web/src/pages/RemoveLiquidity/RemoveLiquidityModal.tsx index a1a410f1cd7..71d5acf78c9 100644 --- a/apps/web/src/pages/RemoveLiquidity/RemoveLiquidityModal.tsx +++ b/apps/web/src/pages/RemoveLiquidity/RemoveLiquidityModal.tsx @@ -1,9 +1,10 @@ // eslint-disable-next-line no-restricted-imports -import { CurrencyAmount } from '@uniswap/sdk-core' import { LiquidityModalDetailRows } from 'components/Liquidity/LiquidityModalDetailRows' import { LiquidityModalHeader } from 'components/Liquidity/LiquidityModalHeader' import { LiquidityPositionInfo } from 'components/Liquidity/LiquidityPositionInfo' +import { TokenInfo } from 'components/Liquidity/TokenInfo' import { StyledPercentInput } from 'components/PercentInput' +import useSelectChain from 'hooks/useSelectChain' import { RemoveLiquidityModalContextProvider, useLiquidityModalContext, @@ -14,12 +15,22 @@ import { } from 'pages/RemoveLiquidity/RemoveLiquidityTxContext' import { ClickablePill } from 'pages/Swap/Buy/PredefinedAmount' import { NumericalInputMimic, NumericalInputSymbolContainer, NumericalInputWrapper } from 'pages/Swap/common/shared' +import { useState } from 'react' +import { useDispatch } from 'react-redux' import { useCloseModal } from 'state/application/hooks' +import { liquiditySaga } from 'state/sagas/liquidity/liquiditySaga' import { Button, Flex, Text, useSporeColors } from 'ui/src' +import { ProgressIndicator } from 'uniswap/src/components/ConfirmSwapModal/ProgressIndicator' import { Modal } from 'uniswap/src/components/modals/Modal' +import { useAccountMeta } from 'uniswap/src/contexts/UniswapContext' +import { AccountType } from 'uniswap/src/features/accounts/types' import { ModalName } from 'uniswap/src/features/telemetry/constants' +import { isValidLiquidityTxContext } from 'uniswap/src/features/transactions/liquidity/types' +import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice' +import { TransactionStep } from 'uniswap/src/features/transactions/swap/utils/generateTransactionSteps' import { Trans, useTranslation } from 'uniswap/src/i18n' import useResizeObserver from 'use-resize-observer' +import { useAccount } from 'wagmi' function RemoveLiquidityModalInner() { const closeModal = useCloseModal(ModalName.RemoveLiquidity) @@ -27,85 +38,132 @@ function RemoveLiquidityModalInner() { const { t } = useTranslation() const colors = useSporeColors() const { percent, positionInfo, setPercent, percentInvalid } = useLiquidityModalContext() - const { decreaseGasFeeUsd, v2ApprovalGasFeeUSD } = useRemoveLiquidityTxContext() + const removeLiquidityTxContext = useRemoveLiquidityTxContext() + const { gasFeeEstimateUSD, txContext } = removeLiquidityTxContext + const [steps, setSteps] = useState([]) + const [currentStep, setCurrentStep] = useState<{ step: TransactionStep; accepted: boolean } | undefined>() + const selectChain = useSelectChain() + const startChainId = useAccount().chainId + const account = useAccountMeta() + const dispatch = useDispatch() + const currency0FiatAmount = useUSDCValue(positionInfo?.currency0Amount) ?? undefined + const currency1FiatAmount = useUSDCValue(positionInfo?.currency1Amount) ?? undefined + + const onFailure = () => { + setCurrentStep(undefined) + } + + const onDecreaseLiquidity = () => { + const isValidTx = isValidLiquidityTxContext(txContext) + if (!account || account?.type !== AccountType.SignerMnemonic || !isValidTx) { + return + } + dispatch( + liquiditySaga.actions.trigger({ + selectChain, + startChainId, + account, + liquidityTxContext: txContext, + setCurrentStep, + setSteps, + onSuccess: closeModal, + onFailure, + }), + ) + } if (!positionInfo) { throw new Error('RemoveLiquidityModal must have an initial state when opening') } - const { restPosition, currency0Amount, currency1Amount } = positionInfo + const { currency0Amount, currency1Amount } = positionInfo return ( - {/* Position info */} - - {/* Percent input panel */} - - - - - - - { - setPercent(value) - }} - placeholder="0" - $width={percent && hiddenObserver.width ? hiddenObserver.width + 1 : undefined} - maxDecimals={1} - maxLength={2} + + {currentStep ? ( + <> + + + + {t('common.and')} + + - % - {percent} - - - - {[25, 50, 75, 100].map((option) => { - const active = percent === option.toString() - const disabled = false - return ( - { - setPercent(option.toString()) - }} - $disabled={disabled} - $active={active} - customBorderColor={colors.surface3.val} - foregroundColor={colors[disabled ? 'neutral3' : active ? 'neutral1' : 'neutral2'].val} - label={option < 100 ? option + '%' : t('swap.button.max')} - px="$spacing16" - textVariant="buttonLabel2" - /> - ) - })} - - - {/* Detail rows */} - - + + + + ) : ( + <> + {/* Position info */} + + + + {/* Percent input panel */} + + + + + + + { + setPercent(value) + }} + placeholder="0" + $width={percent && hiddenObserver.width ? hiddenObserver.width + 1 : undefined} + maxDecimals={1} + maxLength={2} + /> + % + {percent} + + + + {[25, 50, 75, 100].map((option) => { + const active = percent === option.toString() + const disabled = false + return ( + { + setPercent(option.toString()) + }} + $disabled={disabled} + $active={active} + customBorderColor={colors.surface3.val} + foregroundColor={colors[disabled ? 'neutral3' : active ? 'neutral1' : 'neutral2'].val} + label={option < 100 ? option + '%' : t('swap.button.max')} + px="$spacing16" + textVariant="buttonLabel2" + /> + ) + })} + + + {/* Detail rows */} + + + + )} ) diff --git a/apps/web/src/pages/RemoveLiquidity/RemoveLiquidityModalContext.tsx b/apps/web/src/pages/RemoveLiquidity/RemoveLiquidityModalContext.tsx index cdfec8a27b3..d95d6afcb2a 100644 --- a/apps/web/src/pages/RemoveLiquidity/RemoveLiquidityModalContext.tsx +++ b/apps/web/src/pages/RemoveLiquidity/RemoveLiquidityModalContext.tsx @@ -1,4 +1,5 @@ -import { PositionInfo, useModalLiquidityPositionInfo } from 'components/Liquidity/utils' +import { PositionInfo } from 'components/Liquidity/types' +import { useModalLiquidityPositionInfo } from 'components/Liquidity/utils' import { PropsWithChildren, createContext, useContext, useState } from 'react' type RemoveLiquidityModalState = { diff --git a/apps/web/src/pages/RemoveLiquidity/RemoveLiquidityTxContext.tsx b/apps/web/src/pages/RemoveLiquidity/RemoveLiquidityTxContext.tsx index 1d079bd6788..0ae607b2937 100644 --- a/apps/web/src/pages/RemoveLiquidity/RemoveLiquidityTxContext.tsx +++ b/apps/web/src/pages/RemoveLiquidity/RemoveLiquidityTxContext.tsx @@ -1,32 +1,68 @@ import { Currency, CurrencyAmount } from '@uniswap/sdk-core' +import { useLiquidityModalContext } from 'pages/RemoveLiquidity/RemoveLiquidityModalContext' import { useRemoveLiquidityTxAndGasInfo } from 'pages/RemoveLiquidity/hooks' -import { PropsWithChildren, createContext, useContext, useEffect } from 'react' +import { PropsWithChildren, createContext, useContext, useEffect, useMemo } from 'react' import { useAccountMeta } from 'uniswap/src/contexts/UniswapContext' import { CheckApprovalLPResponse, DecreaseLPPositionResponse } from 'uniswap/src/data/tradingApi/__generated__' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' +import { ValidatedDecreasePositionTxAndGasInfo } from 'uniswap/src/features/transactions/liquidity/types' +import { validateTransactionRequest } from 'uniswap/src/features/transactions/swap/utils/trade' import { logContextUpdate } from 'utilities/src/logger/contextEnhancer' export type RemoveLiquidityTxInfo = { - v2ApprovalGasFeeUSD?: CurrencyAmount - decreaseGasFeeUsd?: CurrencyAmount + gasFeeEstimateUSD?: CurrencyAmount v2LpTokenApproval?: CheckApprovalLPResponse - reduceCalldata?: DecreaseLPPositionResponse + decreaseCalldata?: DecreaseLPPositionResponse + decreaseCalldataLoading: boolean + approvalLoading: boolean + txContext?: ValidatedDecreasePositionTxAndGasInfo } const RemoveLiquidityTxContext = createContext(undefined) export function RemoveLiquidityTxContextProvider({ children }: PropsWithChildren): JSX.Element { const account = useAccountMeta() + const { positionInfo } = useLiquidityModalContext() const removeLiquidityTxInfo = useRemoveLiquidityTxAndGasInfo({ account: account?.address }) + const { approvalLoading, decreaseCalldataLoading, decreaseCalldata } = removeLiquidityTxInfo const datadogEnabled = useFeatureFlag(FeatureFlags.Datadog) useEffect(() => { logContextUpdate('RemoveLiquidityTxContext', removeLiquidityTxInfo, datadogEnabled) }, [removeLiquidityTxInfo, datadogEnabled]) - return {children} + const decreaseLiquidityTxContext: ValidatedDecreasePositionTxAndGasInfo | undefined = useMemo(() => { + if (!positionInfo || approvalLoading || decreaseCalldataLoading || !decreaseCalldata) { + return undefined + } + const approvePositionTokenRequest = validateTransactionRequest( + removeLiquidityTxInfo.v2LpTokenApproval?.positionTokenApproval, + ) + const txRequest = validateTransactionRequest(decreaseCalldata.decrease) + if (!txRequest) { + return undefined + } + return { + type: 'decrease', + approvalError: false, + protocolVersion: positionInfo.version, + action: positionInfo, + approvePositionTokenRequest, + txRequest, + approveToken0Request: undefined, + approveToken1Request: undefined, + revocationTxRequest: undefined, + permit: undefined, + } + }, [approvalLoading, positionInfo, decreaseCalldataLoading, decreaseCalldata, removeLiquidityTxInfo]) + + return ( + + {children} + + ) } export const useRemoveLiquidityTxContext = (): RemoveLiquidityTxInfo => { diff --git a/apps/web/src/pages/RemoveLiquidity/hooks.ts b/apps/web/src/pages/RemoveLiquidity/hooks.ts index aecff21e4d0..b86999dc51c 100644 --- a/apps/web/src/pages/RemoveLiquidity/hooks.ts +++ b/apps/web/src/pages/RemoveLiquidity/hooks.ts @@ -1,44 +1,28 @@ // eslint-disable-next-line no-restricted-imports import { ProtocolVersion } from '@uniswap/client-pools/dist/pools/v1/types_pb' -import { CurrencyAmount } from '@uniswap/sdk-core' -import { getProtocolItems } from 'components/Liquidity/utils' +import { getProtocolItems, useV3PositionDerivedInfo } from 'components/Liquidity/utils' import { ZERO_ADDRESS } from 'constants/misc' import JSBI from 'jsbi' -import { usePool } from 'pages/Pool/Positions/create/hooks' import { useLiquidityModalContext } from 'pages/RemoveLiquidity/RemoveLiquidityModalContext' import { RemoveLiquidityTxInfo } from 'pages/RemoveLiquidity/RemoveLiquidityTxContext' import { useMemo } from 'react' -import { nativeOnChain } from 'uniswap/src/constants/tokens' import { useCheckLpApprovalQuery } from 'uniswap/src/data/apiClients/tradingApi/useCheckLpApprovalQuery' -import { useReduceLpPositionCalldataQuery } from 'uniswap/src/data/apiClients/tradingApi/useReduceLpPositionCalldataQuery' +import { useDecreaseLpPositionCalldataQuery } from 'uniswap/src/data/apiClients/tradingApi/useDecreaseLpPositionCalldataQuery' import { CheckApprovalLPRequest, DecreaseLPPositionRequest, ProtocolItems, } from 'uniswap/src/data/tradingApi/__generated__' -import { useTransactionGasFee } from 'uniswap/src/features/gas/hooks' -import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice' -import { UniverseChainId } from 'uniswap/src/types/chains' +import { useTransactionGasFee, useUSDCurrencyAmountOfGasFee } from 'uniswap/src/features/gas/hooks' import { ONE_SECOND_MS } from 'utilities/src/time/time' export function useRemoveLiquidityTxAndGasInfo({ account }: { account?: string }): RemoveLiquidityTxInfo { const { positionInfo, percent, percentInvalid } = useLiquidityModalContext() - const pool = usePool( - positionInfo?.currency0Amount.currency, - positionInfo?.currency1Amount.currency, - positionInfo?.feeTier ? Number(positionInfo.feeTier) : undefined, - positionInfo?.currency0Amount.currency.chainId ?? UniverseChainId.Mainnet, - positionInfo?.version, - ) + const pool = positionInfo?.version === ProtocolVersion.V3 ? positionInfo.pool : undefined const v2LpTokenApprovalQueryParams: CheckApprovalLPRequest | undefined = useMemo(() => { - if ( - !positionInfo?.restPosition || - !positionInfo.liquidityToken || - percentInvalid || - !positionInfo.liquidityAmount - ) { + if (!positionInfo || !positionInfo.liquidityToken || percentInvalid || !positionInfo.liquidityAmount) { return undefined } return { @@ -52,43 +36,42 @@ export function useRemoveLiquidityTxAndGasInfo({ account }: { account?: string } .quotient.toString(), } }, [positionInfo, percent, account, percentInvalid]) - // eslint-disable-next-line @typescript-eslint/no-unused-vars const { data: v2LpTokenApproval, isLoading: v2ApprovalLoading } = useCheckLpApprovalQuery({ params: v2LpTokenApprovalQueryParams, staleTime: 5 * ONE_SECOND_MS, }) - const { value: v2ApprovalGasFee } = useTransactionGasFee(v2LpTokenApproval?.positionTokenApproval) - const gasFeeCurrencyAmount = CurrencyAmount.fromRawAmount( - nativeOnChain(positionInfo?.liquidityToken?.chainId ?? UniverseChainId.Mainnet), - v2ApprovalGasFee ?? '0', - ) - const v2ApprovalGasFeeUSD = useUSDCValue(gasFeeCurrencyAmount) ?? undefined + const v2ApprovalGasFeeUSD = + useUSDCurrencyAmountOfGasFee( + positionInfo?.liquidityToken?.chainId, + v2LpTokenApproval?.gasFeePositionTokenApproval, + ) ?? undefined - const reduceCalldataQueryParams: DecreaseLPPositionRequest | undefined = useMemo(() => { + const approvalsNeeded = Boolean(v2LpTokenApproval) + + const { feeValue0, feeValue1 } = useV3PositionDerivedInfo(positionInfo) + + const decreaseCalldataQueryParams: DecreaseLPPositionRequest | undefined = useMemo(() => { const apiProtocolItems = getProtocolItems(positionInfo?.version) - if (!positionInfo?.restPosition || !apiProtocolItems || !account || percentInvalid) { + if (!positionInfo || !apiProtocolItems || !account || percentInvalid) { return undefined } return { - simulateTransaction: false, + simulateTransaction: !approvalsNeeded, protocol: apiProtocolItems, tokenId: positionInfo.tokenId ? Number(positionInfo.tokenId) : undefined, chainId: positionInfo.currency0Amount.currency.chainId, walletAddress: account, collectAsWeth: false, liquidityPercentageToDecrease: Number(percent), - poolLiquidity: pool?.liquidity, - currentTick: pool?.tick, - sqrtRatioX96: pool?.sqrtPriceX96, + poolLiquidity: pool?.liquidity.toString(), + currentTick: pool?.tickCurrent, + sqrtRatioX96: pool?.sqrtRatioX96.toString(), positionLiquidity: positionInfo.version === ProtocolVersion.V2 ? positionInfo.liquidityAmount?.quotient.toString() : positionInfo.liquidity, - // todo: use correct values here when included in Positions API response (v3+v4 only) - expectedTokenOwed0RawAmount: positionInfo.version === ProtocolVersion.V2 ? undefined : '10', - expectedTokenOwed1RawAmount: positionInfo.version === ProtocolVersion.V2 ? undefined : '10', - // expectedTokenOwed0RawAmount: positionInfo.token0UncollectedFees, - // expectedTokenOwed1RawAmount: positionInfo.token1UncollectedFees, + expectedTokenOwed0RawAmount: feeValue0?.quotient.toString(), + expectedTokenOwed1RawAmount: feeValue1?.quotient.toString(), position: { tickLower: positionInfo.tickLower ? Number(positionInfo.tickLower) : undefined, tickUpper: positionInfo.tickUpper ? Number(positionInfo.tickUpper) : undefined, @@ -105,19 +88,25 @@ export function useRemoveLiquidityTxAndGasInfo({ account }: { account?: string } }, }, } - }, [account, positionInfo, percentInvalid, percent, pool]) + }, [account, positionInfo, percentInvalid, percent, pool, approvalsNeeded, feeValue0, feeValue1]) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { data: reduceCalldata } = useReduceLpPositionCalldataQuery({ - params: reduceCalldataQueryParams, + const { data: decreaseCalldata, isLoading: decreaseCalldataLoading } = useDecreaseLpPositionCalldataQuery({ + params: decreaseCalldataQueryParams, staleTime: 5 * ONE_SECOND_MS, }) - const { value: decreaseGasFee } = useTransactionGasFee(reduceCalldata?.decrease) - const decreaseGasFeeCurrencyAmount = CurrencyAmount.fromRawAmount( - nativeOnChain(positionInfo?.liquidityToken?.chainId ?? UniverseChainId.Mainnet), - decreaseGasFee ?? '0', - ) - const decreaseGasFeeUsd = useUSDCValue(decreaseGasFeeCurrencyAmount) ?? undefined - return { decreaseGasFeeUsd, v2ApprovalGasFeeUSD, reduceCalldata, v2LpTokenApproval } + const { value: estimatedGasFee } = useTransactionGasFee(decreaseCalldata?.decrease, !!decreaseCalldata?.gasFee) + const decreaseGasFeeUsd = + useUSDCurrencyAmountOfGasFee(decreaseCalldata?.decrease?.chainId, decreaseCalldata?.gasFee || estimatedGasFee) ?? + undefined + + const totalGasFeeEstimate = v2ApprovalGasFeeUSD ? decreaseGasFeeUsd?.add(v2ApprovalGasFeeUSD) : decreaseGasFeeUsd + + return { + gasFeeEstimateUSD: totalGasFeeEstimate, + decreaseCalldataLoading, + decreaseCalldata, + v2LpTokenApproval, + approvalLoading: v2ApprovalLoading, + } } diff --git a/apps/web/src/pages/RouteDefinitions.tsx b/apps/web/src/pages/RouteDefinitions.tsx index c033c879757..488cbc7f1c4 100644 --- a/apps/web/src/pages/RouteDefinitions.tsx +++ b/apps/web/src/pages/RouteDefinitions.tsx @@ -222,7 +222,7 @@ export const routes: RouteDefinition[] = [ getDescription: getPositionPageDescription, }), createRouteDefinition({ - path: '/positions/v2/:currencyIdA/:currencyIdB', + path: '/positions/v2/:pairAddress', getElement: () => , getTitle: getPositionPageTitle, getDescription: getPositionPageDescription, diff --git a/apps/web/src/pages/Swap/Limit/LimitForm.tsx b/apps/web/src/pages/Swap/Limit/LimitForm.tsx index 2b0b77375f8..fe90706ce94 100644 --- a/apps/web/src/pages/Swap/Limit/LimitForm.tsx +++ b/apps/web/src/pages/Swap/Limit/LimitForm.tsx @@ -1,7 +1,7 @@ import { InterfaceElementName, InterfaceSectionName, SwapEventName } from '@uniswap/analytics-events' import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core' -import { UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk' -import { MenuState, miniPortfolioMenuStateAtom } from 'components/AccountDrawer/DefaultMenu' +import { UNIVERSAL_ROUTER_ADDRESS, UniversalRouterVersion } from '@uniswap/universal-router-sdk' +import { MenuState, miniPortfolioMenuStateAtom } from 'components/AccountDrawer' import { OpenLimitOrdersButton } from 'components/AccountDrawer/MiniPortfolio/Limits/OpenLimitOrdersButton' import { useAccountDrawer } from 'components/AccountDrawer/MiniPortfolio/hooks' import { ButtonError, ButtonLight } from 'components/Button/buttons' @@ -243,7 +243,7 @@ function LimitForm({ onCurrencyChange }: LimitFormProps) { const allowance = usePermit2Allowance( parsedAmounts.input?.currency?.isNative ? undefined : (parsedAmounts.input as CurrencyAmount), - isSupportedChain ? UNIVERSAL_ROUTER_ADDRESS(chainId) : undefined, + isSupportedChain ? UNIVERSAL_ROUTER_ADDRESS(UniversalRouterVersion.V1_2, chainId) : undefined, TradeFillType.UniswapX, ) diff --git a/apps/web/src/pages/Swap/Send/SendCurrencyInputForm.tsx b/apps/web/src/pages/Swap/Send/SendCurrencyInputForm.tsx index 03659cd04c3..eb76d7c8db0 100644 --- a/apps/web/src/pages/Swap/Send/SendCurrencyInputForm.tsx +++ b/apps/web/src/pages/Swap/Send/SendCurrencyInputForm.tsx @@ -28,6 +28,7 @@ import { CurrencyState } from 'state/swap/types' import { useSwapAndLimitContext } from 'state/swap/useSwapContext' import { ClickableStyle, ThemedText } from 'theme/components' import { Text } from 'ui/src' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import Trace from 'uniswap/src/features/telemetry/Trace' import { Trans } from 'uniswap/src/i18n' import { UniverseChainId } from 'uniswap/src/types/chains' @@ -195,6 +196,7 @@ export default function SendCurrencyInputForm({ }) { const { chainId } = useSwapAndLimitContext() const supportedChain = useSupportedChainId(chainId) + const { isTestnetModeEnabled } = useEnabledChains() const { formatCurrencyAmount } = useFormatter() const { symbol: fiatSymbol } = useActiveLocalCurrencyComponents() const { formatNumber } = useFormatter() @@ -314,12 +316,14 @@ export default function SendCurrencyInputForm({ /> {displayValue} - - - + {isTestnetModeEnabled ? null : ( + + + + )} diff --git a/apps/web/src/pages/Swap/SwapForm.tsx b/apps/web/src/pages/Swap/SwapForm.tsx index acf915c6738..ed575db0efa 100644 --- a/apps/web/src/pages/Swap/SwapForm.tsx +++ b/apps/web/src/pages/Swap/SwapForm.tsx @@ -6,7 +6,7 @@ import { SwapPriceImpactUserResponse, } from '@uniswap/analytics-events' import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core' -import { UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk' +import { UNIVERSAL_ROUTER_ADDRESS, UniversalRouterVersion } from '@uniswap/universal-router-sdk' import { useAccountDrawer } from 'components/AccountDrawer/MiniPortfolio/hooks' import { ButtonError, ButtonLight, ButtonPrimary } from 'components/Button/buttons' import { GrayCard } from 'components/Card/cards' @@ -318,7 +318,7 @@ export function SwapForm({ (parsedAmounts[CurrencyField.INPUT]?.currency.isToken ? (parsedAmounts[CurrencyField.INPUT] as CurrencyAmount) : undefined), - supportedChainId ? UNIVERSAL_ROUTER_ADDRESS(supportedChainId) : undefined, + supportedChainId ? UNIVERSAL_ROUTER_ADDRESS(UniversalRouterVersion.V1_2, supportedChainId) : undefined, trade?.fillType, ) diff --git a/apps/web/src/pages/Swap/index.tsx b/apps/web/src/pages/Swap/index.tsx index ce75a488e0d..434e3421524 100644 --- a/apps/web/src/pages/Swap/index.tsx +++ b/apps/web/src/pages/Swap/index.tsx @@ -7,6 +7,7 @@ import SwapHeader, { PathnameToTab } from 'components/swap/SwapHeader' import { PageWrapper, SwapWrapper } from 'components/swap/styled' import { PrefetchBalancesWrapper } from 'graphql/data/apollo/AdaptiveTokenBalancesProvider' import { useScreenSize } from 'hooks/screenSize/useScreenSize' +import { useIsExplorePage } from 'hooks/useIsExplorePage' import { BuyForm } from 'pages/Swap/Buy/BuyForm' import { LimitFormWrapper } from 'pages/Swap/Limit/LimitForm' import { SendForm } from 'pages/Swap/Send/SendForm' @@ -21,13 +22,15 @@ import { SwapAndLimitContextProvider, SwapContextProvider } from 'state/swap/Swa import { useInitialCurrencyState } from 'state/swap/hooks' import { CurrencyState, SwapAndLimitContext } from 'state/swap/types' import { useIsDarkMode } from 'theme/components/ThemeToggle' -import { Flex, SegmentedControl, Text } from 'ui/src' +import { Flex, SegmentedControl, Text, Tooltip, styled } from 'ui/src' import { AppTFunction } from 'ui/src/i18n/types' +import { zIndices } from 'ui/src/theme' import { useUniswapContext } from 'uniswap/src/contexts/UniswapContext' import { SafetyLevel } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import Trace from 'uniswap/src/features/telemetry/Trace' import { InterfaceEventNameLocal } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' @@ -43,7 +46,7 @@ import { useSwapPrefilledState } from 'uniswap/src/features/transactions/swap/ho import { Deadline } from 'uniswap/src/features/transactions/swap/settings/configs/Deadline' import { currencyToAsset } from 'uniswap/src/features/transactions/swap/utils/asset' import { useTranslation } from 'uniswap/src/i18n' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { SwapTab } from 'uniswap/src/types/screens/interface' import { currencyId } from 'uniswap/src/utils/currencyId' @@ -124,7 +127,7 @@ export function Swap({ swapRedirectCallback, }: { className?: string - chainId?: InterfaceChainId + chainId?: UniverseChainId onCurrencyChange?: (selected: CurrencyState) => void disableTokenInputs?: boolean initialInputCurrency?: Currency @@ -141,9 +144,12 @@ export function Swap({ }) { const isDark = useIsDarkMode() const screenSize = useScreenSize() + const isExplore = useIsExplorePage() const forAggregatorEnabled = useFeatureFlag(FeatureFlags.ForAggregator) const universalSwapFlow = useFeatureFlag(FeatureFlags.UniversalSwap) + const { isTestnetModeEnabled } = useEnabledChains() + const isSharedSwapDisabled = isTestnetModeEnabled && isExplore const input = currencyToAsset(initialInputCurrency) const output = currencyToAsset(initialOutputCurrency) @@ -158,7 +164,7 @@ export function Swap({ selectingCurrencyField: isSwapTokenSelectorOpen ? CurrencyField.OUTPUT : undefined, }) - if (universalSwapFlow) { + if (universalSwapFlow || isTestnetModeEnabled) { return ( - + + {isSharedSwapDisabled && } ) } + +const DisabledOverlay = styled(Flex, { + position: 'absolute', + width: '100%', + height: '100%', + zIndex: zIndices.overlay, +}) + +const DisabledSwapOverlay = () => { + const { t } = useTranslation() + + return ( + + + + + {t('testnet.unsupported')} + + + + + + + ) +} diff --git a/apps/web/src/pages/TokenDetails/TDPContext.tsx b/apps/web/src/pages/TokenDetails/TDPContext.tsx index cecce1dc1a0..1ecdad7a8fd 100644 --- a/apps/web/src/pages/TokenDetails/TDPContext.tsx +++ b/apps/web/src/pages/TokenDetails/TDPContext.tsx @@ -1,11 +1,12 @@ import { QueryResult } from '@apollo/client' import { Currency } from '@uniswap/sdk-core' import { TDPChartState } from 'components/Tokens/TokenDetails/ChartSection' -import { InterfaceGqlChain, SupportedInterfaceChainId } from 'constants/chains' +import { InterfaceGqlChain } from 'constants/chains' import { Warning } from 'constants/deprecatedTokenSafety' import { PortfolioBalance } from 'graphql/data/portfolios' import { PropsWithChildren, createContext, useContext } from 'react' import { Chain, Exact, TokenWebQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' +import { UniverseChainId } from 'uniswap/src/types/chains' export type MultiChainMap = { [chain in Chain]?: { address?: string; balance?: PortfolioBalance } | undefined @@ -14,7 +15,7 @@ export type MultiChainMap = { type BaseTDPContext = { currencyChain: InterfaceGqlChain /** Equivalent to `currency.chainId`, typed as `ChainId` instead of `number` */ - currencyChainId: SupportedInterfaceChainId + currencyChainId: UniverseChainId /** Set to `NATIVE_CHAIN_ID` if currency is native, else equal to `currency.address` */ address: string diff --git a/apps/web/src/pages/TokenDetails/index.tsx b/apps/web/src/pages/TokenDetails/index.tsx index 02dd545479c..b7d2b20b366 100644 --- a/apps/web/src/pages/TokenDetails/index.tsx +++ b/apps/web/src/pages/TokenDetails/index.tsx @@ -21,11 +21,11 @@ import { formatTokenMetatagTitleName } from 'shared-cloud/metatags' import { ThemeProvider } from 'theme' import { nativeOnChain } from 'uniswap/src/constants/tokens' import { useTokenWebQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { InterfaceChainId, UniverseChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { isAddress } from 'utilities/src/addresses' import { getNativeTokenDBAddress } from 'utils/nativeTokens' -function useOnChainToken(address: string | undefined, chainId: InterfaceChainId, skip: boolean) { +function useOnChainToken(address: string | undefined, chainId: UniverseChainId, skip: boolean) { const token = useCurrency(!skip ? address : undefined, chainId) if (skip || !address || (token && token?.symbol === UNKNOWN_TOKEN_SYMBOL)) { @@ -39,7 +39,7 @@ function useOnChainToken(address: string | undefined, chainId: InterfaceChainId, function useTDPCurrency( tokenQuery: ReturnType, tokenAddress: string, - currencyChainId: InterfaceChainId, + currencyChainId: UniverseChainId, isNative: boolean, ) { const { chainId } = useAccount() diff --git a/apps/web/src/pages/TokenDetails/utils.ts b/apps/web/src/pages/TokenDetails/utils.ts index 1b4088a3fb1..bb49ccf0ef7 100644 --- a/apps/web/src/pages/TokenDetails/utils.ts +++ b/apps/web/src/pages/TokenDetails/utils.ts @@ -1,10 +1,9 @@ import { Currency } from '@uniswap/sdk-core' -import { SupportedInterfaceChainId } from 'constants/chains' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { t } from 'uniswap/src/i18n' import { UniverseChainId } from 'uniswap/src/types/chains' -export const getTokenPageTitle = (currency?: Currency, chainId?: SupportedInterfaceChainId) => { +export const getTokenPageTitle = (currency?: Currency, chainId?: UniverseChainId) => { const tokenName = currency?.name const tokenSymbol = currency?.symbol const baseTitle = t('common.buyAndSell') @@ -22,7 +21,7 @@ export const getTokenPageTitle = (currency?: Currency, chainId?: SupportedInterf return `${tokenName} (${tokenSymbol})${chainSuffix}: ${baseTitle}` } -export const getTokenPageDescription = (currency?: Currency, chainId?: SupportedInterfaceChainId) => { +export const getTokenPageDescription = (currency?: Currency, chainId?: UniverseChainId) => { const tokenPageName = currency?.name && currency?.symbol ? `${currency?.name} (${currency?.symbol})` diff --git a/apps/web/src/pages/__snapshots__/routes.test.ts.snap b/apps/web/src/pages/__snapshots__/routes.test.ts.snap index 21f17bdddf9..c3680a05dbe 100644 --- a/apps/web/src/pages/__snapshots__/routes.test.ts.snap +++ b/apps/web/src/pages/__snapshots__/routes.test.ts.snap @@ -156,7 +156,7 @@ Array [ "getElement": [Function], "getTitle": [Function], "nestedPaths": Array [], - "path": "/positions/v2/:currencyIdA/:currencyIdB", + "path": "/positions/v2/:pairAddress", }, Object { "enabled": [Function], diff --git a/apps/web/src/pages/paths.ts b/apps/web/src/pages/paths.ts index 1d2e50cd60f..9c0564be26c 100644 --- a/apps/web/src/pages/paths.ts +++ b/apps/web/src/pages/paths.ts @@ -32,7 +32,7 @@ export const paths = [ '/positions', '/positions/create', '/positions/create/:protocolVersion', - '/positions/v2/:currencyIdA/:currencyIdB', + '/positions/v2/:pairAddress', '/positions/v3/:tokenId', '/positions/v4/:tokenId', '/add/v2', diff --git a/apps/web/src/rpc/ConfiguredJsonRpcProvider.ts b/apps/web/src/rpc/ConfiguredJsonRpcProvider.ts index 99c900d5234..e521dc84d6c 100644 --- a/apps/web/src/rpc/ConfiguredJsonRpcProvider.ts +++ b/apps/web/src/rpc/ConfiguredJsonRpcProvider.ts @@ -1,12 +1,13 @@ import { Networkish } from '@ethersproject/networks' import { StaticJsonRpcProvider } from '@ethersproject/providers' -import { AVERAGE_L1_BLOCK_TIME, SupportedInterfaceChainId } from 'constants/chains' +import { AVERAGE_L1_BLOCK_TIME } from 'constants/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' export default class ConfiguredJsonRpcProvider extends StaticJsonRpcProvider { constructor( url: string | undefined, // Including networkish allows ethers to skip the initial detectNetwork call. - networkish: Networkish & { chainId: SupportedInterfaceChainId }, + networkish: Networkish & { chainId: UniverseChainId }, pollingInterval = AVERAGE_L1_BLOCK_TIME, ) { super(url, networkish) diff --git a/apps/web/src/state/activity/polling/transactions.ts b/apps/web/src/state/activity/polling/transactions.ts index e553133dd95..05ee1a2f876 100644 --- a/apps/web/src/state/activity/polling/transactions.ts +++ b/apps/web/src/state/activity/polling/transactions.ts @@ -1,6 +1,6 @@ import { NEVER_RELOAD } from '@uniswap/redux-multicall' import { useWeb3React } from '@web3-react/core' -import { SupportedInterfaceChainId, getChain } from 'constants/chains' +import { getChain } from 'constants/chains' import { useAccount } from 'hooks/useAccount' import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp' import useBlockNumber from 'lib/hooks/useBlockNumber' @@ -16,7 +16,7 @@ import { isPendingTx } from 'state/transactions/utils' import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' -import { InterfaceChainId, RetryOptions } from 'uniswap/src/types/chains' +import { RetryOptions, UniverseChainId } from 'uniswap/src/types/chains' import { SUBSCRIPTION_CHAINIDS } from 'utilities/src/apollo/constants' interface Transaction { @@ -51,7 +51,7 @@ export function shouldCheck(lastBlockNumber: number, tx: Transaction): boolean { const DEFAULT_RETRY_OPTIONS: RetryOptions = { n: 1, minWait: 0, maxWait: 0 } -function usePendingTransactions(chainId?: SupportedInterfaceChainId) { +function usePendingTransactions(chainId?: UniverseChainId) { const multichainTransactions = useMultichainTransactions() return useMemo(() => { if (!chainId) { @@ -76,7 +76,7 @@ export function usePollPendingTransactions(onActivityUpdate: OnActivityUpdate) { // We can skip polling when the app's current chain is supported by the subscription service. realtimeEnabled && account.chainId && - (SUBSCRIPTION_CHAINIDS as unknown as InterfaceChainId[]).includes(account.chainId) + (SUBSCRIPTION_CHAINIDS as unknown as UniverseChainId[]).includes(account.chainId) ? undefined : account.chainId, ) diff --git a/apps/web/src/state/activity/subscription.ts b/apps/web/src/state/activity/subscription.ts index 50fd03625f5..5a5f4a9242b 100644 --- a/apps/web/src/state/activity/subscription.ts +++ b/apps/web/src/state/activity/subscription.ts @@ -14,7 +14,7 @@ import { TransactionDirection, TransactionStatus, } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' export function useOnAssetActivity(onActivityUpdate: OnActivityUpdate) { const onOrderActivity = useOnOrderActivity(onActivityUpdate) @@ -57,7 +57,7 @@ function useOnOrderActivity(onActivityUpdate: OnActivityUpdate) { function useOnTransactionActivity(onActivityUpdate: OnActivityUpdate) { // Updates should only trigger from the AssetActivity subscription, so the pending transactions are behind a ref. - const pendingTransactions = useRef<[TransactionDetails, InterfaceChainId][]>() + const pendingTransactions = useRef<[TransactionDetails, UniverseChainId][]>() pendingTransactions.current = useMultichainTransactions() return useCallback( diff --git a/apps/web/src/state/activity/types.ts b/apps/web/src/state/activity/types.ts index 49079e186a5..08028f85fe4 100644 --- a/apps/web/src/state/activity/types.ts +++ b/apps/web/src/state/activity/types.ts @@ -1,10 +1,10 @@ -import { SupportedInterfaceChainId } from 'constants/chains' import { FilledUniswapXOrderDetails, SignatureDetails, UnfilledUniswapXOrderDetails } from 'state/signatures/types' import { ConfirmedTransactionDetails, TransactionDetails } from 'state/transactions/types' +import { UniverseChainId } from 'uniswap/src/types/chains' interface BaseUpdate { type: string - chainId: SupportedInterfaceChainId + chainId: UniverseChainId original: T update: Partial } diff --git a/apps/web/src/state/application/reducer.ts b/apps/web/src/state/application/reducer.ts index c7afb5ca341..2c248e5a524 100644 --- a/apps/web/src/state/application/reducer.ts +++ b/apps/web/src/state/application/reducer.ts @@ -1,10 +1,9 @@ import { createSlice, nanoid, PayloadAction } from '@reduxjs/toolkit' +import { PositionInfo } from 'components/Liquidity/types' import { DEFAULT_TXN_DISMISS_MS } from 'constants/misc' import { ModalName, ModalNameType } from 'uniswap/src/features/telemetry/constants' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { SwapTab } from 'uniswap/src/types/screens/interface' -/* eslint-disable-next-line no-restricted-imports */ -import { Position } from '@uniswap/client-pools/dist/pools/v1/types_pb' export enum PopupType { Transaction = 'transaction', @@ -24,11 +23,11 @@ export type PopupContent = } | { type: PopupType.FailedSwitchNetwork - failedSwitchNetwork: InterfaceChainId + failedSwitchNetwork: UniverseChainId } | { type: PopupType.SwitchNetwork - chainId: InterfaceChainId + chainId: UniverseChainId action: SwapTab } @@ -55,16 +54,16 @@ export enum ApplicationModal { type AddLiquidityModalParams = { name: typeof ModalName.AddLiquidity - initialState: Position + initialState: PositionInfo } type RemoveLiquidityModalParams = { name: typeof ModalName.RemoveLiquidity - initialState: Position + initialState: PositionInfo } export type OpenModalParams = - | { name: ApplicationModal; initialState?: undefined } + | { name: ModalNameType | ApplicationModal; initialState?: undefined } | AddLiquidityModalParams | RemoveLiquidityModalParams diff --git a/apps/web/src/state/migrations/3.ts b/apps/web/src/state/migrations/3.ts index bdf5bb8be46..7d5b422981a 100644 --- a/apps/web/src/state/migrations/3.ts +++ b/apps/web/src/state/migrations/3.ts @@ -1,7 +1,7 @@ import { Token } from '@uniswap/sdk-core' import { PersistState } from 'redux-persist' import { PreV16UserState } from 'state/migrations/oldTypes' -import { InterfaceChainId, UniverseChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { serializeToken } from 'uniswap/src/utils/currency' export type PersistAppStateV3 = { @@ -15,17 +15,14 @@ export type PersistAppStateV3 = { export const migration3 = (state: PersistAppStateV3 | undefined) => { if (state?.user) { // Update USDC.e tokens to use the the new USDC.e symbol (from USDC) - const USDCe_ADDRESSES: { [key in InterfaceChainId]?: string } = { + const USDCe_ADDRESSES: { [key in UniverseChainId]?: string } = { [UniverseChainId.Optimism]: '0x7F5c764cBc14f9669B88837ca1490cCa17c31607', - [UniverseChainId.OptimismGoerli]: '0x7E07E15D2a87A24492740D16f5bdF58c16db0c4E', [UniverseChainId.ArbitrumOne]: '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8', - [UniverseChainId.ArbitrumGoerli]: '0x8FB1E3fC51F3b789dED7557E680551d93Ea9d892', [UniverseChainId.Avalanche]: '0xA7D7079b0FEaD91F3e65f86E8915Cb59c1a4C664', [UniverseChainId.Polygon]: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174', - [UniverseChainId.PolygonMumbai]: '0xe11a86849d99f524cac3e7a0ec1241828e332c62', } for (const [chainId, address] of Object.entries(USDCe_ADDRESSES)) { - const chainIdKey = Number(chainId) as InterfaceChainId + const chainIdKey = Number(chainId) as UniverseChainId if (state.user.tokens?.[chainIdKey]?.[address]) { state.user.tokens[chainIdKey][address] = serializeToken( new Token(chainIdKey, address, 6, 'USDC.e', 'Bridged USDC'), diff --git a/apps/web/src/state/reducerTypeTest.ts b/apps/web/src/state/reducerTypeTest.ts index 686d8cf2796..04fa41fde3a 100644 --- a/apps/web/src/state/reducerTypeTest.ts +++ b/apps/web/src/state/reducerTypeTest.ts @@ -33,7 +33,7 @@ import { UserSettingsState } from 'uniswap/src/features/settings/slice' import { TimingState } from 'uniswap/src/features/timing/slice' import { TokensState } from 'uniswap/src/features/tokens/slice/slice' import { TransactionsState } from 'uniswap/src/features/transactions/slice' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' /** * WARNING: @@ -138,7 +138,7 @@ assert>() interface ExpectedWalletState { connectedWallets: Wallet[] - switchingChain: InterfaceChainId | false + switchingChain: UniverseChainId | false } assert>() diff --git a/apps/web/src/state/routing/gas.ts b/apps/web/src/state/routing/gas.ts index 54b347f1746..40bd57f7bd7 100644 --- a/apps/web/src/state/routing/gas.ts +++ b/apps/web/src/state/routing/gas.ts @@ -1,6 +1,5 @@ import { MaxUint256, permit2Address } from '@uniswap/permit2-sdk' import { Currency } from '@uniswap/sdk-core' -import { SupportedInterfaceChainId } from 'constants/chains' import { RPC_PROVIDERS } from 'constants/providers' import { ApproveInfo, WrapInfo } from 'state/routing/types' import ERC20_ABI from 'uniswap/src/abis/erc20.json' @@ -32,11 +31,11 @@ export async function getApproveInfo( // routing-api under estimates gas for Arbitrum swaps so it inflates cost per gas by a lot // so disable showing approves for Arbitrum until routing-api gives more accurate gas estimates - if (currency.chainId === UniverseChainId.ArbitrumOne || currency.chainId === UniverseChainId.ArbitrumGoerli) { + if (currency.chainId === UniverseChainId.ArbitrumOne) { return { needsApprove: false } } - const provider = RPC_PROVIDERS[currency.chainId as SupportedInterfaceChainId] + const provider = RPC_PROVIDERS[currency.chainId as UniverseChainId] const tokenContract = getContract(currency.address, ERC20_ABI, provider) as Erc20 let approveGasUseEstimate @@ -64,7 +63,7 @@ export async function getApproveInfo( export async function getWrapInfo( needsWrap: boolean, account: string | undefined, - chainId: SupportedInterfaceChainId, + chainId: UniverseChainId, amount: string, usdCostPerGas?: number, ): Promise { diff --git a/apps/web/src/state/routing/types.ts b/apps/web/src/state/routing/types.ts index df471da34b2..26dce1726ae 100644 --- a/apps/web/src/state/routing/types.ts +++ b/apps/web/src/state/routing/types.ts @@ -17,7 +17,7 @@ import { Route as V2Route } from '@uniswap/v2-sdk' import { Route as V3Route } from '@uniswap/v3-sdk' import { ZERO_PERCENT } from 'constants/misc' import { BigNumber } from 'ethers/lib/ethers' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' export enum TradeState { LOADING = 'loading', @@ -53,11 +53,11 @@ export enum QuoteIntent { export interface GetQuoteArgs { tokenInAddress: string - tokenInChainId: InterfaceChainId + tokenInChainId: UniverseChainId tokenInDecimals: number tokenInSymbol?: string tokenOutAddress: string - tokenOutChainId: InterfaceChainId + tokenOutChainId: UniverseChainId tokenOutDecimals: number tokenOutSymbol?: string amount: string @@ -81,11 +81,11 @@ export interface GetQuoteArgs { export type GetQuickQuoteArgs = { amount: string tokenInAddress: string - tokenInChainId: InterfaceChainId + tokenInChainId: UniverseChainId tokenInDecimals: number tokenInSymbol?: string tokenOutAddress: string - tokenOutChainId: InterfaceChainId + tokenOutChainId: UniverseChainId tokenOutDecimals: number tokenOutSymbol?: string tradeType: TradeType diff --git a/apps/web/src/state/sagas/liquidity/liquiditySaga.ts b/apps/web/src/state/sagas/liquidity/liquiditySaga.ts index 7d2eb98d324..93e77c5fdaa 100644 --- a/apps/web/src/state/sagas/liquidity/liquiditySaga.ts +++ b/apps/web/src/state/sagas/liquidity/liquiditySaga.ts @@ -5,13 +5,18 @@ import { handleOnChainStep, handleSignatureStep, } from 'state/sagas/transactions/utils' -import { IncreaseLiquidityTransactionInfo, TransactionType } from 'state/transactions/types' +import { + DecreaseLiquidityTransactionInfo, + IncreaseLiquidityTransactionInfo, + TransactionType, +} from 'state/transactions/types' import invariant from 'tiny-invariant' import { call, put } from 'typed-redux-saga' import { SignerMnemonicAccountMeta } from 'uniswap/src/features/accounts/types' import { LiquidityAction, ValidatedLiquidityTxContext } from 'uniswap/src/features/transactions/liquidity/types' import { SetCurrentStepFn } from 'uniswap/src/features/transactions/swap/types/swapCallback' import { + DecreasePositionTransactionStep, IncreasePositionTransactionStep, IncreasePositionTransactionStepAsync, TransactionStep, @@ -34,10 +39,13 @@ type LiquidityParams = { } function* getLiquidityTxRequest( - step: IncreasePositionTransactionStep | IncreasePositionTransactionStepAsync, + step: IncreasePositionTransactionStep | IncreasePositionTransactionStepAsync | DecreasePositionTransactionStep, signature: string | undefined, ) { - if (step.type === TransactionStepType.IncreasePositionTransaction) { + if ( + step.type === TransactionStepType.IncreasePositionTransaction || + step.type === TransactionStepType.DecreasePositionTransaction + ) { return step.txRequest } @@ -55,29 +63,31 @@ function* getLiquidityTxRequest( } } -interface HandleIncreasePositionStepParams extends Omit { - step: IncreasePositionTransactionStep | IncreasePositionTransactionStepAsync +interface HandlePositionStepParams extends Omit { + step: IncreasePositionTransactionStep | IncreasePositionTransactionStepAsync | DecreasePositionTransactionStep signature?: string action: LiquidityAction } -function* handleIncreasePositionTransactionStep(params: HandleIncreasePositionStepParams) { +function* handlePositionTransactionStep(params: HandlePositionStepParams) { const { action, step, signature } = params - const info = getIncreaseLiquidityTransactionInfo(action) + const info = getLiquidityTransactionInfo(action, step.type) const txRequest = yield* call(getLiquidityTxRequest, step, signature) // Now that we have the txRequest, we can create a definitive LiquidityTransactionStep, incase we started with an async step. const onChainStep = { ...step, txRequest } - const hash = yield* call(handleOnChainStep, { ...params, info, step: onChainStep }) + const hash = yield* call(handleOnChainStep, { ...params, info, step: onChainStep, shouldWaitForConfirmation: false }) yield* put(addPopup({ content: { type: PopupType.Transaction, hash }, key: hash })) } -function* increaseLiquidity(params: LiquidityParams & { steps: TransactionStep[] }) { +function* modifyLiquidity(params: LiquidityParams & { steps: TransactionStep[] }) { const { account, setCurrentStep, steps, liquidityTxContext: { action }, + onSuccess, + onFailure, } = params let signature: string | undefined @@ -95,7 +105,8 @@ function* increaseLiquidity(params: LiquidityParams & { steps: TransactionStep[] } case TransactionStepType.IncreasePositionTransaction: case TransactionStepType.IncreasePositionTransactionAsync: - yield* call(handleIncreasePositionTransactionStep, { account, signature, step, setCurrentStep, action }) + case TransactionStepType.DecreasePositionTransaction: + yield* call(handlePositionTransactionStep, { account, step, setCurrentStep, action, signature }) break default: { throw new Error('Unexpected step type') @@ -103,9 +114,12 @@ function* increaseLiquidity(params: LiquidityParams & { steps: TransactionStep[] } } } catch (e) { - // TODO(5082): pass errors to onFailure and to handle in UI - logger.error(e, { tags: { file: 'liquiditySaga', function: 'increaseLiquidity' } }) + logger.error(e, { tags: { file: 'liquiditySaga', function: 'modifyLiquidity' } }) + onFailure() + return } + + yield* call(onSuccess) } function* liquidity(params: LiquidityParams) { @@ -126,7 +140,7 @@ function* liquidity(params: LiquidityParams) { } } - return yield* increaseLiquidity({ + return yield* modifyLiquidity({ ...params, steps, }) @@ -134,13 +148,19 @@ function* liquidity(params: LiquidityParams) { export const liquiditySaga = createSaga(liquidity, 'liquiditySaga') -function getIncreaseLiquidityTransactionInfo(action: LiquidityAction): IncreaseLiquidityTransactionInfo { +function getLiquidityTransactionInfo( + action: LiquidityAction, + type: TransactionStepType, +): IncreaseLiquidityTransactionInfo | DecreaseLiquidityTransactionInfo { const { currency0Amount: { currency: currency0, quotient: quotient0 }, currency1Amount: { currency: currency1, quotient: quotient1 }, } = action return { - type: TransactionType.INCREASE_LIQUIDITY, + type: + type === TransactionStepType.DecreasePositionTransaction + ? TransactionType.DECREASE_LIQUIDITY + : TransactionType.INCREASE_LIQUIDITY, token0CurrencyId: currencyId(currency0), token1CurrencyId: currencyId(currency1), token0CurrencyAmountRaw: quotient0.toString(), diff --git a/apps/web/src/state/sagas/transactions/swapSaga.ts b/apps/web/src/state/sagas/transactions/swapSaga.ts index ec6f4a69383..ea509c2f8d3 100644 --- a/apps/web/src/state/sagas/transactions/swapSaga.ts +++ b/apps/web/src/state/sagas/transactions/swapSaga.ts @@ -22,6 +22,8 @@ import { call, put } from 'typed-redux-saga' import { FetchError } from 'uniswap/src/data/apiClients/FetchError' import { Routing } from 'uniswap/src/data/tradingApi/__generated__/index' import { SignerMnemonicAccountMeta } from 'uniswap/src/features/accounts/types' +import { FeatureFlags } from 'uniswap/src/features/gating/flags' +import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { selectSwapStartTimestamp } from 'uniswap/src/features/timing/selectors' @@ -133,14 +135,15 @@ type SwapParams = { setSteps: (steps: TransactionStep[]) => void onSuccess: () => void onFailure: (error?: Error) => void + v4Enabled: boolean } // eslint-disable-next-line consistent-return function* swap(params: SwapParams) { - const { swapTxContext, setSteps, selectChain, startChainId, onFailure } = params + const { swapTxContext, setSteps, selectChain, startChainId, v4Enabled, onFailure } = params try { - const steps = yield* call(generateTransactionSteps, swapTxContext) + const steps = yield* call(generateTransactionSteps, swapTxContext, v4Enabled) setSteps(steps) // Switch chains if needed @@ -293,6 +296,7 @@ export function useSwapCallback(): SwapCallback { const swapStartTimestamp = useSelector(selectSwapStartTimestamp) const selectChain = useSelectChain() const startChainId = useAccount().chainId + const v4Enabled = useFeatureFlag(FeatureFlags.V4Everywhere) const portfolioBalanceUsd = useTotalBalancesUsdForAnalytics() @@ -329,6 +333,7 @@ export function useSwapCallback(): SwapCallback { setSteps, selectChain, startChainId, + v4Enabled, } appDispatch(swapSaga.actions.trigger(swapParams)) @@ -348,6 +353,6 @@ export function useSwapCallback(): SwapCallback { // Reset swap start timestamp now that the swap has been submitted appDispatch(updateSwapStartTimestamp({ timestamp: undefined })) }, - [formatter, portfolioBalanceUsd, selectChain, startChainId, appDispatch, swapStartTimestamp], + [formatter, portfolioBalanceUsd, selectChain, startChainId, appDispatch, swapStartTimestamp, v4Enabled], ) } diff --git a/apps/web/src/state/send/SendContext.tsx b/apps/web/src/state/send/SendContext.tsx index e0fdae5ad2d..153ae2c40a6 100644 --- a/apps/web/src/state/send/SendContext.tsx +++ b/apps/web/src/state/send/SendContext.tsx @@ -11,6 +11,7 @@ import { } from 'react' import { RecipientData, SendInfo, useDerivedSendInfo } from 'state/send/hooks' import { useSwapAndLimitContext } from 'state/swap/useSwapContext' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' export type SendState = { readonly exactAmountToken?: string @@ -47,6 +48,15 @@ const DEFAULT_SEND_STATE: SendState = { validatedRecipientData: undefined, } +const DEFAULT_TESTNET_SEND_STATE: SendState = { + exactAmountToken: '', + exactAmountFiat: undefined, + recipient: '', + inputCurrency: undefined, + inputInFiat: false, + validatedRecipientData: undefined, +} + // exported for testing export const SendContext = createContext({ sendState: DEFAULT_SEND_STATE, @@ -73,11 +83,16 @@ export function SendContextProvider({ children }: PropsWithChildren) { setCurrencyState, } = useSwapAndLimitContext() + const { isTestnetModeEnabled } = useEnabledChains() + const initialCurrency = useMemo(() => { return inputCurrency ?? outputCurrency }, [inputCurrency, outputCurrency]) - const [sendState, setSendState] = useState({ ...DEFAULT_SEND_STATE, inputCurrency: initialCurrency }) + const [sendState, setSendState] = useState({ + ...(isTestnetModeEnabled ? DEFAULT_TESTNET_SEND_STATE : DEFAULT_SEND_STATE), + inputCurrency: initialCurrency, + }) const derivedSendInfo = useDerivedSendInfo(sendState) useEffect(() => { diff --git a/apps/web/src/state/signatures/hooks.ts b/apps/web/src/state/signatures/hooks.ts index 23eff76e5b6..be2b78c1e63 100644 --- a/apps/web/src/state/signatures/hooks.ts +++ b/apps/web/src/state/signatures/hooks.ts @@ -1,4 +1,3 @@ -import { SupportedInterfaceChainId } from 'constants/chains' import { useAccount } from 'hooks/useAccount' import { useCallback, useMemo } from 'react' import { useDispatch } from 'react-redux' @@ -12,6 +11,7 @@ import { UniswapXOrderDetails, } from 'state/signatures/types' import { UniswapXOrderStatus } from 'types/uniswapx' +import { UniverseChainId } from 'uniswap/src/types/chains' export function useAllSignatures(): { [id: string]: SignatureDetails } { const account = useAccount() @@ -55,7 +55,7 @@ export function useAddOrder() { ( offerer: string, orderHash: string, - chainId: SupportedInterfaceChainId, + chainId: UniverseChainId, expiry: number, swapInfo: UniswapXOrderDetails['swapInfo'], encodedOrder: string, diff --git a/apps/web/src/state/signatures/parseRemote.ts b/apps/web/src/state/signatures/parseRemote.ts index 8d67ce84e6f..0a307b3385d 100644 --- a/apps/web/src/state/signatures/parseRemote.ts +++ b/apps/web/src/state/signatures/parseRemote.ts @@ -14,7 +14,7 @@ const SIGNATURE_TYPE_MAP: { [key in SwapOrderType]: SignatureType } = { [SwapOrderType.Limit]: SignatureType.SIGN_LIMIT, [SwapOrderType.Dutch]: SignatureType.SIGN_UNISWAPX_ORDER, [SwapOrderType.DutchV2]: SignatureType.SIGN_UNISWAPX_V2_ORDER, - // [SwapOrderType.Priority]: SignatureType.SIGN_PRIORITY_ORDER, + [SwapOrderType.Priority]: SignatureType.SIGN_PRIORITY_ORDER, } const ORDER_STATUS_MAP: { [key in SwapOrderStatus]: UniswapXOrderStatus } = { diff --git a/apps/web/src/state/signatures/types.ts b/apps/web/src/state/signatures/types.ts index 1e74fb7fb65..0464566922b 100644 --- a/apps/web/src/state/signatures/types.ts +++ b/apps/web/src/state/signatures/types.ts @@ -1,4 +1,3 @@ -import { SupportedInterfaceChainId } from 'constants/chains' import { OffchainOrderType } from 'state/routing/types' import { ExactInputSwapTransactionInfo, ExactOutputSwapTransactionInfo } from 'state/transactions/types' import { UniswapXOrderStatus } from 'types/uniswapx' @@ -6,6 +5,7 @@ import { AssetActivityPartsFragment, SwapOrderDetailsPartsFragment, } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' +import { UniverseChainId } from 'uniswap/src/types/chains' import { Prettify } from 'viem/chains' export type OrderActivity = AssetActivityPartsFragment & { details: SwapOrderDetailsPartsFragment } @@ -28,7 +28,7 @@ interface BaseSignatureFields { type?: SignatureType id: string addedTime: number - chainId: SupportedInterfaceChainId + chainId: UniverseChainId expiry?: number offerer: string } diff --git a/apps/web/src/state/swap/SwapContext.tsx b/apps/web/src/state/swap/SwapContext.tsx index 810866cc7e6..5b1be57efd9 100644 --- a/apps/web/src/state/swap/SwapContext.tsx +++ b/apps/web/src/state/swap/SwapContext.tsx @@ -8,7 +8,8 @@ import { PropsWithChildren, useEffect, useMemo, useState } from 'react' import { useDerivedSwapInfo } from 'state/swap/hooks' import { CurrencyState, SwapAndLimitContext, SwapContext, SwapState, initialSwapState } from 'state/swap/types' import { useSwapAndLimitContext } from 'state/swap/useSwapContext' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { SwapTab } from 'uniswap/src/types/screens/interface' import { areCurrenciesEqual } from 'uniswap/src/utils/currencyId' @@ -20,12 +21,12 @@ export function SwapAndLimitContextProvider({ initialOutputCurrency, multichainUXEnabled, }: PropsWithChildren<{ - initialChainId?: InterfaceChainId + initialChainId?: UniverseChainId initialInputCurrency?: Currency initialOutputCurrency?: Currency multichainUXEnabled?: boolean }>) { - const [selectedChainId, setSelectedChainId] = useState(initialChainId) + const [selectedChainId, setSelectedChainId] = useState(initialChainId) const [isUserSelectedToken, setIsUserSelectedToken] = useState(false) const [currentTab, setCurrentTab] = useState(SwapTab.Swap) const [currencyState, setCurrencyState] = useState({ @@ -45,11 +46,14 @@ export function SwapAndLimitContextProvider({ const previousInitialInputCurrency = usePrevious(initialInputCurrency) const previousInitialOutputCurrency = usePrevious(initialOutputCurrency) const previousInitialChainId = usePrevious(initialChainId) + const { isTestnetModeEnabled } = useEnabledChains() + const previousIsTestnetModeEnabled = usePrevious(isTestnetModeEnabled) useEffect(() => { if ( !areCurrenciesEqual(previousInitialInputCurrency, initialInputCurrency) || - !areCurrenciesEqual(previousInitialOutputCurrency, initialOutputCurrency) + !areCurrenciesEqual(previousInitialOutputCurrency, initialOutputCurrency) || + previousIsTestnetModeEnabled !== isTestnetModeEnabled ) { // prefilled state may load in -- i.e. `outputCurrency` URL param pulling from gql setCurrencyState(prefilledState) @@ -60,6 +64,8 @@ export function SwapAndLimitContextProvider({ prefilledState, previousInitialInputCurrency, previousInitialOutputCurrency, + isTestnetModeEnabled, + previousIsTestnetModeEnabled, ]) useEffect(() => { diff --git a/apps/web/src/state/swap/hooks.tsx b/apps/web/src/state/swap/hooks.tsx index 55ddd9e41f3..06d5de1ea67 100644 --- a/apps/web/src/state/swap/hooks.tsx +++ b/apps/web/src/state/swap/hooks.tsx @@ -20,8 +20,9 @@ import { CurrencyState, SerializedCurrencyState, SwapInfo, SwapState } from 'sta import { useSwapAndLimitContext, useSwapContext } from 'state/swap/useSwapContext' import { useUserSlippageToleranceWithDefault } from 'state/user/hooks' import { useTokenProjects } from 'uniswap/src/features/dataApi/tokenProjects' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { Trans } from 'uniswap/src/i18n' -import { InterfaceChainId, UniverseChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { areCurrencyIdsEqual, currencyId } from 'uniswap/src/utils/currencyId' import { isAddress } from 'utilities/src/addresses' @@ -387,10 +388,11 @@ export function useInitialCurrencyState(): { initialOutputCurrency?: Currency initialTypedValue?: string initialField?: CurrencyField - initialChainId: InterfaceChainId + initialChainId: UniverseChainId initialCurrencyLoading: boolean } { const { chainId, setIsUserSelectedToken } = useSwapAndLimitContext() + const { defaultChainId } = useEnabledChains() const parsedQs = useParsedQueryString() const parsedCurrencyState = useMemo(() => { @@ -412,7 +414,7 @@ export function useInitialCurrencyState(): { if (!hasCurrencyQueryParams) { return { initialInputCurrencyAddress: 'ETH', - initialChainId: UniverseChainId.Mainnet, + initialChainId: defaultChainId, } } // Handle query params or disconnected state @@ -431,6 +433,7 @@ export function useInitialCurrencyState(): { hasCurrencyQueryParams, parsedCurrencyState.inputCurrencyId, parsedCurrencyState.outputCurrencyId, + defaultChainId, supportedChainId, ]) diff --git a/apps/web/src/state/swap/types.ts b/apps/web/src/state/swap/types.ts index 21a88263666..55c0cefd07d 100644 --- a/apps/web/src/state/swap/types.ts +++ b/apps/web/src/state/swap/types.ts @@ -1,7 +1,7 @@ import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core' import { Dispatch, ReactNode, SetStateAction, createContext } from 'react' import { InterfaceTrade, RouterPreference, TradeState } from 'state/routing/types' -import { InterfaceChainId, UniverseChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { SwapTab } from 'uniswap/src/types/screens/interface' @@ -59,7 +59,7 @@ type SwapAndLimitContextType = { inputCurrency?: Currency outputCurrency?: Currency } - setSelectedChainId: Dispatch> + setSelectedChainId: Dispatch> isUserSelectedToken: boolean setIsUserSelectedToken: Dispatch> setCurrencyState: Dispatch> @@ -67,9 +67,9 @@ type SwapAndLimitContextType = { setCurrentTab: Dispatch> // The chainId of the context - can be different from the connected Chain ID // if multichain UX is enabled, otherwise it will be the same as the connected chain ID - chainId?: InterfaceChainId + chainId?: UniverseChainId // The initial chain ID - used by TDP and PDP pages to keep swap scoped to the initial chain - initialChainId?: InterfaceChainId + initialChainId?: UniverseChainId multichainUXEnabled?: boolean // Components may use swap and limit context while outside of the context // this flag is used to determine if we should fallback to account.chainId diff --git a/apps/web/src/state/transactions/hooks.tsx b/apps/web/src/state/transactions/hooks.tsx index 48b56416f53..fc7efc0a525 100644 --- a/apps/web/src/state/transactions/hooks.tsx +++ b/apps/web/src/state/transactions/hooks.tsx @@ -1,7 +1,6 @@ import { BigNumber } from '@ethersproject/bignumber' import type { TransactionResponse } from '@ethersproject/providers' import { Token } from '@uniswap/sdk-core' -import { SupportedInterfaceChainId } from 'constants/chains' import { useAccount } from 'hooks/useAccount' import { SwapResult } from 'hooks/useSwapCallback' import { useCallback, useMemo } from 'react' @@ -16,7 +15,7 @@ import { } from 'state/transactions/types' import { isConfirmedTx, isPendingTx } from 'state/transactions/utils' import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { WEB_SUPPORTED_CHAIN_IDS } from 'uniswap/src/types/chains' +import { COMBINED_CHAIN_IDS, UniverseChainId } from 'uniswap/src/types/chains' // helper that can take a ethers library transaction response and add it to the list of transactions export function useTransactionAdder(): ( @@ -71,11 +70,11 @@ export function useTransactionCanceller() { ) } -export function useMultichainTransactions(): [TransactionDetails, SupportedInterfaceChainId][] { +export function useMultichainTransactions(): [TransactionDetails, UniverseChainId][] { const state = useAppSelector((state) => state.localWebTransactions) - return WEB_SUPPORTED_CHAIN_IDS.flatMap((chainId) => + return COMBINED_CHAIN_IDS.flatMap((chainId) => state[chainId] - ? Object.values(state[chainId]).map((tx): [TransactionDetails, SupportedInterfaceChainId] => [tx, chainId]) + ? Object.values(state[chainId]).map((tx): [TransactionDetails, UniverseChainId] => [tx, chainId]) : [], ) } diff --git a/apps/web/src/state/transactions/reducer.ts b/apps/web/src/state/transactions/reducer.ts index 1c80b64bb7f..4b047654c4d 100644 --- a/apps/web/src/state/transactions/reducer.ts +++ b/apps/web/src/state/transactions/reducer.ts @@ -1,7 +1,7 @@ import { createSlice } from '@reduxjs/toolkit' import { PendingTransactionDetails, TransactionDetails, TransactionInfo } from 'state/transactions/types' import { TransactionStatus } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' // TODO(WEB-2053): update this to be a map of account -> chainId -> txHash -> TransactionDetails // to simplify usage, once we're able to invalidate localstorage @@ -21,7 +21,7 @@ const localTransactionSlice = createSlice({ transactions, { payload: { chainId, hash, ...details }, - }: { payload: { chainId: InterfaceChainId } & Omit }, + }: { payload: { chainId: UniverseChainId } & Omit }, ) { if (transactions[chainId]?.[hash]) { throw Error('Attempted to add existing transaction.') @@ -35,7 +35,7 @@ const localTransactionSlice = createSlice({ } transactions[chainId] = txs }, - clearAllTransactions(transactions, { payload: { chainId } }: { payload: { chainId: InterfaceChainId } }) { + clearAllTransactions(transactions, { payload: { chainId } }: { payload: { chainId: UniverseChainId } }) { if (!transactions[chainId]) { return } @@ -43,7 +43,7 @@ const localTransactionSlice = createSlice({ }, removeTransaction( transactions, - { payload: { chainId, hash } }: { payload: { chainId: InterfaceChainId; hash: string } }, + { payload: { chainId, hash } }: { payload: { chainId: UniverseChainId; hash: string } }, ) { if (transactions[chainId][hash]) { delete transactions[chainId][hash] @@ -53,7 +53,7 @@ const localTransactionSlice = createSlice({ transactions, { payload: { chainId, hash, blockNumber }, - }: { payload: { chainId: InterfaceChainId; hash: string; blockNumber: number } }, + }: { payload: { chainId: UniverseChainId; hash: string; blockNumber: number } }, ) { const tx = transactions[chainId]?.[hash] if (!tx || tx.status !== TransactionStatus.Pending) { @@ -71,7 +71,7 @@ const localTransactionSlice = createSlice({ payload: { chainId, hash, status, info }, }: { payload: { - chainId: InterfaceChainId + chainId: UniverseChainId hash: string status: TransactionStatus info?: TransactionInfo @@ -93,7 +93,7 @@ const localTransactionSlice = createSlice({ transactions, { payload: { chainId, hash, cancelHash }, - }: { payload: { chainId: InterfaceChainId; hash: string; cancelHash: string } }, + }: { payload: { chainId: UniverseChainId; hash: string; cancelHash: string } }, ) { const tx = transactions[chainId]?.[hash] diff --git a/apps/web/src/state/transactions/types.ts b/apps/web/src/state/transactions/types.ts index 5bf0cc53219..de967f083db 100644 --- a/apps/web/src/state/transactions/types.ts +++ b/apps/web/src/state/transactions/types.ts @@ -40,6 +40,7 @@ export enum TransactionType { CANCEL, LIMIT, INCREASE_LIQUIDITY, + DECREASE_LIQUIDITY, // Always add to the bottom of this enum } @@ -140,6 +141,14 @@ export interface IncreaseLiquidityTransactionInfo { token1CurrencyAmountRaw: string } +export interface DecreaseLiquidityTransactionInfo { + type: TransactionType.DECREASE_LIQUIDITY + token0CurrencyId: string + token1CurrencyId: string + token0CurrencyAmountRaw: string + token1CurrencyAmountRaw: string +} + export interface AddLiquidityV3PoolTransactionInfo { type: TransactionType.ADD_LIQUIDITY_V3_POOL createPool: boolean @@ -213,6 +222,7 @@ export type TransactionInfo = | SubmitProposalTransactionInfo | SendTransactionInfo | IncreaseLiquidityTransactionInfo + | DecreaseLiquidityTransactionInfo interface BaseTransactionDetails { status: TransactionStatus diff --git a/apps/web/src/state/wallets/reducer.ts b/apps/web/src/state/wallets/reducer.ts index cfdaaa96d03..00de61abdc3 100644 --- a/apps/web/src/state/wallets/reducer.ts +++ b/apps/web/src/state/wallets/reducer.ts @@ -1,13 +1,13 @@ import { createSlice } from '@reduxjs/toolkit' import { shallowEqual } from 'react-redux' import { Wallet } from 'state/wallets/types' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' export interface ConnectedWalletsState { // Used to track wallets that have been connected by the user in current session, and remove them when deliberately disconnected. // Used to compute is_reconnect event property for analytics connectedWallets: Wallet[] - switchingChain: InterfaceChainId | false + switchingChain: UniverseChainId | false } const initialState: ConnectedWalletsState = { diff --git a/apps/web/src/test-utils/bundle-size-test.ts b/apps/web/src/test-utils/bundle-size-test.ts index eee5d4e838f..107f19d3d5c 100644 --- a/apps/web/src/test-utils/bundle-size-test.ts +++ b/apps/web/src/test-utils/bundle-size-test.ts @@ -32,8 +32,8 @@ const entryGzipSize = report.reduce( 0, ) -// somewhat arbitrary, based on current size (10/2/2024) -const limit = 2_216_000 +// somewhat arbitrary, based on current size (10/4/2024) +const limit = 2_300_000 if (entryGzipSize > limit) { console.error(`Bundle size has grown too big! Entry JS size is ${entryGzipSize}, over the limit of ${limit}.`) diff --git a/apps/web/src/test-utils/tokens/mocks.ts b/apps/web/src/test-utils/tokens/mocks.ts index 2fc1e9747d5..f3498e8f7c2 100644 --- a/apps/web/src/test-utils/tokens/mocks.ts +++ b/apps/web/src/test-utils/tokens/mocks.ts @@ -26,12 +26,12 @@ import { import { mocked } from 'test-utils/mocked' import { COMMON_BASES } from 'uniswap/src/constants/routing' import { DAI, DAI_ARBITRUM_ONE, USDC_ARBITRUM, USDC_MAINNET, USDT, WBTC } from 'uniswap/src/constants/tokens' -import { InterfaceChainId, UniverseChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { isSameAddress } from 'utilities/src/addresses' beforeEach(() => { // Global mocks for token lookups. To override in a test, use `mocked().mockImplementation(...)`. - mocked(getCurrency).mockImplementation(async (currencyId: string, chainId: InterfaceChainId) => { + mocked(getCurrency).mockImplementation(async (currencyId: string, chainId: UniverseChainId) => { if (currencyId?.toLowerCase() === 'eth') { return NATIVE_INFO?.currency } @@ -69,7 +69,7 @@ beforeEach(() => { base.currency.isNative ? base.currency.symbol === 'ETH' : base.currency.address === currencyId, )?.currency }) - mocked(useCurrency).mockImplementation((address?: string, chainId?: InterfaceChainId) => { + mocked(useCurrency).mockImplementation((address?: string, chainId?: UniverseChainId) => { if (address?.toLowerCase() === 'eth') { return NATIVE_INFO?.currency } diff --git a/apps/web/src/theme/components/RadialGradientByChainUpdater.ts b/apps/web/src/theme/components/RadialGradientByChainUpdater.ts index c7eb3e8c169..2926c649883 100644 --- a/apps/web/src/theme/components/RadialGradientByChainUpdater.ts +++ b/apps/web/src/theme/components/RadialGradientByChainUpdater.ts @@ -52,8 +52,7 @@ export default function RadialGradientByChainUpdater(): null { } switch (chainId) { - case UniverseChainId.ArbitrumOne: - case UniverseChainId.ArbitrumGoerli: { + case UniverseChainId.ArbitrumOne: { setBackground(backgroundResetStyles) const arbitrumLightGradient = 'radial-gradient(100% 100% at 50% 0%, rgba(205, 232, 251, 0) 0%, rgba(252, 243, 249, 0) 49.48%, rgba(255, 255, 255, 0) 100%), #FFFFFF' @@ -62,8 +61,7 @@ export default function RadialGradientByChainUpdater(): null { backgroundRadialGradientElement.style.background = darkMode ? arbitrumDarkGradient : arbitrumLightGradient break } - case UniverseChainId.Optimism: - case UniverseChainId.OptimismGoerli: { + case UniverseChainId.Optimism: { setBackground(backgroundResetStyles) const optimismLightGradient = 'radial-gradient(100% 100% at 50% 0%, rgba(255, 251, 242, 0) 0%, rgba(255, 244, 249, 0) 50.52%, rgba(255, 255, 255, 0) 100%), #FFFFFF' @@ -72,8 +70,7 @@ export default function RadialGradientByChainUpdater(): null { backgroundRadialGradientElement.style.background = darkMode ? optimismDarkGradient : optimismLightGradient break } - case UniverseChainId.Polygon: - case UniverseChainId.PolygonMumbai: { + case UniverseChainId.Polygon: { setBackground(backgroundResetStyles) const polygonLightGradient = 'radial-gradient(100% 100% at 50% 0%, rgba(130, 71, 229, 0) 0%, rgba(200, 168, 255, 0.05) 52.6%, rgba(0, 0, 0, 0) 100%), #FFFFFF' @@ -82,8 +79,7 @@ export default function RadialGradientByChainUpdater(): null { backgroundRadialGradientElement.style.background = darkMode ? polygonDarkGradient : polygonLightGradient break } - case UniverseChainId.Celo: - case UniverseChainId.CeloAlfajores: { + case UniverseChainId.Celo: { setBackground(backgroundResetStyles) const celoLightGradient = 'radial-gradient(100% 100% at 50% 0%, rgba(186, 228, 210, 0) 0%, rgba(252, 243, 249, 0) 49.48%, rgba(255, 255, 255, 0) 100%), #FFFFFF' diff --git a/apps/web/src/theme/index.tsx b/apps/web/src/theme/index.tsx index b570a7ea807..571b4a292cd 100644 --- a/apps/web/src/theme/index.tsx +++ b/apps/web/src/theme/index.tsx @@ -1,6 +1,4 @@ import { useConnectorWithId } from 'components/WalletModal/useOrderedConnections' -import { CONNECTION } from 'components/Web3Provider/constants' -import { WalletConnectConnector } from 'components/Web3Provider/walletConnect' import { ThemeProvider as StyledComponentsThemeProvider, createGlobalStyle, css } from 'lib/styled-components' import { rootCssString } from 'nft/css/cssStringFromTheme' import { PropsWithChildren, useEffect, useMemo } from 'react' @@ -8,6 +6,8 @@ import { ThemeColors, darkTheme, lightTheme } from 'theme/colors' import { useIsDarkMode } from 'theme/components/ThemeToggle' import { darkDeprecatedTheme, lightDeprecatedTheme } from 'theme/deprecatedColors' import { getAccent2, getNeutralContrast } from 'theme/utils' +import { CONNECTION_PROVIDER_IDS } from 'uniswap/src/constants/web3' +import { WalletConnectConnector } from 'uniswap/src/features/web3/walletConnect' export const MEDIA_WIDTHS = { deprecated_upToExtraSmall: 500, @@ -149,7 +149,7 @@ export function ThemeProvider({ children, ...overriddenColors }: PropsWithChildr // eslint-disable-next-line react-hooks/exhaustive-deps -- only update when darkMode or overriddenColors' entries change const themeObject = useMemo(() => getTheme(darkMode, overriddenColors), [darkMode, JSON.stringify(overriddenColors)]) - const walletConnectConnector = useConnectorWithId(CONNECTION.WALLET_CONNECT_CONNECTOR_ID, { + const walletConnectConnector = useConnectorWithId(CONNECTION_PROVIDER_IDS.WALLET_CONNECT_CONNECTOR_ID, { shouldThrow: true, }) as WalletConnectConnector useEffect(() => { diff --git a/apps/web/src/utils/__snapshots__/getRoutingDiagramEntries.test.ts.snap b/apps/web/src/utils/__snapshots__/getRoutingDiagramEntries.test.ts.snap index 07bc609652a..e05d0cd6845 100644 --- a/apps/web/src/utils/__snapshots__/getRoutingDiagramEntries.test.ts.snap +++ b/apps/web/src/utils/__snapshots__/getRoutingDiagramEntries.test.ts.snap @@ -28,6 +28,7 @@ Array [ "symbol": "DEF", }, 10000, + "V3", ], ], "percent": Percent { diff --git a/apps/web/src/utils/anonymizeLink.test.ts b/apps/web/src/utils/anonymizeLink.test.ts index 8f7621a9685..a070cf061bb 100644 --- a/apps/web/src/utils/anonymizeLink.test.ts +++ b/apps/web/src/utils/anonymizeLink.test.ts @@ -8,18 +8,18 @@ describe('#anonymizeLink', () => { expect(anonymizeLink('https://etherscan.io/address/0xabcd')).toEqual('https://etherscan.io/address/***') }) it('anonymizes any addresses in testnet etherscan urls', () => { - expect(anonymizeLink('https://goerli.etherscan.io/address/0xabcd')).toEqual( - 'https://goerli.etherscan.io/address/***', + expect(anonymizeLink('https://sepolia.etherscan.io/address/0xabcd')).toEqual( + 'https://sepolia.etherscan.io/address/***', ) }) it('anonymizes hashes in the middle of the url', () => { - expect(anonymizeLink('https://goerli.etherscan.io/address/0xabcd/test')).toEqual( - 'https://goerli.etherscan.io/address/***/test', + expect(anonymizeLink('https://sepolia.etherscan.io/address/0xabcd/test')).toEqual( + 'https://sepolia.etherscan.io/address/***/test', ) }) it('does not anonymize 0x', () => { - expect(anonymizeLink('https://goerli.etherscan.io/address/0x/test')).toEqual( - 'https://goerli.etherscan.io/address/0x/test', + expect(anonymizeLink('https://sepolia.etherscan.io/address/0x/test')).toEqual( + 'https://sepolia.etherscan.io/address/0x/test', ) }) it('works for arbitrum urls', () => { diff --git a/apps/web/src/utils/anonymizeLink.ts b/apps/web/src/utils/anonymizeLink.ts index 4fd94400ed6..5f38108f5ff 100644 --- a/apps/web/src/utils/anonymizeLink.ts +++ b/apps/web/src/utils/anonymizeLink.ts @@ -1,7 +1,6 @@ const EXPLORER_HOSTNAMES: { [hostname: string]: true } = { 'bscscan.com': true, 'etherscan.io': true, - 'goerli.etherscan.io': true, 'sepolia.etherscan.io': true, 'optimistic.etherscan.io': true, 'goerli-optimism.etherscan.io': true, diff --git a/apps/web/src/utils/currencyKey.ts b/apps/web/src/utils/currencyKey.ts index d242a336e78..1caeb79f474 100644 --- a/apps/web/src/utils/currencyKey.ts +++ b/apps/web/src/utils/currencyKey.ts @@ -2,11 +2,11 @@ import { Currency } from '@uniswap/sdk-core' import { NATIVE_CHAIN_ID } from 'constants/tokens' import { supportedChainIdFromGQLChain } from 'graphql/data/util' import { Chain, TokenStandard } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' export type CurrencyKey = string -export function buildCurrencyKey(chainId: InterfaceChainId, address: string): CurrencyKey { +export function buildCurrencyKey(chainId: UniverseChainId, address: string): CurrencyKey { // We lowercase for compatibility/indexability between gql tokens and sdk currencies return `${chainId}-${address.toLowerCase()}` } diff --git a/apps/web/src/utils/formatNumbers.ts b/apps/web/src/utils/formatNumbers.ts index 6733fa12442..0b92b262abd 100644 --- a/apps/web/src/utils/formatNumbers.ts +++ b/apps/web/src/utils/formatNumbers.ts @@ -513,6 +513,7 @@ interface FormatNumberOptions { locale?: Locale localCurrency?: FiatCurrency conversionRate?: number + forceShowCurrencySymbol?: boolean } function formatNumber({ @@ -522,8 +523,17 @@ function formatNumber({ locale = DEFAULT_LOCALE, localCurrency = DEFAULT_LOCAL_CURRENCY, conversionRate, + forceShowCurrencySymbol = false, }: FormatNumberOptions): string { if (input === null || input === undefined) { + if (forceShowCurrencySymbol) { + const parts = new Intl.NumberFormat(locale, { style: 'currency', currency: localCurrency }).formatToParts(0) + const currencySymbol = parts.find((part) => part.type === 'currency')?.value + const isSymbolBeforeNumber = parts[0].type === 'currency' + + return isSymbolBeforeNumber ? `${currencySymbol}${placeholder}` : `${placeholder}${currencySymbol}` + } + return placeholder } diff --git a/apps/web/src/utils/getRoutingDiagramEntries.ts b/apps/web/src/utils/getRoutingDiagramEntries.ts index 6d071cf7d11..42497682df8 100644 --- a/apps/web/src/utils/getRoutingDiagramEntries.ts +++ b/apps/web/src/utils/getRoutingDiagramEntries.ts @@ -1,5 +1,7 @@ +import { Protocol } from '@uniswap/router-sdk' import { Percent, TradeType } from '@uniswap/sdk-core' import { Pair } from '@uniswap/v2-sdk' +import { Pool as V3Pool } from '@uniswap/v3-sdk' import { ClassicTrade } from 'state/routing/types' import { RoutingDiagramEntry } from 'uniswap/src/utils/getRoutingDiagramEntries' @@ -20,10 +22,13 @@ export default function getRoutingDiagramEntries(trade: ClassicTrade): RoutingDi const nextPool = pools[i] const tokenIn = tokenPath[i] const tokenOut = tokenPath[i + 1] + const poolProtocol = + nextPool instanceof Pair ? Protocol.V2 : nextPool instanceof V3Pool ? Protocol.V3 : Protocol.V4 const entry: RoutingDiagramEntry['path'][0] = [ tokenIn, tokenOut, nextPool instanceof Pair ? V2_DEFAULT_FEE_TIER : nextPool.fee, + poolProtocol, ] path.push(entry) } diff --git a/apps/web/src/utils/getSupportedChainIdsFromWalletConnectSession.ts b/apps/web/src/utils/getSupportedChainIdsFromWalletConnectSession.ts index 47ce0f31860..62e85f67502 100644 --- a/apps/web/src/utils/getSupportedChainIdsFromWalletConnectSession.ts +++ b/apps/web/src/utils/getSupportedChainIdsFromWalletConnectSession.ts @@ -1,5 +1,5 @@ import type { SessionTypes } from '@walletconnect/types' -import { SupportedInterfaceChainId } from 'constants/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' // Helper function to extract chainId from string in format 'eip155:{chainId}' function getChainIdFromFormattedString(item: string): number | null { @@ -7,9 +7,7 @@ function getChainIdFromFormattedString(item: string): number | null { return splitItem.length > 1 && !isNaN(Number(splitItem[1])) ? Number(splitItem[1]) : null } -export function getSupportedChainIdsFromWalletConnectSession( - session?: SessionTypes.Struct, -): SupportedInterfaceChainId[] { +export function getSupportedChainIdsFromWalletConnectSession(session?: SessionTypes.Struct): UniverseChainId[] { if (!session?.namespaces) { return [] } @@ -35,5 +33,5 @@ export function getSupportedChainIdsFromWalletConnectSession( }) .filter((item) => item !== null) // Filter out any null values - return Array.from(new Set(allChainIds)) as SupportedInterfaceChainId[] + return Array.from(new Set(allChainIds)) as UniverseChainId[] } diff --git a/apps/web/src/utils/transfer.ts b/apps/web/src/utils/transfer.ts index 834052f3bb1..620e950fc5c 100644 --- a/apps/web/src/utils/transfer.ts +++ b/apps/web/src/utils/transfer.ts @@ -5,7 +5,7 @@ import { useWeb3React } from '@web3-react/core' import { useCallback } from 'react' import ERC20_ABI from 'uniswap/src/abis/erc20.json' import { Erc20 } from 'uniswap/src/abis/types' -import { InterfaceChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { getContract } from 'utilities/src/contracts/getContract' import { logger } from 'utilities/src/logger/logger' import { useAsyncData } from 'utilities/src/react/hooks' @@ -13,7 +13,7 @@ import { useAsyncData } from 'utilities/src/react/hooks' interface TransferInfo { provider?: Web3Provider account?: string - chainId?: InterfaceChainId + chainId?: UniverseChainId currencyAmount?: CurrencyAmount toAddress?: string } @@ -21,7 +21,7 @@ interface TransferInfo { interface TransferCurrencyParams { provider: Web3Provider account: string - chainId: InterfaceChainId + chainId: UniverseChainId toAddress: string tokenAddress: string amountInWei: string diff --git a/config/jest-presets/jest/globals.js b/config/jest-presets/jest/globals.js index e520a13571b..e09031bda0a 100644 --- a/config/jest-presets/jest/globals.js +++ b/config/jest-presets/jest/globals.js @@ -14,6 +14,7 @@ module.exports = { QUICKNODE_ARBITRUM_RPC_URL: 'https://api.uniswap.org', QUICKNODE_BNB_RPC_URL: 'https://api.uniswap.org', QUICKNODE_MAINNET_RPC_URL: 'https://api.uniswap.org', + QUICKNODE_SEPOLIA_RPC_URL: 'https://api.uniswap.org', QUICKNODE_ZORA_RPC_URL: 'https://api.uniswap.org', QUICKNODE_ZKSYNC_RPC_URL: 'https://api.uniswap.org', QUICKNODE_BLAST_RPC_URL: 'https://api.uniswap.org', @@ -22,6 +23,8 @@ module.exports = { QUICKNODE_CELO_RPC_URL: 'https://api.uniswap.org', QUICKNODE_OP_RPC_URL: 'https://api.uniswap.org', QUICKNODE_POLYGON_RPC_URL: 'https://api.uniswap.org', + QUICKNODE_WORLDCHAIN_RPC_URL: 'https://api.uniswap.org', + QUICKNODE_ASTROCHAIN_SEPOLIA_RPC_URL: 'https://api.uniswap.org', SENTRY_DSN: 'http://sentry.com', SHAKE_CLIENT_ID: 123, SHAKE_CLIENT_SECRET: 123, diff --git a/dangerfile.ts b/dangerfile.ts index 4cac15b38b5..306feaa902b 100644 --- a/dangerfile.ts +++ b/dangerfile.ts @@ -125,10 +125,10 @@ async function processAddChanges() { `'ui/src/loading'`, `'ui/src/assets'`, `'ui/src/components/icons'`, - `'ui/src/components/logos'`, `'ui/src/icons'`, `'ui/src/animations'`, `'ui/src/hooks/useDeviceDimensions'`, + `'ui/src/hooks/useDeviceInsets'`, `'ui/src/components/layout/AnimatedFlex'`, `'ui/src/components/text/AnimatedText'`, `'ui/src/components/AnimatedFlashList/AnimatedFlashList'`, diff --git a/package.json b/package.json index b8a40c6ed7c..41a9feabe64 100644 --- a/package.json +++ b/package.json @@ -43,10 +43,10 @@ "cypress": "13.7.3", "@babel/preset-env": "7.23.3", "immer": "9.0.21", - "@uniswap/v2-sdk": "4.3.2", - "@uniswap/router-sdk": "1.9.2", + "@uniswap/v2-sdk": "4.6.0", + "@uniswap/router-sdk": "1.14.2", "@apollo/client": "3.10.4", - "@uniswap/sdk-core": "5.3.0", + "@uniswap/sdk-core": "5.8.0", "@react-navigation/routers": "6.1.9", "@react-navigation/core": "6.2.2", "@sideway/formula": "3.0.1", diff --git a/packages/eslint-config/__snapshots__/preset.test.ts.snap b/packages/eslint-config/__snapshots__/preset.test.ts.snap index 3435a4e9c4a..cc5026bf32c 100644 --- a/packages/eslint-config/__snapshots__/preset.test.ts.snap +++ b/packages/eslint-config/__snapshots__/preset.test.ts.snap @@ -884,6 +884,13 @@ exports[`should have a correct configuration for a React file 1`] = ` "message": "Use via \`useLocalizationContext\` instead.", "name": "uniswap/src/features/language/formatter", }, + { + "importNames": [ + "useDeviceInsets", + ], + "message": "Use \`useAppInsets\` instead.", + "name": "ui/src/hooks/useDeviceInsets", + }, { "message": "Please import from '@ethersproject/module' directly to support tree-shaking.", "name": "ethers", diff --git a/packages/eslint-config/native.js b/packages/eslint-config/native.js index bb71a7dd2cf..9fa81917c5c 100644 --- a/packages/eslint-config/native.js +++ b/packages/eslint-config/native.js @@ -144,7 +144,7 @@ module.exports = { { name: 'react-native-safe-area-context', importNames: ['useSafeAreaInsets'], - message: 'Use our internal `useDeviceInsets` hook instead.', + message: 'Use our internal `useAppInsets` hook instead.', }, { name: 'react-native', @@ -162,6 +162,11 @@ module.exports = { importNames: ['usePortfolioBalancesQuery'], message: 'Use `usePortfolioBalances` instead.', }, + { + name: 'uniswap/src/features/settings/selectors', + importNames: ['selectIsTestnetModeEnabled'], + message: 'Use `useEnabledChains` instead.', + }, { name: 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks', importNames: ['useAccountListQuery'], diff --git a/packages/eslint-config/restrictedImports.js b/packages/eslint-config/restrictedImports.js index 751c9e57d5b..c79f22cf8c7 100644 --- a/packages/eslint-config/restrictedImports.js +++ b/packages/eslint-config/restrictedImports.js @@ -38,6 +38,11 @@ exports.shared = { importNames: ['useLocalizedFormatter'], message: 'Use via `useLocalizationContext` instead.', }, + { + name: 'ui/src/hooks/useDeviceInsets', + importNames: ['useDeviceInsets'], + message: 'Use `useAppInsets` instead.' + }, ], patterns: [ { diff --git a/packages/ui/src/assets/graphics/bridging-banner.png b/packages/ui/src/assets/graphics/bridging-banner.png index a4938d1a50b..e75042d7556 100644 Binary files a/packages/ui/src/assets/graphics/bridging-banner.png and b/packages/ui/src/assets/graphics/bridging-banner.png differ diff --git a/packages/ui/src/assets/icons/order-routing.svg b/packages/ui/src/assets/icons/order-routing.svg new file mode 100644 index 00000000000..fbfa8a4a116 --- /dev/null +++ b/packages/ui/src/assets/icons/order-routing.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/src/assets/icons/wrench.svg b/packages/ui/src/assets/icons/wrench.svg new file mode 100644 index 00000000000..2c0a98dc5c3 --- /dev/null +++ b/packages/ui/src/assets/icons/wrench.svg @@ -0,0 +1,5 @@ + + + diff --git a/packages/ui/src/assets/index.ts b/packages/ui/src/assets/index.ts index def08a07bbb..33714cff4b0 100644 --- a/packages/ui/src/assets/index.ts +++ b/packages/ui/src/assets/index.ts @@ -8,10 +8,10 @@ export const POLYGON_LOGO = require('./logos/png/polygon-logo.png') export const BLAST_LOGO = require('./logos/png/blast-logo.png') export const AVALANCHE_LOGO = require('./logos/png/avalanche-logo.png') export const CELO_LOGO = require('./logos/png/celo-logo.png') +export const WORLD_CHAIN_LOGO = require('./logos/png/world-chain-logo.png') export const ZORA_LOGO = require('./logos/png/zora-logo.png') export const ZKSYNC_LOGO = require('./logos/png/zksync-logo.png') -export const GOERLI_LOGO = require('./logos/png/goerli-logo.png') -export const MUMBAI_LOGO = require('./logos/png/mumbai-logo.png') +export const ASTROCHAIN_SEPOLIA_LOGO = require('./logos/png/astrochain-sepolia-logo.png') export const UNISWAP_LOGO = require('./logos/png/uniswap-logo.png') export const UNISWAP_LOGO_LARGE = require('./logos/png/uniswap-logo-large.png') export const UNISWAP_APP_ICON = require('./logos/png/uniswap-app-icon.png') @@ -37,7 +37,7 @@ export const ETH_LOGO = require('./logos/png/eth-logo.png') export const OPENSEA_LOGO = require('./logos/png/opensea-logo.png') export const ENS_LOGO = require('./logos/png/ens-logo.png') export const FROGGY = require('./graphics/froggy.png') -export const ACROSS_LOGO = require('./logos/png/across-logo.png') +export const UNICHAIN_LOGO = require('./logos/png/unichain-logo.png') export const CEX_TRANSFER_MODAL_BG_LIGHT = require('./graphics/cex-transfer-modal-bg-light.png') export const CEX_TRANSFER_MODAL_BG_DARK = require('./graphics/cex-transfer-modal-bg-dark.png') diff --git a/packages/ui/src/assets/logos/png/across-logo.png b/packages/ui/src/assets/logos/png/across-logo.png deleted file mode 100644 index 3eac46903fb..00000000000 Binary files a/packages/ui/src/assets/logos/png/across-logo.png and /dev/null differ diff --git a/packages/ui/src/assets/logos/png/astrochain-sepolia-logo.png b/packages/ui/src/assets/logos/png/astrochain-sepolia-logo.png new file mode 100644 index 00000000000..5851478ca4e Binary files /dev/null and b/packages/ui/src/assets/logos/png/astrochain-sepolia-logo.png differ diff --git a/packages/ui/src/assets/logos/png/goerli-logo.png b/packages/ui/src/assets/logos/png/goerli-logo.png deleted file mode 100644 index 49efdab2811..00000000000 Binary files a/packages/ui/src/assets/logos/png/goerli-logo.png and /dev/null differ diff --git a/packages/ui/src/assets/logos/png/mumbai-logo.png b/packages/ui/src/assets/logos/png/mumbai-logo.png deleted file mode 100644 index c52754d615f..00000000000 Binary files a/packages/ui/src/assets/logos/png/mumbai-logo.png and /dev/null differ diff --git a/packages/ui/src/assets/logos/png/unichain-logo.png b/packages/ui/src/assets/logos/png/unichain-logo.png new file mode 100644 index 00000000000..f4ef3bf35bd Binary files /dev/null and b/packages/ui/src/assets/logos/png/unichain-logo.png differ diff --git a/packages/ui/src/assets/logos/png/world-chain-logo.png b/packages/ui/src/assets/logos/png/world-chain-logo.png new file mode 100644 index 00000000000..e86ab9e5903 Binary files /dev/null and b/packages/ui/src/assets/logos/png/world-chain-logo.png differ diff --git a/packages/ui/src/assets/logos/svg/across-logo-full.svg b/packages/ui/src/assets/logos/svg/across-logo-full.svg new file mode 100644 index 00000000000..96a5478e9a1 --- /dev/null +++ b/packages/ui/src/assets/logos/svg/across-logo-full.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/packages/ui/src/components/icons/OrderRouting.tsx b/packages/ui/src/components/icons/OrderRouting.tsx new file mode 100644 index 00000000000..89ce40d0cdb --- /dev/null +++ b/packages/ui/src/components/icons/OrderRouting.tsx @@ -0,0 +1,19 @@ +import { Path, Svg } from 'react-native-svg' + +// eslint-disable-next-line no-relative-import-paths/no-relative-import-paths +import { createIcon } from '../factories/createIcon' + +export const [OrderRouting, AnimatedOrderRouting] = createIcon({ + name: 'OrderRouting', + getIcon: (props) => ( + + + + ), + defaultFill: '#9B9B9B', +}) diff --git a/packages/ui/src/components/icons/Wrench.tsx b/packages/ui/src/components/icons/Wrench.tsx new file mode 100644 index 00000000000..d2ba97fd84a --- /dev/null +++ b/packages/ui/src/components/icons/Wrench.tsx @@ -0,0 +1,17 @@ +import { Path, Svg } from 'react-native-svg' + +// eslint-disable-next-line no-relative-import-paths/no-relative-import-paths +import { createIcon } from '../factories/createIcon' + +export const [Wrench, AnimatedWrench] = createIcon({ + name: 'Wrench', + getIcon: (props) => ( + + + + ), + defaultFill: '#222222', +}) diff --git a/packages/ui/src/components/icons/exported.ts b/packages/ui/src/components/icons/exported.ts index 84e97ace412..cabce5d822e 100644 --- a/packages/ui/src/components/icons/exported.ts +++ b/packages/ui/src/components/icons/exported.ts @@ -130,6 +130,7 @@ export * from './NoPools' export * from './NoTokens' export * from './NoTransactions' export * from './OctagonExclamation' +export * from './OrderRouting' export * from './PaperStack' export * from './PapersText' export * from './Paste' @@ -216,6 +217,7 @@ export * from './Walletconnect' export * from './WavePulse' export * from './Wifi' export * from './WifiSlash' +export * from './Wrench' export * from './X' export * from './XOctagon' export * from './XTwitter' diff --git a/packages/ui/src/components/logos/AcrossLogo.tsx b/packages/ui/src/components/logos/AcrossLogo.tsx new file mode 100644 index 00000000000..c2520eae72f --- /dev/null +++ b/packages/ui/src/components/logos/AcrossLogo.tsx @@ -0,0 +1,30 @@ +import { ClipPath, Defs, G, Path, Rect, Svg } from 'react-native-svg' + +// eslint-disable-next-line no-relative-import-paths/no-relative-import-paths +import { createIcon } from '../factories/createIcon' + +export const [AcrossLogo, AnimatedAcrossLogo] = createIcon({ + name: 'AcrossLogo', + getIcon: (props) => ( + + + + + + + + + + + + ), + defaultFill: '#6CF9D8', +}) diff --git a/packages/ui/src/components/logos/AcrossLogoFull.tsx b/packages/ui/src/components/logos/AcrossLogoFull.tsx new file mode 100644 index 00000000000..019f57e43ac --- /dev/null +++ b/packages/ui/src/components/logos/AcrossLogoFull.tsx @@ -0,0 +1,50 @@ +import { ClipPath, Defs, G, Path, Rect, Svg } from 'react-native-svg' + +// eslint-disable-next-line no-relative-import-paths/no-relative-import-paths +import { createIcon } from '../factories/createIcon' + +export const [AcrossLogoFull, AnimatedAcrossLogoFull] = createIcon({ + name: 'AcrossLogoFull', + getIcon: (props) => ( + + + + + + + + + + + + + + + + + ), + defaultFill: '#5E5E5E', +}) diff --git a/packages/ui/src/components/logos/exported.ts b/packages/ui/src/components/logos/exported.ts index 0afb3e2a93f..1678ff32fbf 100644 --- a/packages/ui/src/components/logos/exported.ts +++ b/packages/ui/src/components/logos/exported.ts @@ -1,3 +1,4 @@ +export * from './AcrossLogoFull' export * from './ArbiscanLogoDark' export * from './ArbiscanLogoLight' export * from './BlockaidLogo' diff --git a/packages/ui/src/components/logos/index.ts b/packages/ui/src/components/logos/index.ts deleted file mode 100644 index d1de7e5d11f..00000000000 --- a/packages/ui/src/components/logos/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -export * from './ArbiscanLogoDark' -export * from './ArbiscanLogoLight' -export * from './Ethereum' -export * from './EthereumLogo' -export * from './EtherscanLogoDark' -export * from './EtherscanLogoLight' -export * from './Moonpay' -export * from './OpEtherscanLogoDark' -export * from './OpEtherscanLogoLight' -export * from './PolygonPurple' -export * from './PolygonscanLogoDark' -export * from './PolygonscanLogoLight' diff --git a/packages/ui/src/components/text/ElementAfterText.tsx b/packages/ui/src/components/text/ElementAfterText.tsx index 06a83b2341c..9dd5e63f46b 100644 --- a/packages/ui/src/components/text/ElementAfterText.tsx +++ b/packages/ui/src/components/text/ElementAfterText.tsx @@ -20,7 +20,7 @@ export function ElementAfterText({ element, text, wrapperProps, textProps }: Ele if (isWeb) { return ( - + {text} {element} @@ -29,7 +29,7 @@ export function ElementAfterText({ element, text, wrapperProps, textProps }: Ele ) } else { return ( - + {text} diff --git a/packages/ui/src/hooks/useIsShortMobileDevice.native.ts b/packages/ui/src/hooks/useIsShortMobileDevice.native.ts index 22a449d786b..5ff809e31f8 100644 --- a/packages/ui/src/hooks/useIsShortMobileDevice.native.ts +++ b/packages/ui/src/hooks/useIsShortMobileDevice.native.ts @@ -1,6 +1,7 @@ import { useSafeAreaFrame } from 'react-native-safe-area-context' import { isWeb } from 'tamagui' import { DEFAULT_BOTTOM_INSET } from 'ui/src/hooks/constants' +// eslint-disable-next-line no-restricted-imports import { useDeviceInsets } from 'ui/src/hooks/useDeviceInsets' const IPHONE_MINI_SAFE_AREA_HEIGHT = 812 - DEFAULT_BOTTOM_INSET diff --git a/packages/ui/src/hooks/useIsShortMobileDevice.ts b/packages/ui/src/hooks/useIsShortMobileDevice.ts index bfd114e218b..ce4bfb78b7d 100644 --- a/packages/ui/src/hooks/useIsShortMobileDevice.ts +++ b/packages/ui/src/hooks/useIsShortMobileDevice.ts @@ -1,3 +1,5 @@ +import { PlatformSplitStubError } from 'utilities/src/errors' + export const useIsShortMobileDevice = (): boolean => { - return false + throw new PlatformSplitStubError('useIsShortMobileDevice') } diff --git a/packages/ui/src/hooks/useIsShortMobileDevice.web.ts b/packages/ui/src/hooks/useIsShortMobileDevice.web.ts new file mode 100644 index 00000000000..bfd114e218b --- /dev/null +++ b/packages/ui/src/hooks/useIsShortMobileDevice.web.ts @@ -0,0 +1,3 @@ +export const useIsShortMobileDevice = (): boolean => { + return false +} diff --git a/packages/ui/src/hooks/usePreventOverflowBelowFold.tsx b/packages/ui/src/hooks/usePreventOverflowBelowFold.tsx new file mode 100644 index 00000000000..9b0941281a5 --- /dev/null +++ b/packages/ui/src/hooks/usePreventOverflowBelowFold.tsx @@ -0,0 +1,25 @@ +import { useEffect, useRef, useState } from 'react' + +export function usePreventOverflowBelowFold(isVisible = true): { + maxHeight: number + ref: React.RefObject +} { + const ref = useRef(null) + const [maxHeight, setMaxHeight] = useState(0) + + const getMaxHeight = (): number => { + const menuTopY = ref.current?.getBoundingClientRect().top || 0 + + const diff = window.innerHeight - menuTopY + return diff > 0 ? diff : 0 + } + + useEffect(() => { + // Effectively waits for the menu to render before calculating the offset + setTimeout(() => { + setMaxHeight(getMaxHeight()) + }, 0) + }, [isVisible]) + + return { maxHeight, ref } +} diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 9e90698b345..117a07076b0 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -38,6 +38,7 @@ export type { ColorTokens, GetProps, GetRef, + ImageProps, InputProps, PopperProps, SpaceTokens, @@ -74,7 +75,6 @@ export { Switch, type SwitchProps } from './components/switch/Switch' export * from './components/text' export { Tooltip } from './components/tooltip/Tooltip' export * from './components/touchable' -export { useDeviceInsets } from './hooks/useDeviceInsets' export { useIsDarkMode } from './hooks/useIsDarkMode' export { useIsShortMobileDevice } from './hooks/useIsShortMobileDevice' export { useSporeColors, type DynamicColor } from './hooks/useSporeColors' diff --git a/packages/ui/src/theme/color/colors.ts b/packages/ui/src/theme/color/colors.ts index 5272e03cdf9..352ee029fa2 100644 --- a/packages/ui/src/theme/color/colors.ts +++ b/packages/ui/src/theme/color/colors.ts @@ -138,6 +138,14 @@ export const networkColors = { light: '#222222', dark: '#FCFF52', }, + worldchain: { + light: '#222222', + dark: '#FFFFFF', + }, + astrochain: { + light: '#fc0fa4', + dark: '#fc0fa4', + }, zora: { light: '#222222', dark: '#FFFFFF', @@ -182,8 +190,8 @@ const sporeLight = { statusSuccess: '#21C95E', statusSuccessHovered: '#15863C', statusSuccess2: '#EEFBF1', - statusWarning: '#FFBF17', - statusWarningHovered: '#FFDD0D', + statusWarning: '#996F01', + statusWarningHovered: '#7A5801', statusWarning2: '#FFFBEB', statusWarning2Hovered: '#FFFBD7', statusCritical: '#FF5F52', @@ -309,11 +317,11 @@ export const colorsLight = { chain_42220: networkColors.celo.light, chain_43114: networkColors.avalanche.light, chain_324: networkColors.zksync.light, + chain_480: networkColors.worldchain.light, // Testnets - chain_3: colors.yellowVibrant, - chain_4: colors.pinkVibrant, - chain_5: colors.greenVibrant, + chain_11155111: networkColors.ethereum.light, + chain_1301: networkColors.astrochain.light, } export type ColorKeys = keyof typeof colorsLight @@ -390,9 +398,9 @@ export const colorsDark = { chain_42220: networkColors.celo.dark, chain_43114: networkColors.avalanche.dark, chain_324: networkColors.zksync.dark, + chain_480: networkColors.worldchain.dark, // Testnets - chain_3: colors.yellowVibrant, - chain_4: colors.pinkVibrant, - chain_5: colors.greenVibrant, + chain_11155111: networkColors.ethereum.dark, + chain_1301: networkColors.astrochain.dark, } diff --git a/packages/uniswap/.eslintignore b/packages/uniswap/.eslintignore index aaa6e487462..df2f6af5994 100644 --- a/packages/uniswap/.eslintignore +++ b/packages/uniswap/.eslintignore @@ -2,5 +2,3 @@ # Generated contract types src/abis/types - -src/i18n/locales/@types/resources.d.ts diff --git a/packages/uniswap/package.json b/packages/uniswap/package.json index 1091f5c824e..a4521c9be33 100644 --- a/packages/uniswap/package.json +++ b/packages/uniswap/package.json @@ -9,7 +9,6 @@ "check:deps:usage": "depcheck", "graphql:generate": "graphql-codegen --config codegen.ts", "graphql:schema": "get-graphql-schema https://api.uniswap.org/v1/graphql -h Origin=https://app.uniswap.org > ./src/data/graphql/uniswap-data-api/schema.graphql", - "i18n:generate": "i18next-resources-for-ts interface -i ./src/i18n/locales/source -o ./src/i18n/locales/@types/resources.d.ts", "format": "../../scripts/prettier.sh", "lint": "eslint . --ext ts,tsx --max-warnings=0", "lint:fix": "eslint . --ext ts,tsx --fix", @@ -41,15 +40,16 @@ "@tanstack/react-query": "5.51.16", "@tanstack/react-query-persist-client": "5.51.23", "@typechain/ethers-v5": "7.2.0", - "@uniswap/analytics-events": "2.37.0", + "@uniswap/analytics-events": "2.38.0", "@uniswap/client-explore": "0.0.9", "@uniswap/client-pools": "0.0.5", "@uniswap/permit2-sdk": "1.3.0", - "@uniswap/router-sdk": "1.9.2", - "@uniswap/sdk-core": "5.3.0", + "@uniswap/router-sdk": "1.14.2", + "@uniswap/sdk-core": "5.8.0", "@uniswap/uniswapx-sdk": "^2.1.0-beta.14", - "@uniswap/v2-sdk": "4.3.2", - "@uniswap/v3-sdk": "3.14.0", + "@uniswap/v2-sdk": "4.6.0", + "@uniswap/v3-sdk": "3.17.0", + "@uniswap/v4-sdk": "1.10.0", "apollo-link-rest": "0.9.0", "axios": "1.6.5", "dayjs": "1.11.7", @@ -76,6 +76,7 @@ "react-native-mmkv": "2.10.1", "react-native-reanimated": "3.15.0", "react-native-restart": "0.0.27", + "react-native-svg": "15.1.0", "react-redux": "8.0.5", "react-test-renderer": "18.2.0", "react-virtualized-auto-sizer": "1.0.20", diff --git a/packages/uniswap/src/assets/chainLogos.tsx b/packages/uniswap/src/assets/chainLogos.tsx index 1b38658eb7b..b0db87dbc90 100644 --- a/packages/uniswap/src/assets/chainLogos.tsx +++ b/packages/uniswap/src/assets/chainLogos.tsx @@ -23,24 +23,12 @@ export const UNIVERSE_CHAIN_LOGO = { logoDark: EtherscanLogoDark, }, } as const satisfies UniverseChainLogoInfo, - [UniverseChainId.Goerli]: { - explorer: { - logoLight: EtherscanLogoLight, - logoDark: EtherscanLogoDark, - }, - } as const satisfies UniverseChainLogoInfo, [UniverseChainId.ArbitrumOne]: { explorer: { logoLight: ArbiscanLogoLight, logoDark: ArbiscanLogoDark, }, } as const satisfies UniverseChainLogoInfo, - [UniverseChainId.ArbitrumGoerli]: { - explorer: { - logoLight: ArbiscanLogoLight, - logoDark: ArbiscanLogoDark, - }, - } as const satisfies UniverseChainLogoInfo, [UniverseChainId.Optimism]: { explorer: { logoLight: OpEtherscanLogoLight, @@ -53,12 +41,6 @@ export const UNIVERSE_CHAIN_LOGO = { logoDark: EtherscanLogoDark, }, } as const satisfies UniverseChainLogoInfo, - [UniverseChainId.OptimismGoerli]: { - explorer: { - logoLight: OpEtherscanLogoLight, - logoDark: OpEtherscanLogoDark, - }, - } as const satisfies UniverseChainLogoInfo, [UniverseChainId.Bnb]: { explorer: { logoLight: EtherscanLogoLight, @@ -71,12 +53,6 @@ export const UNIVERSE_CHAIN_LOGO = { logoDark: PolygonscanLogoDark, }, } as const satisfies UniverseChainLogoInfo, - [UniverseChainId.PolygonMumbai]: { - explorer: { - logoLight: PolygonscanLogoLight, - logoDark: PolygonscanLogoDark, - }, - } as const satisfies UniverseChainLogoInfo, [UniverseChainId.Blast]: { explorer: { logoLight: BlockExplorer, @@ -95,7 +71,7 @@ export const UNIVERSE_CHAIN_LOGO = { logoDark: BlockExplorer, }, } as const satisfies UniverseChainLogoInfo, - [UniverseChainId.CeloAlfajores]: { + [UniverseChainId.WorldChain]: { explorer: { logoLight: BlockExplorer, logoDark: BlockExplorer, @@ -113,4 +89,10 @@ export const UNIVERSE_CHAIN_LOGO = { logoDark: BlockExplorer, }, } as const satisfies UniverseChainLogoInfo, + [UniverseChainId.AstrochainSepolia]: { + explorer: { + logoLight: BlockExplorer, + logoDark: BlockExplorer, + }, + } as const satisfies UniverseChainLogoInfo, } diff --git a/packages/uniswap/src/components/ConfirmSwapModal/ProgressIndicator.tsx b/packages/uniswap/src/components/ConfirmSwapModal/ProgressIndicator.tsx index 3a9ccdf6b0a..40ba94028db 100644 --- a/packages/uniswap/src/components/ConfirmSwapModal/ProgressIndicator.tsx +++ b/packages/uniswap/src/components/ConfirmSwapModal/ProgressIndicator.tsx @@ -18,9 +18,24 @@ interface ProgressIndicatorProps { currentStep?: { step: TransactionStep; accepted: boolean } } +function areStepsEqual(currentStep: TransactionStep | undefined): (step: TransactionStep) => boolean { + return (step: TransactionStep) => { + if (step.type !== currentStep?.type) { + return false + } + + // There can be multiple approval steps with different tokens so both the type and the approval has to match + if (currentStep.type === TransactionStepType.TokenApprovalTransaction) { + return step.type === TransactionStepType.TokenApprovalTransaction && step.token === currentStep.token + } + + return true + } +} + export function ProgressIndicator({ currentStep, steps }: ProgressIndicatorProps): JSX.Element | null { function getStatus(targetStep: TransactionStep): StepStatus { - const currentIndex = steps.findIndex((step) => step.type === currentStep?.step.type) + const currentIndex = steps.findIndex(areStepsEqual(currentStep?.step)) const targetIndex = steps.indexOf(targetStep) if (currentIndex < targetIndex) { return StepStatus.Preview @@ -64,6 +79,7 @@ function Step({ step, status }: { step: TransactionStep; status: StepStatus }): return case TransactionStepType.IncreasePositionTransaction: case TransactionStepType.IncreasePositionTransactionAsync: + case TransactionStepType.DecreasePositionTransaction: return } } diff --git a/packages/uniswap/src/components/ConfirmSwapModal/steps/LP.tsx b/packages/uniswap/src/components/ConfirmSwapModal/steps/LP.tsx index 83d6a0613b1..4b96f2bc785 100644 --- a/packages/uniswap/src/components/ConfirmSwapModal/steps/LP.tsx +++ b/packages/uniswap/src/components/ConfirmSwapModal/steps/LP.tsx @@ -5,6 +5,7 @@ import { StepRowProps, StepRowSkeleton } from 'uniswap/src/components/ConfirmSwa import { StepStatus } from 'uniswap/src/components/ConfirmSwapModal/types' import { uniswapUrls } from 'uniswap/src/constants/urls' import { + DecreasePositionTransactionStep, IncreasePositionTransactionStep, IncreasePositionTransactionStepAsync, } from 'uniswap/src/features/transactions/swap/utils/generateTransactionSteps' @@ -15,17 +16,16 @@ const LPIcon = (): JSX.Element => ( ) -type LPSteps = IncreasePositionTransactionStep | IncreasePositionTransactionStepAsync +type LPSteps = IncreasePositionTransactionStep | IncreasePositionTransactionStepAsync | DecreasePositionTransactionStep export function LPTransactionStepRow({ status }: StepRowProps): JSX.Element { const { t } = useTranslation() const colors = useSporeColors() - // TODO: update to LP language const title = { - [StepStatus.Preview]: t('swap.confirmSwap'), - [StepStatus.Active]: t('common.confirmSwap'), - [StepStatus.InProgress]: t('common.swapPending'), - [StepStatus.Complete]: t('swap.confirmSwap'), + [StepStatus.Preview]: t('common.confirmWallet'), + [StepStatus.Active]: t('common.confirmWallet'), + [StepStatus.InProgress]: t('common.transactionPending'), + [StepStatus.Complete]: t('common.confirmWallet'), }[status] return ( diff --git a/packages/uniswap/src/components/CurrencyInputPanel/CurrencyInputPanel.tsx b/packages/uniswap/src/components/CurrencyInputPanel/CurrencyInputPanel.tsx index f9bc9e56121..05fc242a214 100644 --- a/packages/uniswap/src/components/CurrencyInputPanel/CurrencyInputPanel.tsx +++ b/packages/uniswap/src/components/CurrencyInputPanel/CurrencyInputPanel.tsx @@ -31,6 +31,7 @@ import { useAppFiatCurrencyInfo } from 'uniswap/src/features/fiatCurrency/hooks' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { useTokenAndFiatDisplayAmounts } from 'uniswap/src/features/transactions/hooks/useTokenAndFiatDisplayAmounts' import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { CurrencyField } from 'uniswap/src/types/currency' @@ -114,6 +115,7 @@ export const CurrencyInputPanel = memo( const indicativeDisplay = useIndicativeTextDisplay(props) const legacyDisplay = useLegacyTextDisplay(props) + const { isTestnetModeEnabled } = useEnabledChains() const display = indicativeQuotesEnabled ? indicativeDisplay : legacyDisplay const { value, color, usdValue } = display @@ -327,13 +329,15 @@ export const CurrencyInputPanel = memo( - - - {inputPanelFormattedValue} - - + {!isTestnetModeEnabled && ( + + + {inputPanelFormattedValue} + + + )} {showInsufficientBalanceWarning && } diff --git a/packages/uniswap/src/components/CurrencyLogo/NetworkLogo.tsx b/packages/uniswap/src/components/CurrencyLogo/NetworkLogo.tsx index 4b386897dfc..ae134e0a47b 100644 --- a/packages/uniswap/src/components/CurrencyLogo/NetworkLogo.tsx +++ b/packages/uniswap/src/components/CurrencyLogo/NetworkLogo.tsx @@ -19,7 +19,7 @@ export function TransactionSummaryNetworkLogo({ chainId, size = iconSizes.icon20, }: Pick): JSX.Element { - return + return } function _NetworkLogo({ diff --git a/packages/uniswap/src/components/CurrencyLogo/SplitLogo.tsx b/packages/uniswap/src/components/CurrencyLogo/SplitLogo.tsx index 8d780d621f8..34be7608e87 100644 --- a/packages/uniswap/src/components/CurrencyLogo/SplitLogo.tsx +++ b/packages/uniswap/src/components/CurrencyLogo/SplitLogo.tsx @@ -3,18 +3,15 @@ import { Flex } from 'ui/src' import { Shuffle } from 'ui/src/components/icons/Shuffle' import { iconSizes } from 'ui/src/theme' import { CurrencyLogo, STATUS_RATIO } from 'uniswap/src/components/CurrencyLogo/CurrencyLogo' -import { - SQUIRCLE_BORDER_RADIUS_RATIO, - TransactionSummaryNetworkLogo, -} from 'uniswap/src/components/CurrencyLogo/NetworkLogo' +import { TransactionSummaryNetworkLogo } from 'uniswap/src/components/CurrencyLogo/NetworkLogo' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' interface Props { inputCurrencyInfo: Maybe outputCurrencyInfo: Maybe size: number - chainId: WalletChainId | null + chainId: UniverseChainId | null customIcon?: ReactNode } @@ -65,10 +62,12 @@ export const BridgeIcon = ( - + ) diff --git a/packages/uniswap/src/components/CurrencyLogo/TokenLogo.test.tsx b/packages/uniswap/src/components/CurrencyLogo/TokenLogo.test.tsx index f53e6057d0c..11071e9b847 100644 --- a/packages/uniswap/src/components/CurrencyLogo/TokenLogo.test.tsx +++ b/packages/uniswap/src/components/CurrencyLogo/TokenLogo.test.tsx @@ -50,12 +50,16 @@ describe('TokenLogo', () => { expect(fallbackText).toBeTruthy() }) - it('renders fallback text when url is invalid', () => { - const { queryByText } = render() + it('renders image for an absolute path (local file)', () => { + const { queryByTestId } = render( + , + ) - const fallbackText = queryByText('DAI') + const tokenRemoteSvg = queryByTestId('svg-token-image') + const tokenImage = queryByTestId('img-token-image') - expect(fallbackText).toBeTruthy() + expect(tokenRemoteSvg).toBeFalsy() + expect(tokenImage).toBeTruthy() }) it('does not render fallback text when url is valid', () => { diff --git a/packages/uniswap/src/components/CurrencyLogo/TokenLogo.tsx b/packages/uniswap/src/components/CurrencyLogo/TokenLogo.tsx index 00075a5068f..92af8edf860 100644 --- a/packages/uniswap/src/components/CurrencyLogo/TokenLogo.tsx +++ b/packages/uniswap/src/components/CurrencyLogo/TokenLogo.tsx @@ -3,6 +3,7 @@ import { Flex, Text, UniversalImage, useColorSchemeFromSeed, useSporeColors } fr import { iconSizes, validColor } from 'ui/src/theme' import { STATUS_RATIO } from 'uniswap/src/components/CurrencyLogo/CurrencyLogo' import { NetworkLogo } from 'uniswap/src/components/CurrencyLogo/NetworkLogo' +import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { UniverseChainId } from 'uniswap/src/types/chains' interface TokenLogoProps { @@ -15,6 +16,9 @@ interface TokenLogoProps { networkLogoBorderWidth?: number } +const TESTNET_BORDER_DIVISOR = 15 +const BORDER_OFFSET = 4 + export const TokenLogo = memo(function _TokenLogo({ url, symbol, @@ -29,18 +33,25 @@ export const TokenLogo = memo(function _TokenLogo({ const colors = useSporeColors() const { foreground, background } = useColorSchemeFromSeed(name ?? symbol ?? '') + const isTestnetToken = UNIVERSE_CHAIN_INFO[chainId as UniverseChainId]?.testnet + const borderWidth = isTestnetToken ? size / TESTNET_BORDER_DIVISOR : 0 + const showNetworkLogo = !hideNetworkLogo && chainId && chainId !== UniverseChainId.Mainnet const networkLogoSize = Math.round(size * STATUS_RATIO) + const borderOffset = isTestnetToken ? BORDER_OFFSET : 0 + + const tokenSize = size - borderWidth - borderOffset + const fallback = ( - + {isTestnetToken ? null : ( + + )} {tokenImage} + {isTestnetToken && ( + + )} {showNetworkLogo && ( diff --git a/packages/uniswap/src/components/CurrencyLogo/__snapshots__/NetworkLogo.test.tsx.snap b/packages/uniswap/src/components/CurrencyLogo/__snapshots__/NetworkLogo.test.tsx.snap index c996ad95b69..e440286c687 100644 --- a/packages/uniswap/src/components/CurrencyLogo/__snapshots__/NetworkLogo.test.tsx.snap +++ b/packages/uniswap/src/components/CurrencyLogo/__snapshots__/NetworkLogo.test.tsx.snap @@ -55,19 +55,19 @@ exports[`TransactionSummaryNetworkLogo renders without error 1`] = ` style={ { "borderBottomColor": "rgba(19,19,19,1.00)", - "borderBottomLeftRadius": "6.6px", - "borderBottomRightRadius": "6.6px", - "borderBottomWidth": "1px", + "borderBottomLeftRadius": "6.8999999999999995px", + "borderBottomRightRadius": "6.8999999999999995px", + "borderBottomWidth": "1.5px", "borderLeftColor": "rgba(19,19,19,1.00)", - "borderLeftWidth": "1px", + "borderLeftWidth": "1.5px", "borderRightColor": "rgba(19,19,19,1.00)", - "borderRightWidth": "1px", + "borderRightWidth": "1.5px", "borderTopColor": "rgba(19,19,19,1.00)", - "borderTopLeftRadius": "6.6px", - "borderTopRightRadius": "6.6px", - "borderTopWidth": "1px", - "height": "22px", - "width": "22px", + "borderTopLeftRadius": "6.8999999999999995px", + "borderTopRightRadius": "6.8999999999999995px", + "borderTopWidth": "1.5px", + "height": "23px", + "width": "23px", } } > diff --git a/packages/uniswap/src/components/CurrencyLogo/__snapshots__/SplitLogo.test.tsx.snap b/packages/uniswap/src/components/CurrencyLogo/__snapshots__/SplitLogo.test.tsx.snap index 0c2307afe57..9e08bec8008 100644 --- a/packages/uniswap/src/components/CurrencyLogo/__snapshots__/SplitLogo.test.tsx.snap +++ b/packages/uniswap/src/components/CurrencyLogo/__snapshots__/SplitLogo.test.tsx.snap @@ -174,19 +174,19 @@ exports[`SplitLogo renders without error 1`] = ` style={ { "borderBottomColor": "rgba(19,19,19,1.00)", - "borderBottomLeftRadius": "2.1px", - "borderBottomRightRadius": "2.1px", - "borderBottomWidth": "1px", + "borderBottomLeftRadius": "2.4px", + "borderBottomRightRadius": "2.4px", + "borderBottomWidth": "1.5px", "borderLeftColor": "rgba(19,19,19,1.00)", - "borderLeftWidth": "1px", + "borderLeftWidth": "1.5px", "borderRightColor": "rgba(19,19,19,1.00)", - "borderRightWidth": "1px", + "borderRightWidth": "1.5px", "borderTopColor": "rgba(19,19,19,1.00)", - "borderTopLeftRadius": "2.1px", - "borderTopRightRadius": "2.1px", - "borderTopWidth": "1px", - "height": "7px", - "width": "7px", + "borderTopLeftRadius": "2.4px", + "borderTopRightRadius": "2.4px", + "borderTopWidth": "1.5px", + "height": "8px", + "width": "8px", } } > diff --git a/packages/uniswap/src/components/RoutingDiagram/RoutingDiagram.tsx b/packages/uniswap/src/components/RoutingDiagram/RoutingDiagram.tsx index 8d11fe42290..44d2fbd2e94 100644 --- a/packages/uniswap/src/components/RoutingDiagram/RoutingDiagram.tsx +++ b/packages/uniswap/src/components/RoutingDiagram/RoutingDiagram.tsx @@ -110,17 +110,18 @@ export default function RoutingDiagram({ } function Route({ entry: { percent, path, protocol } }: { entry: RoutingDiagramEntry }): JSX.Element { + const badgeText = + protocol === Protocol.MIXED + ? [...new Set(path.map(([, , , p]) => p.toUpperCase()))].sort().join(' + ') // extract all protocols involved in mixed path + : protocol.toUpperCase() + return ( - {protocol === Protocol.MIXED ? ( - V3 + V2 - ) : ( - {protocol.toUpperCase()} - )} + {badgeText} {percent.toSignificant(2)}% diff --git a/packages/uniswap/src/components/TokenSelector/TokenOptionItem.tsx b/packages/uniswap/src/components/TokenSelector/TokenOptionItem.tsx index 031355f00c8..e3f04ba356a 100644 --- a/packages/uniswap/src/components/TokenSelector/TokenOptionItem.tsx +++ b/packages/uniswap/src/components/TokenSelector/TokenOptionItem.tsx @@ -31,7 +31,7 @@ interface OptionProps { // TODO(WEB-3643): Share localization context with WEB // (balance, quantityFormatted) balance: string - quantityFormatted: string + quantityFormatted?: string isSelected?: boolean } @@ -175,9 +175,11 @@ function _TokenOptionItem({ {!isSelected && quantity && quantity !== 0 ? ( {balance} - - {quantityFormatted} - + {quantityFormatted && ( + + {quantityFormatted} + + )} ) : null} diff --git a/packages/uniswap/src/components/TokenSelector/TokenSectionBaseList.native.tsx b/packages/uniswap/src/components/TokenSelector/TokenSectionBaseList.native.tsx index 8f02be66f20..6fa29b795c6 100644 --- a/packages/uniswap/src/components/TokenSelector/TokenSectionBaseList.native.tsx +++ b/packages/uniswap/src/components/TokenSelector/TokenSectionBaseList.native.tsx @@ -1,9 +1,9 @@ import { BottomSheetSectionList } from '@gorhom/bottom-sheet' import { useEffect, useRef } from 'react' import { SectionList } from 'react-native' -import { useDeviceInsets } from 'ui/src' import { TokenSectionBaseListProps } from 'uniswap/src/components/TokenSelector/TokenSectionBaseList' import { TokenOption, TokenSection } from 'uniswap/src/components/TokenSelector/types' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' export function TokenSectionBaseList({ sectionListRef, @@ -14,7 +14,7 @@ export function TokenSectionBaseList({ renderSectionHeader, sections, }: TokenSectionBaseListProps): JSX.Element { - const insets = useDeviceInsets() + const insets = useAppInsets() const ref = useRef>(null) useEffect(() => { diff --git a/packages/uniswap/src/components/TokenSelector/TokenSectionHeader.tsx b/packages/uniswap/src/components/TokenSelector/TokenSectionHeader.tsx index 9b4629b31db..cb86cd8ca71 100644 --- a/packages/uniswap/src/components/TokenSelector/TokenSectionHeader.tsx +++ b/packages/uniswap/src/components/TokenSelector/TokenSectionHeader.tsx @@ -15,7 +15,7 @@ export type TokenSectionHeaderProps = { } export function SectionHeader({ sectionKey, rightElement, name }: TokenSectionHeaderProps): JSX.Element | null { - const title = useTokenOptionsSectionTitle(sectionKey, name) + const title = useTokenOptionsSectionTitle(sectionKey) const icon = getTokenOptionsSectionIcon(sectionKey) if (sectionKey === TokenOptionSection.SuggestedTokens) { return null @@ -25,14 +25,14 @@ export function SectionHeader({ sectionKey, rightElement, name }: TokenSectionHe {icon} - + ) } -export function useTokenOptionsSectionTitle(section: TokenOptionSection, name?: string): string { +export function useTokenOptionsSectionTitle(section: TokenOptionSection): string { const { t } = useTranslation() switch (section) { case TokenOptionSection.BridgingTokens: @@ -49,8 +49,6 @@ export function useTokenOptionsSectionTitle(section: TokenOptionSection, name?: return t('tokens.selector.section.search') case TokenOptionSection.SuggestedTokens: return '' // no suggested tokens header - case TokenOptionSection.SearchResultsByNetwork: - return name ?? '' default: return section } diff --git a/packages/uniswap/src/components/TokenSelector/TokenSelector.tsx b/packages/uniswap/src/components/TokenSelector/TokenSelector.tsx index c3acfd3c9ef..2caf6155a6b 100644 --- a/packages/uniswap/src/components/TokenSelector/TokenSelector.tsx +++ b/packages/uniswap/src/components/TokenSelector/TokenSelector.tsx @@ -23,11 +23,12 @@ import { TradeableAsset } from 'uniswap/src/entities/assets' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { SearchContext } from 'uniswap/src/features/search/SearchContext' import { SearchTextInput } from 'uniswap/src/features/search/SearchTextInput' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import Trace from 'uniswap/src/features/telemetry/Trace' import { ElementName, ModalName, SectionName, UniswapEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import useIsKeyboardOpen from 'uniswap/src/hooks/useIsKeyboardOpen' -import { UniverseChainId, WALLET_SUPPORTED_CHAIN_IDS } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { getClipboard } from 'uniswap/src/utils/clipboard' import { currencyAddress } from 'uniswap/src/utils/currencyId' @@ -71,7 +72,7 @@ export function TokenSelectorContent({ input, activeAccountAddress, chainId, - chainIds = WALLET_SUPPORTED_CHAIN_IDS, + chainIds, isSurfaceReady = true, isLimits, onClose, @@ -91,6 +92,8 @@ export function TokenSelectorContent({ const [hasClipboardString, setHasClipboardString] = useState(false) + const { chains: enabledChains, isTestnetModeEnabled } = useEnabledChains() + // Check if user clipboard has any text to show paste button useEffect(() => { async function checkClipboard(): Promise { @@ -176,7 +179,7 @@ export function TokenSelectorContent({ const shouldAutoFocusSearch = isWeb && !media.sm const tokenSelector = useMemo(() => { - if (searchInFocus && !searchFilter) { + if (searchInFocus && !searchFilter && !isTestnetModeEnabled) { return ( {hasClipboardString && } { diff --git a/packages/uniswap/src/components/TokenSelector/TokenSelectorList.tsx b/packages/uniswap/src/components/TokenSelector/TokenSelectorList.tsx index 3653a4ff983..2779b595cae 100644 --- a/packages/uniswap/src/components/TokenSelector/TokenSelectorList.tsx +++ b/packages/uniswap/src/components/TokenSelector/TokenSelectorList.tsx @@ -14,6 +14,7 @@ import { SectionHeader, TokenSectionHeaderProps } from 'uniswap/src/components/T import { OnSelectCurrency, TokenOption, TokenSection } from 'uniswap/src/components/TokenSelector/types' import { useBottomSheetFocusHook } from 'uniswap/src/components/modals/hooks' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { useDismissedTokenWarnings } from 'uniswap/src/features/tokens/slice/hooks' import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyId } from 'uniswap/src/types/currency' @@ -46,21 +47,30 @@ function TokenOptionItemWrapper({ [index, onSelectCurrency, section, tokenOption.currencyInfo], ) + const { isTestnetModeEnabled } = useEnabledChains() + const { tokenWarningDismissed, onDismissTokenWarning: dismissWarningCallback } = useDismissedTokenWarnings( tokenOption.currencyInfo.currency, ) + const tokenBalance = formatNumberOrString({ + value: tokenOption.quantity, + type: NumberType.TokenTx, + }) + + const fiatBalance = convertFiatAmountFormatted(tokenOption.balanceUSD, NumberType.FiatTokenPrice) + + const title = isTestnetModeEnabled ? tokenBalance : fiatBalance + const subtitle = isTestnetModeEnabled ? undefined : tokenBalance + return ( { + const { defaultChainId, isTestnetModeEnabled } = useEnabledChains() const { data: portfolioTokenOptions, error: portfolioTokenOptionsError, @@ -39,8 +40,8 @@ function useTokenSectionsForSwapInput({ error: popularTokenOptionsError, refetch: refetchPopularTokenOptions, loading: popularTokenOptionsLoading, - // if there is no chain filter then we show mainnet tokens - } = usePopularTokensOptions(activeAccountAddress, chainFilter ?? UniverseChainId.Mainnet) + // if there is no chain filter then we show default chain tokens + } = usePopularTokensOptions(activeAccountAddress, chainFilter ?? defaultChainId) const { data: favoriteTokenOptions, @@ -51,7 +52,7 @@ function useTokenSectionsForSwapInput({ const { data: commonTokenOptions } = useCommonTokensOptionsWithFallback( activeAccountAddress, - chainFilter ?? UniverseChainId.Mainnet, + chainFilter ?? defaultChainId, ) const recentlySearchedTokenOptions = useRecentlySearchedTokens(chainFilter) @@ -85,6 +86,10 @@ function useTokenSectionsForSwapInput({ return undefined } + if (isTestnetModeEnabled) { + return [...(suggestedSection ?? []), ...(portfolioSection ?? [])] + } + return [ ...(suggestedSection ?? []), ...(portfolioSection ?? []), @@ -94,7 +99,15 @@ function useTokenSectionsForSwapInput({ ...(isMobileApp ? favoriteSection ?? [] : []), ...(popularSection ?? []), ] satisfies TokenSection[] - }, [suggestedSection, favoriteSection, loading, popularSection, portfolioSection, recentSection]) + }, [ + suggestedSection, + favoriteSection, + loading, + popularSection, + portfolioSection, + recentSection, + isTestnetModeEnabled, + ]) return useMemo( () => ({ diff --git a/packages/uniswap/src/components/TokenSelector/TokenSelectorSwapOutputList.tsx b/packages/uniswap/src/components/TokenSelector/TokenSelectorSwapOutputList.tsx index 8e57f322bad..23db606ca3a 100644 --- a/packages/uniswap/src/components/TokenSelector/TokenSelectorSwapOutputList.tsx +++ b/packages/uniswap/src/components/TokenSelector/TokenSelectorSwapOutputList.tsx @@ -1,4 +1,5 @@ -import { memo, useCallback, useMemo } from 'react' +import { memo, useCallback, useMemo, useRef } from 'react' +import { Flex } from 'ui/src' import { TokenSelectorList } from 'uniswap/src/components/TokenSelector/TokenSelectorList' import { useCommonTokensOptionsWithFallback, @@ -23,15 +24,18 @@ import { GqlResult } from 'uniswap/src/data/types' import { useBridgingTokensOptions } from 'uniswap/src/features/bridging/hooks/tokens' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { UniverseChainId } from 'uniswap/src/types/chains' import { isMobileApp } from 'utilities/src/platform' +// eslint-disable-next-line complexity function useTokenSectionsForSwapOutput({ activeAccountAddress, chainFilter, input, }: TokenSectionsHookProps): GqlResult { const isBridgingEnabled = useFeatureFlag(FeatureFlags.Bridging) + const { defaultChainId, isTestnetModeEnabled } = useEnabledChains() const { data: portfolioTokenOptions, @@ -46,7 +50,7 @@ function useTokenSectionsForSwapOutput({ refetch: refetchPopularTokenOptions, loading: popularTokenOptionsLoading, // if there is no chain filter then we show mainnet tokens - } = usePopularTokensOptions(activeAccountAddress, chainFilter ?? UniverseChainId.Mainnet) + } = usePopularTokensOptions(activeAccountAddress, chainFilter ?? defaultChainId) const { data: favoriteTokenOptions, @@ -60,8 +64,8 @@ function useTokenSectionsForSwapOutput({ error: commonTokenOptionsError, refetch: refetchCommonTokenOptions, loading: commonTokenOptionsLoading, - // if there is no chain filter then we show mainnet tokens - } = useCommonTokensOptionsWithFallback(activeAccountAddress, chainFilter ?? UniverseChainId.Mainnet) + // if there is no chain filter then we show default chain tokens + } = useCommonTokensOptionsWithFallback(activeAccountAddress, chainFilter ?? defaultChainId) const { data: bridgingTokenOptions, @@ -81,45 +85,69 @@ function useTokenSectionsForSwapOutput({ (!bridgingTokenOptions && bridgingTokenOptionsError) const loading = - portfolioTokenOptionsLoading || - popularTokenOptionsLoading || - favoriteTokenOptionsLoading || - commonTokenOptionsLoading || - bridgingTokenOptionsLoading + (!portfolioTokenOptions && portfolioTokenOptionsLoading) || + (!popularTokenOptions && popularTokenOptionsLoading) || + (!favoriteTokenOptions && favoriteTokenOptionsLoading) || + (!commonTokenOptions && commonTokenOptionsLoading) || + (!bridgingTokenOptions && bridgingTokenOptionsLoading) + + const refetchAllRef = useRef<() => void>(() => {}) - const refetchAll = useCallback(() => { + refetchAllRef.current = (): void => { refetchPortfolioTokenOptions?.() refetchPopularTokenOptions?.() refetchFavoriteTokenOptions?.() refetchCommonTokenOptions?.() refetchBridgingTokenOptions?.() - }, [ - refetchBridgingTokenOptions, - refetchCommonTokenOptions, - refetchFavoriteTokenOptions, - refetchPopularTokenOptions, - refetchPortfolioTokenOptions, - ]) + } + + const refetch = useCallback(() => { + refetchAllRef.current() + }, []) + + const newTag = useMemo( + () => + isMobileApp ? ( + // Hack for vertically centering the new tag with text + + + + ) : ( + + ), + [], + ) // we draw the Suggested pills as a single item of a section list, so `data` is TokenOption[][] - const suggestedSection = useTokenOptionsSection(TokenOptionSection.SuggestedTokens, [commonTokenOptions ?? []]) + + const suggestedSectionOptions = useMemo(() => [commonTokenOptions ?? []], [commonTokenOptions]) + const suggestedSection = useTokenOptionsSection(TokenOptionSection.SuggestedTokens, suggestedSectionOptions) + const portfolioSection = useTokenOptionsSection(TokenOptionSection.YourTokens, portfolioTokenOptions) const recentSection = useTokenOptionsSection(TokenOptionSection.RecentTokens, recentlySearchedTokenOptions) const favoriteSection = useTokenOptionsSection(TokenOptionSection.FavoriteTokens, favoriteTokenOptions) - const popularMinusPortfolioTokens = tokenOptionDifference(popularTokenOptions, portfolioTokenOptions) + const popularMinusPortfolioTokens = useMemo( + () => tokenOptionDifference(popularTokenOptions, portfolioTokenOptions), + [popularTokenOptions, portfolioTokenOptions], + ) const popularSection = useTokenOptionsSection(TokenOptionSection.PopularTokens, popularMinusPortfolioTokens) - const bridgingSection = useTokenOptionsSection( - TokenOptionSection.BridgingTokens, - shouldNestBridgingTokens ? [bridgingTokenOptions ?? []] : bridgingTokenOptions ?? [], - , + + const bridgingSectionTokenOptions = useMemo( + () => (shouldNestBridgingTokens ? [bridgingTokenOptions ?? []] : bridgingTokenOptions ?? []), + [bridgingTokenOptions, shouldNestBridgingTokens], ) + const bridgingSection = useTokenOptionsSection(TokenOptionSection.BridgingTokens, bridgingSectionTokenOptions, newTag) const sections = useMemo(() => { if (isSwapListLoading(loading, portfolioSection, popularSection)) { return undefined } + if (isTestnetModeEnabled) { + return [...(suggestedSection ?? []), ...(portfolioSection ?? [])] + } + return [ ...(suggestedSection ?? []), ...(isBridgingEnabled ? bridgingSection ?? [] : []), @@ -139,6 +167,7 @@ function useTokenSectionsForSwapOutput({ bridgingSection, recentSection, favoriteSection, + isTestnetModeEnabled, ]) return useMemo( @@ -146,9 +175,9 @@ function useTokenSectionsForSwapOutput({ data: sections, loading, error: error || undefined, - refetch: refetchAll, + refetch, }), - [error, loading, refetchAll, sections], + [error, loading, refetch, sections], ) } @@ -179,7 +208,7 @@ function _TokenSelectorSwapOutputList({ isKeyboardOpen={isKeyboardOpen} loading={loading} refetch={refetch} - sections={sections} + sections={loading ? undefined : sections} showTokenWarnings={true} onSelectCurrency={onSelectCurrency} /> diff --git a/packages/uniswap/src/components/TokenSelector/filter.test.ts b/packages/uniswap/src/components/TokenSelector/filter.test.ts index f5e8d698bff..c3c3457d335 100644 --- a/packages/uniswap/src/components/TokenSelector/filter.test.ts +++ b/packages/uniswap/src/components/TokenSelector/filter.test.ts @@ -3,7 +3,7 @@ import { filter } from 'uniswap/src/components/TokenSelector/filter' import { TokenOption } from 'uniswap/src/components/TokenSelector/types' import { DAI, DAI_ARBITRUM_ONE } from 'uniswap/src/constants/tokens' import { NativeCurrency } from 'uniswap/src/features/tokens/NativeCurrency' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { currencyId } from 'uniswap/src/utils/currencyId' const ETH = NativeCurrency.onChain(UniverseChainId.Mainnet) @@ -43,7 +43,7 @@ const TEST_TOKEN_INPUT: TokenOption[] = [ const filterAndGetCurrencies = ( currencies: TokenOption[], - chainFilter: WalletChainId | null, + chainFilter: UniverseChainId | null, searchFilter?: string, ): Currency[] => filter(currencies, chainFilter, searchFilter).map((cm) => cm.currencyInfo.currency) diff --git a/packages/uniswap/src/components/TokenSelector/hooks.test.ts b/packages/uniswap/src/components/TokenSelector/hooks.test.ts index ae0acd5d2e9..779e1935677 100644 --- a/packages/uniswap/src/components/TokenSelector/hooks.test.ts +++ b/packages/uniswap/src/components/TokenSelector/hooks.test.ts @@ -39,7 +39,7 @@ import { } from 'uniswap/src/test/fixtures' import { act, renderHook, waitFor } from 'uniswap/src/test/test-utils' import { createArray, queryResolvers } from 'uniswap/src/test/utils' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { portfolioBalancesById } from 'uniswap/src/utils/balances' import { buildCurrencyId } from 'uniswap/src/utils/currencyId' @@ -651,7 +651,7 @@ describe(useCommonTokensOptionsWithFallback, () => { input: { portfolios: [portfolio({ tokenBalances })], tokenProjects: [tokenProject({ tokens })], - chainFilter: UniverseChainId.Mainnet as WalletChainId, + chainFilter: UniverseChainId.Mainnet as UniverseChainId, }, output: { data: expect.toIncludeSameMembers([ @@ -721,7 +721,7 @@ describe(useFavoriteTokensOptions, () => { input: { portfolios: [portfolio({ tokenBalances })], tokenProjects: [tokenProject({ tokens: favoriteTokens })], - chainFilter: UniverseChainId.Mainnet as WalletChainId, + chainFilter: UniverseChainId.Mainnet as UniverseChainId, }, output: { data: expect.toIncludeSameMembers([ diff --git a/packages/uniswap/src/components/TokenSelector/hooks.tsx b/packages/uniswap/src/components/TokenSelector/hooks.tsx index f0c27dab120..8c78a2d391c 100644 --- a/packages/uniswap/src/components/TokenSelector/hooks.tsx +++ b/packages/uniswap/src/components/TokenSelector/hooks.tsx @@ -43,29 +43,25 @@ import { SearchResultType, TokenSearchResult } from 'uniswap/src/features/search import { addToSearchHistory, clearSearchHistory } from 'uniswap/src/features/search/searchHistorySlice' import { selectSearchHistory } from 'uniswap/src/features/search/selectSearchHistory' import { tokenAddressOrNativeAddress } from 'uniswap/src/features/search/utils' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { WalletEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { usePopularTokens } from 'uniswap/src/features/tokens/hooks' -import { - UniverseChainId, - WALLET_SUPPORTED_CHAIN_IDS, - WEB_SUPPORTED_CHAIN_IDS, - WalletChainId, -} from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { areAddressesEqual } from 'uniswap/src/utils/addresses' import { buildNativeCurrencyId, buildWrappedNativeCurrencyId, currencyId } from 'uniswap/src/utils/currencyId' -import { isInterface } from 'utilities/src/platform' - -const nativeCurrencyNames = (isInterface ? WEB_SUPPORTED_CHAIN_IDS : WALLET_SUPPORTED_CHAIN_IDS) - .map((chainId) => { - return UNIVERSE_CHAIN_INFO[chainId].testnet - ? false - : { - chainId, - name: UNIVERSE_CHAIN_INFO[chainId].nativeCurrency.name.toLowerCase(), - } - }) - .filter(Boolean) as { chainId: WalletChainId; name: string }[] + +const getNativeCurrencyNames = (chains: UniverseChainId[]): { chainId: UniverseChainId; name: string }[] => + chains + .map((chainId) => { + return UNIVERSE_CHAIN_INFO[chainId].testnet + ? false + : { + chainId, + name: UNIVERSE_CHAIN_INFO[chainId].nativeCurrency.name.toLowerCase(), + } + }) + .filter(Boolean) as { chainId: UniverseChainId; name: string }[] // Use Mainnet base token addresses since TokenProjects query returns each token // on each network @@ -104,7 +100,7 @@ export function searchResultToCurrencyInfo({ safetyInfo, }: TokenSearchResult): CurrencyInfo | null { const currency = buildCurrency({ - chainId: chainId as WalletChainId, + chainId: chainId as UniverseChainId, address, decimals: 0, // this does not matter in a context of CurrencyInfo here, as we do not provide any balance symbol, @@ -128,7 +124,8 @@ export function searchResultToCurrencyInfo({ } export function useAllCommonBaseCurrencies(): GqlResult { - return useCurrencies(baseCurrencyIds) + const { isTestnetModeEnabled } = useEnabledChains() + return useCurrencies(isTestnetModeEnabled ? [] : baseCurrencyIds) } export function useCurrencies(currencyIds: string[]): GqlResult { @@ -494,6 +491,8 @@ export function useFilterCallbacks( const [searchFilter, setSearchFilter] = useState(null) const [parsedSearchFilter, setParsedSearchFilter] = useState(null) + const { chains: enabledChains } = useEnabledChains() + // Parses the user input to determine if the user is searching for a chain + token // i.e "eth dai" // parsedChainFilter: 1 @@ -502,7 +501,9 @@ export function useFilterCallbacks( const splitSearch = searchFilter?.split(' ') const maybeChainName = splitSearch?.[0]?.toLowerCase() - const chainMatch = nativeCurrencyNames.find((currency) => currency.name.startsWith(maybeChainName ?? '')) + const chainMatch = getNativeCurrencyNames(enabledChains).find((currency) => + currency.name.startsWith(maybeChainName ?? ''), + ) const search = splitSearch?.slice(1).join(' ') if (!chainFilter && chainMatch && search) { @@ -512,7 +513,7 @@ export function useFilterCallbacks( setParsedChainFilter(null) setParsedSearchFilter(null) } - }, [searchFilter, chainFilter]) + }, [searchFilter, chainFilter, enabledChains]) useEffect(() => { setChainFilter(chainId) @@ -551,12 +552,16 @@ export const MAX_RECENT_SEARCH_RESULTS = 4 export function useRecentlySearchedTokens(chainFilter: UniverseChainId | null): TokenOption[] | undefined { const searchHistory = useSelector(selectSearchHistory) - return currencyInfosToTokenOptions( - searchHistory - .filter((searchResult): searchResult is TokenSearchResult => searchResult.type === SearchResultType.Token) - .filter((searchResult) => (chainFilter ? searchResult.chainId === chainFilter : true)) - .slice(0, MAX_RECENT_SEARCH_RESULTS) - .map(searchResultToCurrencyInfo), + return useMemo( + () => + currencyInfosToTokenOptions( + searchHistory + .filter((searchResult): searchResult is TokenSearchResult => searchResult.type === SearchResultType.Token) + .filter((searchResult) => (chainFilter ? searchResult.chainId === chainFilter : true)) + .slice(0, MAX_RECENT_SEARCH_RESULTS) + .map(searchResultToCurrencyInfo), + ), + [chainFilter, searchHistory], ) } diff --git a/packages/uniswap/src/components/TokenSelector/types.ts b/packages/uniswap/src/components/TokenSelector/types.ts index 701aa99af81..d0df0121070 100644 --- a/packages/uniswap/src/components/TokenSelector/types.ts +++ b/packages/uniswap/src/components/TokenSelector/types.ts @@ -20,7 +20,6 @@ export enum TokenOptionSection { SearchResults = 'searchResults', SuggestedTokens = 'suggestedTokens', BridgingTokens = 'bridgingTokens', - SearchResultsByNetwork = 'searchResultsByNetwork', } export type TokenSection = { diff --git a/packages/uniswap/src/components/TokenSelector/utils.tsx b/packages/uniswap/src/components/TokenSelector/utils.tsx index 5afa7fbafaf..f36d67453b6 100644 --- a/packages/uniswap/src/components/TokenSelector/utils.tsx +++ b/packages/uniswap/src/components/TokenSelector/utils.tsx @@ -1,3 +1,4 @@ +import { useMemo } from 'react' import { TokenOption, TokenOptionSection, TokenSection } from 'uniswap/src/components/TokenSelector/types' import { tradingApiSwappableTokenToCurrencyInfo } from 'uniswap/src/data/apiClients/tradingApi/utils/tradingApiSwappableTokenToCurrencyInfo' import { SafetyLevel as GqlSafetyLevel } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' @@ -169,27 +170,29 @@ export function useTokenOptionsSection( rightElement?: JSX.Element, name?: string, ): TokenSection[] | undefined { - if (!tokenOptions) { - return undefined - } + return useMemo(() => { + if (!tokenOptions) { + return undefined + } - // If it is a 2D array, check if any of the inner arrays are not empty - // Otherwise, check if the array is not empty - const is2DArray = tokenOptions?.length > 0 && Array.isArray(tokenOptions[0]) - const hasData = is2DArray - ? tokenOptions.some((item) => isTokenOptionArray(item) && item.length > 0) - : tokenOptions.length > 0 - - return hasData - ? [ - { - sectionKey, - data: tokenOptions, - name, - rightElement, - }, - ] - : undefined + // If it is a 2D array, check if any of the inner arrays are not empty + // Otherwise, check if the array is not empty + const is2DArray = tokenOptions?.length > 0 && Array.isArray(tokenOptions[0]) + const hasData = is2DArray + ? tokenOptions.some((item) => isTokenOptionArray(item) && item.length > 0) + : tokenOptions.length > 0 + + return hasData + ? [ + { + sectionKey, + data: tokenOptions, + name, + rightElement, + }, + ] + : undefined + }, [name, rightElement, sectionKey, tokenOptions]) } export function isSwapListLoading( diff --git a/packages/uniswap/src/components/banners/TestnetModeBanner.tsx b/packages/uniswap/src/components/banners/TestnetModeBanner.tsx new file mode 100644 index 00000000000..367f9c92a14 --- /dev/null +++ b/packages/uniswap/src/components/banners/TestnetModeBanner.tsx @@ -0,0 +1,44 @@ +import { useTranslation } from 'react-i18next' +import { Flex, FlexProps, Text, isWeb } from 'ui/src' +import { Wrench } from 'ui/src/components/icons/Wrench' +// eslint-disable-next-line no-restricted-imports +import { useDeviceInsets } from 'ui/src/hooks/useDeviceInsets' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' +import { TESTNET_MODE_BANNER_HEIGHT } from 'uniswap/src/hooks/useAppInsets' +import { isInterface, isMobileApp } from 'utilities/src/platform' + +export function TestnetModeBanner(props: FlexProps): JSX.Element | null { + const { isTestnetModeEnabled } = useEnabledChains() + const { t } = useTranslation() + + const { top } = useDeviceInsets() + + if (!isTestnetModeEnabled) { + return null + } + + return ( + + + + {t('home.banner.testnetMode')} + + + ) +} diff --git a/packages/uniswap/src/components/dropdowns/ActionSheetDropdown.tsx b/packages/uniswap/src/components/dropdowns/ActionSheetDropdown.tsx index 57f246516f4..dc202edf07b 100644 --- a/packages/uniswap/src/components/dropdowns/ActionSheetDropdown.tsx +++ b/packages/uniswap/src/components/dropdowns/ActionSheetDropdown.tsx @@ -11,7 +11,6 @@ import { TouchableArea, isWeb, styled, - useDeviceInsets, useIsDarkMode, } from 'ui/src' import { RotatableChevron } from 'ui/src/components/icons/RotatableChevron' @@ -20,6 +19,7 @@ import { iconSizes, spacing, zIndices } from 'ui/src/theme' import { BaseCard } from 'uniswap/src/components/BaseCard/BaseCard' import { Scrollbar } from 'uniswap/src/components/misc/Scrollbar' import { MenuItemProp } from 'uniswap/src/components/modals/ActionSheetModal' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { isAndroid, isInterface, isTouchable } from 'utilities/src/platform' const DEFAULT_MIN_WIDTH = 225 @@ -63,7 +63,7 @@ export function ActionSheetDropdown({ closeOnSelect = true, ...contentProps }: ActionSheetDropdownProps): JSX.Element { - const insets = useDeviceInsets() + const insets = useAppInsets() const containerRef = useRef(null) const [{ isOpen, toggleMeasurements }, setState] = useState({ isOpen: false, @@ -210,7 +210,7 @@ function DropdownContent({ closeOnSelect, ...rest }: DropdownContentProps): JSX.Element { - const insets = useDeviceInsets() + const insets = useAppInsets() const { fullWidth, fullHeight } = useDeviceDimensions() const scrollOffset = useSharedValue(0) diff --git a/packages/uniswap/src/components/gas/NetworkFee.test.tsx b/packages/uniswap/src/components/gas/NetworkFee.test.tsx index 0271dfee7ad..f3b4e55f13d 100644 --- a/packages/uniswap/src/components/gas/NetworkFee.test.tsx +++ b/packages/uniswap/src/components/gas/NetworkFee.test.tsx @@ -1,12 +1,16 @@ import { NetworkFee } from 'uniswap/src/components/gas/NetworkFee' import { render } from 'uniswap/src/test/test-utils' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' jest.mock('uniswap/src/features/gas/hooks', () => { return { useFormattedUniswapXGasFeeInfo: jest.fn(() => undefined), - useUSDValue: (_chainId: WalletChainId, gasFee: string): string => gasFee, + useUSDValue: (_chainId: UniverseChainId, gasFee: string): string => gasFee, useGasFeeHighRelativeToValue: jest.fn(() => false), + useGasFeeFormattedAmounts: jest.fn(() => ({ + gasFeeFormatted: '$1', + gasFeeUSD: '$500.00', + })), } }) diff --git a/packages/uniswap/src/components/gas/NetworkFee.tsx b/packages/uniswap/src/components/gas/NetworkFee.tsx index 3dc8809ec4f..20bad47da92 100644 --- a/packages/uniswap/src/components/gas/NetworkFee.tsx +++ b/packages/uniswap/src/components/gas/NetworkFee.tsx @@ -7,15 +7,13 @@ import { NetworkLogo } from 'uniswap/src/components/CurrencyLogo/NetworkLogo' import { IndicativeLoadingWrapper } from 'uniswap/src/components/misc/IndicativeLoadingWrapper' import { useFormattedUniswapXGasFeeInfo, + useGasFeeFormattedAmounts, useGasFeeHighRelativeToValue, - useUSDValue, } from 'uniswap/src/features/gas/hooks' import { GasFeeResult } from 'uniswap/src/features/gas/types' -import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { NetworkFeeWarning } from 'uniswap/src/features/transactions/swap/modals/NetworkFeeWarning' import { UniswapXGasBreakdown } from 'uniswap/src/features/transactions/swap/types/swapTxAndGasInfo' import { UniverseChainId } from 'uniswap/src/types/chains' -import { NumberType } from 'utilities/src/format/types' export function NetworkFee({ chainId, @@ -31,10 +29,12 @@ export function NetworkFee({ indicative?: boolean }): JSX.Element { const { t } = useTranslation() - const { convertFiatAmountFormatted } = useLocalizationContext() - const gasFeeUSD = useUSDValue(chainId, gasFee.value ?? undefined) - const gasFeeFormatted = convertFiatAmountFormatted(gasFeeUSD, NumberType.FiatGasPrice) + const { gasFeeFormatted, gasFeeUSD } = useGasFeeFormattedAmounts({ + gasFee, + chainId, + placeholder: '-', + }) const uniswapXGasFeeInfo = useFormattedUniswapXGasFeeInfo(uniswapXGasBreakdown, chainId) diff --git a/packages/uniswap/src/components/gas/__snapshots__/NetworkFee.test.tsx.snap b/packages/uniswap/src/components/gas/__snapshots__/NetworkFee.test.tsx.snap index 29792cef1f1..128111b46e7 100644 --- a/packages/uniswap/src/components/gas/__snapshots__/NetworkFee.test.tsx.snap +++ b/packages/uniswap/src/components/gas/__snapshots__/NetworkFee.test.tsx.snap @@ -538,7 +538,7 @@ exports[`NetworkFee renders a NetworkFee normally 1`] = ` } suppressHighlighting={true} > - $500.00 + $1 diff --git a/packages/wallet/src/components/modals/InfoLinkModal.tsx b/packages/uniswap/src/components/modals/InfoLinkModal.tsx similarity index 98% rename from packages/wallet/src/components/modals/InfoLinkModal.tsx rename to packages/uniswap/src/components/modals/InfoLinkModal.tsx index 447e0cd5257..7e95140b5fd 100644 --- a/packages/wallet/src/components/modals/InfoLinkModal.tsx +++ b/packages/uniswap/src/components/modals/InfoLinkModal.tsx @@ -1,6 +1,6 @@ import { ReactNode } from 'react' import { Button, Flex, isWeb, Text, TouchableArea, useSporeColors } from 'ui/src' -import { X } from 'ui/src/components/icons' +import { X } from 'ui/src/components/icons/X' import { zIndices } from 'ui/src/theme' import { Modal } from 'uniswap/src/components/modals/Modal' import { ModalNameType } from 'uniswap/src/features/telemetry/constants' @@ -11,15 +11,15 @@ export interface ModalProps { name: ModalNameType isOpen: boolean showCloseButton?: boolean - onDismiss?: () => void icon: ReactNode title: string description: string buttonText: string buttonTheme?: 'primary' | 'secondary' | 'tertiary' - onButtonPress?: () => void linkText?: string linkUrl?: string + onDismiss?: () => void + onButtonPress?: () => void onAnalyticsEvent?: () => void } @@ -27,15 +27,15 @@ export function InfoLinkModal({ name, isOpen, showCloseButton, - onDismiss, icon, title, description, buttonText, buttonTheme, - onButtonPress, linkText, linkUrl, + onDismiss, + onButtonPress, onAnalyticsEvent, }: React.PropsWithChildren): JSX.Element { const colors = useSporeColors() diff --git a/packages/uniswap/src/components/modals/Modal.native.tsx b/packages/uniswap/src/components/modals/Modal.native.tsx index 5a5680c45dd..6baae6d9731 100644 --- a/packages/uniswap/src/components/modals/Modal.native.tsx +++ b/packages/uniswap/src/components/modals/Modal.native.tsx @@ -11,13 +11,14 @@ import { BlurView } from 'expo-blur' import React, { ComponentProps, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { BackHandler, StyleProp, StyleSheet, ViewStyle } from 'react-native' import Animated, { Extrapolate, interpolate, useAnimatedStyle, useSharedValue } from 'react-native-reanimated' -import { Flex, useDeviceInsets, useIsDarkMode, useMedia, useSporeColors } from 'ui/src' +import { Flex, useIsDarkMode, useMedia, useSporeColors } from 'ui/src' import { useDeviceDimensions } from 'ui/src/hooks/useDeviceDimensions' import { borderRadii, spacing } from 'ui/src/theme' import { BottomSheetContextProvider } from 'uniswap/src/components/modals/BottomSheetContext' import { HandleBar } from 'uniswap/src/components/modals/HandleBar' import { ModalProps } from 'uniswap/src/components/modals/ModalProps' import Trace from 'uniswap/src/features/telemetry/Trace' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { useKeyboardLayout } from 'uniswap/src/utils/useKeyboardLayout' import { dismissNativeKeyboard } from 'utilities/src/device/keyboard' import { isIOS } from 'utilities/src/platform' @@ -94,7 +95,7 @@ function BottomSheetModalContents({ hideScrim = false, }: ModalProps): JSX.Element { const dimensions = useDeviceDimensions() - const insets = useDeviceInsets() + const insets = useAppInsets() const media = useMedia() const keyboard = useKeyboardLayout() const colors = useSporeColors() @@ -289,7 +290,7 @@ export function BottomSheetDetachedModal({ hideHandlebar, backgroundColor, }: ModalProps): JSX.Element { - const insets = useDeviceInsets() + const insets = useAppInsets() const dimensions = useDeviceDimensions() const modalRef = useRef(null) const colors = useSporeColors() diff --git a/packages/uniswap/src/components/modals/ModalProps.tsx b/packages/uniswap/src/components/modals/ModalProps.tsx index 1280085f004..5e9cb7f14d8 100644 --- a/packages/uniswap/src/components/modals/ModalProps.tsx +++ b/packages/uniswap/src/components/modals/ModalProps.tsx @@ -1,7 +1,7 @@ import { BottomSheetModal as BaseModal } from '@gorhom/bottom-sheet' import { ComponentProps, PropsWithChildren } from 'react' import { SharedValue } from 'react-native-reanimated' -import { ColorTokens, SpaceTokens } from 'ui/src' +import { ColorTokens, SpaceTokens, View } from 'ui/src' import { ModalNameType } from 'uniswap/src/features/telemetry/constants' export type ModalProps = PropsWithChildren<{ @@ -33,6 +33,6 @@ export type ModalProps = PropsWithChildren<{ alignment?: 'center' | 'top' hideScrim?: boolean maxWidth?: number - maxHeight?: number + maxHeight?: ComponentProps['maxHeight'] padding?: SpaceTokens }> diff --git a/packages/uniswap/src/components/modals/WarningModal/WarningModal.tsx b/packages/uniswap/src/components/modals/WarningModal/WarningModal.tsx index 1c1c58045fb..3e81e7bf5ed 100644 --- a/packages/uniswap/src/components/modals/WarningModal/WarningModal.tsx +++ b/packages/uniswap/src/components/modals/WarningModal/WarningModal.tsx @@ -1,4 +1,4 @@ -import { PropsWithChildren, ReactNode } from 'react' +import { PropsWithChildren, ReactNode, useContext } from 'react' // eslint-disable-next-line no-restricted-imports -- type import is safe import type { ColorValue } from 'react-native' import { Button, Flex, Text, useSporeColors } from 'ui/src' @@ -8,6 +8,7 @@ import { Modal } from 'uniswap/src/components/modals/Modal' import { getAlertColor } from 'uniswap/src/components/modals/WarningModal/getAlertColor' import { WarningSeverity } from 'uniswap/src/components/modals/WarningModal/types' import { ModalNameType } from 'uniswap/src/features/telemetry/constants' +import { SwapFormContext } from 'uniswap/src/features/transactions/swap/contexts/SwapFormContext' import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { isWeb } from 'utilities/src/platform' @@ -133,6 +134,8 @@ export function WarningModal(props: PropsWithChildren): JSX.E const { hideHandlebar, isDismissible = true, isOpen, maxWidth, modalName, onClose } = props const colors = useSporeColors() + const swapFormContext = useContext(SwapFormContext) + return ( ): JSX.E name={modalName} onClose={onClose} > - + {swapFormContext ? ( + // When we render this modal inside the swap flow, we want to forward the context so that it's available inside the modal's Portal. + + + + ) : ( + + )} ) } diff --git a/packages/uniswap/src/components/modals/WarningModal/types.ts b/packages/uniswap/src/components/modals/WarningModal/types.ts index 69e417a713b..79c0419eae9 100644 --- a/packages/uniswap/src/components/modals/WarningModal/types.ts +++ b/packages/uniswap/src/components/modals/WarningModal/types.ts @@ -34,6 +34,7 @@ export enum WarningAction { } export enum WarningLabel { + EnterLargerAmount = 'enter_larger_amount', InsufficientFunds = 'insufficient_funds', InsufficientGasFunds = 'insufficient_gas_funds', FormIncomplete = 'form_incomplete', diff --git a/packages/uniswap/src/components/network/NetworkFilter.test.tsx b/packages/uniswap/src/components/network/NetworkFilter.test.tsx index 20d5935a4e8..a3b04333504 100644 --- a/packages/uniswap/src/components/network/NetworkFilter.test.tsx +++ b/packages/uniswap/src/components/network/NetworkFilter.test.tsx @@ -2,7 +2,7 @@ import { NetworkFilter } from 'uniswap/src/components/network/NetworkFilter' import { render } from 'uniswap/src/test/test-utils' import ReactDOM from 'react-dom' -import { WALLET_SUPPORTED_CHAIN_IDS } from 'uniswap/src/types/chains' +import { SUPPORTED_CHAIN_IDS } from 'uniswap/src/types/chains' ReactDOM.createPortal = jest.fn((element) => { return element as React.ReactPortal @@ -10,9 +10,7 @@ ReactDOM.createPortal = jest.fn((element) => { describe(NetworkFilter, () => { it('renders a NetworkFilter', () => { - const tree = render( - null} />, - ) + const tree = render( null} />) expect(tree).toMatchSnapshot() }) }) diff --git a/packages/uniswap/src/components/network/NetworkFilter.tsx b/packages/uniswap/src/components/network/NetworkFilter.tsx index 369df1bfe66..910ca943866 100644 --- a/packages/uniswap/src/components/network/NetworkFilter.tsx +++ b/packages/uniswap/src/components/network/NetworkFilter.tsx @@ -10,7 +10,8 @@ import { ActionSheetDropdownStyleProps, } from 'uniswap/src/components/dropdowns/ActionSheetDropdown' import { useNetworkOptions } from 'uniswap/src/components/network/hooks' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' +import { UniverseChainId } from 'uniswap/src/types/chains' import { isMobileApp } from 'utilities/src/platform' const ELLIPSIS = 'ellipsis' @@ -45,7 +46,7 @@ export function NetworksInSeries({ ...(ellipsisPosition === 'start' ? [ELLIPSIS] : []), ...networks, ...(ellipsisPosition === 'end' ? [ELLIPSIS] : []), - ] as Array + ] as Array const renderItem = useCallback( ({ item: chainId }: { item: ListItem }) => ( @@ -86,6 +87,7 @@ export function NetworkFilter({ hideArrow = false, }: NetworkFilterProps): JSX.Element { const { hapticFeedback } = useHapticFeedback() + const { defaultChainId } = useEnabledChains() const onPress = useCallback( async (chainId: UniverseChainId | null) => { // Ensures smooth animation on mobile @@ -119,10 +121,7 @@ export function NetworkFilter({ {showUnsupportedConnectedChainWarning ? ( ) : ( - + )} ) diff --git a/packages/uniswap/src/components/network/NetworkLogos.tsx b/packages/uniswap/src/components/network/NetworkLogos.tsx index 47fb4ec47a1..1ee55ab5bfc 100644 --- a/packages/uniswap/src/components/network/NetworkLogos.tsx +++ b/packages/uniswap/src/components/network/NetworkLogos.tsx @@ -1,39 +1,135 @@ -import 'react-native-reanimated' -import { Flex, FlexProps, Text } from 'ui/src' -import { iconSizes } from 'ui/src/theme' -import { NetworkLogo } from 'uniswap/src/components/CurrencyLogo/NetworkLogo' -import { NetworksInSeries } from 'uniswap/src/components/network/NetworkFilter' +import { useCallback, useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { + Button, + Flex, + Image, + Text, + TouchableArea, + UniversalImage, + UniversalImageResizeMode, + useSporeColors, +} from 'ui/src' +import { ALL_NETWORKS_LOGO } from 'ui/src/assets' +import { GlobeFilled } from 'ui/src/components/icons/GlobeFilled' +import { X } from 'ui/src/components/icons/X' +import { borderRadii, iconSizes, zIndices } from 'ui/src/theme' +import { Modal } from 'uniswap/src/components/modals/Modal' +import { LearnMoreLink } from 'uniswap/src/components/text/LearnMoreLink' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' +import { uniswapUrls } from 'uniswap/src/constants/urls' +import { ModalName } from 'uniswap/src/features/telemetry/constants' import { UniverseChainId } from 'uniswap/src/types/chains' +import { isInterface } from 'utilities/src/platform' export type NetworkLogosProps = { chains: UniverseChainId[] - showFirstChainLabel?: boolean - negativeGap?: boolean - size?: number -} & FlexProps +} + +export function NetworkLogos({ chains }: NetworkLogosProps): JSX.Element { + const { t } = useTranslation() + const colors = useSporeColors() + + const [isShowingModal, setIsShowingModal] = useState(false) + const closeModal = useCallback(() => setIsShowingModal(false), []) + const openModal = useCallback(() => setIsShowingModal(true), []) -export function NetworkLogos({ - chains, - showFirstChainLabel, - size = iconSizes.icon20, - ...rest -}: NetworkLogosProps): JSX.Element { - const firstChain = chains[0] + const chainPills = useMemo( + () => ( + + {chains.map((chain) => { + const { label, logo } = UNIVERSE_CHAIN_INFO[chain] + return ( + + {logo && ( + + )} + + + {label} + + + ) + })} + + ), + [chains], + ) return ( - - {chains.length === 1 && firstChain && showFirstChainLabel ? ( - - - - {UNIVERSE_CHAIN_INFO[firstChain].label} + <> + {/* TRIGGER BUTTON */} + + {/* SHEET/MODAL */} + + + {/* X BUTTON */} + {isInterface && ( + + + + )} + {/* HEADER */} + + + + + {t('qrScanner.wallet.networks')} - + {/* CONTENT */} + {chainPills} + {/* FOOTER */} + + + - ) : ( - - )} - + + ) } diff --git a/packages/uniswap/src/components/network/NetworkPill.test.tsx b/packages/uniswap/src/components/network/NetworkPill.test.tsx index 73c6cdb3294..b208bcc1f13 100644 --- a/packages/uniswap/src/components/network/NetworkPill.test.tsx +++ b/packages/uniswap/src/components/network/NetworkPill.test.tsx @@ -4,17 +4,17 @@ import { UniverseChainId } from 'uniswap/src/types/chains' describe(NetworkPill, () => { it('renders a NetworkPill without image', () => { - const tree = render() + const tree = render() expect(tree).toMatchSnapshot() }) it('renders a NetworkPill with border', () => { - const tree = render() + const tree = render() expect(tree).toMatchSnapshot() }) it('renders an InlineNetworkPill', () => { - const tree = render() + const tree = render() expect(tree).toMatchSnapshot() }) }) diff --git a/packages/uniswap/src/components/network/NetworkPill.tsx b/packages/uniswap/src/components/network/NetworkPill.tsx index f38f00da0b6..55868a1a643 100644 --- a/packages/uniswap/src/components/network/NetworkPill.tsx +++ b/packages/uniswap/src/components/network/NetworkPill.tsx @@ -3,11 +3,11 @@ import { iconSizes } from 'ui/src/theme' import { NetworkLogo } from 'uniswap/src/components/CurrencyLogo/NetworkLogo' import { Pill } from 'uniswap/src/components/pill/Pill' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { useNetworkColors } from 'uniswap/src/utils/colors' export type NetworkPillProps = { - chainId: WalletChainId + chainId: UniverseChainId showBackgroundColor?: boolean showBorder?: boolean showIcon?: boolean diff --git a/packages/uniswap/src/components/network/__snapshots__/NetworkPill.test.tsx.snap b/packages/uniswap/src/components/network/__snapshots__/NetworkPill.test.tsx.snap index d1a0fc63e90..314dfc2bbf8 100644 --- a/packages/uniswap/src/components/network/__snapshots__/NetworkPill.test.tsx.snap +++ b/packages/uniswap/src/components/network/__snapshots__/NetworkPill.test.tsx.snap @@ -5,11 +5,11 @@ exports[`NetworkPill renders a NetworkPill with border 1`] = ` style={ { "alignItems": "center", - "backgroundColor": "#21C95E1a", + "backgroundColor": "#FFFFFF1a", "borderBottomLeftRadius": 999999, "borderBottomRightRadius": 999999, "borderBottomWidth": 1, - "borderColor": "#21C95E", + "borderColor": "#FFFFFF", "borderLeftWidth": 1, "borderRightWidth": 1, "borderStyle": "solid", @@ -31,7 +31,7 @@ exports[`NetworkPill renders a NetworkPill with border 1`] = ` maxFontSizeMultiplier={1.4} style={ { - "color": "#21C95E", + "color": "#FFFFFF", "fontFamily": "Basel Grotesk", "fontSize": 16, "fontWeight": "400", @@ -41,7 +41,7 @@ exports[`NetworkPill renders a NetworkPill with border 1`] = ` } suppressHighlighting={true} > - Görli + Ethereum `; @@ -51,7 +51,7 @@ exports[`NetworkPill renders a NetworkPill without image 1`] = ` style={ { "alignItems": "center", - "backgroundColor": "#21C95E1a", + "backgroundColor": "#FFFFFF1a", "borderBottomLeftRadius": 999999, "borderBottomRightRadius": 999999, "borderBottomWidth": 1, @@ -77,7 +77,7 @@ exports[`NetworkPill renders a NetworkPill without image 1`] = ` maxFontSizeMultiplier={1.4} style={ { - "color": "#21C95E", + "color": "#FFFFFF", "fontFamily": "Basel Grotesk", "fontSize": 16, "fontWeight": "400", @@ -87,7 +87,7 @@ exports[`NetworkPill renders a NetworkPill without image 1`] = ` } suppressHighlighting={true} > - Görli + Ethereum `; @@ -97,7 +97,7 @@ exports[`NetworkPill renders an InlineNetworkPill 1`] = ` style={ { "alignItems": "center", - "backgroundColor": "#21C95E1a", + "backgroundColor": "#FFFFFF1a", "borderBottomLeftRadius": 8, "borderBottomRightRadius": 8, "borderBottomWidth": 1, @@ -123,7 +123,7 @@ exports[`NetworkPill renders an InlineNetworkPill 1`] = ` maxFontSizeMultiplier={1.2} style={ { - "color": "#21C95E", + "color": "#FFFFFF", "fontFamily": "Basel Grotesk", "fontSize": 14, "fontWeight": "500", @@ -133,7 +133,7 @@ exports[`NetworkPill renders an InlineNetworkPill 1`] = ` } suppressHighlighting={true} > - Görli + Ethereum `; diff --git a/packages/uniswap/src/components/pill/NewTag.tsx b/packages/uniswap/src/components/pill/NewTag.tsx index 1f2cf28c3a4..22deeb05f2c 100644 --- a/packages/uniswap/src/components/pill/NewTag.tsx +++ b/packages/uniswap/src/components/pill/NewTag.tsx @@ -10,7 +10,8 @@ function _NewTag(): JSX.Element { shrink ml="$spacing6" px="$spacing4" - py="$spacing2" + pb="$spacing2" + pt={3} // hack to make box look visually more vertically centered with text backgroundColor="$accent2Hovered" borderRadius="$rounded6" alignItems="center" diff --git a/packages/uniswap/src/config.ts b/packages/uniswap/src/config.ts index 8949f270285..6cefe2baacf 100644 --- a/packages/uniswap/src/config.ts +++ b/packages/uniswap/src/config.ts @@ -9,6 +9,7 @@ import { ONESIGNAL_APP_ID, OPENAI_API_KEY, QUICKNODE_ARBITRUM_RPC_URL, + QUICKNODE_ASTROCHAIN_SEPOLIA_RPC_URL, QUICKNODE_AVAX_RPC_URL, QUICKNODE_BASE_RPC_URL, QUICKNODE_BLAST_RPC_URL, @@ -17,6 +18,8 @@ import { QUICKNODE_MAINNET_RPC_URL, QUICKNODE_OP_RPC_URL, QUICKNODE_POLYGON_RPC_URL, + QUICKNODE_SEPOLIA_RPC_URL, + QUICKNODE_WORLDCHAIN_RPC_URL, QUICKNODE_ZKSYNC_RPC_URL, QUICKNODE_ZORA_RPC_URL, SENTRY_DSN, @@ -63,7 +66,10 @@ export interface Config { quicknodePolygonRpcUrl: string quicknodeZoraRpcUrl: string quicknodeZkSyncRpcUrl: string + quicknodeWorldChainRpcUrl: string + quicknodeAstrochainSepoliaRpcUrl: string quicknodeMainnetRpcUrl: string + quicknodeSepoliaRpcUrl: string tradingApiKey: string firebaseAppCheckDebugToken: string } @@ -117,10 +123,22 @@ const _config: Config = { process.env.REACT_APP_QUICKNODE_ZORA_RPC_URL || process.env.QUICKNODE_ZORA_RPC_URL || QUICKNODE_ZORA_RPC_URL, quicknodeZkSyncRpcUrl: process.env.REACT_APP_QUICKNODE_ZKSYNC_RPC_URL || process.env.QUICKNODE_ZKSYNC_RPC_URL || QUICKNODE_ZKSYNC_RPC_URL, + quicknodeWorldChainRpcUrl: + process.env.REACT_APP_QUICKNODE_WORLDCHAIN_RPC_URL || + process.env.QUICKNODE_WORLDCHAIN_RPC_URL || + QUICKNODE_WORLDCHAIN_RPC_URL, + quicknodeAstrochainSepoliaRpcUrl: + process.env.REACT_APP_QUICKNODE_ASTROCHAIN_SEPOLIA_RPC_URL || + process.env.QUICKNODE_ASTROCHAIN_SEPOLIA_RPC_URL || + QUICKNODE_ASTROCHAIN_SEPOLIA_RPC_URL, quicknodeMainnetRpcUrl: process.env.REACT_APP_QUICKNODE_MAINNET_RPC_URL || process.env.QUICKNODE_MAINNET_RPC_URL || QUICKNODE_MAINNET_RPC_URL, + quicknodeSepoliaRpcUrl: + process.env.REACT_APP_QUICKNODE_SEPOLIA_RPC_URL || + process.env.QUICKNODE_SEPOLIA_RPC_URL || + QUICKNODE_SEPOLIA_RPC_URL, tradingApiKey: process.env.REACT_APP_TRADING_API_KEY || process.env.TRADING_API_KEY || TRADING_API_KEY, firebaseAppCheckDebugToken: process.env.FIREBASE_APP_CHECK_DEBUG_TOKEN || FIREBASE_APP_CHECK_DEBUG_TOKEN, } diff --git a/packages/uniswap/src/constants/chains.ts b/packages/uniswap/src/constants/chains.ts index 9c1ef44e41b..1ed904a1f91 100644 --- a/packages/uniswap/src/constants/chains.ts +++ b/packages/uniswap/src/constants/chains.ts @@ -2,22 +2,22 @@ import { CurrencyAmount, ChainId as UniswapSDKChainId } from '@uniswap/sdk-core' import { ARBITRUM_LOGO, + ASTROCHAIN_SEPOLIA_LOGO, AVALANCHE_LOGO, BASE_LOGO, BLAST_LOGO, BNB_LOGO, CELO_LOGO, ETHEREUM_LOGO, - MUMBAI_LOGO, OPTIMISM_LOGO, POLYGON_LOGO, + WORLD_CHAIN_LOGO, ZKSYNC_LOGO, ZORA_LOGO, } from 'ui/src/assets' import { config } from 'uniswap/src/config' import { CUSD_CELO, - CUSD_CELO_ALFAJORES, DAI, DAI_ARBITRUM_ONE, DAI_OPTIMISM, @@ -25,17 +25,15 @@ import { USDB_BLAST, USDC, USDC_ARBITRUM, - USDC_ARBITRUM_GOERLI, + USDC_ASTROCHAIN_SEPOLIA, USDC_AVALANCHE, USDC_BASE, USDC_BNB, USDC_CELO, - USDC_GOERLI, USDC_OPTIMISM, - USDC_OPTIMISM_GOERLI, USDC_POLYGON, - USDC_POLYGON_MUMBAI, USDC_SEPOLIA, + USDC_WORLD_CHAIN, USDC_ZKSYNC, USDC_ZORA, USDT, @@ -54,19 +52,14 @@ import { isInterface } from 'utilities/src/platform' import { ONE_MINUTE_MS } from 'utilities/src/time/time' import { arbitrum, - arbitrumGoerli, avalanche, base, blast, bsc, celo, - celoAlfajores, - goerli, mainnet, optimism, - optimismGoerli, polygon, - polygonMumbai, sepolia, zkSync, zora, @@ -186,6 +179,9 @@ export const UNIVERSE_CHAIN_INFO: Record = { networkLayer: NetworkLayer.L1, pendingTransactionsRetryOptions: undefined, rpcUrls: { + [RPCType.Public]: { + http: ['https://rpc.sepolia.org/'], + }, default: { http: ['https://rpc.sepolia.org/'], }, @@ -201,80 +197,17 @@ export const UNIVERSE_CHAIN_INFO: Record = { }, appOnly: { http: [`https://sepolia.infura.io/v3/${config.infuraKey}`] }, }, - spotPriceStablecoinAmount: CurrencyAmount.fromRawAmount(USDC_SEPOLIA, 10_000e6), + spotPriceStablecoinAmount: CurrencyAmount.fromRawAmount(USDC_SEPOLIA, 100e6), stablecoins: [USDC_SEPOLIA], statusPage: undefined, supportsClientSideRouting: true, supportsGasEstimates: false, - urlParam: 'sepolia', - wrappedNativeCurrency: { - name: 'Wrapped Ether', - symbol: 'WETH', - decimals: 18, - address: '0x7b79995e5f793a07bc00c21412e50ecae098e7f9', - }, - } as const satisfies UniverseChainInfo, - [UniverseChainId.Goerli]: { - ...goerli, - id: UniverseChainId.Goerli, - sdkId: UniswapSDKChainId.GOERLI, - assetRepoNetworkName: undefined, - backendChain: { - chain: BackendChainId.EthereumGoerli as InterfaceGqlChain, - backendSupported: true, - isSecondaryChain: false, - nativeTokenBackendAddress: undefined, - }, - blockPerMainnetEpochForChainId: 1, - blockWaitMsBeforeWarning: isInterface ? DEFAULT_MS_BEFORE_WARNING : 180000, // 3 minutes - bridge: undefined, - chainPriority: 0, - docs: 'https://docs.uniswap.org/', - elementName: ElementName.ChainEthereumGoerli, - explorer: { - name: 'Etherscan', - url: 'https://goerli.etherscan.io/', - apiURL: 'https://api-goerli.etherscan.io', - }, - helpCenterUrl: undefined, - infoLink: 'https://app.uniswap.org/explore', - infuraPrefix: 'goerli', - interfaceName: 'goerli', - label: 'Görli', - logo: ETHEREUM_LOGO, - nativeCurrency: { - name: 'Görli ETH', - symbol: 'görETH', - decimals: 18, - address: DEFAULT_NATIVE_ADDRESS, - explorerLink: 'https://etherscan.io/chart/etherprice', // goerli.etherscan.io doesn't work - logo: ETHEREUM_LOGO, - }, - networkLayer: NetworkLayer.L1, - pendingTransactionsRetryOptions: undefined, - spotPriceStablecoinAmount: CurrencyAmount.fromRawAmount(USDC_GOERLI, 10_000e6), - stablecoins: [USDC_GOERLI], - statusPage: undefined, - supportsClientSideRouting: true, - supportsGasEstimates: false, - urlParam: 'goerli', - rpcUrls: { - [RPCType.Public]: { http: ['https://rpc.goerli.mudit.blog'] }, - default: { - http: ['https://rpc.goerli.mudit.blog/'], - }, - fallback: { - http: ['https://rpc.ankr.com/eth_goerli'], - }, - appOnly: { - http: [`https://goerli.infura.io/v3/${config.infuraKey}`], - }, - }, + urlParam: 'ethereum_sepolia', wrappedNativeCurrency: { name: 'Wrapped Ether', symbol: 'WETH', decimals: 18, - address: '0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6', + address: '0xfff9976782d46cc05630d1f6ebab18b2324d6b14', }, } as const satisfies UniverseChainInfo, [UniverseChainId.ArbitrumOne]: { @@ -337,61 +270,6 @@ export const UNIVERSE_CHAIN_INFO: Record = { address: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', }, } as const satisfies UniverseChainInfo, - [UniverseChainId.ArbitrumGoerli]: { - ...arbitrumGoerli, - id: UniverseChainId.ArbitrumGoerli, - sdkId: UniswapSDKChainId.ARBITRUM_GOERLI, - assetRepoNetworkName: undefined, - backendChain: { - chain: BackendChainId.Arbitrum as InterfaceGqlChain, - isSecondaryChain: true, - backendSupported: true, - nativeTokenBackendAddress: undefined, - }, - blockPerMainnetEpochForChainId: 1, - blockWaitMsBeforeWarning: 600000, - bridge: 'https://bridge.arbitrum.io/', - chainPriority: 1, - docs: 'https://offchainlabs.com/', - elementName: ElementName.ChainArbitrumGoerli, - explorer: { - name: 'Arbiscan', - url: 'https://goerli.arbiscan.io/', - }, - helpCenterUrl: 'https://help.uniswap.org/en/collections/3137787-uniswap-on-arbitrum', - infoLink: 'https://app.uniswap.org/explore/tokens/arbitrum', - infuraPrefix: 'arbitrum-goerli', - interfaceName: 'arbitrum_goerli', - label: 'Arbitrum Goerli', - logo: ARBITRUM_LOGO, - nativeCurrency: { - name: 'Arbitrum Görli ETH', - symbol: 'görETH', - decimals: 18, - address: DEFAULT_NATIVE_ADDRESS, - explorerLink: 'https://goerli.arbiscan.io/chart/etherprice', - logo: ETHEREUM_LOGO, - }, - networkLayer: NetworkLayer.L2, - pendingTransactionsRetryOptions: DEFAULT_RETRY_OPTIONS, - rpcUrls: { - [RPCType.Public]: { http: ['https://goerli-rollup.arbitrum.io/rpc'] }, - default: { http: ['https://goerli-rollup.arbitrum.io/rpc'] }, - appOnly: { http: [`https://arbitrum-goerli.infura.io/v3/${config.infuraKey}`] }, - }, - spotPriceStablecoinAmount: CurrencyAmount.fromRawAmount(USDC_ARBITRUM_GOERLI, 10_000e6), - stablecoins: [USDC_ARBITRUM_GOERLI], - statusPage: undefined, - supportsClientSideRouting: true, - supportsGasEstimates: false, - urlParam: 'arbitrum_goerli', - wrappedNativeCurrency: { - name: 'Wrapped Ether', - symbol: 'WETH', - decimals: 18, - address: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', - }, - } as const satisfies UniverseChainInfo, [UniverseChainId.Optimism]: { ...optimism, id: UniverseChainId.Optimism, @@ -507,61 +385,6 @@ export const UNIVERSE_CHAIN_INFO: Record = { address: '0x4200000000000000000000000000000000000006', }, } as const satisfies UniverseChainInfo, - [UniverseChainId.OptimismGoerli]: { - ...optimismGoerli, - id: UniverseChainId.OptimismGoerli, - sdkId: UniswapSDKChainId.OPTIMISM_GOERLI, - assetRepoNetworkName: undefined, - backendChain: { - chain: BackendChainId.Optimism as InterfaceGqlChain, - isSecondaryChain: true, - backendSupported: true, - nativeTokenBackendAddress: undefined, - }, - blockPerMainnetEpochForChainId: 1, - blockWaitMsBeforeWarning: 1500000, - bridge: 'https://app.optimism.io/bridge', - chainPriority: 2, - docs: 'https://optimism.io/', - elementName: ElementName.ChainOptimismGoerli, - explorer: { - name: 'OP Etherscan', - url: 'https://goerli-optimism.etherscan.io/', - apiURL: 'https://api-goerli.etherscan.io', - }, - helpCenterUrl: 'https://help.uniswap.org/en/collections/3137778-uniswap-on-optimistic-ethereum-oξ', - infoLink: 'https://app.uniswap.org/explore/tokens/optimism', - infuraPrefix: 'optimism-goerli', - interfaceName: 'optimism_goerli', - label: 'Optimism Görli', - logo: OPTIMISM_LOGO, - nativeCurrency: { - name: 'Optimistim Görli ETH', - symbol: 'görETH', - decimals: 18, - address: DEFAULT_NATIVE_ADDRESS, - explorerLink: 'https://goerli-optimism.etherscan.io/chart/etherprice', - logo: ETHEREUM_LOGO, - }, - networkLayer: NetworkLayer.L2, - pendingTransactionsRetryOptions: DEFAULT_RETRY_OPTIONS, - rpcUrls: { - default: { http: ['https://goerli.optimism.io'] }, - appOnly: { http: [`https://optimism-goerli.infura.io/v3/${config.infuraKey}`] }, - }, - spotPriceStablecoinAmount: CurrencyAmount.fromRawAmount(USDC_OPTIMISM_GOERLI, 10_000e6), - stablecoins: [USDC_OPTIMISM_GOERLI], - statusPage: 'https://optimism.io/status', - supportsClientSideRouting: true, - supportsGasEstimates: false, - urlParam: 'optimism_goerli', - wrappedNativeCurrency: { - name: 'Wrapped Ether', - symbol: 'WETH', - decimals: 18, - address: '0x4200000000000000000000000000000000000006', - }, - } as const satisfies UniverseChainInfo, [UniverseChainId.Bnb]: { ...bsc, sdkId: UniswapSDKChainId.BNB, @@ -675,61 +498,6 @@ export const UNIVERSE_CHAIN_INFO: Record = { address: '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270', }, } as const satisfies UniverseChainInfo, - [UniverseChainId.PolygonMumbai]: { - ...polygonMumbai, - id: UniverseChainId.PolygonMumbai, - sdkId: UniswapSDKChainId.POLYGON_MUMBAI, - assetRepoNetworkName: undefined, - backendChain: { - chain: BackendChainId.Polygon as InterfaceGqlChain, - isSecondaryChain: true, - backendSupported: true, - nativeTokenBackendAddress: '0x0000000000000000000000000000000000001010', - }, - blockPerMainnetEpochForChainId: 1, - blockWaitMsBeforeWarning: 600000, - bridge: 'https://portal.polygon.technology/bridge', - chainPriority: 3, - docs: 'https://polygon.io/', - elementName: ElementName.ChainPolygonMumbai, - explorer: { - name: 'PolygonScan', - url: 'https://mumbai.polygonscan.com/', - apiURL: 'https://api-testnet.polygonscan.com', - }, - helpCenterUrl: undefined, - infoLink: 'https://app.uniswap.org/explore/tokens/polygon', - infuraPrefix: 'polygon-mumbai', - interfaceName: 'polygon_mumbai', - label: 'Polygon Mumbai', - logo: MUMBAI_LOGO, - nativeCurrency: { - name: 'Polygon Mumbai POL', - symbol: 'mPOL', - decimals: 18, - address: '0x0000000000000000000000000000000000001010', - logo: MUMBAI_LOGO, - }, - networkLayer: NetworkLayer.L1, - pendingTransactionsRetryOptions: undefined, - rpcUrls: { - [RPCType.PublicAlt]: { http: ['https://rpc-endpoints.superfluid.dev/mumbai'] }, - default: { http: ['https://rpc-mumbai.maticvigil.com'] }, - appOnly: { http: [`https://polygon-mumbai.infura.io/v3/${config.infuraKey}`] }, - }, - spotPriceStablecoinAmount: CurrencyAmount.fromRawAmount(USDC_POLYGON_MUMBAI, 10_000e6), - stablecoins: [USDC_POLYGON_MUMBAI], - statusPage: undefined, - supportsClientSideRouting: true, - supportsGasEstimates: false, - urlParam: 'polygon_mumbai', - wrappedNativeCurrency: { - name: 'Wrapped POL', - symbol: 'WPOL', - decimals: 18, - address: '0x9c3c9283d3e44854697cd22d3faa240cfb032889', - }, - } as const satisfies UniverseChainInfo, [UniverseChainId.Blast]: { ...blast, id: UniverseChainId.Blast, @@ -897,59 +665,63 @@ export const UNIVERSE_CHAIN_INFO: Record = { address: '0x471EcE3750Da237f93B8E339c536989b8978a438', }, } as const satisfies UniverseChainInfo, - [UniverseChainId.CeloAlfajores]: { - ...celoAlfajores, - id: UniverseChainId.CeloAlfajores, - sdkId: UniswapSDKChainId.CELO_ALFAJORES, - assetRepoNetworkName: undefined, + [UniverseChainId.WorldChain]: { + // ...worldChain, + name: 'World Chain', + id: UniverseChainId.WorldChain, + sdkId: UniswapSDKChainId.WORLDCHAIN, + assetRepoNetworkName: 'worldcoin', backendChain: { - chain: BackendChainId.Celo as InterfaceGqlChain, - isSecondaryChain: true, + chain: BackendChainId.Worldchain as InterfaceGqlChain, backendSupported: true, - nativeTokenBackendAddress: '0xF194afDf50B03e69Bd7D057c1Aa9e10c9954E4C9', + isSecondaryChain: false, + nativeTokenBackendAddress: undefined, }, - blockPerMainnetEpochForChainId: 1, - blockWaitMsBeforeWarning: 600000, - bridge: 'https://www.portalbridge.com/#/transfer', - chainPriority: 7, - docs: 'https://docs.celo.org/', - elementName: ElementName.ChainCeloAlfajores, + blockPerMainnetEpochForChainId: 1, // TODO: verify + blockWaitMsBeforeWarning: undefined, + bridge: 'https://superbridge.app/world-chain', + chainPriority: 11, + docs: 'https://docs.worldcoin.org/', + elementName: ElementName.ChainWorldChain, explorer: { - name: 'Celo Explorer', - url: 'https://explorer.celo.org/alfajores/', - apiURL: 'https://api-alfajores.celoscan.io', + name: 'World Chain Explorer', + url: 'https://worldchain-mainnet.explorer.alchemy.com/', }, helpCenterUrl: undefined, - infoLink: 'https://app.uniswap.org/explore/tokens/celo', - infuraPrefix: 'celo-alfajores', - interfaceName: 'celo_alfajores', - label: 'Celo Alfajores', - logo: CELO_LOGO, + infoLink: 'https://app.uniswap.org/explore/tokens/ethereum/0x163f8c2467924be0ae7b5347228cabf260318753', + infuraPrefix: undefined, + interfaceName: 'worldchain', + label: 'World Chain', + logo: WORLD_CHAIN_LOGO, nativeCurrency: { - name: 'Celo', - symbol: 'CELO', + name: 'World Chain ETH', + symbol: 'ETH', decimals: 18, - address: '0xF194afDf50B03e69Bd7D057c1Aa9e10c9954E4C9', - logo: CELO_LOGO, + address: DEFAULT_NATIVE_ADDRESS, + logo: ETHEREUM_LOGO, }, - networkLayer: NetworkLayer.L1, + networkLayer: NetworkLayer.L2, pendingTransactionsRetryOptions: undefined, rpcUrls: { - [RPCType.Public]: { http: ['https://alfajores-forno.celo-testnet.org'] }, - default: { http: [`https://alfajores-forno.celo-testnet.org`] }, - appOnly: { http: [`https://celo-alfajores.infura.io/v3/${config.infuraKey}`] }, + [RPCType.Public]: { + http: [config.quicknodeWorldChainRpcUrl], + }, + default: { http: ['https://worldchain-mainnet.g.alchemy.com/public'] }, + appOnly: { + http: [config.quicknodeWorldChainRpcUrl], + }, }, - spotPriceStablecoinAmount: CurrencyAmount.fromRawAmount(CUSD_CELO_ALFAJORES, 10_000e6), - stablecoins: [USDC_CELO], + urlParam: 'worldchain', statusPage: undefined, - supportsClientSideRouting: true, + spotPriceStablecoinAmount: CurrencyAmount.fromRawAmount(USDC_WORLD_CHAIN, 10_000e6), + stablecoins: [USDC_WORLD_CHAIN], + supportsClientSideRouting: false, supportsGasEstimates: false, - urlParam: 'celo_alfajores', wrappedNativeCurrency: { - name: 'Wrapped Ethereum', - symbol: 'cETH', + name: 'Wrapped Ether', + symbol: 'WETH', decimals: 18, - address: '0x2DEf4285787d58a2f811AF24755A8150622f4361', + address: '0x4200000000000000000000000000000000000006', }, } as const satisfies UniverseChainInfo, [UniverseChainId.Zora]: { @@ -1061,6 +833,68 @@ export const UNIVERSE_CHAIN_INFO: Record = { address: '0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91', }, } as const satisfies UniverseChainInfo, + [UniverseChainId.AstrochainSepolia]: { + // ...astrochainSepolia, + name: 'Unichain Sepolia', + testnet: true, + id: UniverseChainId.AstrochainSepolia, + sdkId: UniswapSDKChainId.ASTROCHAIN_SEPOLIA, + assetRepoNetworkName: undefined, + backendChain: { + chain: BackendChainId.AstrochainSepolia as InterfaceGqlChain, + backendSupported: true, + isSecondaryChain: false, + nativeTokenBackendAddress: undefined, + }, + blockPerMainnetEpochForChainId: 1, + blockWaitMsBeforeWarning: undefined, + bridge: undefined, + chainPriority: 0, + docs: 'https://docs.uniswap.org/', // need docs + elementName: ElementName.ChainSepolia, + explorer: { + name: 'Unichain Sepolia Explorer', + url: 'https://unichain-sepolia.blockscout.com/', + }, + helpCenterUrl: undefined, + infoLink: 'https://app.uniswap.org/explore', // need + infuraPrefix: 'astrochain-sepolia', + interfaceName: 'astrochain', + label: 'Unichain Sepolia', + logo: ASTROCHAIN_SEPOLIA_LOGO, + nativeCurrency: { + name: 'Unichain Sepolia ETH', + symbol: 'ETH', + decimals: 18, + address: DEFAULT_NATIVE_ADDRESS, + logo: ETHEREUM_LOGO, + }, + networkLayer: NetworkLayer.L2, + pendingTransactionsRetryOptions: undefined, + rpcUrls: { + [RPCType.Public]: { + http: [config.quicknodeAstrochainSepoliaRpcUrl], + }, + default: { + http: [config.quicknodeAstrochainSepoliaRpcUrl], + }, + appOnly: { + http: [config.quicknodeAstrochainSepoliaRpcUrl], + }, + }, + spotPriceStablecoinAmount: CurrencyAmount.fromRawAmount(USDC_ASTROCHAIN_SEPOLIA, 10_000e6), + stablecoins: [USDC_ASTROCHAIN_SEPOLIA], + statusPage: undefined, + supportsClientSideRouting: true, + supportsGasEstimates: false, + urlParam: 'astrochain_sepolia', + wrappedNativeCurrency: { + name: 'Wrapped Ether', + symbol: 'WETH', + decimals: 18, + address: '0x4200000000000000000000000000000000000006', + }, + } as const satisfies UniverseChainInfo, } export const GQL_MAINNET_CHAINS = Object.values(UNIVERSE_CHAIN_INFO) @@ -1068,7 +902,11 @@ export const GQL_MAINNET_CHAINS = Object.values(UNIVERSE_CHAIN_INFO) .map((chain) => chain.backendChain.chain) .filter((backendChain) => !!backendChain) +export const GQL_TESTNET_CHAINS = Object.values(UNIVERSE_CHAIN_INFO) + .filter((chain) => chain.testnet && !chain.backendChain.isSecondaryChain) + .map((chain) => chain.backendChain.chain) + .filter((backendChain) => !!backendChain) + /** Used for making graphql queries to all chains supported by the graphql backend. Must be mutable for some apollo typechecking. */ export const GQL_MAINNET_CHAINS_MUTABLE = GQL_MAINNET_CHAINS.map((c) => c) - -export const ALL_CHAIN_IDS: UniverseChainId[] = Object.values(UNIVERSE_CHAIN_INFO).map((chain) => chain.id) +export const GQL_TESTNET_CHAINS_MUTABLE = GQL_TESTNET_CHAINS.map((c) => c) diff --git a/packages/uniswap/src/constants/routing.ts b/packages/uniswap/src/constants/routing.ts index de6276e7b7a..6aad4294bfb 100644 --- a/packages/uniswap/src/constants/routing.ts +++ b/packages/uniswap/src/constants/routing.ts @@ -8,9 +8,7 @@ import { BTC_BSC, BUSD_BSC, CEUR_CELO, - CEUR_CELO_ALFAJORES, CUSD_CELO, - CUSD_CELO_ALFAJORES, DAI, DAI_ARBITRUM_ONE, DAI_AVALANCHE, @@ -20,20 +18,18 @@ import { ETH_BSC, OP, PORTAL_ETH_CELO, + UNI, USDC_ARBITRUM, - USDC_ARBITRUM_GOERLI, + USDC_ASTROCHAIN_SEPOLIA, USDC_AVALANCHE, USDC_BASE, USDC_BSC, USDC_CELO, - USDC_CELO_ALFAJORES, - USDC_GOERLI, USDC_MAINNET, USDC_OPTIMISM, - USDC_OPTIMISM_GOERLI, USDC_POLYGON, - USDC_POLYGON_MUMBAI, USDC_SEPOLIA, + USDC_WORLD_CHAIN, USDC_ZKSYNC, USDC_ZORA, USDT, @@ -49,7 +45,6 @@ import { WBTC_POLYGON, WETH_AVALANCHE, WETH_POLYGON, - WETH_POLYGON_MUMBAI, WRAPPED_NATIVE_CURRENCY, isCelo, nativeOnChain, @@ -75,16 +70,14 @@ export const COMMON_BASES: ChainCurrencyList = { WBTC, WRAPPED_NATIVE_CURRENCY[UniverseChainId.Mainnet] as Token, ].map(buildCurrencyInfo), - [UniverseChainId.Goerli]: [ - nativeOnChain(UniverseChainId.Goerli), - WRAPPED_NATIVE_CURRENCY[UniverseChainId.Goerli] as Token, - USDC_GOERLI, - ].map(buildCurrencyInfo), + [UniverseChainId.Sepolia]: [ nativeOnChain(UniverseChainId.Sepolia), WRAPPED_NATIVE_CURRENCY[UniverseChainId.Sepolia] as Token, USDC_SEPOLIA, + UNI[UniverseChainId.Sepolia], ].map(buildCurrencyInfo), + [UniverseChainId.ArbitrumOne]: [ nativeOnChain(UniverseChainId.ArbitrumOne), ARB, @@ -94,11 +87,6 @@ export const COMMON_BASES: ChainCurrencyList = { WBTC_ARBITRUM_ONE, WRAPPED_NATIVE_CURRENCY[UniverseChainId.ArbitrumOne] as Token, ].map(buildCurrencyInfo), - [UniverseChainId.ArbitrumGoerli]: [ - nativeOnChain(UniverseChainId.ArbitrumGoerli), - WRAPPED_NATIVE_CURRENCY[UniverseChainId.ArbitrumGoerli] as Token, - USDC_ARBITRUM_GOERLI, - ].map(buildCurrencyInfo), [UniverseChainId.Optimism]: [ nativeOnChain(UniverseChainId.Optimism), @@ -109,15 +97,13 @@ export const COMMON_BASES: ChainCurrencyList = { WBTC_OPTIMISM, WETH9[UniverseChainId.Optimism] as Token, ].map(buildCurrencyInfo), - [UniverseChainId.OptimismGoerli]: [nativeOnChain(UniverseChainId.OptimismGoerli), USDC_OPTIMISM_GOERLI].map( - buildCurrencyInfo, - ), [UniverseChainId.Base]: [ nativeOnChain(UniverseChainId.Base), WRAPPED_NATIVE_CURRENCY[UniverseChainId.Base] as Token, USDC_BASE, ].map(buildCurrencyInfo), + [UniverseChainId.Blast]: [ nativeOnChain(UniverseChainId.Blast), WRAPPED_NATIVE_CURRENCY[UniverseChainId.Blast] as Token, @@ -131,12 +117,6 @@ export const COMMON_BASES: ChainCurrencyList = { USDT_POLYGON, WBTC_POLYGON, ].map(buildCurrencyInfo), - [UniverseChainId.PolygonMumbai]: [ - nativeOnChain(UniverseChainId.PolygonMumbai), - WRAPPED_NATIVE_CURRENCY[UniverseChainId.PolygonMumbai] as Token, - USDC_POLYGON_MUMBAI, - WETH_POLYGON_MUMBAI, - ].map(buildCurrencyInfo), [UniverseChainId.Celo]: [ nativeOnChain(UniverseChainId.Celo), @@ -147,13 +127,6 @@ export const COMMON_BASES: ChainCurrencyList = { WBTC_CELO, ].map(buildCurrencyInfo), - [UniverseChainId.CeloAlfajores]: [ - nativeOnChain(UniverseChainId.CeloAlfajores), - CUSD_CELO_ALFAJORES, - CEUR_CELO_ALFAJORES, - USDC_CELO_ALFAJORES, - ].map(buildCurrencyInfo), - [UniverseChainId.Bnb]: [ nativeOnChain(UniverseChainId.Bnb), DAI_BSC, @@ -172,6 +145,12 @@ export const COMMON_BASES: ChainCurrencyList = { WETH_AVALANCHE, ].map(buildCurrencyInfo), + [UniverseChainId.WorldChain]: [ + nativeOnChain(UniverseChainId.WorldChain), + WRAPPED_NATIVE_CURRENCY[UniverseChainId.WorldChain] as Token, + USDC_WORLD_CHAIN, + ].map(buildCurrencyInfo), + [UniverseChainId.Zora]: [ nativeOnChain(UniverseChainId.Zora), WRAPPED_NATIVE_CURRENCY[UniverseChainId.Zora] as Token, @@ -183,6 +162,12 @@ export const COMMON_BASES: ChainCurrencyList = { WRAPPED_NATIVE_CURRENCY[UniverseChainId.Zksync] as Token, USDC_ZKSYNC, ].map(buildCurrencyInfo), + + [UniverseChainId.AstrochainSepolia]: [ + nativeOnChain(UniverseChainId.AstrochainSepolia), + WRAPPED_NATIVE_CURRENCY[UniverseChainId.AstrochainSepolia] as Token, + USDC_ASTROCHAIN_SEPOLIA, + ].map(buildCurrencyInfo), } function getNativeLogoURI(chainId: UniverseChainId = UniverseChainId.Mainnet): ImageSourcePropType { diff --git a/packages/uniswap/src/constants/tokens.ts b/packages/uniswap/src/constants/tokens.ts index 59abbf8fb41..f917684d58e 100644 --- a/packages/uniswap/src/constants/tokens.ts +++ b/packages/uniswap/src/constants/tokens.ts @@ -3,17 +3,17 @@ import { Currency, NativeCurrency, Token, UNI_ADDRESSES, WETH9 } from '@uniswap/ import invariant from 'tiny-invariant' import { UniverseChainId } from 'uniswap/src/types/chains' -export const USDC_GOERLI = new Token( - UniverseChainId.Goerli, - '0x07865c6e87b9f70255377e024ace6630c1eaa37f', +export const USDC_SEPOLIA = new Token( + UniverseChainId.Sepolia, + '0x6f14C02Fc1F78322cFd7d707aB90f18baD3B54f5', 6, 'USDC', 'USD//C', ) -export const USDC_SEPOLIA = new Token( - UniverseChainId.Sepolia, - '0x6f14C02Fc1F78322cFd7d707aB90f18baD3B54f5', +export const USDC_ASTROCHAIN_SEPOLIA = new Token( + UniverseChainId.AstrochainSepolia, + '0x31d0220469e10c4E71834a79b1f276d740d3768F', 6, 'USDC', 'USD//C', @@ -43,6 +43,14 @@ export const USDC_MAINNET = new Token( 'USD//C', ) +export const USDC = new Token( + UniverseChainId.Mainnet, + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + 6, + 'USDC', + 'USD//C', +) + export const USDC_OPTIMISM = new Token( UniverseChainId.Optimism, '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', @@ -75,14 +83,6 @@ export const WBTC_OPTIMISM = new Token( 'Wrapped BTC', ) -export const USDC_OPTIMISM_GOERLI = new Token( - UniverseChainId.OptimismGoerli, - '0xe05606174bac4A6364B31bd0eCA4bf4dD368f8C6', - 6, - 'USDC', - 'USD//C', -) - export const USDC_BASE = new Token( UniverseChainId.Base, '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', @@ -142,6 +142,7 @@ export const USDT_POLYGON = new Token( 'USDT', 'Tether USD', ) + export const WBTC_POLYGON = new Token( UniverseChainId.Polygon, '0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6', @@ -150,14 +151,6 @@ export const WBTC_POLYGON = new Token( 'Wrapped BTC', ) -export const USDC_POLYGON_MUMBAI = new Token( - UniverseChainId.PolygonMumbai, - '0x0fa8781a83e46826621b3bc094ea2a0212e71b23', - 6, - 'USDC', - 'USD Coin', -) - export const WETH_POLYGON = new Token( UniverseChainId.Polygon, '0x7ceb23fd6bc0add59e62ac25578270cff1b9f619', @@ -166,14 +159,6 @@ export const WETH_POLYGON = new Token( 'Wrapped Ether', ) -export const WETH_POLYGON_MUMBAI = new Token( - UniverseChainId.PolygonMumbai, - '0xa6fa4fb5f76172d178d61b04b0ecd319c5d1c0aa', - 18, - 'WETH', - 'Wrapped Ether', -) - export const USDB_BLAST = new Token( UniverseChainId.Blast, '0x4300000000000000000000000000000000000003', @@ -222,14 +207,6 @@ export const DAI_ARBITRUM_ONE = new Token( 'Dai stable coin', ) -export const USDC_ARBITRUM_GOERLI = new Token( - UniverseChainId.ArbitrumGoerli, - '0x8FB1E3fC51F3b789dED7557E680551d93Ea9d892', - 6, - 'USDC', - 'USD//C', -) - export const USDC_AVALANCHE = new Token( UniverseChainId.Avalanche, '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E', @@ -308,38 +285,6 @@ export const CUSD_CELO = new Token( 'Celo Dollar', ) -export const CELO_CELO_ALFAJORES = new Token( - UniverseChainId.CeloAlfajores, - '0xF194afDf50B03e69Bd7D057c1Aa9e10c9954E4C9', - 18, - 'CELO', - 'Celo', -) - -export const CUSD_CELO_ALFAJORES = new Token( - UniverseChainId.CeloAlfajores, - '0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1', - 18, - 'CUSD', - 'Celo Dollar', -) - -export const CEUR_CELO_ALFAJORES = new Token( - UniverseChainId.CeloAlfajores, - '0x10c892A6EC43a53E45D0B916B4b7D383B1b78C0F', - 18, - 'CEUR', - 'Celo Euro Stablecoin', -) - -export const USDC_CELO_ALFAJORES = new Token( - UniverseChainId.CeloAlfajores, - '0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B', - 6, - 'USDC', - 'USDC CELO Testnet', -) - export const USDC_ZORA = new Token( UniverseChainId.Zora, '0xCccCCccc7021b32EBb4e8C08314bD62F7c653EC4', @@ -348,12 +293,12 @@ export const USDC_ZORA = new Token( 'USD Coin', ) -export const USDC = new Token( - UniverseChainId.Mainnet, - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', +export const USDC_WORLD_CHAIN = new Token( + UniverseChainId.WorldChain, + '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1', 6, - 'USDC', - 'USD//C', + 'USDC.e', + 'Bridged USDC', ) export const USDC_ZKSYNC = new Token( @@ -395,13 +340,6 @@ export const UNI = { 'UNI', 'Uniswap', ), - [UniverseChainId.Goerli]: new Token( - UniverseChainId.Goerli, - UNI_ADDRESSES[UniverseChainId.Goerli] as string, - 18, - 'UNI', - 'Uniswap', - ), [UniverseChainId.Sepolia]: new Token( UniverseChainId.Sepolia, UNI_ADDRESSES[UniverseChainId.Sepolia] as string, @@ -450,13 +388,6 @@ export const WRAPPED_NATIVE_CURRENCY: { [chainId: number]: Token } = { 'WETH', 'Wrapped Ether', ), - [UniverseChainId.OptimismGoerli]: new Token( - UniverseChainId.OptimismGoerli, - '0x4200000000000000000000000000000000000006', - 18, - 'WETH', - 'Wrapped Ether', - ), [UniverseChainId.Base]: new Token( UniverseChainId.Base, '0x4200000000000000000000000000000000000006', @@ -471,13 +402,6 @@ export const WRAPPED_NATIVE_CURRENCY: { [chainId: number]: Token } = { 'WETH', 'Wrapped Ether', ), - [UniverseChainId.ArbitrumGoerli]: new Token( - UniverseChainId.ArbitrumGoerli, - '0xe39Ab88f8A4777030A534146A9Ca3B52bd5D43A3', - 18, - 'WETH', - 'Wrapped Ether', - ), [UniverseChainId.Sepolia]: new Token( UniverseChainId.Sepolia, '0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14', @@ -492,13 +416,6 @@ export const WRAPPED_NATIVE_CURRENCY: { [chainId: number]: Token } = { 'WMATIC', 'Wrapped MATIC', ), - [UniverseChainId.PolygonMumbai]: new Token( - UniverseChainId.PolygonMumbai, - '0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889', - 18, - 'WMATIC', - 'Wrapped MATIC', - ), [UniverseChainId.Celo]: new Token( UniverseChainId.Celo, '0x471ece3750da237f93b8e339c536989b8978a438', @@ -506,13 +423,6 @@ export const WRAPPED_NATIVE_CURRENCY: { [chainId: number]: Token } = { 'CELO', 'Celo native asset', ), - [UniverseChainId.CeloAlfajores]: new Token( - UniverseChainId.CeloAlfajores, - '0xf194afdf50b03e69bd7d057c1aa9e10c9954e4c9', - 18, - 'CELO', - 'Celo native asset', - ), [UniverseChainId.Bnb]: new Token( UniverseChainId.Bnb, '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', @@ -534,6 +444,13 @@ export const WRAPPED_NATIVE_CURRENCY: { [chainId: number]: Token } = { 'WETH', 'Wrapped Ether', ), + [UniverseChainId.WorldChain]: new Token( + UniverseChainId.WorldChain, + '0x4200000000000000000000000000000000000006', + 18, + 'WETH', + 'Wrapped Ether', + ), [UniverseChainId.Zora]: new Token( UniverseChainId.Zora, '0x4200000000000000000000000000000000000006', @@ -548,18 +465,23 @@ export const WRAPPED_NATIVE_CURRENCY: { [chainId: number]: Token } = { 'WETH', 'Wrapped Ether', ), + [UniverseChainId.AstrochainSepolia]: new Token( + UniverseChainId.AstrochainSepolia, + '0x4200000000000000000000000000000000000006', + 18, + 'WETH', + 'Wrapped Ether', + ), } -export function isCelo(chainId: number): chainId is UniverseChainId.Celo | UniverseChainId.CeloAlfajores { - return chainId === UniverseChainId.CeloAlfajores || chainId === UniverseChainId.Celo +export function isCelo(chainId: number): chainId is UniverseChainId.Celo { + return chainId === UniverseChainId.Celo } // Celo has a precompile for its native asset that is fully-compliant with ERC20 interface // so we can treat it as an ERC20 token. (i.e. $CELO pools are created with its ERC20 precompile) function getCeloNativeCurrency(chainId: number): Token { switch (chainId) { - case UniverseChainId.CeloAlfajores: - return CELO_CELO_ALFAJORES case UniverseChainId.Celo: return CELO_CELO default: @@ -567,8 +489,8 @@ function getCeloNativeCurrency(chainId: number): Token { } } -export function isPolygon(chainId: number): chainId is UniverseChainId.Polygon | UniverseChainId.PolygonMumbai { - return chainId === UniverseChainId.PolygonMumbai || chainId === UniverseChainId.Polygon +export function isPolygon(chainId: number): chainId is UniverseChainId.Polygon { + return chainId === UniverseChainId.Polygon } // Polygon also has a precompile, but its precompile is not fully erc20-compatible. diff --git a/packages/uniswap/src/constants/urls.ts b/packages/uniswap/src/constants/urls.ts index e5c66b149af..2800d6654ef 100644 --- a/packages/uniswap/src/constants/urls.ts +++ b/packages/uniswap/src/constants/urls.ts @@ -38,6 +38,7 @@ export const uniswapUrls = { limitsFailure: `${helpUrl}/articles/24300813697933-Why-did-my-limit-order-fail-or-not-execute`, limitsInfo: `${helpUrl}/sections/24372644881293`, limitsNetworkSupport: `${helpUrl}/articles/24470251716237-What-networks-do-limits-support`, + lpCollectFees: `${helpUrl}/articles/20901267003789-How-to-collect-fees-from-a-liquidity-pool-on-Uniswap-v3`, fiatOnRampHelp: `${helpUrl}/articles/11306574799117`, transferCryptoHelp: `${helpUrl}/articles/27103878635661-How-to-transfer-crypto-from-a-Robinhood-or-Coinbase-account-to-the-Uniswap-Wallet`, moonpayRegionalAvailability: `${helpUrl}/articles/11306664890381-Why-isn-t-MoonPay-available-in-my-region-`, diff --git a/packages/uniswap/src/constants/web3.ts b/packages/uniswap/src/constants/web3.ts new file mode 100644 index 00000000000..f8d882e4b44 --- /dev/null +++ b/packages/uniswap/src/constants/web3.ts @@ -0,0 +1,11 @@ +export const CONNECTION_PROVIDER_IDS = { + WALLET_CONNECT_CONNECTOR_ID: 'walletConnect', + UNISWAP_WALLET_CONNECT_CONNECTOR_ID: 'uniswapWalletConnect', + INJECTED_CONNECTOR_ID: 'injected', + INJECTED_CONNECTOR_TYPE: 'injected', + COINBASE_SDK_CONNECTOR_ID: 'coinbaseWalletSDK', + COINBASE_RDNS: 'com.coinbase.wallet', + METAMASK_RDNS: 'io.metamask', + UNISWAP_EXTENSION_RDNS: 'org.uniswap.app', + SAFE_CONNECTOR_ID: 'safe', +} as const diff --git a/packages/uniswap/src/contexts/UniswapContext.tsx b/packages/uniswap/src/contexts/UniswapContext.tsx index 3166f92049e..c737530789e 100644 --- a/packages/uniswap/src/contexts/UniswapContext.tsx +++ b/packages/uniswap/src/contexts/UniswapContext.tsx @@ -4,10 +4,12 @@ import { createContext, PropsWithChildren, useContext, useMemo, useState } from import { AccountMeta } from 'uniswap/src/features/accounts/types' import { FiatOnRampCurrency } from 'uniswap/src/features/fiatOnRamp/types' import { UniverseChainId } from 'uniswap/src/types/chains' +import { Connector } from 'wagmi' /** Stores objects/utils that exist on all platforms, abstracting away app-level specifics for each, in order to allow usage in cross-platform code. */ interface UniswapContext { account?: AccountMeta + connector?: Connector navigateToBuyOrReceiveWithEmptyWallet?: () => void navigateToFiatOnRamp: (args: { prefilledCurrency?: FiatOnRampCurrency }) => void onSwapChainsChanged: (inputChainId: UniverseChainId, outputChainId?: UniverseChainId) => void @@ -26,6 +28,7 @@ export const UniswapContext = createContext(null) export function UniswapProvider({ children, account, + connector, navigateToBuyOrReceiveWithEmptyWallet, navigateToFiatOnRamp, onSwapChainsChanged, @@ -39,6 +42,7 @@ export function UniswapProvider({ const value: UniswapContext = useMemo( () => ({ account, + connector, navigateToBuyOrReceiveWithEmptyWallet, onSwapChainsChanged: (inputChainId: UniverseChainId, outputChanId?: UniverseChainId): void => { onSwapChainsChanged(inputChainId, outputChanId) @@ -54,6 +58,7 @@ export function UniswapProvider({ }), [ account, + connector, navigateToBuyOrReceiveWithEmptyWallet, signer, useProviderHook, @@ -84,6 +89,11 @@ export function useAccountMeta(): AccountMeta | undefined { return useUniswapContext().account } +/** Cross-platform util for getting connector for the active account/wallet, only applicable to web, other platforms are undefined. */ +export function useConnector(): Connector | undefined { + return useUniswapContext().connector +} + /** Cross-platform util for getting an RPC provider for the given `chainId`, regardless of platform/environment. */ export function useProvider(chainId: number): JsonRpcProvider | undefined { return useUniswapContext().useProviderHook(chainId) diff --git a/packages/uniswap/src/data/apiClients/tradingApi/TradingApiClient.ts b/packages/uniswap/src/data/apiClients/tradingApi/TradingApiClient.ts index 54d2a39b967..1cb10234b8c 100644 --- a/packages/uniswap/src/data/apiClients/tradingApi/TradingApiClient.ts +++ b/packages/uniswap/src/data/apiClients/tradingApi/TradingApiClient.ts @@ -9,6 +9,8 @@ import { ChainId, CheckApprovalLPRequest, CheckApprovalLPResponse, + ClaimLPFeesRequest, + ClaimLPFeesResponse, ClassicQuote, CreateLPPositionRequest, CreateLPPositionResponse, @@ -31,6 +33,7 @@ import { QuoteResponse, Routing, TransactionHash, + UniversalRouterVersion, } from 'uniswap/src/data/tradingApi/__generated__' // TradingAPI team is looking into updating type generation to produce the following types for it's current QuoteResponse type: @@ -70,9 +73,17 @@ const TradingApiClient = createApiClient({ }, }) -export async function fetchQuote(params: QuoteRequest): Promise { +export type WithV4Flag = T & { v4Enabled: boolean } + +export async function fetchQuote({ + v4Enabled, + ...params +}: WithV4Flag): Promise { return await TradingApiClient.post(uniswapUrls.tradingApiPaths.quote, { body: JSON.stringify(params), + headers: { + 'x-universal-router-version': v4Enabled ? UniversalRouterVersion._2_0 : UniversalRouterVersion._1_2, + }, }) } @@ -82,9 +93,12 @@ export async function fetchIndicativeQuote(params: IndicativeQuoteRequest): Prom }) } -export async function fetchSwap(params: CreateSwapRequest): Promise { +export async function fetchSwap({ v4Enabled, ...params }: WithV4Flag): Promise { return await TradingApiClient.post(uniswapUrls.tradingApiPaths.swap, { body: JSON.stringify(params), + headers: { + 'x-universal-router-version': v4Enabled ? '2.0' : '1.2', + }, }) } @@ -126,7 +140,6 @@ export async function createLpPosition(params: CreateLPPositionRequest): Promise }), }) } - export async function decreaseLpPosition(params: DecreaseLPPositionRequest): Promise { return await TradingApiClient.post(uniswapUrls.tradingApiPaths.decreaseLp, { body: JSON.stringify({ @@ -134,7 +147,6 @@ export async function decreaseLpPosition(params: DecreaseLPPositionRequest): Pro }), }) } - export async function increaseLpPosition(params: IncreaseLPPositionRequest): Promise { return await TradingApiClient.post(uniswapUrls.tradingApiPaths.increaseLp, { body: JSON.stringify({ @@ -142,7 +154,6 @@ export async function increaseLpPosition(params: IncreaseLPPositionRequest): Pro }), }) } - export async function checkLpApproval(params: CheckApprovalLPRequest): Promise { return await TradingApiClient.post(uniswapUrls.tradingApiPaths.lpApproval, { body: JSON.stringify({ @@ -151,6 +162,14 @@ export async function checkLpApproval(params: CheckApprovalLPRequest): Promise { + return await TradingApiClient.post(uniswapUrls.tradingApiPaths.claimLpFees, { + body: JSON.stringify({ + ...params, + }), + }) +} + export async function fetchSwaps(params: { txHashes: TransactionHash[]; chainId: ChainId }): Promise { return await TradingApiClient.get(uniswapUrls.tradingApiPaths.swaps, { params: { diff --git a/packages/uniswap/src/data/apiClients/tradingApi/useClaimLpFeesCalldataQuery.ts b/packages/uniswap/src/data/apiClients/tradingApi/useClaimLpFeesCalldataQuery.ts new file mode 100644 index 00000000000..692ecb1c437 --- /dev/null +++ b/packages/uniswap/src/data/apiClients/tradingApi/useClaimLpFeesCalldataQuery.ts @@ -0,0 +1,18 @@ +import { UseQueryResult, skipToken, useQuery } from '@tanstack/react-query' +import { uniswapUrls } from 'uniswap/src/constants/urls' +import { TRADING_API_CACHE_KEY, claimLpFees } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient' +import { UseQueryApiHelperHookArgs } from 'uniswap/src/data/apiClients/types' +import { ClaimLPFeesRequest, ClaimLPFeesResponse } from 'uniswap/src/data/tradingApi/__generated__' + +export function useClaimLpFeesCalldataQuery({ + params, + ...rest +}: UseQueryApiHelperHookArgs): UseQueryResult { + const queryKey = [TRADING_API_CACHE_KEY, uniswapUrls.tradingApiPaths.claimLpFees, params] + + return useQuery({ + queryKey, + queryFn: params ? async (): ReturnType => await claimLpFees(params) : skipToken, + ...rest, + }) +} diff --git a/packages/uniswap/src/data/apiClients/tradingApi/useReduceLpPositionCalldataQuery.ts b/packages/uniswap/src/data/apiClients/tradingApi/useDecreaseLpPositionCalldataQuery.ts similarity index 94% rename from packages/uniswap/src/data/apiClients/tradingApi/useReduceLpPositionCalldataQuery.ts rename to packages/uniswap/src/data/apiClients/tradingApi/useDecreaseLpPositionCalldataQuery.ts index 82cdea6b247..cf15301f400 100644 --- a/packages/uniswap/src/data/apiClients/tradingApi/useReduceLpPositionCalldataQuery.ts +++ b/packages/uniswap/src/data/apiClients/tradingApi/useDecreaseLpPositionCalldataQuery.ts @@ -4,7 +4,7 @@ import { TRADING_API_CACHE_KEY, decreaseLpPosition } from 'uniswap/src/data/apiC import { UseQueryApiHelperHookArgs } from 'uniswap/src/data/apiClients/types' import { DecreaseLPPositionRequest, DecreaseLPPositionResponse } from 'uniswap/src/data/tradingApi/__generated__' -export function useReduceLpPositionCalldataQuery({ +export function useDecreaseLpPositionCalldataQuery({ params, ...rest }: UseQueryApiHelperHookArgs< diff --git a/packages/uniswap/src/data/apiClients/tradingApi/useTradingApiQuoteQuery.ts b/packages/uniswap/src/data/apiClients/tradingApi/useTradingApiQuoteQuery.ts index 869c7fee5e7..96c722d9126 100644 --- a/packages/uniswap/src/data/apiClients/tradingApi/useTradingApiQuoteQuery.ts +++ b/packages/uniswap/src/data/apiClients/tradingApi/useTradingApiQuoteQuery.ts @@ -4,23 +4,27 @@ import { useQueryWithImmediateGarbageCollection } from 'uniswap/src/data/apiClie import { DiscriminatedQuoteResponse, TRADING_API_CACHE_KEY, + WithV4Flag, fetchQuote, } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient' import { UseQueryWithImmediateGarbageCollectionApiHelperHookArgs } from 'uniswap/src/data/apiClients/types' import { QuoteRequest } from 'uniswap/src/data/tradingApi/__generated__' +import { FeatureFlags } from 'uniswap/src/features/gating/flags' +import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' export function useTradingApiQuoteQuery({ params, ...rest }: UseQueryWithImmediateGarbageCollectionApiHelperHookArgs< - QuoteRequest, + WithV4Flag, DiscriminatedQuoteResponse >): UseQueryResult { const queryKey = [TRADING_API_CACHE_KEY, uniswapUrls.tradingApiPaths.quote, params] + const v4Enabled = useFeatureFlag(FeatureFlags.V4Swap) return useQueryWithImmediateGarbageCollection({ queryKey, - queryFn: params ? async (): ReturnType => await fetchQuote(params) : skipToken, + queryFn: params ? async (): ReturnType => await fetchQuote({ ...params, v4Enabled }) : skipToken, ...rest, }) } diff --git a/packages/uniswap/src/data/apiClients/tradingApi/useTradingApiSwapQuery.ts b/packages/uniswap/src/data/apiClients/tradingApi/useTradingApiSwapQuery.ts index b2af0edfff7..2a8d7e2e51e 100644 --- a/packages/uniswap/src/data/apiClients/tradingApi/useTradingApiSwapQuery.ts +++ b/packages/uniswap/src/data/apiClients/tradingApi/useTradingApiSwapQuery.ts @@ -1,7 +1,7 @@ import { UseQueryResult, skipToken } from '@tanstack/react-query' import { uniswapUrls } from 'uniswap/src/constants/urls' import { useQueryWithImmediateGarbageCollection } from 'uniswap/src/data/apiClients/hooks/useQueryWithImmediateGarbageCollection' -import { TRADING_API_CACHE_KEY, fetchSwap } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient' +import { TRADING_API_CACHE_KEY, WithV4Flag, fetchSwap } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient' import { UseQueryWithImmediateGarbageCollectionApiHelperHookArgs } from 'uniswap/src/data/apiClients/types' import { CreateSwapRequest, CreateSwapResponse } from 'uniswap/src/data/tradingApi/__generated__' @@ -9,7 +9,7 @@ export function useTradingApiSwapQuery({ params, ...rest }: UseQueryWithImmediateGarbageCollectionApiHelperHookArgs< - CreateSwapRequest, + WithV4Flag, CreateSwapResponse >): UseQueryResult { const queryKey = [TRADING_API_CACHE_KEY, uniswapUrls.tradingApiPaths.swap, params] diff --git a/packages/uniswap/src/data/apiClients/tradingApi/useTradingApiSwappableTokensQuery.ts b/packages/uniswap/src/data/apiClients/tradingApi/useTradingApiSwappableTokensQuery.ts index cbc00980640..5713a3f1f24 100644 --- a/packages/uniswap/src/data/apiClients/tradingApi/useTradingApiSwappableTokensQuery.ts +++ b/packages/uniswap/src/data/apiClients/tradingApi/useTradingApiSwappableTokensQuery.ts @@ -1,8 +1,15 @@ -import { UseQueryResult, skipToken, useQuery } from '@tanstack/react-query' +import { QueryFunction, QueryKey, UseQueryResult, skipToken, useQuery, useQueryClient } from '@tanstack/react-query' +import { useEffect } from 'react' import { uniswapUrls } from 'uniswap/src/constants/urls' import { TRADING_API_CACHE_KEY, fetchSwappableTokens } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient' import { UseQueryApiHelperHookArgs } from 'uniswap/src/data/apiClients/types' import { ChainId, GetSwappableTokensResponse } from 'uniswap/src/data/tradingApi/__generated__' +import { TradeableAsset } from 'uniswap/src/entities/assets' +import { + getTokenAddressFromChainForTradingApi, + toTradingApiSupportedChainId, +} from 'uniswap/src/features/transactions/swap/utils/tradingApi' +import { logger } from 'utilities/src/logger/logger' export type SwappableTokensParams = { tokenIn: Address @@ -18,13 +25,52 @@ export function useTradingApiSwappableTokensQuery({ SwappableTokensParams, GetSwappableTokensResponse >): UseQueryResult { - const queryKey = [TRADING_API_CACHE_KEY, uniswapUrls.tradingApiPaths.swappableTokens, params] + const queryKey = swappableTokensQueryKey(params) return useQuery({ queryKey, - queryFn: params - ? async (): ReturnType => await fetchSwappableTokens(params) - : skipToken, + queryFn: params ? swappableTokensQueryFn(params) : skipToken, ...rest, }) } + +export function usePrefetchSwappableTokens(input: Maybe): void { + const queryClient = useQueryClient() + + useEffect(() => { + const prefetchSwappableTokens = async (): Promise => { + const tokenIn = input?.address ? getTokenAddressFromChainForTradingApi(input.address, input.chainId) : undefined + const tokenInChainId = toTradingApiSupportedChainId(input?.chainId) + if (!tokenIn || !tokenInChainId) { + return + } + + await queryClient.prefetchQuery({ + queryKey: swappableTokensQueryKey({ + tokenIn, + tokenInChainId, + }), + queryFn: swappableTokensQueryFn({ + tokenIn, + tokenInChainId, + }), + }) + } + + prefetchSwappableTokens().catch((e) => { + logger.error(e, { + tags: { file: 'useTradingApiSwappableTokensQuery', function: 'prefetchSwappableTokens' }, + }) + }) + }, [input, queryClient]) +} + +const swappableTokensQueryKey = (params?: SwappableTokensParams): QueryKey => { + return [TRADING_API_CACHE_KEY, uniswapUrls.tradingApiPaths.swappableTokens, params?.tokenIn, params?.tokenInChainId] +} + +const swappableTokensQueryFn = ( + params: SwappableTokensParams, +): QueryFunction | undefined => { + return async (): ReturnType => await fetchSwappableTokens(params) +} diff --git a/apps/mobile/src/features/dataApi/balances.ts b/packages/uniswap/src/data/balances/hooks/useBalances.ts similarity index 65% rename from apps/mobile/src/features/dataApi/balances.ts rename to packages/uniswap/src/data/balances/hooks/useBalances.ts index f585434f774..912ad27f457 100644 --- a/apps/mobile/src/features/dataApi/balances.ts +++ b/packages/uniswap/src/data/balances/hooks/useBalances.ts @@ -2,11 +2,14 @@ import { useMemo } from 'react' import { usePortfolioBalances } from 'uniswap/src/features/dataApi/balances' import { PortfolioBalance } from 'uniswap/src/features/dataApi/types' import { CurrencyId } from 'uniswap/src/types/currency' -import { useActiveAccountAddressWithThrow } from 'wallet/src/features/wallet/hooks' -/** Helper hook to retrieve balances for a set of currencies for the active account. */ -export function useBalances(currencies: CurrencyId[] | undefined): PortfolioBalance[] | null { - const address = useActiveAccountAddressWithThrow() +export function useBalances({ + address, + currencies, +}: { + address: Address + currencies: CurrencyId[] | undefined +}): PortfolioBalance[] | null { const { data: balances } = usePortfolioBalances({ address, fetchPolicy: 'cache-and-network', diff --git a/packages/uniswap/src/data/balances/hooks/useCrossChainBalances.ts b/packages/uniswap/src/data/balances/hooks/useCrossChainBalances.ts new file mode 100644 index 00000000000..39e8f1e3e18 --- /dev/null +++ b/packages/uniswap/src/data/balances/hooks/useCrossChainBalances.ts @@ -0,0 +1,52 @@ +import { useMemo } from 'react' +import { useBalances } from 'uniswap/src/data/balances/hooks/useBalances' +import { Chain } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' +import { fromGraphQLChain } from 'uniswap/src/features/chains/utils' +import { PortfolioBalance } from 'uniswap/src/features/dataApi/types' +import { buildCurrencyId, buildNativeCurrencyId, currencyIdToChain } from 'uniswap/src/utils/currencyId' + +export function useCrossChainBalances({ + address, + currencyId, + crossChainTokens, +}: { + address: Address + currencyId: string + crossChainTokens: Maybe<{ chain: Chain; address?: Maybe }[]> +}): { + currentChainBalance: PortfolioBalance | null + otherChainBalances: PortfolioBalance[] | null +} { + const currentChainBalance = + useBalances({ + address, + currencies: [currencyId], + })?.[0] ?? null + + const currentChainId = currencyIdToChain(currencyId) + + const bridgedCurrencyIds = useMemo( + () => + crossChainTokens + ?.map(({ chain, address: currencyAddress }) => { + const chainId = fromGraphQLChain(chain) + if (!chainId || chainId === currentChainId) { + return null + } + if (!currencyAddress) { + return buildNativeCurrencyId(chainId) + } + return buildCurrencyId(chainId, currencyAddress) + }) + .filter((b): b is string => !!b), + + [crossChainTokens, currentChainId], + ) + + const otherChainBalances = useBalances({ address, currencies: bridgedCurrencyIds }) + + return { + currentChainBalance, + otherChainBalances, + } +} diff --git a/packages/uniswap/src/data/balances/utils.tsx b/packages/uniswap/src/data/balances/utils.tsx index e497bfd344d..03f08491fd0 100644 --- a/packages/uniswap/src/data/balances/utils.tsx +++ b/packages/uniswap/src/data/balances/utils.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react' -import { GQL_MAINNET_CHAINS_MUTABLE } from 'uniswap/src/constants/chains' import { PortfolioBalancesQueryResult } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { logger } from 'utilities/src/logger/logger' /** @@ -13,13 +13,15 @@ export function useTotalBalancesUsdPerChain( undefined, ) + const { gqlChains } = useEnabledChains() + useEffect(() => { const calculateBalancesPerChain = async (): Promise => { if (!portfolioBalances.data?.portfolios?.[0]?.tokenBalances) { return } - const totalBalances = GQL_MAINNET_CHAINS_MUTABLE.reduce( + const totalBalances = gqlChains.reduce( (chainAcc, chain) => { chainAcc[chain] = portfolioBalances.data?.portfolios?.[0]?.tokenBalances?.reduce((balanceAcc, tokenBalance) => { @@ -37,7 +39,7 @@ export function useTotalBalancesUsdPerChain( } calculateBalancesPerChain().catch((error) => logger.error('useTotalBalancesUsdPerChain', error)) - }, [portfolioBalances.data?.portfolios]) + }, [portfolioBalances.data?.portfolios, gqlChains]) return totalBalancesUsdPerChain } diff --git a/packages/uniswap/src/data/cache.ts b/packages/uniswap/src/data/cache.ts index 5d7b0c99368..2e69929ad31 100644 --- a/packages/uniswap/src/data/cache.ts +++ b/packages/uniswap/src/data/cache.ts @@ -1,5 +1,5 @@ -import { InMemoryCache } from '@apollo/client' -import { Reference, relayStylePagination } from '@apollo/client/utilities' +import { FieldFunctionOptions, InMemoryCache } from '@apollo/client' +import { Reference, StoreObject, relayStylePagination } from '@apollo/client/utilities' import { isTestEnv } from 'utilities/src/environment/env' export function setupWalletCache(): InMemoryCache { @@ -63,6 +63,18 @@ export function setupWalletCache(): InMemoryCache { return address?.toLowerCase() ?? null }, }, + feeData: { + // TODO(API-482): remove this once the backend bug is fixed. + // There's a bug in our graphql backend where `feeData` can incorrectly be `null` for certain queries (`topTokens`). + // This field policy ensures that the cache doesn't get overwritten with `null` values triggering unnecessary re-renders. + merge: ignoreIncomingNullValue, + }, + protectionInfo: { + // TODO(API-482): remove this once the backend bug is fixed. + // There's a bug in our graphql backend where `protectionInfo` can incorrectly be `null` for certain queries (`topTokens`). + // This field policy ensures that the cache doesn't get overwritten with `null` values triggering unnecessary re-renders. + merge: ignoreIncomingNullValue, + }, }, }, // Disable normalizaton for these types. @@ -74,3 +86,14 @@ export function setupWalletCache(): InMemoryCache { }, }) } + +function ignoreIncomingNullValue( + existing: Reference | StoreObject, + incoming: Reference | StoreObject, + { mergeObjects }: FieldFunctionOptions, Record>, +): Reference | StoreObject { + if (existing && !incoming) { + return existing + } + return mergeObjects(existing, incoming) +} diff --git a/packages/uniswap/src/data/graphql/uniswap-data-api/queries.graphql b/packages/uniswap/src/data/graphql/uniswap-data-api/queries.graphql index fcd6e1b4ecb..cc715c8fd87 100644 --- a/packages/uniswap/src/data/graphql/uniswap-data-api/queries.graphql +++ b/packages/uniswap/src/data/graphql/uniswap-data-api/queries.graphql @@ -245,22 +245,11 @@ query NftsTab( $first: Int $after: String $filter: NftBalancesFilterInput + $chains: [Chain!]! ) { nftBalances( ownerAddress: $ownerAddress - chains: [ - ETHEREUM - POLYGON - ARBITRUM - OPTIMISM - BASE - BLAST - BNB - ZORA - CELO - AVALANCHE - ZKSYNC - ] + chains: $chains first: $first after: $after filter: $filter @@ -312,19 +301,7 @@ query NftsTab( query PortfolioBalances( $ownerAddress: String! $valueModifiers: [PortfolioValueModifier!] - $chains: [Chain!] = [ - ETHEREUM - POLYGON - ARBITRUM - OPTIMISM - BASE - BNB - BLAST - ZORA - CELO - AVALANCHE - ZKSYNC - ] + $chains: [Chain!]! ) { portfolios( ownerAddresses: [$ownerAddress] @@ -391,22 +368,11 @@ query PortfolioBalances( query MultiplePortfolioBalances( $ownerAddresses: [String!]! $valueModifiers: [PortfolioValueModifier!] + $chains: [Chain!]! ) { portfolios( ownerAddresses: $ownerAddresses - chains: [ - ETHEREUM - POLYGON - ARBITRUM - OPTIMISM - BASE - BNB - BLAST - ZORA - CELO - AVALANCHE - ZKSYNC - ] + chains: $chains valueModifiers: $valueModifiers ) { id @@ -458,22 +424,11 @@ query MultiplePortfolioBalances( query SelectWalletScreen( $ownerAddresses: [String!]! $valueModifiers: [PortfolioValueModifier!] + $chains: [Chain!]! ) { portfolios( ownerAddresses: $ownerAddresses - chains: [ - ETHEREUM - POLYGON - ARBITRUM - OPTIMISM - BASE - BNB - BLAST - ZORA - CELO - AVALANCHE - ZKSYNC - ] + chains: $chains valueModifiers: $valueModifiers ) { id @@ -487,41 +442,18 @@ query SelectWalletScreen( query TransactionHistoryUpdater( $addresses: [String!]! $onRampAuth: OnRampTransactionsAuth + $chains: [Chain!]! ) { portfolios( ownerAddresses: $addresses - chains: [ - ETHEREUM - POLYGON - ARBITRUM - OPTIMISM - BASE - BNB - BLAST - ZORA - CELO - AVALANCHE - ZKSYNC - ] + chains: $chains ) { id ownerAddress assetActivities( pageSize: 1 page: 1 - chains: [ - ETHEREUM - POLYGON - ARBITRUM - OPTIMISM - BASE - BNB - BLAST - ZORA - CELO - AVALANCHE - ZKSYNC - ] + chains: $chains onRampTransactionsAuth: $onRampAuth includeBridging: true ) { @@ -660,22 +592,10 @@ query TokenProjects($contracts: [ContractInput!]!) { } } -query TransactionList($address: String!, $onRampAuth: OnRampTransactionsAuth) { +query TransactionList($address: String!, $onRampAuth: OnRampTransactionsAuth, $chains: [Chain!]!) { portfolios( ownerAddresses: [$address] - chains: [ - ETHEREUM - POLYGON - ARBITRUM - OPTIMISM - BASE - BNB - BLAST - ZORA - CELO - AVALANCHE - ZKSYNC - ] + chains: $chains ) { id assetActivities( @@ -683,19 +603,7 @@ query TransactionList($address: String!, $onRampAuth: OnRampTransactionsAuth) { page: 1 includeOffChain: true includeBridging: true - chains: [ - ETHEREUM - POLYGON - ARBITRUM - OPTIMISM - BASE - BNB - BLAST - ZORA - CELO - AVALANCHE - ZKSYNC - ] + chains: $chains onRampTransactionsAuth: $onRampAuth ) { id @@ -889,22 +797,10 @@ query TransactionList($address: String!, $onRampAuth: OnRampTransactionsAuth) { } } -query FeedTransactionList($addresses: [String!]!) { +query FeedTransactionList($addresses: [String!]!, $chains: [Chain!]!) { portfolios( ownerAddresses: $addresses - chains: [ - ETHEREUM - POLYGON - ARBITRUM - OPTIMISM - BASE - BNB - BLAST - ZORA - CELO - AVALANCHE - ZKSYNC - ] + chains: $chains ) { id ownerAddress @@ -912,19 +808,7 @@ query FeedTransactionList($addresses: [String!]!) { pageSize: 30 includeBridging: true page: 1 - chains: [ - ETHEREUM - POLYGON - ARBITRUM - OPTIMISM - BASE - BNB - BLAST - ZORA - CELO - AVALANCHE - ZKSYNC - ] + chains: $chains ) { id timestamp @@ -1050,7 +934,7 @@ query TopTokens( } } -query SearchTokens($searchQuery: String!, $chains: [Chain!]) { +query SearchTokens($searchQuery: String!, $chains: [Chain!]!) { searchTokens(searchQuery: $searchQuery, chains: $chains) { id chain diff --git a/packages/uniswap/src/data/graphql/uniswap-data-api/schema.graphql b/packages/uniswap/src/data/graphql/uniswap-data-api/schema.graphql index 412c355e63d..c0e6da1a1c8 100644 --- a/packages/uniswap/src/data/graphql/uniswap-data-api/schema.graphql +++ b/packages/uniswap/src/data/graphql/uniswap-data-api/schema.graphql @@ -1,26 +1,19 @@ """This directive allows results to be deferred during execution""" directive @defer on FIELD -""" -Tells the service this field/object has access authorized by an API key. -""" -directive @aws_api_key on OBJECT | FIELD_DEFINITION - """Directs the schema to enforce authorization on a field""" directive @aws_auth( """List of cognito user pool groups which have access on this field""" cognito_groups: [String] ) on FIELD_DEFINITION -""" -Tells the service this field/object has access authorized by a Lambda Authorizer. -""" -directive @aws_lambda on OBJECT | FIELD_DEFINITION - -""" -Tells the service this field/object has access authorized by an OIDC token. -""" -directive @aws_oidc on OBJECT | FIELD_DEFINITION +"""Tells the service which mutation triggers this subscription.""" +directive @aws_subscribe( + """ + List of mutations which will trigger this subscription when they are called. + """ + mutations: [String] +) on FIELD_DEFINITION """ Tells the service which subscriptions will be published to when this mutation is @@ -33,6 +26,21 @@ directive @aws_publish( subscriptions: [String] ) on FIELD_DEFINITION +""" +Tells the service this field/object has access authorized by an API key. +""" +directive @aws_api_key on OBJECT | FIELD_DEFINITION + +""" +Tells the service this field/object has access authorized by an OIDC token. +""" +directive @aws_oidc on OBJECT | FIELD_DEFINITION + +""" +Tells the service this field/object has access authorized by sigv4 signing. +""" +directive @aws_iam on OBJECT | FIELD_DEFINITION + """ Tells the service this field/object has access authorized by a Cognito User Pools token. """ @@ -42,17 +50,9 @@ directive @aws_cognito_user_pools( ) on OBJECT | FIELD_DEFINITION """ -Tells the service this field/object has access authorized by sigv4 signing. +Tells the service this field/object has access authorized by a Lambda Authorizer. """ -directive @aws_iam on OBJECT | FIELD_DEFINITION - -"""Tells the service which mutation triggers this subscription.""" -directive @aws_subscribe( - """ - List of mutations which will trigger this subscription when they are called. - """ - mutations: [String] -) on FIELD_DEFINITION +directive @aws_lambda on OBJECT | FIELD_DEFINITION """ Types, unions, and inputs (alphabetized): @@ -187,6 +187,8 @@ enum Chain { BLAST ZORA ZKSYNC + ASTROCHAIN_SEPOLIA + WORLDCHAIN UNKNOWN_CHAIN } @@ -1091,6 +1093,7 @@ enum SwapOrderType { DUTCH LIMIT DUTCH_V2 + PRIORITY } type TimestampedAmount implements IAmount { diff --git a/packages/uniswap/src/data/graphql/uniswap-data-api/web/RecentTokenTransfers.graphql b/packages/uniswap/src/data/graphql/uniswap-data-api/web/RecentTokenTransfers.graphql index 5aa6d976c60..860f8d07f36 100644 --- a/packages/uniswap/src/data/graphql/uniswap-data-api/web/RecentTokenTransfers.graphql +++ b/packages/uniswap/src/data/graphql/uniswap-data-api/web/RecentTokenTransfers.graphql @@ -1,14 +1,14 @@ -query RecentTokenTransfers($address: String!) { +query RecentTokenTransfers($address: String!, $chains: [Chain!]) { portfolios( ownerAddresses: [$address] - chains: [ETHEREUM, POLYGON, ARBITRUM, OPTIMISM, BASE, BNB] + chains: $chains ) { id ownerAddress assetActivities( pageSize: 100 page: 1 - chains: [ETHEREUM, POLYGON, ARBITRUM, OPTIMISM, BASE, BNB] + chains: $chains ) { id timestamp diff --git a/packages/uniswap/src/data/graphql/uniswap-data-api/web/activity.graphql b/packages/uniswap/src/data/graphql/uniswap-data-api/web/activity.graphql index c1ed73a4fd5..6da09108552 100644 --- a/packages/uniswap/src/data/graphql/uniswap-data-api/web/activity.graphql +++ b/packages/uniswap/src/data/graphql/uniswap-data-api/web/activity.graphql @@ -220,10 +220,10 @@ fragment AssetActivityParts on AssetActivity { } } -query ActivityWeb($account: String!, $chains: [Chain!]!, $onRampTransactionIDs: [String!]) { +query ActivityWeb($account: String!, $chains: [Chain!]!, $onRampTransactionIDs: [String!], $includeOffChain: Boolean!) { portfolios(ownerAddresses: [$account], chains: $chains) { id - assetActivities(pageSize: 100, page: 1, includeOffChain: true, chains: $chains, onRampTransactionIDs: $onRampTransactionIDs) { + assetActivities(pageSize: 100, page: 1, includeOffChain: $includeOffChain, chains: $chains, onRampTransactionIDs: $onRampTransactionIDs) { ...AssetActivityParts } } diff --git a/packages/uniswap/src/data/rest/getPair.ts b/packages/uniswap/src/data/rest/getPair.ts index c7a57f09749..4ca695cc109 100644 --- a/packages/uniswap/src/data/rest/getPair.ts +++ b/packages/uniswap/src/data/rest/getPair.ts @@ -7,6 +7,9 @@ import { getPair } from '@uniswap/client-pools/dist/pools/v1/api-PoolsService_co import { GetPairRequest, GetPairResponse } from '@uniswap/client-pools/dist/pools/v1/api_pb' import { getPositionsTestTransport } from 'uniswap/src/data/rest/getPositions' -export function useGetPair(input?: PartialMessage): UseQueryResult { - return useQuery(getPair, input, { transport: getPositionsTestTransport }) +export function useGetPair( + input?: PartialMessage, + enabled = true, +): UseQueryResult { + return useQuery(getPair, input, { transport: getPositionsTestTransport, enabled }) } diff --git a/packages/uniswap/src/data/rest/getPools.ts b/packages/uniswap/src/data/rest/getPools.ts index afa1211c150..3e233199605 100644 --- a/packages/uniswap/src/data/rest/getPools.ts +++ b/packages/uniswap/src/data/rest/getPools.ts @@ -9,6 +9,7 @@ import { getPositionsTestTransport } from 'uniswap/src/data/rest/getPositions' export function useGetPoolsByTokens( input?: PartialMessage, + enabled = true, ): UseQueryResult { - return useQuery(listPools, input, { transport: getPositionsTestTransport }) + return useQuery(listPools, input, { transport: getPositionsTestTransport, enabled }) } diff --git a/packages/uniswap/src/data/rest/getPosition.ts b/packages/uniswap/src/data/rest/getPosition.ts new file mode 100644 index 00000000000..96207d3c30f --- /dev/null +++ b/packages/uniswap/src/data/rest/getPosition.ts @@ -0,0 +1,14 @@ +/* eslint-disable no-restricted-imports */ +import { PartialMessage } from '@bufbuild/protobuf' +import { ConnectError } from '@connectrpc/connect' +import { useQuery } from '@connectrpc/connect-query' +import { UseQueryResult } from '@tanstack/react-query' +import { getPosition } from '@uniswap/client-pools/dist/pools/v1/api-PoolsService_connectquery' +import { GetPositionRequest, GetPositionResponse } from '@uniswap/client-pools/dist/pools/v1/api_pb' +import { getPositionsTestTransport } from 'uniswap/src/data/rest/getPositions' + +export function useGetPositionQuery( + input?: PartialMessage, +): UseQueryResult { + return useQuery(getPosition, input, { transport: getPositionsTestTransport, enabled: !!input }) +} diff --git a/packages/uniswap/src/data/tradingApi/api.json b/packages/uniswap/src/data/tradingApi/api.json index bdcc19b6402..528ce7f8ab0 100644 --- a/packages/uniswap/src/data/tradingApi/api.json +++ b/packages/uniswap/src/data/tradingApi/api.json @@ -1 +1 @@ -{"openapi":"3.0.0","servers":[{"description":"Uniswap trading APIs Beta","url":"https://beta.trade-api.gateway.uniswap.org/v1"},{"description":"Uniswap trading APIs","url":"https://trade-api.gateway.uniswap.org/v1"}],"info":{"version":"1.0.0","title":"Token Trading","description":"Uniswap trading APIs for fungible tokens."},"paths":{"/check_approval":{"post":{"tags":["Approval"],"summary":"Check if token approval is required","description":"Checks if the swapper has the required approval. If the swapper does not have the required approval, then the response will include the transaction to approve the token. If the swapper has the required approval, then the response will be empty. If the parameter `includeGasInfo` is set to `true`, then the response will include the gas fee for the approval transaction.","operationId":"check_approval","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApprovalRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/ApprovalSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/ApprovalNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/quote":{"post":{"tags":["Quote"],"summary":"Get a quote","description":"Get a quote according to the provided configuration. Optionally adds a fee to the quote according to the API key being used. The fee is **ALWAYS** taken from the output token. If there is a fee and the trade is `EXACT_INPUT`, then the output amount will **NOT** include the fee subtraction. For `EXACT_INPUT` swaps, use `portionBips` to calculate the fee from the quoted amount. If there is a fee and the trade is `EXACT_OUTPUT`, then the input amount will **NOT** include the fee addition to account for the fee. For `EXACT_OUTPUT` swaps, use `portionAmount` to get the fee. \n \n We also support Wrapping and Unwrapping of native tokens on their respective chains. Wrapping and Unwrapping only works for when `routingPreference` is `CLASSIC`, `BEST_PRICE`, or `BEST_PRICE_V2`. We do not support `UNISWAPX` or `UNISWAPX_V2` for these actions.","operationId":"aggregator_quote","security":[{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/universalRouterVersionHeader"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/QuoteSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/Unauthorized401"},"404":{"$ref":"#/components/responses/QuoteNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/order":{"post":{"tags":["Order"],"summary":"Create a gasless order","description":"Submits a new gasless encoded order. The order will be validated and if valid, will be submitted to the filler network. The network will try to fill the order at the quoted `startAmount`, and if not, the amount will start decaying until the `endAmount` is reached. While the order is within `decayEndTime`, the `orderStatus` is `open`. If the order does not get filled after the `decayEndTime` has passed, that is reflected in the `expired` `orderStatus`. then The order will be filled at the best price possible. Once the order is filled, `orderStatus` becomes `filled`.","operationId":"post_order","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderRequest"}}}},"responses":{"201":{"$ref":"#/components/responses/OrderSuccess201"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/Unauthorized401"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/orders":{"get":{"tags":["Order"],"summary":"Get gasless orders","description":"Retrieve gasless orders filtered by query param(s). Some fields on the order can be used as query param.","operationId":"get_order","security":[{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/orderTypeParam"},{"$ref":"#/components/parameters/orderIdParam"},{"$ref":"#/components/parameters/orderIdsParam"},{"$ref":"#/components/parameters/limitParam"},{"$ref":"#/components/parameters/orderStatusParam"},{"$ref":"#/components/parameters/swapperParam"},{"$ref":"#/components/parameters/sortKeyParam"},{"$ref":"#/components/parameters/sortParam"},{"$ref":"#/components/parameters/fillerParam"},{"$ref":"#/components/parameters/cursorParam"}],"responses":{"200":{"$ref":"#/components/responses/OrdersSuccess200"},"400":{"$ref":"#/components/responses/OrdersBadRequest400"},"404":{"$ref":"#/components/responses/OrdersNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/swap":{"post":{"tags":["Swap"],"summary":"Create swap calldata","description":"Create the calldata for a swap transaction (including wrap/unwrap) against the Uniswap Protocols. If the `quote` parameter includes the fee parameters, then the calldata will include the fee disbursement. The gas estimates will be **more precise** when the the response calldata would be valid if submitted on-chain.","operationId":"create_swap_transaction","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSwapRequest"}}}},"parameters":[{"$ref":"#/components/parameters/universalRouterVersionHeader"}],"responses":{"200":{"$ref":"#/components/responses/CreateSwapSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/SwapUnauthorized401"},"404":{"$ref":"#/components/responses/SwapNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/swaps":{"get":{"tags":["Swap"],"summary":"Get swaps status","description":"Get the status of a swap or bridge transactions.","operationId":"get_swaps","security":[{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/transactionHashesParam"},{"$ref":"#/components/parameters/chainIdParam"}],"responses":{"200":{"$ref":"#/components/responses/GetSwapsSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"404":{"$ref":"#/components/responses/SwapNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/indicative_quote":{"post":{"tags":["IndicativeQuote"],"summary":"Get an indicative quote","description":"Get an indicative quote according to the provided configuration. The quote will not include a fee.","operationId":"indicative_quote","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndicativeQuoteRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/IndicativeQuoteSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"404":{"$ref":"#/components/responses/QuoteNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/send":{"post":{"tags":["Send"],"summary":"Create send calldata","description":"Create the calldata for a send transaction.","operationId":"create_send","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSendRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/CreateSendSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/Unauthorized401"},"404":{"$ref":"#/components/responses/SendNotFound404"},"429":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/swappable_tokens":{"get":{"tags":["SwappableTokens"],"summary":"Get swappable tokens","description":"Get the swappable tokens for the given configuration. Either tokenIn (with tokenInChainId or (tokenInChainId and tokenOutChainId)) or tokenOut (with tokenOutChainId or (tokenOutChainId and tokenInChainId)) must be provided but not both.","operationId":"get_swappable_tokens","security":[{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/tokenInParam"},{"$ref":"#/components/parameters/tokenOutParam"},{"$ref":"#/components/parameters/bridgeTokenInChainIdParam"},{"$ref":"#/components/parameters/bridgeTokenOutChainIdParam"}],"responses":{"200":{"$ref":"#/components/responses/GetSwappableTokensSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/Unauthorized401"},"404":{"$ref":"#/components/responses/QuoteNotFound404"},"429":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/limit_order_quote":{"post":{"tags":["LimitOrderQuote"],"summary":"Get a limit order quote","description":"Get a quote for a limit order according to the provided configuration.","operationId":"get_limit_order_quote","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LimitOrderQuoteRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/LimitOrderQuoteSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/Unauthorized401"},"404":{"$ref":"#/components/responses/QuoteNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/lp/approve":{"post":{"tags":["Liquidity"],"summary":"Check if tokens and permits need to be approved to add liquidity","description":"Checks if the wallet address has the required approvals. If the wallet address does not have the required approval, then the response will include the transactions to approve the tokens. If the wallet address has the required approval, then the response will be empty for the corresponding tokens. If the parameter `simulateTransaction` is set to `true`, then the response will include the gas fees for the approval transactions.","operationId":"check_approval_lp","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckApprovalLPRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/CheckApprovalLPSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/ApprovalNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/lp/create":{"post":{"tags":["Liquidity"],"summary":"Create pool and position calldata","description":"Create pool and position calldata. If the pool is not yet created, then the response will include the transaction to create the new pool with the initial price. If the pool is already created, then the response will not have the transaction to create the pool. The response will also have the transaction to create the position for the corresponding pool. If the parameter `simulateTransaction` is set to `true`, then the response will include the gas fees for the creation transactions.","operationId":"create_lp_position","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLPPositionRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/CreateLPPositionSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/LPNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/lp/increase":{"post":{"tags":["Liquidity"],"summary":"Increase LP position calldata","description":"The response will also have the transaction to increase the position for the corresponding pool. If the parameter `simulateTransaction` is set to `true`, then the response will include the gas fees for the increase transaction.","operationId":"increase_lp_position","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IncreaseLPPositionRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/IncreaseLPPositionSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/LPNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/lp/decrease":{"post":{"tags":["Liquidity"],"summary":"Decrease LP position calldata","description":"The response will also have the transaction to decrease the position for the corresponding pool. If the parameter `simulateTransaction` is set to `true`, then the response will include the gas fees for the decrease transaction.","operationId":"decrease_lp_position","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecreaseLPPositionRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/DecreaseLPPositionSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/LPNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/lp/claim":{"post":{"tags":["Liquidity"],"summary":"Claim LP fees calldata","description":"The response will also have the transaction to claim the fees for an LP position for the corresponding pool. If the parameter `simulateTransaction` is set to `true`, then the response will include the gas fees for the claim transaction.","operationId":"claim_lp_fees","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimLPFeesRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/ClaimLPFeesSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/LPNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/lp/migrate":{"post":{"tags":["Liquidity"],"summary":"Migrate LP position calldata","description":"The response will also have the transaction to migrate the position for the corresponding pool. If the parameter `simulateTransaction` is set to `true`, then the response will include the gas fees for the migrate transaction.","operationId":"migrate_lp_position","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MigrateLPPositionRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/MigrateLPPositionSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/LPNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}}},"components":{"responses":{"OrdersSuccess200":{"description":"The request orders matching the query parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetOrdersResponse"}}}},"OrderSuccess201":{"description":"Encoded order submitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderResponse"}}}},"QuoteSuccess200":{"description":"Quote request successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteResponse"}}}},"LimitOrderQuoteSuccess200":{"description":"Limit Order Quote request successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LimitOrderQuoteResponse"}}}},"CheckApprovalLPSuccess200":{"description":"Approve LP successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckApprovalLPResponse"}}}},"ApprovalSuccess200":{"description":"Check approval successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApprovalResponse"}}}},"CreateSendSuccess200":{"description":"Create send successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSendResponse"}}}},"CreateSwapSuccess200":{"description":"Create swap successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSwapResponse"}}}},"GetSwapsSuccess200":{"description":"Get swap successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSwapsResponse"}}}},"GetSwappableTokensSuccess200":{"description":"Get swappable tokens successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSwappableTokensResponse"}}}},"CreateLPPositionSuccess200":{"description":"Create LP Position successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLPPositionResponse"}}}},"IncreaseLPPositionSuccess200":{"description":"Create LP Position successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IncreaseLPPositionResponse"}}}},"DecreaseLPPositionSuccess200":{"description":"Decrease LP Position successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecreaseLPPositionResponse"}}}},"ClaimLPFeesSuccess200":{"description":"Claim LP Fees successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimLPFeesResponse"}}}},"MigrateLPPositionSuccess200":{"description":"Migrate LP Position successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MigrateLPPositionResponse"}}}},"BadRequest400":{"description":"RequestValidationError, Bad Input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err400"}}}},"ApprovalUnauthorized401":{"description":"UnauthorizedError eg. Account is blocked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err401"}}}},"ApprovalNotFound404":{"description":"ResourceNotFound eg. Token allowance not found or Gas info not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"Unauthorized401":{"description":"UnauthorizedError eg. Account is blocked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err401"}}}},"QuoteNotFound404":{"description":"ResourceNotFound eg. No quotes available or Gas fee/price not available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"SendNotFound404":{"description":"ResourceNotFound eg. Gas fee not available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"SwapBadRequest400":{"description":"RequestValidationError, Bad Input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err400"}}}},"SwapUnauthorized401":{"description":"UnauthorizedError eg. Account is blocked or Fee is not enabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err401"}}}},"SwapNotFound404":{"description":"ResourceNotFound eg. No quotes available or Gas fee/price not available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"OrdersNotFound404":{"description":"Orders not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"LPNotFound404":{"description":"ResourceNotFound eg. Cant Find LP Position.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"OrdersBadRequest400":{"description":"RequestValidationError eg. Token allowance not valid or Insufficient Funds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err400"}}}},"RateLimitedErr429":{"description":"Ratelimited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err429"}}}},"InternalErr500":{"description":"Unexpected error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err500"}}}},"Timeout504":{"description":"Request duration limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err504"}}}},"IndicativeQuoteSuccess200":{"description":"Indicative quote request successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndicativeQuoteResponse"}}}}},"schemas":{"NullablePermit":{"allOf":[{"$ref":"#/components/schemas/Permit"},{"type":"object","nullable":true}]},"TokenAmount":{"type":"string"},"SwapStatus":{"type":"string","enum":["PENDING","SUCCESS","NOT_FOUND","FAILED","EXPIRED"]},"GetSwapsResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"swaps":{"type":"array","items":{"type":"object","properties":{"swapType":{"$ref":"#/components/schemas/Routing"},"status":{"$ref":"#/components/schemas/SwapStatus"},"txHash":{"type":"string"},"swapId":{"type":"number"}}}}},"required":["requestId","status"]},"GetSwappableTokensResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"tokens":{"type":"array","items":{"type":"object","properties":{"address":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"name":{"type":"string"},"symbol":{"type":"string"},"project":{"$ref":"#/components/schemas/TokenProject"},"isSpam":{"type":"boolean"},"decimals":{"type":"number"}},"required":["address","chainId","name","symbol","project","decimals"]}}},"required":["requestId","tokens"]},"CreateSwapRequest":{"type":"object","description":"The parameters **signature** and **permitData** should only be included if *permitData* was returned from **/quote**.","properties":{"quote":{"oneOf":[{"$ref":"#/components/schemas/ClassicQuote"},{"$ref":"#/components/schemas/WrapUnwrapQuote"},{"$ref":"#/components/schemas/BridgeQuote"}]},"signature":{"type":"string","description":"The signed permit."},"includeGasInfo":{"type":"boolean","default":false,"deprecated":true,"description":"Use `refreshGasPrice` instead."},"refreshGasPrice":{"type":"boolean","default":false,"description":"If true, the gas price will be re-fetched from the network."},"simulateTransaction":{"type":"boolean","default":false,"description":"If true, the transaction will be simulated. If the simulation results on an onchain error, endpoint will return an error."},"permitData":{"allOf":[{"$ref":"#/components/schemas/Permit"}]},"safetyMode":{"$ref":"#/components/schemas/SwapSafetyMode"},"deadline":{"type":"integer","description":"The deadline for the swap in unix timestamp format. If the deadline is not defined OR in the past then the default deadline is 30 minutes."},"urgency":{"$ref":"#/components/schemas/Urgency"}},"required":["quote"]},"CreateSendRequest":{"type":"object","properties":{"sender":{"$ref":"#/components/schemas/Address"},"recipient":{"$ref":"#/components/schemas/Address"},"token":{"$ref":"#/components/schemas/Address"},"amount":{"$ref":"#/components/schemas/TokenAmount"},"chainId":{"$ref":"#/components/schemas/ChainId"},"urgency":{"$ref":"#/components/schemas/Urgency"}},"required":["sender","recipient","token","amount"]},"UniversalRouterVersion":{"type":"string","enum":["1.2","2.0"],"default":"1.2"},"Address":{"type":"string","pattern":"^(0x)?[0-9a-fA-F]{40}$"},"Position":{"type":"object","properties":{"pool":{"$ref":"#/components/schemas/Pool"},"tickLower":{"type":"number"},"tickUpper":{"type":"number"}},"required":["pool"]},"Pool":{"type":"object","properties":{"token0":{"$ref":"#/components/schemas/Address"},"token1":{"$ref":"#/components/schemas/Address"},"fee":{"type":"number"},"tickSpacing":{"type":"number"},"hooks":{"$ref":"#/components/schemas/Address"}},"required":["token0","token1"]},"ClassicGasUseEstimateUSD":{"description":"The gas fee you would pay if you opted for a CLASSIC swap over a Uniswap X order in terms of USD.","type":"string"},"CreateSwapResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"swap":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"}},"required":["requestId","swap"]},"CreateSendResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"send":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"},"gasFeeUSD":{"type":"number"}},"required":["requestId","send"]},"QuoteResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"quote":{"$ref":"#/components/schemas/Quote"},"routing":{"$ref":"#/components/schemas/Routing"},"permitData":{"$ref":"#/components/schemas/NullablePermit"}},"required":["routing","quote","permitData","requestId"]},"LimitOrderQuoteResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"quote":{"$ref":"#/components/schemas/DutchQuote"},"routing":{"type":"string","enum":["LIMIT_ORDER"]},"permitData":{"$ref":"#/components/schemas/NullablePermit"}},"required":["routing","quote","permitData","requestId"]},"QuoteRequest":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/TradeType"},"amount":{"type":"string"},"tokenInChainId":{"$ref":"#/components/schemas/ChainId"},"tokenOutChainId":{"$ref":"#/components/schemas/ChainId"},"tokenIn":{"type":"string"},"tokenOut":{"type":"string"},"swapper":{"$ref":"#/components/schemas/Address"},"slippageTolerance":{"description":"For **Classic** swaps, the slippage tolerance is the maximum amount the price can change between the time the transaction is submitted and the time it is executed. The slippage tolerance is represented as a percentage of the total value of the swap. \n\n Slippage tolerance works differently in **DutchLimit** swaps, it does not set a limit on the Spread in an order. See [here](https://uniswap-docs.readme.io/reference/faqs#why-do-the-uniswapx-quotes-have-more-slippage-than-the-tolerance-i-set) for more information. \n\n **NOTE**: slippage is in terms of trade type. If the trade type is `EXACT_INPUT`, then the slippage is in terms of the output token. If the trade type is `EXACT_OUTPUT`, then the slippage is in terms of the input token.","type":"number"},"autoSlippage":{"$ref":"#/components/schemas/AutoSlippage"},"routingPreference":{"$ref":"#/components/schemas/RoutingPreference"},"protocols":{"$ref":"#/components/schemas/Protocols"},"spreadOptimization":{"$ref":"#/components/schemas/SpreadOptimization"},"urgency":{"$ref":"#/components/schemas/Urgency"}},"required":["type","amount","tokenInChainId","tokenOutChainId","tokenIn","tokenOut","swapper"]},"LimitOrderQuoteRequest":{"type":"object","properties":{"swapper":{"$ref":"#/components/schemas/Address"},"limitPrice":{"type":"string"},"amount":{"type":"string"},"orderDeadline":{"type":"number"},"type":{"$ref":"#/components/schemas/TradeType"},"tokenIn":{"type":"string"},"tokenOut":{"type":"string"},"tokenInChainId":{"$ref":"#/components/schemas/ChainId"},"tokenOutChainId":{"$ref":"#/components/schemas/ChainId"}},"required":["swapper","type","amount","tokenIn","tokenOut","tokenInChainId","tokenOutChainId"]},"GetOrdersResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"orders":{"type":"array","items":{"$ref":"#/components/schemas/UniswapXOrder"}},"cursor":{"type":"string"}},"required":["orders","requestId"]},"OrderResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"orderId":{"type":"string"},"orderStatus":{"$ref":"#/components/schemas/OrderStatus"}},"required":["requestId","orderId","orderStatus"]},"OrderRequest":{"type":"object","properties":{"signature":{"type":"string","description":"The signed permit."},"quote":{"oneOf":[{"$ref":"#/components/schemas/DutchQuote"},{"$ref":"#/components/schemas/DutchQuoteV2"},{"$ref":"#/components/schemas/PriorityQuote"}]},"routing":{"$ref":"#/components/schemas/Routing"}},"required":["signature","quote"]},"Urgency":{"type":"string","enum":["normal","fast","urgent"],"description":"The urgency determines the urgency of the transaction. The default value is `urgent`.","default":"urgent"},"Protocols":{"type":"array","items":{"$ref":"#/components/schemas/ProtocolItems"},"description":"The protocols to use for the swap/order. If the `protocols` field is defined, then you can only set the `routingPreference` to `BEST_PRICE`"},"Err400":{"type":"object","properties":{"errorCode":{"default":"RequestValidationError","type":"string"},"detail":{"type":"string"}}},"Err401":{"type":"object","properties":{"errorCode":{"default":"UnauthorizedError","type":"string"},"detail":{"type":"string"}}},"Err404":{"type":"object","properties":{"errorCode":{"default":"ResourceNotFound","type":"string"},"detail":{"type":"string"}}},"Err429":{"type":"object","properties":{"errorCode":{"default":"Ratelimited","type":"string"},"detail":{"type":"string"}}},"Err500":{"type":"object","properties":{"errorCode":{"default":"InternalServerError","type":"string"},"detail":{"type":"string"}}},"Err504":{"type":"object","properties":{"errorCode":{"default":"Timeout","type":"string"},"detail":{"type":"string"}}},"ChainId":{"type":"number","enum":[1,10,56,137,8453,42161,81457,43114,42220,7777777,324,11155111]},"OrderInput":{"type":"object","properties":{"token":{"type":"string"},"startAmount":{"type":"string"},"endAmount":{"type":"string"}},"required":["token"]},"OrderOutput":{"type":"object","properties":{"token":{"type":"string"},"startAmount":{"type":"string"},"endAmount":{"type":"string"},"isFeeOutput":{"type":"boolean"},"recipient":{"type":"string"}},"required":["token"]},"CosignerData":{"type":"object","properties":{"decayStartTime":{"type":"number"},"decayEndTime":{"type":"number"},"exclusiveFiller":{"type":"string"},"inputOverride":{"type":"string"},"outputOverrides":{"type":"array","items":{"type":"string"}}}},"SettledAmount":{"type":"object","properties":{"tokenOut":{"$ref":"#/components/schemas/Address"},"amountOut":{"type":"string"},"tokenIn":{"$ref":"#/components/schemas/Address"},"amountIn":{"type":"string"}}},"OrderType":{"type":"string","enum":["DutchLimit","Dutch","Dutch_V2"]},"OrderTypeQuery":{"type":"string","enum":["Dutch","Dutch_V2","Dutch_V1_V2","Limit","Priority"]},"UniswapXOrder":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/OrderType"},"encodedOrder":{"type":"string"},"signature":{"type":"string"},"nonce":{"type":"string"},"orderStatus":{"$ref":"#/components/schemas/OrderStatus"},"orderId":{"type":"string"},"chainId":{"$ref":"#/components/schemas/ChainId"},"quoteId":{"type":"string"},"swapper":{"type":"string"},"txHash":{"type":"string"},"input":{"$ref":"#/components/schemas/OrderInput"},"outputs":{"type":"array","items":{"$ref":"#/components/schemas/OrderOutput"}},"settledAmounts":{"type":"array","items":{"$ref":"#/components/schemas/SettledAmount"}},"cosignature":{"type":"string"},"cosignerData":{"$ref":"#/components/schemas/CosignerData"}},"required":["encodedOrder","signature","nonce","orderId","orderStatus","chainId","type"]},"SortKey":{"type":"string","enum":["createdAt"]},"OrderId":{"type":"string"},"OrderIds":{"type":"string"},"OrderStatus":{"type":"string","enum":["open","expired","error","cancelled","filled","unverified","insufficient-funds"]},"Permit":{"type":"object","properties":{"domain":{"type":"object"},"values":{"type":"object"},"types":{"type":"object"}}},"TokenProject":{"type":"object","properties":{"logo":{"$ref":"#/components/schemas/TokenProjectLogo","nullable":true},"safetyLevel":{"$ref":"#/components/schemas/SafetyLevel"},"isSpam":{"type":"boolean"}},"required":["logo","safetyLevel","isSpam"]},"TokenProjectLogo":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"]},"DutchInput":{"type":"object","properties":{"startAmount":{"type":"string"},"endAmount":{"type":"string"},"token":{"type":"string"}},"required":["startAmount","endAmount","type"]},"DutchOutput":{"type":"object","properties":{"startAmount":{"type":"string"},"endAmount":{"type":"string"},"token":{"type":"string"},"recipient":{"type":"string"}},"required":["startAmount","endAmount","token","recipient"]},"DutchOrderInfo":{"type":"object","properties":{"chainId":{"$ref":"#/components/schemas/ChainId"},"nonce":{"type":"string"},"reactor":{"type":"string"},"swapper":{"type":"string"},"deadline":{"type":"number"},"additionalValidationContract":{"type":"string"},"additionalValidationData":{"type":"string"},"decayStartTime":{"type":"number"},"decayEndTime":{"type":"number"},"exclusiveFiller":{"type":"string"},"exclusivityOverrideBps":{"type":"string"},"input":{"$ref":"#/components/schemas/DutchInput"},"outputs":{"type":"array","items":{"$ref":"#/components/schemas/DutchOutput"}}},"required":["chainId","nonce","reactor","swapper","deadline","validationContract","validationData","startTime","endTime","exclusiveFiller","exclusivityOverrideBps","input","outputs"]},"DutchOrderInfoV2":{"type":"object","properties":{"chainId":{"$ref":"#/components/schemas/ChainId"},"nonce":{"type":"string"},"reactor":{"type":"string"},"swapper":{"type":"string"},"deadline":{"type":"number"},"additionalValidationContract":{"type":"string"},"additionalValidationData":{"type":"string"},"input":{"$ref":"#/components/schemas/DutchInput"},"outputs":{"type":"array","items":{"$ref":"#/components/schemas/DutchOutput"}},"cosigner":{"$ref":"#/components/schemas/Address"}},"required":["chainId","nonce","reactor","swapper","deadline","validationContract","validationData","startTime","endTime","exclusiveFiller","exclusivityOverrideBps","input","outputs"]},"DutchQuote":{"type":"object","properties":{"encodedOrder":{"type":"string"},"orderId":{"type":"string"},"orderInfo":{"$ref":"#/components/schemas/DutchOrderInfo"},"portionBips":{"type":"number"},"portionAmount":{"type":"string"},"portionRecipient":{"$ref":"#/components/schemas/Address"},"quoteId":{"type":"string"},"slippageTolerance":{"type":"number"},"classicGasUseEstimateUSD":{"$ref":"#/components/schemas/ClassicGasUseEstimateUSD"}},"required":["encodedOrder","orderInfo","orderId"]},"DutchQuoteV2":{"type":"object","properties":{"encodedOrder":{"type":"string"},"orderId":{"type":"string"},"orderInfo":{"$ref":"#/components/schemas/DutchOrderInfoV2"},"portionBips":{"type":"number"},"portionAmount":{"type":"string"},"portionRecipient":{"$ref":"#/components/schemas/Address"},"quoteId":{"type":"string"},"slippageTolerance":{"type":"number"},"deadlineBufferSecs":{"type":"number"},"classicGasUseEstimateUSD":{"$ref":"#/components/schemas/ClassicGasUseEstimateUSD"}},"required":["encodedOrder","orderInfo","orderId"]},"PriorityInput":{"type":"object","properties":{"amount":{"type":"string"},"token":{"type":"string"},"mpsPerPriorityFeeWei":{"type":"string"}},"required":["amount","token","mpsPerPriorityFeeWei"]},"PriorityOutput":{"type":"object","properties":{"amount":{"type":"string"},"token":{"type":"string"},"recipient":{"type":"string"},"mpsPerPriorityFeeWei":{"type":"string"}},"required":["amount","token","recipient","mpsPerPriorityFeeWei"]},"PriorityOrderInfo":{"type":"object","properties":{"chainId":{"$ref":"#/components/schemas/ChainId"},"nonce":{"type":"string"},"reactor":{"type":"string"},"swapper":{"type":"string"},"deadline":{"type":"number"},"additionalValidationContract":{"type":"string"},"additionalValidationData":{"type":"string"},"auctionStartBlock":{"type":"string"},"baselinePriorityFeeWei":{"type":"string"},"input":{"$ref":"#/components/schemas/PriorityInput"},"outputs":{"type":"array","items":{"$ref":"#/components/schemas/PriorityOutput"}},"cosigner":{"$ref":"#/components/schemas/Address"}},"required":["chainId","nonce","reactor","swapper","deadline","validationContract","validationData","auctionStartBlock","baselinePriorityFeeWei","input","outputs","cosigner"]},"PriorityQuote":{"type":"object","properties":{"encodedOrder":{"type":"string"},"orderId":{"type":"string"},"orderInfo":{"$ref":"#/components/schemas/PriorityOrderInfo"},"portionBips":{"type":"number"},"portionAmount":{"type":"string"},"portionRecipient":{"$ref":"#/components/schemas/Address"},"quoteId":{"type":"string"},"slippageTolerance":{"type":"number"},"deadlineBufferSecs":{"type":"number"},"classicGasUseEstimateUSD":{"$ref":"#/components/schemas/ClassicGasUseEstimateUSD"}},"required":["encodedOrder","orderInfo","orderId"]},"BridgeQuote":{"type":"object","properties":{"quoteId":{"type":"string"},"chainId":{"$ref":"#/components/schemas/ChainId"},"destinationChainId":{"$ref":"#/components/schemas/ChainId"},"swapper":{"$ref":"#/components/schemas/Address"},"input":{"$ref":"#/components/schemas/ClassicInput"},"output":{"$ref":"#/components/schemas/ClassicOutput"},"tradeType":{"$ref":"#/components/schemas/TradeType"},"quoteTimestamp":{"type":"number"},"gasPrice":{"type":"string"},"maxFeePerGas":{"type":"string"},"maxPriorityFeePerGas":{"type":"string"},"gasFee":{"type":"string"},"gasUseEstimate":{"type":"string"},"gasFeeUSD":{"type":"string"},"portionBips":{"type":"number"},"portionAmount":{"type":"string"},"portionRecipient":{"$ref":"#/components/schemas/Address"},"estimatedFillTimeMs":{"type":"number"}}},"SafetyLevel":{"type":"string","enum":["BLOCKED","MEDIUM_WARNING","STRONG_WARNING","VERIFIED"]},"TradeType":{"type":"string","enum":["EXACT_INPUT","EXACT_OUTPUT"]},"Routing":{"type":"string","enum":["DUTCH_LIMIT","CLASSIC","DUTCH_V2","BRIDGE","LIMIT_ORDER","PRIORITY"]},"Quote":{"oneOf":[{"$ref":"#/components/schemas/DutchQuote"},{"$ref":"#/components/schemas/ClassicQuote"},{"$ref":"#/components/schemas/WrapUnwrapQuote"},{"$ref":"#/components/schemas/DutchQuoteV2"},{"$ref":"#/components/schemas/BridgeQuote"},{"$ref":"#/components/schemas/PriorityQuote"}]},"CheckApprovalLPRequest":{"type":"object","properties":{"protocol":{"$ref":"#/components/schemas/ProtocolItems"},"token0":{"$ref":"#/components/schemas/Address"},"token1":{"$ref":"#/components/schemas/Address"},"positionToken":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"walletAddress":{"$ref":"#/components/schemas/Address"},"amount0":{"type":"string"},"amount1":{"type":"string"},"positionAmount":{"type":"string"},"simulateTransaction":{"type":"boolean"}}},"CheckApprovalLPResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"token0Approval":{"$ref":"#/components/schemas/TransactionRequest"},"token1Approval":{"$ref":"#/components/schemas/TransactionRequest"},"positionTokenApproval":{"$ref":"#/components/schemas/TransactionRequest"},"permitData":{"$ref":"#/components/schemas/NullablePermit"},"gasFeeToken0Approval":{"type":"string"},"gasFeeToken1Approval":{"type":"string"},"gasFeePositionTokenApproval":{"type":"string"}}},"ApprovalRequest":{"type":"object","properties":{"walletAddress":{"$ref":"#/components/schemas/Address"},"token":{"$ref":"#/components/schemas/Address"},"amount":{"$ref":"#/components/schemas/TokenAmount"},"chainId":{"$ref":"#/components/schemas/ChainId"},"urgency":{"$ref":"#/components/schemas/Urgency"},"includeGasInfo":{"type":"boolean","default":false},"tokenOut":{"$ref":"#/components/schemas/Address"},"tokenOutChainId":{"$ref":"#/components/schemas/ChainId"}},"required":["walletAddress","token","amount"]},"ApprovalResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"approval":{"$ref":"#/components/schemas/TransactionRequest"},"cancel":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"},"cancelGasFee":{"type":"string"}},"required":["requestId","approval","cancel"]},"ClassicQuote":{"type":"object","properties":{"input":{"$ref":"#/components/schemas/ClassicInput"},"output":{"$ref":"#/components/schemas/ClassicOutput"},"swapper":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"slippage":{"type":"number"},"tradeType":{"$ref":"#/components/schemas/TradeType"},"gasFee":{"type":"string","description":"The gas fee in terms of wei. It does NOT include the additional gas for token approvals."},"gasFeeUSD":{"type":"string","description":"The gas fee in terms of USD. It does NOT include the additional gas for token approvals."},"gasFeeQuote":{"type":"string","description":"The gas fee in terms of the quoted currency. It does NOT include the additional gas for token approvals."},"route":{"type":"array","items":{"type":"array","items":{"oneOf":[{"$ref":"#/components/schemas/V3PoolInRoute"},{"$ref":"#/components/schemas/V2PoolInRoute"}]}}},"portionBips":{"type":"number","description":"The portion of the swap that will be taken as a fee. The fee will be taken from the output token."},"portionAmount":{"type":"string","description":"The amount of the swap that will be taken as a fee. The fee will be taken from the output token."},"portionRecipient":{"$ref":"#/components/schemas/Address"},"routeString":{"type":"string","description":"The route in string format."},"quoteId":{"type":"string","description":"The quote id. Used for analytics purposes."},"gasUseEstimate":{"type":"string","description":"The estimated gas use. It does NOT include the additional gas for token approvals."},"blockNumber":{"type":"string","description":"The current block number."},"gasPrice":{"type":"string","description":"The gas price in terms of wei for pre EIP1559 transactions."},"maxFeePerGas":{"type":"string","description":"The maximum fee per gas in terms of wei for EIP1559 transactions."},"maxPriorityFeePerGas":{"type":"string","description":"The maximum priority fee per gas in terms of wei for EIP1559 transactions."},"txFailureReasons":{"type":"array","items":{"$ref":"#/components/schemas/TransactionFailureReason"}},"priceImpact":{"type":"number","description":"The impact the trade has on the market price of the pool, between 0-100 percent"}}},"WrapUnwrapQuote":{"type":"object","properties":{"swapper":{"$ref":"#/components/schemas/Address"},"input":{"$ref":"#/components/schemas/ClassicInput"},"output":{"$ref":"#/components/schemas/ClassicOutput"},"chainId":{"$ref":"#/components/schemas/ChainId"},"tradeType":{"$ref":"#/components/schemas/TradeType"},"gasFee":{"type":"string","description":"The gas fee in terms of wei."},"gasFeeUSD":{"type":"string","description":"The gas fee in terms of USD."},"gasFeeQuote":{"type":"string","description":"The gas fee in terms of the quoted currency."},"gasUseEstimate":{"type":"string","description":"The estimated gas use."},"gasPrice":{"type":"string","description":"The gas price in terms of wei for pre EIP1559 transactions."},"maxFeePerGas":{"type":"string","description":"The maximum fee per gas in terms of wei for EIP1559 transactions."},"maxPriorityFeePerGas":{"type":"string","description":"The maximum priority fee per gas in terms of wei for EIP1559 transactions."}}},"TokenInRoute":{"type":"object","properties":{"address":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"symbol":{"type":"string"},"decimals":{"type":"string"},"buyFeeBps":{"type":"string"},"sellFeeBps":{"type":"string"}}},"V2Reserve":{"type":"object","properties":{"token":{"$ref":"#/components/schemas/TokenInRoute"},"quotient":{"type":"string"}}},"V2PoolInRoute":{"type":"object","properties":{"type":{"type":"string","default":"v2-pool"},"address":{"$ref":"#/components/schemas/Address"},"tokenIn":{"$ref":"#/components/schemas/TokenInRoute"},"tokenOut":{"$ref":"#/components/schemas/TokenInRoute"},"reserve0":{"$ref":"#/components/schemas/V2Reserve"},"reserve1":{"$ref":"#/components/schemas/V2Reserve"},"amountIn":{"type":"string"},"amountOut":{"type":"string"}}},"V3PoolInRoute":{"type":"object","properties":{"type":{"type":"string","default":"v3-pool"},"address":{"$ref":"#/components/schemas/Address"},"tokenIn":{"$ref":"#/components/schemas/TokenInRoute"},"tokenOut":{"$ref":"#/components/schemas/TokenInRoute"},"sqrtRatioX96":{"type":"string"},"liquidity":{"type":"string"},"tickCurrent":{"type":"string"},"fee":{"type":"string"},"amountIn":{"type":"string"},"amountOut":{"type":"string"}}},"TransactionHash":{"type":"string","pattern":"^(0x)?[0-9a-fA-F]{64}$"},"ClassicInput":{"type":"object","properties":{"token":{"$ref":"#/components/schemas/Address"},"amount":{"type":"string"}}},"ClassicOutput":{"type":"object","properties":{"token":{"$ref":"#/components/schemas/Address"},"amount":{"type":"string"},"recipient":{"$ref":"#/components/schemas/Address"}}},"RequestId":{"type":"string"},"SpreadOptimization":{"type":"string","enum":["EXECUTION","PRICE"],"description":"For **Dutch Limit** orders only. When set to `EXECUTION`, quotes optimize for looser spreads at higher fill rates. When set to `PRICE`, quotes optimize for tighter spreads at lower fill rates","default":"EXECUTION"},"AutoSlippage":{"type":"string","enum":["DEFAULT"],"description":"For **Classic** swaps only. The auto slippage strategy to employ. If auto slippage is not defined then we don't compute it. If the auto slippage strategy is `DEFAULT`, then the swap will use the default slippage tolerance computation. You cannot define auto slippage and slippage tolerance at the same time. \n\n **NOTE**: slippage is in terms of trade type. If the trade type is `EXACT_INPUT`, then the slippage is in terms of the output token. If the trade type is `EXACT_OUTPUT`, then the slippage is in terms of the input token.","default":"undefined"},"RoutingPreference":{"type":"string","description":"The routing preference determines which protocol to use for the swap. If the routing preference is `UNISWAPX`, then the swap will be routed through the UniswapX Dutch Auction Protocol. If the routing preference is `CLASSIC`, then the swap will be routed through the Classic Protocol. If the routing preference is `BEST_PRICE`, then the swap will be routed through the protocol that provides the best price. When `UNIXWAPX_V2` is passed, the swap will be routed through the UniswapX V2 Dutch Auction Protocol. When `V3_ONLY` is passed, the swap will be routed ONLY through the Uniswap V3 Protocol. When `V2_ONLY` is passed, the swap will be routed ONLY through the Uniswap V2 Protocol.","enum":["CLASSIC","UNISWAPX","BEST_PRICE","BEST_PRICE_V2","UNISWAPX_V2","V3_ONLY","V2_ONLY"],"default":"BEST_PRICE"},"ProtocolItems":{"type":"string","enum":["V2","V3","V4","UNISWAPX","UNISWAPX_V2","PRIORITY"]},"TransactionRequest":{"type":"object","properties":{"to":{"$ref":"#/components/schemas/Address"},"from":{"$ref":"#/components/schemas/Address"},"data":{"type":"string","description":"The calldata for the transaction."},"value":{"type":"string","description":"The value of the transaction in terms of wei in hex format."},"gasLimit":{"type":"string"},"chainId":{"type":"integer"},"maxFeePerGas":{"type":"string"},"maxPriorityFeePerGas":{"type":"string"},"gasPrice":{"type":"string"}},"required":["to","from","data","value","chainId"]},"TransactionFailureReason":{"type":"string","enum":["SIMULATION_ERROR","UNSUPPORTED_SIMULATION"]},"SwapSafetyMode":{"type":"string","enum":["SAFE"],"description":"The safety mode determines the safety level of the swap. If the safety mode is `SAFE`, then the swap will include a SWEEP for the native token."},"IndicativeQuoteRequest":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/TradeType"},"amount":{"type":"string"},"tokenInChainId":{"$ref":"#/components/schemas/ChainId"},"tokenOutChainId":{"$ref":"#/components/schemas/ChainId"},"tokenIn":{"type":"string"},"tokenOut":{"type":"string"}},"required":["type","amount","tokenInChainId","tokenOutChainId","tokenIn","tokenOut"]},"IndicativeQuoteResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"input":{"$ref":"#/components/schemas/IndicativeQuoteToken"},"output":{"$ref":"#/components/schemas/IndicativeQuoteToken"},"type":{"$ref":"#/components/schemas/TradeType"}},"required":["requestId","input","output","type"]},"CreateLPPositionRequest":{"type":"object","properties":{"protocol":{"$ref":"#/components/schemas/ProtocolItems"},"position":{"$ref":"#/components/schemas/Position"},"walletAddress":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"initialPrice":{"type":"string"},"poolLiquidity":{"type":"string"},"currentTick":{"type":"number"},"sqrtRatioX96":{"type":"string"},"amount0":{"type":"string"},"amount1":{"type":"string"},"slippageTolerance":{"type":"string"},"deadline":{"type":"number"},"signature":{"type":"string","description":"The signed permit."},"batchPermitData":{"allOf":[{"$ref":"#/components/schemas/Permit"}]},"simulateTransaction":{"type":"boolean"}}},"CreateLPPositionResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"create":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"}}},"IncreaseLPPositionRequest":{"type":"object","properties":{"protocol":{"$ref":"#/components/schemas/ProtocolItems"},"tokenId":{"type":"number"},"position":{"$ref":"#/components/schemas/Position"},"poolLiquidity":{"type":"string"},"currentTick":{"type":"number"},"sqrtRatioX96":{"type":"string"},"walletAddress":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"amount0":{"type":"string"},"amount1":{"type":"string"},"slippageTolerance":{"type":"string"},"deadline":{"type":"number"},"signature":{"type":"string","description":"The signed permit."},"batchPermitData":{"allOf":[{"$ref":"#/components/schemas/Permit"}]},"simulateTransaction":{"type":"boolean"}}},"IncreaseLPPositionResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"increase":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"}}},"DecreaseLPPositionRequest":{"type":"object","properties":{"protocol":{"$ref":"#/components/schemas/ProtocolItems"},"tokenId":{"type":"number"},"position":{"$ref":"#/components/schemas/Position"},"walletAddress":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"liquidityPercentageToDecrease":{"type":"number"},"slippageTolerance":{"type":"string"},"poolLiquidity":{"type":"string"},"currentTick":{"type":"number"},"sqrtRatioX96":{"type":"string"},"positionLiquidity":{"type":"string"},"expectedTokenOwed0RawAmount":{"type":"string"},"expectedTokenOwed1RawAmount":{"type":"string"},"deadline":{"type":"number"},"simulateTransaction":{"type":"boolean"}}},"DecreaseLPPositionResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"decrease":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"}}},"ClaimLPFeesRequest":{"type":"object","properties":{"protocol":{"$ref":"#/components/schemas/ProtocolItems"},"tokenId":{"type":"number"},"position":{"$ref":"#/components/schemas/Position"},"walletAddress":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"expectedTokenOwed0RawAmount":{"type":"string"},"expectedTokenOwed1RawAmount":{"type":"string"},"simulateTransaction":{"type":"boolean"}}},"ClaimLPFeesResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"claim":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"}}},"MigrateLPPositionRequest":{"type":"object","properties":{"chainId":{"$ref":"#/components/schemas/ChainId"},"tokenId":{"type":"number"},"walletAddress":{"$ref":"#/components/schemas/Address"},"signature":{"type":"string"},"simulateTransaction":{"type":"boolean","default":false}},"required":["chainId","tokenId","walletAddress"]},"MigrateLPPositionResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"migrate":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"}}},"IndicativeQuoteToken":{"type":"object","properties":{"amount":{"type":"string"},"chainId":{"$ref":"#/components/schemas/ChainId"},"token":{"$ref":"#/components/schemas/Address"}}}},"parameters":{"universalRouterVersionHeader":{"name":"x-universal-router-version","in":"header","description":"The version of the Universal Router to use for the swap journey. *MUST* be consistent throughout the API calls.","required":false,"schema":{"$ref":"#/components/schemas/UniversalRouterVersion"}},"addressParam":{"name":"address","in":"path","schema":{"$ref":"#/components/schemas/Address"},"required":true},"tokenIdParam":{"name":"tokenId","in":"path","schema":{"type":"string"},"required":true},"cursorParam":{"name":"cursor","in":"query","schema":{"type":"string"},"required":false},"limitParam":{"name":"limit","in":"query","schema":{"type":"number"},"required":false},"chainIdParam":{"name":"chainId","in":"query","schema":{"$ref":"#/components/schemas/ChainId"},"required":false},"bridgeTokenInChainIdParam":{"name":"tokenInChainId","in":"query","schema":{"$ref":"#/components/schemas/ChainId"},"required":false},"bridgeTokenOutChainIdParam":{"name":"tokenOutChainId","in":"query","schema":{"$ref":"#/components/schemas/ChainId"},"required":false},"tokenInParam":{"name":"tokenIn","in":"query","schema":{"$ref":"#/components/schemas/Address"},"required":false},"tokenOutParam":{"name":"tokenOut","in":"query","schema":{"$ref":"#/components/schemas/Address"},"required":false},"addressPathParam":{"name":"address","in":"query","schema":{"$ref":"#/components/schemas/Address"},"required":false},"orderStatusParam":{"name":"orderStatus","in":"query","description":"Filter by order status.","required":false,"schema":{"$ref":"#/components/schemas/OrderStatus"}},"orderTypeParam":{"name":"orderType","in":"query","description":"The default orderType is Dutch_V1_V2 and will grab both Dutch and Dutch_V2 orders.","required":false,"schema":{"$ref":"#/components/schemas/OrderTypeQuery"}},"orderIdParam":{"name":"orderId","in":"query","required":false,"schema":{"$ref":"#/components/schemas/OrderId"}},"orderIdsParam":{"name":"orderIds","in":"query","required":false,"description":"ids split by commas","schema":{"$ref":"#/components/schemas/OrderIds"}},"swapperParam":{"name":"swapper","in":"query","description":"Filter by swapper address.","required":false,"schema":{"$ref":"#/components/schemas/Address"}},"fillerParam":{"name":"filler","in":"query","description":"Filter by filler address.","required":false,"schema":{"$ref":"#/components/schemas/Address"}},"sortKeyParam":{"name":"sortKey","in":"query","description":"Order the query results by the sort key.","required":false,"schema":{"$ref":"#/components/schemas/SortKey"}},"sortParam":{"name":"sort","in":"query","description":"Sort query. For example: `sort=gt(UNIX_TIMESTAMP)`, `sort=between(1675872827, 1675872930)`, or `lt(1675872930)`.","required":false,"schema":{"type":"string"}},"descParam":{"description":"Sort query results by sortKey in descending order.","name":"desc","in":"query","required":false,"schema":{"type":"string"}},"transactionHashesParam":{"description":"The transaction hashes.","name":"txHashes","in":"query","required":true,"style":"form","explode":false,"schema":{"type":"array","items":{"$ref":"#/components/schemas/TransactionHash"}}}},"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"x-api-key"}}},"security":[{"apiKey":[]}]} \ No newline at end of file +{"openapi":"3.0.0","servers":[{"description":"Uniswap trading APIs Beta","url":"https://beta.trade-api.gateway.uniswap.org/v1"},{"description":"Uniswap trading APIs","url":"https://trade-api.gateway.uniswap.org/v1"}],"info":{"version":"1.0.0","title":"Token Trading","description":"Uniswap trading APIs for fungible tokens."},"paths":{"/check_approval":{"post":{"tags":["Approval"],"summary":"Check if token approval is required","description":"Checks if the swapper has the required approval. If the swapper does not have the required approval, then the response will include the transaction to approve the token. If the swapper has the required approval, then the response will be empty. If the parameter `includeGasInfo` is set to `true`, then the response will include the gas fee for the approval transaction.","operationId":"check_approval","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApprovalRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/ApprovalSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/ApprovalNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/quote":{"post":{"tags":["Quote"],"summary":"Get a quote","description":"Get a quote according to the provided configuration. Optionally adds a fee to the quote according to the API key being used. The fee is **ALWAYS** taken from the output token. If there is a fee and the trade is `EXACT_INPUT`, then the output amount will **NOT** include the fee subtraction. For `EXACT_INPUT` swaps, use `portionBips` to calculate the fee from the quoted amount. If there is a fee and the trade is `EXACT_OUTPUT`, then the input amount will **NOT** include the fee addition to account for the fee. For `EXACT_OUTPUT` swaps, use `portionAmount` to get the fee. \n \n We also support Wrapping and Unwrapping of native tokens on their respective chains. Wrapping and Unwrapping only works for when `routingPreference` is `CLASSIC`, `BEST_PRICE`, or `BEST_PRICE_V2`. We do not support `UNISWAPX` or `UNISWAPX_V2` for these actions.","operationId":"aggregator_quote","security":[{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/universalRouterVersionHeader"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/QuoteSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/Unauthorized401"},"404":{"$ref":"#/components/responses/QuoteNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/order":{"post":{"tags":["Order"],"summary":"Create a gasless order","description":"Submits a new gasless encoded order. The order will be validated and if valid, will be submitted to the filler network. The network will try to fill the order at the quoted `startAmount`, and if not, the amount will start decaying until the `endAmount` is reached. While the order is within `decayEndTime`, the `orderStatus` is `open`. If the order does not get filled after the `decayEndTime` has passed, that is reflected in the `expired` `orderStatus`. then The order will be filled at the best price possible. Once the order is filled, `orderStatus` becomes `filled`.","operationId":"post_order","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderRequest"}}}},"responses":{"201":{"$ref":"#/components/responses/OrderSuccess201"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/Unauthorized401"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/orders":{"get":{"tags":["Order"],"summary":"Get gasless orders","description":"Retrieve gasless orders filtered by query param(s). Some fields on the order can be used as query param.","operationId":"get_order","security":[{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/orderTypeParam"},{"$ref":"#/components/parameters/orderIdParam"},{"$ref":"#/components/parameters/orderIdsParam"},{"$ref":"#/components/parameters/limitParam"},{"$ref":"#/components/parameters/orderStatusParam"},{"$ref":"#/components/parameters/swapperParam"},{"$ref":"#/components/parameters/sortKeyParam"},{"$ref":"#/components/parameters/sortParam"},{"$ref":"#/components/parameters/fillerParam"},{"$ref":"#/components/parameters/cursorParam"}],"responses":{"200":{"$ref":"#/components/responses/OrdersSuccess200"},"400":{"$ref":"#/components/responses/OrdersBadRequest400"},"404":{"$ref":"#/components/responses/OrdersNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/swap":{"post":{"tags":["Swap"],"summary":"Create swap calldata","description":"Create the calldata for a swap transaction (including wrap/unwrap) against the Uniswap Protocols. If the `quote` parameter includes the fee parameters, then the calldata will include the fee disbursement. The gas estimates will be **more precise** when the the response calldata would be valid if submitted on-chain.","operationId":"create_swap_transaction","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSwapRequest"}}}},"parameters":[{"$ref":"#/components/parameters/universalRouterVersionHeader"}],"responses":{"200":{"$ref":"#/components/responses/CreateSwapSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/SwapUnauthorized401"},"404":{"$ref":"#/components/responses/SwapNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/swaps":{"get":{"tags":["Swap"],"summary":"Get swaps status","description":"Get the status of a swap or bridge transactions.","operationId":"get_swaps","security":[{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/transactionHashesParam"},{"$ref":"#/components/parameters/chainIdParam"}],"responses":{"200":{"$ref":"#/components/responses/GetSwapsSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"404":{"$ref":"#/components/responses/SwapNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/indicative_quote":{"post":{"tags":["IndicativeQuote"],"summary":"Get an indicative quote","description":"Get an indicative quote according to the provided configuration. The quote will not include a fee.","operationId":"indicative_quote","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndicativeQuoteRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/IndicativeQuoteSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"404":{"$ref":"#/components/responses/QuoteNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/send":{"post":{"tags":["Send"],"summary":"Create send calldata","description":"Create the calldata for a send transaction.","operationId":"create_send","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSendRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/CreateSendSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/Unauthorized401"},"404":{"$ref":"#/components/responses/SendNotFound404"},"429":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/swappable_tokens":{"get":{"tags":["SwappableTokens"],"summary":"Get swappable tokens","description":"Get the swappable tokens for the given configuration. Either tokenIn (with tokenInChainId or (tokenInChainId and tokenOutChainId)) or tokenOut (with tokenOutChainId or (tokenOutChainId and tokenInChainId)) must be provided but not both.","operationId":"get_swappable_tokens","security":[{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/tokenInParam"},{"$ref":"#/components/parameters/tokenOutParam"},{"$ref":"#/components/parameters/bridgeTokenInChainIdParam"},{"$ref":"#/components/parameters/bridgeTokenOutChainIdParam"}],"responses":{"200":{"$ref":"#/components/responses/GetSwappableTokensSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/Unauthorized401"},"404":{"$ref":"#/components/responses/QuoteNotFound404"},"429":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/limit_order_quote":{"post":{"tags":["LimitOrderQuote"],"summary":"Get a limit order quote","description":"Get a quote for a limit order according to the provided configuration.","operationId":"get_limit_order_quote","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LimitOrderQuoteRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/LimitOrderQuoteSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/Unauthorized401"},"404":{"$ref":"#/components/responses/QuoteNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/lp/approve":{"post":{"tags":["Liquidity"],"summary":"Check if tokens and permits need to be approved to add liquidity","description":"Checks if the wallet address has the required approvals. If the wallet address does not have the required approval, then the response will include the transactions to approve the tokens. If the wallet address has the required approval, then the response will be empty for the corresponding tokens. If the parameter `simulateTransaction` is set to `true`, then the response will include the gas fees for the approval transactions.","operationId":"check_approval_lp","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckApprovalLPRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/CheckApprovalLPSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/ApprovalNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/lp/create":{"post":{"tags":["Liquidity"],"summary":"Create pool and position calldata","description":"Create pool and position calldata. If the pool is not yet created, then the response will include the transaction to create the new pool with the initial price. If the pool is already created, then the response will not have the transaction to create the pool. The response will also have the transaction to create the position for the corresponding pool. If the parameter `simulateTransaction` is set to `true`, then the response will include the gas fees for the creation transactions.","operationId":"create_lp_position","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLPPositionRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/CreateLPPositionSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/LPNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/lp/increase":{"post":{"tags":["Liquidity"],"summary":"Increase LP position calldata","description":"The response will also have the transaction to increase the position for the corresponding pool. If the parameter `simulateTransaction` is set to `true`, then the response will include the gas fees for the increase transaction.","operationId":"increase_lp_position","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IncreaseLPPositionRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/IncreaseLPPositionSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/LPNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/lp/decrease":{"post":{"tags":["Liquidity"],"summary":"Decrease LP position calldata","description":"The response will also have the transaction to decrease the position for the corresponding pool. If the parameter `simulateTransaction` is set to `true`, then the response will include the gas fees for the decrease transaction.","operationId":"decrease_lp_position","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecreaseLPPositionRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/DecreaseLPPositionSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/LPNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/lp/claim":{"post":{"tags":["Liquidity"],"summary":"Claim LP fees calldata","description":"The response will also have the transaction to claim the fees for an LP position for the corresponding pool. If the parameter `simulateTransaction` is set to `true`, then the response will include the gas fees for the claim transaction.","operationId":"claim_lp_fees","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimLPFeesRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/ClaimLPFeesSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/LPNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}},"/lp/migrate":{"post":{"tags":["Liquidity"],"summary":"Migrate LP position calldata","description":"The response will also have the transaction to migrate the position for the corresponding pool. If the parameter `simulateTransaction` is set to `true`, then the response will include the gas fees for the migrate transaction.","operationId":"migrate_lp_position","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MigrateLPPositionRequest"}}}},"responses":{"200":{"$ref":"#/components/responses/MigrateLPPositionSuccess200"},"400":{"$ref":"#/components/responses/BadRequest400"},"401":{"$ref":"#/components/responses/ApprovalUnauthorized401"},"404":{"$ref":"#/components/responses/LPNotFound404"},"419":{"$ref":"#/components/responses/RateLimitedErr429"},"500":{"$ref":"#/components/responses/InternalErr500"},"504":{"$ref":"#/components/responses/Timeout504"}}}}},"components":{"responses":{"OrdersSuccess200":{"description":"The request orders matching the query parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetOrdersResponse"}}}},"OrderSuccess201":{"description":"Encoded order submitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderResponse"}}}},"QuoteSuccess200":{"description":"Quote request successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteResponse"}}}},"LimitOrderQuoteSuccess200":{"description":"Limit Order Quote request successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LimitOrderQuoteResponse"}}}},"CheckApprovalLPSuccess200":{"description":"Approve LP successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckApprovalLPResponse"}}}},"ApprovalSuccess200":{"description":"Check approval successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApprovalResponse"}}}},"CreateSendSuccess200":{"description":"Create send successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSendResponse"}}}},"CreateSwapSuccess200":{"description":"Create swap successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSwapResponse"}}}},"GetSwapsSuccess200":{"description":"Get swap successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSwapsResponse"}}}},"GetSwappableTokensSuccess200":{"description":"Get swappable tokens successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSwappableTokensResponse"}}}},"CreateLPPositionSuccess200":{"description":"Create LP Position successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLPPositionResponse"}}}},"IncreaseLPPositionSuccess200":{"description":"Create LP Position successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IncreaseLPPositionResponse"}}}},"DecreaseLPPositionSuccess200":{"description":"Decrease LP Position successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecreaseLPPositionResponse"}}}},"ClaimLPFeesSuccess200":{"description":"Claim LP Fees successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimLPFeesResponse"}}}},"MigrateLPPositionSuccess200":{"description":"Migrate LP Position successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MigrateLPPositionResponse"}}}},"BadRequest400":{"description":"RequestValidationError, Bad Input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err400"}}}},"ApprovalUnauthorized401":{"description":"UnauthorizedError eg. Account is blocked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err401"}}}},"ApprovalNotFound404":{"description":"ResourceNotFound eg. Token allowance not found or Gas info not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"Unauthorized401":{"description":"UnauthorizedError eg. Account is blocked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err401"}}}},"QuoteNotFound404":{"description":"ResourceNotFound eg. No quotes available or Gas fee/price not available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"SendNotFound404":{"description":"ResourceNotFound eg. Gas fee not available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"SwapBadRequest400":{"description":"RequestValidationError, Bad Input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err400"}}}},"SwapUnauthorized401":{"description":"UnauthorizedError eg. Account is blocked or Fee is not enabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err401"}}}},"SwapNotFound404":{"description":"ResourceNotFound eg. No quotes available or Gas fee/price not available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"OrdersNotFound404":{"description":"Orders not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"LPNotFound404":{"description":"ResourceNotFound eg. Cant Find LP Position.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err404"}}}},"OrdersBadRequest400":{"description":"RequestValidationError eg. Token allowance not valid or Insufficient Funds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err400"}}}},"RateLimitedErr429":{"description":"Ratelimited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err429"}}}},"InternalErr500":{"description":"Unexpected error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err500"}}}},"Timeout504":{"description":"Request duration limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Err504"}}}},"IndicativeQuoteSuccess200":{"description":"Indicative quote request successful.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndicativeQuoteResponse"}}}}},"schemas":{"NullablePermit":{"allOf":[{"$ref":"#/components/schemas/Permit"},{"type":"object","nullable":true}]},"TokenAmount":{"type":"string"},"SwapStatus":{"type":"string","enum":["PENDING","SUCCESS","NOT_FOUND","FAILED","EXPIRED"]},"GetSwapsResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"swaps":{"type":"array","items":{"type":"object","properties":{"swapType":{"$ref":"#/components/schemas/Routing"},"status":{"$ref":"#/components/schemas/SwapStatus"},"txHash":{"type":"string"},"swapId":{"type":"number"}}}}},"required":["requestId","status"]},"GetSwappableTokensResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"tokens":{"type":"array","items":{"type":"object","properties":{"address":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"name":{"type":"string"},"symbol":{"type":"string"},"project":{"$ref":"#/components/schemas/TokenProject"},"isSpam":{"type":"boolean"},"decimals":{"type":"number"}},"required":["address","chainId","name","symbol","project","decimals"]}}},"required":["requestId","tokens"]},"CreateSwapRequest":{"type":"object","description":"The parameters **signature** and **permitData** should only be included if *permitData* was returned from **/quote**.","properties":{"quote":{"oneOf":[{"$ref":"#/components/schemas/ClassicQuote"},{"$ref":"#/components/schemas/WrapUnwrapQuote"},{"$ref":"#/components/schemas/BridgeQuote"}]},"signature":{"type":"string","description":"The signed permit."},"includeGasInfo":{"type":"boolean","default":false,"deprecated":true,"description":"Use `refreshGasPrice` instead."},"refreshGasPrice":{"type":"boolean","default":false,"description":"If true, the gas price will be re-fetched from the network."},"simulateTransaction":{"type":"boolean","default":false,"description":"If true, the transaction will be simulated. If the simulation results on an onchain error, endpoint will return an error."},"permitData":{"allOf":[{"$ref":"#/components/schemas/Permit"}]},"safetyMode":{"$ref":"#/components/schemas/SwapSafetyMode"},"deadline":{"type":"integer","description":"The deadline for the swap in unix timestamp format. If the deadline is not defined OR in the past then the default deadline is 30 minutes."},"urgency":{"$ref":"#/components/schemas/Urgency"}},"required":["quote"]},"CreateSendRequest":{"type":"object","properties":{"sender":{"$ref":"#/components/schemas/Address"},"recipient":{"$ref":"#/components/schemas/Address"},"token":{"$ref":"#/components/schemas/Address"},"amount":{"$ref":"#/components/schemas/TokenAmount"},"chainId":{"$ref":"#/components/schemas/ChainId"},"urgency":{"$ref":"#/components/schemas/Urgency"}},"required":["sender","recipient","token","amount"]},"UniversalRouterVersion":{"type":"string","enum":["1.2","2.0"],"default":"1.2"},"Address":{"type":"string","pattern":"^(0x)?[0-9a-fA-F]{40}$"},"Position":{"type":"object","properties":{"pool":{"$ref":"#/components/schemas/Pool"},"tickLower":{"type":"number"},"tickUpper":{"type":"number"}},"required":["pool"]},"Pool":{"type":"object","properties":{"token0":{"$ref":"#/components/schemas/Address"},"token1":{"$ref":"#/components/schemas/Address"},"fee":{"type":"number"},"tickSpacing":{"type":"number"},"hooks":{"$ref":"#/components/schemas/Address"}},"required":["token0","token1"]},"ClassicGasUseEstimateUSD":{"description":"The gas fee you would pay if you opted for a CLASSIC swap over a Uniswap X order in terms of USD.","type":"string"},"CreateSwapResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"swap":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"}},"required":["requestId","swap"]},"CreateSendResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"send":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"},"gasFeeUSD":{"type":"number"}},"required":["requestId","send"]},"QuoteResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"quote":{"$ref":"#/components/schemas/Quote"},"routing":{"$ref":"#/components/schemas/Routing"},"permitData":{"$ref":"#/components/schemas/NullablePermit"}},"required":["routing","quote","permitData","requestId"]},"LimitOrderQuoteResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"quote":{"$ref":"#/components/schemas/DutchQuote"},"routing":{"type":"string","enum":["LIMIT_ORDER"]},"permitData":{"$ref":"#/components/schemas/NullablePermit"}},"required":["routing","quote","permitData","requestId"]},"QuoteRequest":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/TradeType"},"amount":{"type":"string"},"tokenInChainId":{"$ref":"#/components/schemas/ChainId"},"tokenOutChainId":{"$ref":"#/components/schemas/ChainId"},"tokenIn":{"type":"string"},"tokenOut":{"type":"string"},"swapper":{"$ref":"#/components/schemas/Address"},"slippageTolerance":{"description":"For **Classic** swaps, the slippage tolerance is the maximum amount the price can change between the time the transaction is submitted and the time it is executed. The slippage tolerance is represented as a percentage of the total value of the swap. \n\n Slippage tolerance works differently in **DutchLimit** swaps, it does not set a limit on the Spread in an order. See [here](https://uniswap-docs.readme.io/reference/faqs#why-do-the-uniswapx-quotes-have-more-slippage-than-the-tolerance-i-set) for more information. \n\n **NOTE**: slippage is in terms of trade type. If the trade type is `EXACT_INPUT`, then the slippage is in terms of the output token. If the trade type is `EXACT_OUTPUT`, then the slippage is in terms of the input token.","type":"number"},"autoSlippage":{"$ref":"#/components/schemas/AutoSlippage"},"routingPreference":{"$ref":"#/components/schemas/RoutingPreference"},"protocols":{"$ref":"#/components/schemas/Protocols"},"spreadOptimization":{"$ref":"#/components/schemas/SpreadOptimization"},"urgency":{"$ref":"#/components/schemas/Urgency"}},"required":["type","amount","tokenInChainId","tokenOutChainId","tokenIn","tokenOut","swapper"]},"LimitOrderQuoteRequest":{"type":"object","properties":{"swapper":{"$ref":"#/components/schemas/Address"},"limitPrice":{"type":"string"},"amount":{"type":"string"},"orderDeadline":{"type":"number"},"type":{"$ref":"#/components/schemas/TradeType"},"tokenIn":{"type":"string"},"tokenOut":{"type":"string"},"tokenInChainId":{"$ref":"#/components/schemas/ChainId"},"tokenOutChainId":{"$ref":"#/components/schemas/ChainId"}},"required":["swapper","type","amount","tokenIn","tokenOut","tokenInChainId","tokenOutChainId"]},"GetOrdersResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"orders":{"type":"array","items":{"$ref":"#/components/schemas/UniswapXOrder"}},"cursor":{"type":"string"}},"required":["orders","requestId"]},"OrderResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"orderId":{"type":"string"},"orderStatus":{"$ref":"#/components/schemas/OrderStatus"}},"required":["requestId","orderId","orderStatus"]},"OrderRequest":{"type":"object","properties":{"signature":{"type":"string","description":"The signed permit."},"quote":{"oneOf":[{"$ref":"#/components/schemas/DutchQuote"},{"$ref":"#/components/schemas/DutchQuoteV2"},{"$ref":"#/components/schemas/PriorityQuote"}]},"routing":{"$ref":"#/components/schemas/Routing"}},"required":["signature","quote"]},"Urgency":{"type":"string","enum":["normal","fast","urgent"],"description":"The urgency determines the urgency of the transaction. The default value is `urgent`.","default":"urgent"},"Protocols":{"type":"array","items":{"$ref":"#/components/schemas/ProtocolItems"},"description":"The protocols to use for the swap/order. If the `protocols` field is defined, then you can only set the `routingPreference` to `BEST_PRICE`"},"Err400":{"type":"object","properties":{"errorCode":{"default":"RequestValidationError","type":"string"},"detail":{"type":"string"}}},"Err401":{"type":"object","properties":{"errorCode":{"default":"UnauthorizedError","type":"string"},"detail":{"type":"string"}}},"Err404":{"type":"object","properties":{"errorCode":{"enum":["ResourceNotFound","QuoteAmountTooLowError"],"type":"string"},"detail":{"type":"string"}}},"Err429":{"type":"object","properties":{"errorCode":{"default":"Ratelimited","type":"string"},"detail":{"type":"string"}}},"Err500":{"type":"object","properties":{"errorCode":{"default":"InternalServerError","type":"string"},"detail":{"type":"string"}}},"Err504":{"type":"object","properties":{"errorCode":{"default":"Timeout","type":"string"},"detail":{"type":"string"}}},"ChainId":{"type":"number","enum":[1,10,56,137,8453,42161,81457,43114,42220,7777777,324,11155111,1301,480]},"OrderInput":{"type":"object","properties":{"token":{"type":"string"},"startAmount":{"type":"string"},"endAmount":{"type":"string"}},"required":["token"]},"OrderOutput":{"type":"object","properties":{"token":{"type":"string"},"startAmount":{"type":"string"},"endAmount":{"type":"string"},"isFeeOutput":{"type":"boolean"},"recipient":{"type":"string"}},"required":["token"]},"CosignerData":{"type":"object","properties":{"decayStartTime":{"type":"number"},"decayEndTime":{"type":"number"},"exclusiveFiller":{"type":"string"},"inputOverride":{"type":"string"},"outputOverrides":{"type":"array","items":{"type":"string"}}}},"SettledAmount":{"type":"object","properties":{"tokenOut":{"$ref":"#/components/schemas/Address"},"amountOut":{"type":"string"},"tokenIn":{"$ref":"#/components/schemas/Address"},"amountIn":{"type":"string"}}},"OrderType":{"type":"string","enum":["DutchLimit","Dutch","Dutch_V2"]},"OrderTypeQuery":{"type":"string","enum":["Dutch","Dutch_V2","Dutch_V1_V2","Limit","Priority"]},"UniswapXOrder":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/OrderType"},"encodedOrder":{"type":"string"},"signature":{"type":"string"},"nonce":{"type":"string"},"orderStatus":{"$ref":"#/components/schemas/OrderStatus"},"orderId":{"type":"string"},"chainId":{"$ref":"#/components/schemas/ChainId"},"quoteId":{"type":"string"},"swapper":{"type":"string"},"txHash":{"type":"string"},"input":{"$ref":"#/components/schemas/OrderInput"},"outputs":{"type":"array","items":{"$ref":"#/components/schemas/OrderOutput"}},"settledAmounts":{"type":"array","items":{"$ref":"#/components/schemas/SettledAmount"}},"cosignature":{"type":"string"},"cosignerData":{"$ref":"#/components/schemas/CosignerData"}},"required":["encodedOrder","signature","nonce","orderId","orderStatus","chainId","type"]},"SortKey":{"type":"string","enum":["createdAt"]},"OrderId":{"type":"string"},"OrderIds":{"type":"string"},"OrderStatus":{"type":"string","enum":["open","expired","error","cancelled","filled","unverified","insufficient-funds"]},"Permit":{"type":"object","properties":{"domain":{"type":"object"},"values":{"type":"object"},"types":{"type":"object"}}},"TokenProject":{"type":"object","properties":{"logo":{"$ref":"#/components/schemas/TokenProjectLogo","nullable":true},"safetyLevel":{"$ref":"#/components/schemas/SafetyLevel"},"isSpam":{"type":"boolean"}},"required":["logo","safetyLevel","isSpam"]},"TokenProjectLogo":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"]},"DutchInput":{"type":"object","properties":{"startAmount":{"type":"string"},"endAmount":{"type":"string"},"token":{"type":"string"}},"required":["startAmount","endAmount","type"]},"DutchOutput":{"type":"object","properties":{"startAmount":{"type":"string"},"endAmount":{"type":"string"},"token":{"type":"string"},"recipient":{"type":"string"}},"required":["startAmount","endAmount","token","recipient"]},"DutchOrderInfo":{"type":"object","properties":{"chainId":{"$ref":"#/components/schemas/ChainId"},"nonce":{"type":"string"},"reactor":{"type":"string"},"swapper":{"type":"string"},"deadline":{"type":"number"},"additionalValidationContract":{"type":"string"},"additionalValidationData":{"type":"string"},"decayStartTime":{"type":"number"},"decayEndTime":{"type":"number"},"exclusiveFiller":{"type":"string"},"exclusivityOverrideBps":{"type":"string"},"input":{"$ref":"#/components/schemas/DutchInput"},"outputs":{"type":"array","items":{"$ref":"#/components/schemas/DutchOutput"}}},"required":["chainId","nonce","reactor","swapper","deadline","validationContract","validationData","startTime","endTime","exclusiveFiller","exclusivityOverrideBps","input","outputs"]},"DutchOrderInfoV2":{"type":"object","properties":{"chainId":{"$ref":"#/components/schemas/ChainId"},"nonce":{"type":"string"},"reactor":{"type":"string"},"swapper":{"type":"string"},"deadline":{"type":"number"},"additionalValidationContract":{"type":"string"},"additionalValidationData":{"type":"string"},"input":{"$ref":"#/components/schemas/DutchInput"},"outputs":{"type":"array","items":{"$ref":"#/components/schemas/DutchOutput"}},"cosigner":{"$ref":"#/components/schemas/Address"}},"required":["chainId","nonce","reactor","swapper","deadline","validationContract","validationData","startTime","endTime","exclusiveFiller","exclusivityOverrideBps","input","outputs"]},"DutchQuote":{"type":"object","properties":{"encodedOrder":{"type":"string"},"orderId":{"type":"string"},"orderInfo":{"$ref":"#/components/schemas/DutchOrderInfo"},"portionBips":{"type":"number"},"portionAmount":{"type":"string"},"portionRecipient":{"$ref":"#/components/schemas/Address"},"quoteId":{"type":"string"},"slippageTolerance":{"type":"number"},"classicGasUseEstimateUSD":{"$ref":"#/components/schemas/ClassicGasUseEstimateUSD"}},"required":["encodedOrder","orderInfo","orderId"]},"DutchQuoteV2":{"type":"object","properties":{"encodedOrder":{"type":"string"},"orderId":{"type":"string"},"orderInfo":{"$ref":"#/components/schemas/DutchOrderInfoV2"},"portionBips":{"type":"number"},"portionAmount":{"type":"string"},"portionRecipient":{"$ref":"#/components/schemas/Address"},"quoteId":{"type":"string"},"slippageTolerance":{"type":"number"},"deadlineBufferSecs":{"type":"number"},"classicGasUseEstimateUSD":{"$ref":"#/components/schemas/ClassicGasUseEstimateUSD"}},"required":["encodedOrder","orderInfo","orderId"]},"PriorityInput":{"type":"object","properties":{"amount":{"type":"string"},"token":{"type":"string"},"mpsPerPriorityFeeWei":{"type":"string"}},"required":["amount","token","mpsPerPriorityFeeWei"]},"PriorityOutput":{"type":"object","properties":{"amount":{"type":"string"},"token":{"type":"string"},"recipient":{"type":"string"},"mpsPerPriorityFeeWei":{"type":"string"}},"required":["amount","token","recipient","mpsPerPriorityFeeWei"]},"PriorityOrderInfo":{"type":"object","properties":{"chainId":{"$ref":"#/components/schemas/ChainId"},"nonce":{"type":"string"},"reactor":{"type":"string"},"swapper":{"type":"string"},"deadline":{"type":"number"},"additionalValidationContract":{"type":"string"},"additionalValidationData":{"type":"string"},"auctionStartBlock":{"type":"string"},"baselinePriorityFeeWei":{"type":"string"},"input":{"$ref":"#/components/schemas/PriorityInput"},"outputs":{"type":"array","items":{"$ref":"#/components/schemas/PriorityOutput"}},"cosigner":{"$ref":"#/components/schemas/Address"}},"required":["chainId","nonce","reactor","swapper","deadline","validationContract","validationData","auctionStartBlock","baselinePriorityFeeWei","input","outputs","cosigner"]},"PriorityQuote":{"type":"object","properties":{"encodedOrder":{"type":"string"},"orderId":{"type":"string"},"orderInfo":{"$ref":"#/components/schemas/PriorityOrderInfo"},"portionBips":{"type":"number"},"portionAmount":{"type":"string"},"portionRecipient":{"$ref":"#/components/schemas/Address"},"quoteId":{"type":"string"},"slippageTolerance":{"type":"number"},"deadlineBufferSecs":{"type":"number"},"classicGasUseEstimateUSD":{"$ref":"#/components/schemas/ClassicGasUseEstimateUSD"}},"required":["encodedOrder","orderInfo","orderId"]},"BridgeQuote":{"type":"object","properties":{"quoteId":{"type":"string"},"chainId":{"$ref":"#/components/schemas/ChainId"},"destinationChainId":{"$ref":"#/components/schemas/ChainId"},"swapper":{"$ref":"#/components/schemas/Address"},"input":{"$ref":"#/components/schemas/ClassicInput"},"output":{"$ref":"#/components/schemas/ClassicOutput"},"tradeType":{"$ref":"#/components/schemas/TradeType"},"quoteTimestamp":{"type":"number"},"gasPrice":{"type":"string"},"maxFeePerGas":{"type":"string"},"maxPriorityFeePerGas":{"type":"string"},"gasFee":{"type":"string"},"gasUseEstimate":{"type":"string"},"gasFeeUSD":{"type":"string"},"portionBips":{"type":"number"},"portionAmount":{"type":"string"},"portionRecipient":{"$ref":"#/components/schemas/Address"},"estimatedFillTimeMs":{"type":"number"}}},"SafetyLevel":{"type":"string","enum":["BLOCKED","MEDIUM_WARNING","STRONG_WARNING","VERIFIED"]},"TradeType":{"type":"string","enum":["EXACT_INPUT","EXACT_OUTPUT"]},"Routing":{"type":"string","enum":["DUTCH_LIMIT","CLASSIC","DUTCH_V2","BRIDGE","LIMIT_ORDER","PRIORITY"]},"Quote":{"oneOf":[{"$ref":"#/components/schemas/DutchQuote"},{"$ref":"#/components/schemas/ClassicQuote"},{"$ref":"#/components/schemas/WrapUnwrapQuote"},{"$ref":"#/components/schemas/DutchQuoteV2"},{"$ref":"#/components/schemas/BridgeQuote"},{"$ref":"#/components/schemas/PriorityQuote"}]},"CheckApprovalLPRequest":{"type":"object","properties":{"protocol":{"$ref":"#/components/schemas/ProtocolItems"},"token0":{"$ref":"#/components/schemas/Address"},"token1":{"$ref":"#/components/schemas/Address"},"positionToken":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"walletAddress":{"$ref":"#/components/schemas/Address"},"amount0":{"type":"string"},"amount1":{"type":"string"},"positionAmount":{"type":"string"},"simulateTransaction":{"type":"boolean"}}},"CheckApprovalLPResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"token0Approval":{"$ref":"#/components/schemas/TransactionRequest"},"token1Approval":{"$ref":"#/components/schemas/TransactionRequest"},"positionTokenApproval":{"$ref":"#/components/schemas/TransactionRequest"},"permitData":{"$ref":"#/components/schemas/NullablePermit"},"gasFeeToken0Approval":{"type":"string"},"gasFeeToken1Approval":{"type":"string"},"gasFeePositionTokenApproval":{"type":"string"}}},"ApprovalRequest":{"type":"object","properties":{"walletAddress":{"$ref":"#/components/schemas/Address"},"token":{"$ref":"#/components/schemas/Address"},"amount":{"$ref":"#/components/schemas/TokenAmount"},"chainId":{"$ref":"#/components/schemas/ChainId"},"urgency":{"$ref":"#/components/schemas/Urgency"},"includeGasInfo":{"type":"boolean","default":false},"tokenOut":{"$ref":"#/components/schemas/Address"},"tokenOutChainId":{"$ref":"#/components/schemas/ChainId"}},"required":["walletAddress","token","amount"]},"ApprovalResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"approval":{"$ref":"#/components/schemas/TransactionRequest"},"cancel":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"},"cancelGasFee":{"type":"string"}},"required":["requestId","approval","cancel"]},"ClassicQuote":{"type":"object","properties":{"input":{"$ref":"#/components/schemas/ClassicInput"},"output":{"$ref":"#/components/schemas/ClassicOutput"},"swapper":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"slippage":{"type":"number"},"tradeType":{"$ref":"#/components/schemas/TradeType"},"gasFee":{"type":"string","description":"The gas fee in terms of wei. It does NOT include the additional gas for token approvals."},"gasFeeUSD":{"type":"string","description":"The gas fee in terms of USD. It does NOT include the additional gas for token approvals."},"gasFeeQuote":{"type":"string","description":"The gas fee in terms of the quoted currency. It does NOT include the additional gas for token approvals."},"route":{"type":"array","items":{"type":"array","items":{"oneOf":[{"$ref":"#/components/schemas/V3PoolInRoute"},{"$ref":"#/components/schemas/V2PoolInRoute"},{"$ref":"#/components/schemas/V4PoolInRoute"}]}}},"portionBips":{"type":"number","description":"The portion of the swap that will be taken as a fee. The fee will be taken from the output token."},"portionAmount":{"type":"string","description":"The amount of the swap that will be taken as a fee. The fee will be taken from the output token."},"portionRecipient":{"$ref":"#/components/schemas/Address"},"routeString":{"type":"string","description":"The route in string format."},"quoteId":{"type":"string","description":"The quote id. Used for analytics purposes."},"gasUseEstimate":{"type":"string","description":"The estimated gas use. It does NOT include the additional gas for token approvals."},"blockNumber":{"type":"string","description":"The current block number."},"gasPrice":{"type":"string","description":"The gas price in terms of wei for pre EIP1559 transactions."},"maxFeePerGas":{"type":"string","description":"The maximum fee per gas in terms of wei for EIP1559 transactions."},"maxPriorityFeePerGas":{"type":"string","description":"The maximum priority fee per gas in terms of wei for EIP1559 transactions."},"txFailureReasons":{"type":"array","items":{"$ref":"#/components/schemas/TransactionFailureReason"}},"priceImpact":{"type":"number","description":"The impact the trade has on the market price of the pool, between 0-100 percent"}}},"WrapUnwrapQuote":{"type":"object","properties":{"swapper":{"$ref":"#/components/schemas/Address"},"input":{"$ref":"#/components/schemas/ClassicInput"},"output":{"$ref":"#/components/schemas/ClassicOutput"},"chainId":{"$ref":"#/components/schemas/ChainId"},"tradeType":{"$ref":"#/components/schemas/TradeType"},"gasFee":{"type":"string","description":"The gas fee in terms of wei."},"gasFeeUSD":{"type":"string","description":"The gas fee in terms of USD."},"gasFeeQuote":{"type":"string","description":"The gas fee in terms of the quoted currency."},"gasUseEstimate":{"type":"string","description":"The estimated gas use."},"gasPrice":{"type":"string","description":"The gas price in terms of wei for pre EIP1559 transactions."},"maxFeePerGas":{"type":"string","description":"The maximum fee per gas in terms of wei for EIP1559 transactions."},"maxPriorityFeePerGas":{"type":"string","description":"The maximum priority fee per gas in terms of wei for EIP1559 transactions."}}},"TokenInRoute":{"type":"object","properties":{"address":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"symbol":{"type":"string"},"decimals":{"type":"string"},"buyFeeBps":{"type":"string"},"sellFeeBps":{"type":"string"}}},"V2Reserve":{"type":"object","properties":{"token":{"$ref":"#/components/schemas/TokenInRoute"},"quotient":{"type":"string"}}},"V2PoolInRoute":{"type":"object","properties":{"type":{"type":"string","default":"v2-pool"},"address":{"$ref":"#/components/schemas/Address"},"tokenIn":{"$ref":"#/components/schemas/TokenInRoute"},"tokenOut":{"$ref":"#/components/schemas/TokenInRoute"},"reserve0":{"$ref":"#/components/schemas/V2Reserve"},"reserve1":{"$ref":"#/components/schemas/V2Reserve"},"amountIn":{"type":"string"},"amountOut":{"type":"string"}}},"V3PoolInRoute":{"type":"object","properties":{"type":{"type":"string","default":"v3-pool"},"address":{"$ref":"#/components/schemas/Address"},"tokenIn":{"$ref":"#/components/schemas/TokenInRoute"},"tokenOut":{"$ref":"#/components/schemas/TokenInRoute"},"sqrtRatioX96":{"type":"string"},"liquidity":{"type":"string"},"tickCurrent":{"type":"string"},"fee":{"type":"string"},"amountIn":{"type":"string"},"amountOut":{"type":"string"}}},"V4PoolInRoute":{"type":"object","properties":{"type":{"type":"string","default":"v4-pool"},"address":{"$ref":"#/components/schemas/Address"},"tokenIn":{"$ref":"#/components/schemas/TokenInRoute"},"tokenOut":{"$ref":"#/components/schemas/TokenInRoute"},"sqrtRatioX96":{"type":"string"},"liquidity":{"type":"string"},"tickCurrent":{"type":"string"},"fee":{"type":"string"},"tickSpacing":{"type":"string"},"hooks":{"type":"string"},"amountIn":{"type":"string"},"amountOut":{"type":"string"}},"required":["type","address","tokenIn","tokenOut","sqrtRatioX96","liquidity","tickCurrent","fee","tickSpacing","hooks"]},"TransactionHash":{"type":"string","pattern":"^(0x)?[0-9a-fA-F]{64}$"},"ClassicInput":{"type":"object","properties":{"token":{"$ref":"#/components/schemas/Address"},"amount":{"type":"string"}}},"ClassicOutput":{"type":"object","properties":{"token":{"$ref":"#/components/schemas/Address"},"amount":{"type":"string"},"recipient":{"$ref":"#/components/schemas/Address"}}},"RequestId":{"type":"string"},"SpreadOptimization":{"type":"string","enum":["EXECUTION","PRICE"],"description":"For **Dutch Limit** orders only. When set to `EXECUTION`, quotes optimize for looser spreads at higher fill rates. When set to `PRICE`, quotes optimize for tighter spreads at lower fill rates","default":"EXECUTION"},"AutoSlippage":{"type":"string","enum":["DEFAULT"],"description":"For **Classic** swaps only. The auto slippage strategy to employ. If auto slippage is not defined then we don't compute it. If the auto slippage strategy is `DEFAULT`, then the swap will use the default slippage tolerance computation. You cannot define auto slippage and slippage tolerance at the same time. \n\n **NOTE**: slippage is in terms of trade type. If the trade type is `EXACT_INPUT`, then the slippage is in terms of the output token. If the trade type is `EXACT_OUTPUT`, then the slippage is in terms of the input token.","default":"undefined"},"RoutingPreference":{"type":"string","description":"The routing preference determines which protocol to use for the swap. If the routing preference is `UNISWAPX`, then the swap will be routed through the UniswapX Dutch Auction Protocol. If the routing preference is `CLASSIC`, then the swap will be routed through the Classic Protocol. If the routing preference is `BEST_PRICE`, then the swap will be routed through the protocol that provides the best price. When `UNIXWAPX_V2` is passed, the swap will be routed through the UniswapX V2 Dutch Auction Protocol. When `V3_ONLY` is passed, the swap will be routed ONLY through the Uniswap V3 Protocol. When `V2_ONLY` is passed, the swap will be routed ONLY through the Uniswap V2 Protocol.","enum":["CLASSIC","UNISWAPX","BEST_PRICE","BEST_PRICE_V2","UNISWAPX_V2","V3_ONLY","V2_ONLY"],"default":"BEST_PRICE"},"ProtocolItems":{"type":"string","enum":["V2","V3","V4","UNISWAPX","UNISWAPX_V2","PRIORITY"]},"TransactionRequest":{"type":"object","properties":{"to":{"$ref":"#/components/schemas/Address"},"from":{"$ref":"#/components/schemas/Address"},"data":{"type":"string","description":"The calldata for the transaction."},"value":{"type":"string","description":"The value of the transaction in terms of wei in hex format."},"gasLimit":{"type":"string"},"chainId":{"type":"integer"},"maxFeePerGas":{"type":"string"},"maxPriorityFeePerGas":{"type":"string"},"gasPrice":{"type":"string"}},"required":["to","from","data","value","chainId"]},"TransactionFailureReason":{"type":"string","enum":["SIMULATION_ERROR","UNSUPPORTED_SIMULATION"]},"SwapSafetyMode":{"type":"string","enum":["SAFE"],"description":"The safety mode determines the safety level of the swap. If the safety mode is `SAFE`, then the swap will include a SWEEP for the native token."},"IndicativeQuoteRequest":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/TradeType"},"amount":{"type":"string"},"tokenInChainId":{"$ref":"#/components/schemas/ChainId"},"tokenOutChainId":{"$ref":"#/components/schemas/ChainId"},"tokenIn":{"type":"string"},"tokenOut":{"type":"string"}},"required":["type","amount","tokenInChainId","tokenOutChainId","tokenIn","tokenOut"]},"IndicativeQuoteResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"input":{"$ref":"#/components/schemas/IndicativeQuoteToken"},"output":{"$ref":"#/components/schemas/IndicativeQuoteToken"},"type":{"$ref":"#/components/schemas/TradeType"}},"required":["requestId","input","output","type"]},"CreateLPPositionRequest":{"type":"object","properties":{"protocol":{"$ref":"#/components/schemas/ProtocolItems"},"position":{"$ref":"#/components/schemas/Position"},"walletAddress":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"initialPrice":{"type":"string"},"poolLiquidity":{"type":"string"},"currentTick":{"type":"number"},"sqrtRatioX96":{"type":"string"},"amount0":{"type":"string"},"amount1":{"type":"string"},"slippageTolerance":{"type":"string"},"deadline":{"type":"number"},"signature":{"type":"string","description":"The signed permit."},"batchPermitData":{"allOf":[{"$ref":"#/components/schemas/Permit"}]},"simulateTransaction":{"type":"boolean"}}},"CreateLPPositionResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"create":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"}}},"IncreaseLPPositionRequest":{"type":"object","properties":{"protocol":{"$ref":"#/components/schemas/ProtocolItems"},"tokenId":{"type":"number"},"position":{"$ref":"#/components/schemas/Position"},"poolLiquidity":{"type":"string"},"currentTick":{"type":"number"},"sqrtRatioX96":{"type":"string"},"walletAddress":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"amount0":{"type":"string"},"amount1":{"type":"string"},"slippageTolerance":{"type":"string"},"deadline":{"type":"number"},"signature":{"type":"string","description":"The signed permit."},"batchPermitData":{"allOf":[{"$ref":"#/components/schemas/Permit"}]},"simulateTransaction":{"type":"boolean"}}},"IncreaseLPPositionResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"increase":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"}}},"DecreaseLPPositionRequest":{"type":"object","properties":{"protocol":{"$ref":"#/components/schemas/ProtocolItems"},"tokenId":{"type":"number"},"position":{"$ref":"#/components/schemas/Position"},"walletAddress":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"liquidityPercentageToDecrease":{"type":"number"},"slippageTolerance":{"type":"string"},"poolLiquidity":{"type":"string"},"currentTick":{"type":"number"},"sqrtRatioX96":{"type":"string"},"positionLiquidity":{"type":"string"},"expectedTokenOwed0RawAmount":{"type":"string"},"expectedTokenOwed1RawAmount":{"type":"string"},"deadline":{"type":"number"},"simulateTransaction":{"type":"boolean"}}},"DecreaseLPPositionResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"decrease":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"}}},"ClaimLPFeesRequest":{"type":"object","properties":{"protocol":{"$ref":"#/components/schemas/ProtocolItems"},"tokenId":{"type":"number"},"position":{"$ref":"#/components/schemas/Position"},"walletAddress":{"$ref":"#/components/schemas/Address"},"chainId":{"$ref":"#/components/schemas/ChainId"},"expectedTokenOwed0RawAmount":{"type":"string"},"expectedTokenOwed1RawAmount":{"type":"string"},"simulateTransaction":{"type":"boolean"}}},"ClaimLPFeesResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"claim":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"}}},"MigrateLPPositionRequest":{"type":"object","properties":{"chainId":{"$ref":"#/components/schemas/ChainId"},"tokenId":{"type":"number"},"walletAddress":{"$ref":"#/components/schemas/Address"},"signature":{"type":"string"},"simulateTransaction":{"type":"boolean","default":false}},"required":["chainId","tokenId","walletAddress"]},"MigrateLPPositionResponse":{"type":"object","properties":{"requestId":{"$ref":"#/components/schemas/RequestId"},"migrate":{"$ref":"#/components/schemas/TransactionRequest"},"gasFee":{"type":"string"}}},"IndicativeQuoteToken":{"type":"object","properties":{"amount":{"type":"string"},"chainId":{"$ref":"#/components/schemas/ChainId"},"token":{"$ref":"#/components/schemas/Address"}}}},"parameters":{"universalRouterVersionHeader":{"name":"x-universal-router-version","in":"header","description":"The version of the Universal Router to use for the swap journey. *MUST* be consistent throughout the API calls.","required":false,"schema":{"$ref":"#/components/schemas/UniversalRouterVersion"}},"addressParam":{"name":"address","in":"path","schema":{"$ref":"#/components/schemas/Address"},"required":true},"tokenIdParam":{"name":"tokenId","in":"path","schema":{"type":"string"},"required":true},"cursorParam":{"name":"cursor","in":"query","schema":{"type":"string"},"required":false},"limitParam":{"name":"limit","in":"query","schema":{"type":"number"},"required":false},"chainIdParam":{"name":"chainId","in":"query","schema":{"$ref":"#/components/schemas/ChainId"},"required":false},"bridgeTokenInChainIdParam":{"name":"tokenInChainId","in":"query","schema":{"$ref":"#/components/schemas/ChainId"},"required":false},"bridgeTokenOutChainIdParam":{"name":"tokenOutChainId","in":"query","schema":{"$ref":"#/components/schemas/ChainId"},"required":false},"tokenInParam":{"name":"tokenIn","in":"query","schema":{"$ref":"#/components/schemas/Address"},"required":false},"tokenOutParam":{"name":"tokenOut","in":"query","schema":{"$ref":"#/components/schemas/Address"},"required":false},"addressPathParam":{"name":"address","in":"query","schema":{"$ref":"#/components/schemas/Address"},"required":false},"orderStatusParam":{"name":"orderStatus","in":"query","description":"Filter by order status.","required":false,"schema":{"$ref":"#/components/schemas/OrderStatus"}},"orderTypeParam":{"name":"orderType","in":"query","description":"The default orderType is Dutch_V1_V2 and will grab both Dutch and Dutch_V2 orders.","required":false,"schema":{"$ref":"#/components/schemas/OrderTypeQuery"}},"orderIdParam":{"name":"orderId","in":"query","required":false,"schema":{"$ref":"#/components/schemas/OrderId"}},"orderIdsParam":{"name":"orderIds","in":"query","required":false,"description":"ids split by commas","schema":{"$ref":"#/components/schemas/OrderIds"}},"swapperParam":{"name":"swapper","in":"query","description":"Filter by swapper address.","required":false,"schema":{"$ref":"#/components/schemas/Address"}},"fillerParam":{"name":"filler","in":"query","description":"Filter by filler address.","required":false,"schema":{"$ref":"#/components/schemas/Address"}},"sortKeyParam":{"name":"sortKey","in":"query","description":"Order the query results by the sort key.","required":false,"schema":{"$ref":"#/components/schemas/SortKey"}},"sortParam":{"name":"sort","in":"query","description":"Sort query. For example: `sort=gt(UNIX_TIMESTAMP)`, `sort=between(1675872827, 1675872930)`, or `lt(1675872930)`.","required":false,"schema":{"type":"string"}},"descParam":{"description":"Sort query results by sortKey in descending order.","name":"desc","in":"query","required":false,"schema":{"type":"string"}},"transactionHashesParam":{"description":"The transaction hashes.","name":"txHashes","in":"query","required":true,"style":"form","explode":false,"schema":{"type":"array","items":{"$ref":"#/components/schemas/TransactionHash"}}}},"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"x-api-key"}}},"security":[{"apiKey":[]}]} \ No newline at end of file diff --git a/packages/uniswap/src/declarations.d.ts b/packages/uniswap/src/declarations.d.ts new file mode 100644 index 00000000000..f7bf7168320 --- /dev/null +++ b/packages/uniswap/src/declarations.d.ts @@ -0,0 +1,7 @@ +declare module '*.svg' { + import React from 'react' + // eslint-disable-next-line no-restricted-imports + import { SvgProps } from 'react-native-svg' + const content: React.FC + export default content +} diff --git a/packages/uniswap/src/features/bridging/hooks/chains.ts b/packages/uniswap/src/features/bridging/hooks/chains.ts index 14346f9318d..b69cbfc7528 100644 --- a/packages/uniswap/src/features/bridging/hooks/chains.ts +++ b/packages/uniswap/src/features/bridging/hooks/chains.ts @@ -18,8 +18,9 @@ export function useNumBridgingChains(): number { }) const chainSet = useMemo(() => new Set(bridgingTokens?.tokens.map((t) => t.chainId)), [bridgingTokens]) + const numChains = chainSet.size + 1 - return chainSet.size + 1 ?? FALLBACK_NUM_CHAINS + return numChains > 4 ? numChains : FALLBACK_NUM_CHAINS } export function useIsBridgingChain(chainId: UniverseChainId): boolean { diff --git a/packages/uniswap/src/features/bridging/hooks/tokens.ts b/packages/uniswap/src/features/bridging/hooks/tokens.ts index a1facf41e26..cdaef138098 100644 --- a/packages/uniswap/src/features/bridging/hooks/tokens.ts +++ b/packages/uniswap/src/features/bridging/hooks/tokens.ts @@ -5,11 +5,14 @@ import { TokenOption } from 'uniswap/src/components/TokenSelector/types' import { createEmptyTokenOptionFromBridgingToken } from 'uniswap/src/components/TokenSelector/utils' import { useTradingApiSwappableTokensQuery } from 'uniswap/src/data/apiClients/tradingApi/useTradingApiSwappableTokensQuery' import { tradingApiSwappableTokenToCurrencyInfo } from 'uniswap/src/data/apiClients/tradingApi/utils/tradingApiSwappableTokenToCurrencyInfo' +import { useCrossChainBalances } from 'uniswap/src/data/balances/hooks/useCrossChainBalances' +import { useTokenProjectsQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { GetSwappableTokensResponse } from 'uniswap/src/data/tradingApi/__generated__' import { GqlResult } from 'uniswap/src/data/types' import { TradeableAsset } from 'uniswap/src/entities/assets' import { toSupportedChainId } from 'uniswap/src/features/chains/utils' import { CurrencyInfo, PortfolioBalance } from 'uniswap/src/features/dataApi/types' +import { currencyIdToContractInput } from 'uniswap/src/features/dataApi/utils' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { @@ -17,18 +20,18 @@ import { getTokenAddressFromChainForTradingApi, toTradingApiSupportedChainId, } from 'uniswap/src/features/transactions/swap/utils/tradingApi' -import { UniverseChainId, WALLET_SUPPORTED_CHAIN_IDS, WalletChainId } from 'uniswap/src/types/chains' +import { COMBINED_CHAIN_IDS, UniverseChainId } from 'uniswap/src/types/chains' import { buildCurrencyId, buildNativeCurrencyId } from 'uniswap/src/utils/currencyId' import { logger } from 'utilities/src/logger/logger' export function useBridgingTokenWithHighestBalance({ + address, currencyAddress, currencyChainId, - otherChainBalances, }: { + address: Address currencyAddress: Address - currencyChainId: WalletChainId - otherChainBalances: PortfolioBalance[] | null + currencyChainId: UniverseChainId }): | { token: GetSwappableTokensResponse['tokens'][number] @@ -38,9 +41,22 @@ export function useBridgingTokenWithHighestBalance({ | undefined { const isBridgingEnabled = useFeatureFlag(FeatureFlags.Bridging) + const currencyId = buildCurrencyId(currencyChainId, currencyAddress) const tokenIn = currencyAddress ? getTokenAddressFromChainForTradingApi(currencyAddress, currencyChainId) : undefined const tokenInChainId = toTradingApiSupportedChainId(currencyChainId) + const { data: tokenProjectsData } = useTokenProjectsQuery({ + variables: { contracts: [currencyIdToContractInput(currencyId)] }, + }) + + const crossChainTokens = tokenProjectsData?.tokenProjects?.[0]?.tokens + + const { otherChainBalances } = useCrossChainBalances({ + address, + currencyId, + crossChainTokens, + }) + const { data: bridgingTokens } = useTradingApiSwappableTokensQuery({ params: otherChainBalances && otherChainBalances?.length > 0 && tokenIn && tokenInChainId && isBridgingEnabled @@ -177,7 +193,7 @@ function useBridgingTokensToTokenOptions( } // We sort the tokens by chain in the same order chains in the network selector - const chainOrder = WALLET_SUPPORTED_CHAIN_IDS + const chainOrder = COMBINED_CHAIN_IDS const sortedBridgingTokens = [...bridgingTokens].sort((a, b) => { if (!a || !b) { return 0 diff --git a/packages/uniswap/src/features/chains/utils.test.ts b/packages/uniswap/src/features/chains/utils.test.ts index 9de7fa8cf11..d72aada4e51 100644 --- a/packages/uniswap/src/features/chains/utils.test.ts +++ b/packages/uniswap/src/features/chains/utils.test.ts @@ -6,12 +6,13 @@ import { fromGraphQLChain, fromMoonpayNetwork, fromUniswapWebAppLink, + getEnabledChains, getPollingIntervalByBlocktime, hexadecimalStringToInt, toSupportedChainId, toUniswapWebAppLink, } from 'uniswap/src/features/chains/utils' -import { UniverseChainId } from 'uniswap/src/types/chains' +import { SUPPORTED_CHAIN_IDS, SUPPORTED_TESTNET_CHAIN_IDS, UniverseChainId } from 'uniswap/src/types/chains' describe(toSupportedChainId, () => { it('handles undefined input', () => { @@ -127,3 +128,80 @@ describe('hexadecimalStringToInt', () => { expect(hexadecimalStringToInt('0xg')).toBeNaN() }) }) + +describe('getEnabledChains', () => { + it('returns all mainnet chains', () => { + const featureFlaggedChainIds = SUPPORTED_CHAIN_IDS.filter((chainId) => chainId !== UniverseChainId.WorldChain) + + expect(getEnabledChains({ isTestnetModeEnabled: false, featureFlaggedChainIds })).toEqual({ + chains: [ + UniverseChainId.Mainnet, + UniverseChainId.Polygon, + UniverseChainId.ArbitrumOne, + UniverseChainId.Optimism, + UniverseChainId.Base, + UniverseChainId.Bnb, + UniverseChainId.Blast, + UniverseChainId.Avalanche, + UniverseChainId.Celo, + UniverseChainId.Zora, + UniverseChainId.Zksync, + ], + gqlChains: [ + Chain.Ethereum, + Chain.Optimism, + Chain.Bnb, + Chain.Polygon, + Chain.Zksync, + Chain.Worldchain, + Chain.Base, + Chain.Arbitrum, + Chain.Celo, + Chain.Avalanche, + Chain.Blast, + Chain.Zora, + ], + defaultChainId: UniverseChainId.Mainnet, + isTestnetModeEnabled: false, + }) + }) + it('returns feature flagged chains', () => { + expect( + getEnabledChains({ + isTestnetModeEnabled: false, + featureFlaggedChainIds: [UniverseChainId.Mainnet, UniverseChainId.Polygon], + }), + ).toEqual({ + chains: [UniverseChainId.Mainnet, UniverseChainId.Polygon], + gqlChains: [ + Chain.Ethereum, + Chain.Optimism, + Chain.Bnb, + Chain.Polygon, + Chain.Zksync, + Chain.Worldchain, + Chain.Base, + Chain.Arbitrum, + Chain.Celo, + Chain.Avalanche, + Chain.Blast, + Chain.Zora, + ], + defaultChainId: UniverseChainId.Mainnet, + isTestnetModeEnabled: false, + }) + }) + it('returns testnet chains', () => { + expect( + getEnabledChains({ + isTestnetModeEnabled: true, + featureFlaggedChainIds: SUPPORTED_TESTNET_CHAIN_IDS, + }), + ).toEqual({ + chains: [UniverseChainId.Sepolia, UniverseChainId.AstrochainSepolia], + gqlChains: [Chain.AstrochainSepolia, Chain.EthereumSepolia], + defaultChainId: UniverseChainId.Sepolia, + isTestnetModeEnabled: true, + }) + }) +}) diff --git a/packages/uniswap/src/features/chains/utils.ts b/packages/uniswap/src/features/chains/utils.ts index 33cad0c1ebe..1ad28fa2a83 100644 --- a/packages/uniswap/src/features/chains/utils.ts +++ b/packages/uniswap/src/features/chains/utils.ts @@ -1,31 +1,35 @@ import { BigNumber, BigNumberish } from '@ethersproject/bignumber' import { ChainId } from '@uniswap/sdk-core' -import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' +import { + GQL_MAINNET_CHAINS_MUTABLE, + GQL_TESTNET_CHAINS_MUTABLE, + UNIVERSE_CHAIN_INFO, +} from 'uniswap/src/constants/chains' import { PollingInterval } from 'uniswap/src/constants/misc' import { Chain } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { NetworkLayer, UniverseChainId, WALLET_SUPPORTED_CHAIN_IDS, WalletChainId } from 'uniswap/src/types/chains' -import { isTestEnv } from 'utilities/src/environment/env' +import { FeatureFlags } from 'uniswap/src/features/gating/flags' +import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' +import { + COMBINED_CHAIN_IDS, + InterfaceGqlChain, + NetworkLayer, + SUPPORTED_CHAIN_IDS, + SUPPORTED_TESTNET_CHAIN_IDS, + UniverseChainId, +} from 'uniswap/src/types/chains' export function toGraphQLChain(chainId: ChainId | number): Chain | undefined { switch (chainId) { case ChainId.MAINNET: return Chain.Ethereum - case ChainId.ARBITRUM_ONE: - return Chain.Arbitrum - case ChainId.ARBITRUM_GOERLI: - return Chain.Arbitrum - case ChainId.GOERLI: - return Chain.EthereumGoerli case ChainId.SEPOLIA: return Chain.EthereumSepolia + case ChainId.ARBITRUM_ONE: + return Chain.Arbitrum case ChainId.OPTIMISM: return Chain.Optimism - case ChainId.OPTIMISM_GOERLI: - return Chain.Optimism case ChainId.POLYGON: return Chain.Polygon - case ChainId.POLYGON_MUMBAI: - return Chain.Polygon case ChainId.BASE: return Chain.Base case ChainId.BNB: @@ -34,31 +38,32 @@ export function toGraphQLChain(chainId: ChainId | number): Chain | undefined { return Chain.Avalanche case ChainId.CELO: return Chain.Celo - case ChainId.CELO_ALFAJORES: - return Chain.Celo case ChainId.BLAST: return Chain.Blast + case ChainId.WORLDCHAIN: + return Chain.Worldchain case ChainId.ZORA: return Chain.Zora case ChainId.ZKSYNC: return Chain.Zksync + case ChainId.ASTROCHAIN_SEPOLIA: + return Chain.AstrochainSepolia } return undefined } // Some code from the web app uses chainId types as numbers // This validates them as coerces into SupportedChainId -export function toSupportedChainId(chainId?: BigNumberish): WalletChainId | null { - // Support Goerli for testing - const ids = isTestEnv() ? [UniverseChainId.Goerli, ...WALLET_SUPPORTED_CHAIN_IDS] : WALLET_SUPPORTED_CHAIN_IDS +export function toSupportedChainId(chainId?: BigNumberish): UniverseChainId | null { + const ids = COMBINED_CHAIN_IDS if (!chainId || !ids.map((c) => c.toString()).includes(chainId.toString())) { return null } - return parseInt(chainId.toString(), 10) as WalletChainId + return parseInt(chainId.toString(), 10) as UniverseChainId } -export function chainIdToHexadecimalString(chainId: WalletChainId): string { +export function chainIdToHexadecimalString(chainId: UniverseChainId): string { return BigNumber.from(chainId).toHexString() } @@ -71,17 +76,17 @@ export function isL2ChainId(chainId?: UniverseChainId): boolean { } export function isMainnetChainId(chainId?: UniverseChainId): boolean { - return chainId === UniverseChainId.Mainnet + return chainId === UniverseChainId.Mainnet || chainId === UniverseChainId.Sepolia } -export function fromGraphQLChain(chain: Chain | undefined): WalletChainId | null { +export function fromGraphQLChain(chain: Chain | undefined): UniverseChainId | null { switch (chain) { case Chain.Ethereum: return UniverseChainId.Mainnet case Chain.Arbitrum: return UniverseChainId.ArbitrumOne - case Chain.EthereumGoerli: - return UniverseChainId.Goerli + case Chain.EthereumSepolia: + return UniverseChainId.Sepolia case Chain.Optimism: return UniverseChainId.Optimism case Chain.Polygon: @@ -96,20 +101,24 @@ export function fromGraphQLChain(chain: Chain | undefined): WalletChainId | null return UniverseChainId.Avalanche case Chain.Celo: return UniverseChainId.Celo + case Chain.Worldchain: + return UniverseChainId.WorldChain case Chain.Zora: return UniverseChainId.Zora case Chain.Zksync: return UniverseChainId.Zksync + case Chain.AstrochainSepolia: + return UniverseChainId.AstrochainSepolia } return null } -export function getPollingIntervalByBlocktime(chainId?: WalletChainId): PollingInterval { +export function getPollingIntervalByBlocktime(chainId?: UniverseChainId): PollingInterval { return isMainnetChainId(chainId) ? PollingInterval.Fast : PollingInterval.LightningMcQueen } -export function fromMoonpayNetwork(moonpayNetwork: string | undefined): WalletChainId | undefined { +export function fromMoonpayNetwork(moonpayNetwork: string | undefined): UniverseChainId | undefined { switch (moonpayNetwork) { case Chain.Arbitrum.toLowerCase(): return UniverseChainId.ArbitrumOne @@ -135,10 +144,12 @@ export function fromMoonpayNetwork(moonpayNetwork: string | undefined): WalletCh } } -export function fromUniswapWebAppLink(network: string | null): WalletChainId | null { +export function fromUniswapWebAppLink(network: string | null): UniverseChainId | null { switch (network) { case Chain.Ethereum.toLowerCase(): return UniverseChainId.Mainnet + case Chain.EthereumSepolia.toLowerCase(): + return UniverseChainId.Sepolia case Chain.Arbitrum.toLowerCase(): return UniverseChainId.ArbitrumOne case Chain.Optimism.toLowerCase(): @@ -155,19 +166,25 @@ export function fromUniswapWebAppLink(network: string | null): WalletChainId | n return UniverseChainId.Avalanche case Chain.Celo.toLowerCase(): return UniverseChainId.Celo + case Chain.Worldchain.toLowerCase(): + return UniverseChainId.WorldChain case Chain.Zora.toLowerCase(): return UniverseChainId.Zora case Chain.Zksync.toLowerCase(): return UniverseChainId.Zksync + case Chain.AstrochainSepolia.toLowerCase(): + return UniverseChainId.AstrochainSepolia default: throw new Error(`Network "${network}" can not be mapped`) } } -export function toUniswapWebAppLink(chainId: WalletChainId): string | null { +export function toUniswapWebAppLink(chainId: UniverseChainId): string | null { switch (chainId) { case UniverseChainId.Mainnet: return Chain.Ethereum.toLowerCase() + case UniverseChainId.Sepolia: + return Chain.EthereumSepolia.toLowerCase() case UniverseChainId.ArbitrumOne: return Chain.Arbitrum.toLowerCase() case UniverseChainId.Optimism: @@ -184,11 +201,81 @@ export function toUniswapWebAppLink(chainId: WalletChainId): string | null { return Chain.Avalanche.toLowerCase() case UniverseChainId.Celo: return Chain.Celo.toLowerCase() + case UniverseChainId.WorldChain: + return Chain.Worldchain.toLowerCase() case UniverseChainId.Zora: return Chain.Zora.toLowerCase() case UniverseChainId.Zksync: return Chain.Zksync.toLowerCase() + case UniverseChainId.AstrochainSepolia: + return Chain.AstrochainSepolia.toLowerCase() default: throw new Error(`ChainID "${chainId}" can not be mapped`) } } + +type ActiveChainIdFeatureFlags = UniverseChainId.WorldChain + +export function filterChainIdsByFeatureFlag(featureFlaggedChainIds: { + [UniverseChainId.WorldChain]: boolean +}): UniverseChainId[] { + return COMBINED_CHAIN_IDS.filter((chainId) => { + return featureFlaggedChainIds[chainId as ActiveChainIdFeatureFlags] ?? true + }) +} + +// Used to feature flag chains. If a chain is not included in the object, it is considered enabled by default. +export function useFeatureFlaggedChainIds(): UniverseChainId[] { + // You can use the useFeatureFlag hook here to enable/disable chains based on feature flags. + // Example: [ChainId.BLAST]: useFeatureFlag(FeatureFlags.BLAST) + // IMPORTANT: Don't forget to also update getEnabledChainIdsSaga + + const worldChainEnabled = useFeatureFlag(FeatureFlags.WorldChain) + + return filterChainIdsByFeatureFlag({ + [UniverseChainId.WorldChain]: worldChainEnabled, + }) +} + +export function getEnabledChains({ + isTestnetModeEnabled, + featureFlaggedChainIds, + connectedWalletChainIds, +}: { + isTestnetModeEnabled: boolean + featureFlaggedChainIds: UniverseChainId[] + connectedWalletChainIds?: UniverseChainId[] +}): { + chains: UniverseChainId[] + gqlChains: InterfaceGqlChain[] + defaultChainId: UniverseChainId + isTestnetModeEnabled: boolean +} { + if (isTestnetModeEnabled) { + const supportedTestnetChainIds = SUPPORTED_TESTNET_CHAIN_IDS.filter( + (chainId) => + featureFlaggedChainIds.includes(chainId) && + (connectedWalletChainIds ? connectedWalletChainIds.includes(chainId) : true), + ) + + return { + chains: supportedTestnetChainIds, + gqlChains: GQL_TESTNET_CHAINS_MUTABLE, + defaultChainId: UniverseChainId.Sepolia as UniverseChainId, + isTestnetModeEnabled, + } + } + + const supportedChainIds = SUPPORTED_CHAIN_IDS.filter( + (chainId) => + featureFlaggedChainIds.includes(chainId) && + (connectedWalletChainIds ? connectedWalletChainIds.includes(chainId) : true), + ) + + return { + chains: supportedChainIds, + gqlChains: GQL_MAINNET_CHAINS_MUTABLE, + defaultChainId: UniverseChainId.Mainnet as UniverseChainId, + isTestnetModeEnabled, + } +} diff --git a/packages/uniswap/src/features/dataApi/balances.ts b/packages/uniswap/src/features/dataApi/balances.ts index 2170c13a395..0f733108f84 100644 --- a/packages/uniswap/src/features/dataApi/balances.ts +++ b/packages/uniswap/src/features/dataApi/balances.ts @@ -1,6 +1,5 @@ import { NetworkStatus, Reference, useApolloClient, WatchQueryFetchPolicy } from '@apollo/client' import { useCallback, useMemo } from 'react' -import { GQL_MAINNET_CHAINS_MUTABLE } from 'uniswap/src/constants/chains' import { PollingInterval } from 'uniswap/src/constants/misc' import { ContractInput, @@ -14,7 +13,11 @@ import { GqlResult } from 'uniswap/src/data/types' import { fromGraphQLChain } from 'uniswap/src/features/chains/utils' import { CurrencyInfo, PortfolioBalance } from 'uniswap/src/features/dataApi/types' import { buildCurrency, currencyIdToContractInput, usePersistedError } from 'uniswap/src/features/dataApi/utils' -import { useHideSmallBalancesSetting, useHideSpamTokensSetting } from 'uniswap/src/features/settings/hooks' +import { + useEnabledChains, + useHideSmallBalancesSetting, + useHideSpamTokensSetting, +} from 'uniswap/src/features/settings/hooks' import { useCurrencyIdToVisibility } from 'uniswap/src/features/transactions/selectors' import { CurrencyId } from 'uniswap/src/types/currency' import { currencyId } from 'uniswap/src/utils/currencyId' @@ -68,6 +71,8 @@ export function usePortfolioBalances({ const valueModifiers = usePortfolioValueModifiers(address) + const { gqlChains } = useEnabledChains() + const { data: balancesData, loading, @@ -79,7 +84,7 @@ export function usePortfolioBalances({ notifyOnNetworkStatusChange: true, onCompleted, pollInterval: internalPollInterval, - variables: address ? { ownerAddress: address, valueModifiers, chains: GQL_MAINNET_CHAINS_MUTABLE } : undefined, + variables: address ? { ownerAddress: address, valueModifiers, chains: gqlChains } : undefined, skip: !address, }) @@ -179,6 +184,7 @@ export function usePortfolioTotalValue({ }) const valueModifiers = usePortfolioValueModifiers(address) + const { gqlChains } = useEnabledChains() const { data: balancesData, @@ -191,7 +197,7 @@ export function usePortfolioTotalValue({ notifyOnNetworkStatusChange: true, onCompleted, pollInterval: internalPollInterval, - variables: address ? { ownerAddress: address, valueModifiers, chains: GQL_MAINNET_CHAINS_MUTABLE } : undefined, + variables: address ? { ownerAddress: address, valueModifiers, chains: gqlChains } : undefined, skip: !address, }) @@ -411,6 +417,7 @@ export function sortPortfolioBalances(balances: PortfolioBalance[]): PortfolioBa */ export function usePortfolioCacheUpdater(address: string): PortfolioCacheUpdater { const apolloClient = useApolloClient() + const { gqlChains } = useEnabledChains() const updater = useCallback( (hidden: boolean, portfolioBalance?: PortfolioBalance) => { @@ -422,6 +429,7 @@ export function usePortfolioCacheUpdater(address: string): PortfolioCacheUpdater query: PortfolioBalanceDocument, variables: { owner: address, + chains: gqlChains, }, })?.portfolios?.[0] @@ -466,7 +474,7 @@ export function usePortfolioCacheUpdater(address: string): PortfolioCacheUpdater }, }) }, - [apolloClient, address], + [apolloClient, address, gqlChains], ) return updater diff --git a/packages/uniswap/src/features/dataApi/topTokens.ts b/packages/uniswap/src/features/dataApi/topTokens.ts index 3acd0ea1735..cff7b2c928e 100644 --- a/packages/uniswap/src/features/dataApi/topTokens.ts +++ b/packages/uniswap/src/features/dataApi/topTokens.ts @@ -9,6 +9,7 @@ import { toGraphQLChain } from 'uniswap/src/features/chains/utils' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { gqlTokenToCurrencyInfo, usePersistedError } from 'uniswap/src/features/dataApi/utils' import { UniverseChainId } from 'uniswap/src/types/chains' +import { useMemoCompare } from 'utilities/src/react/hooks' export function usePopularTokens(chainFilter: UniverseChainId): GqlResult { const gqlChainFilter = toGraphQLChain(chainFilter) @@ -25,21 +26,43 @@ export function usePopularTokens(chainFilter: UniverseChainId): GqlResult { - if (!data || !data.topTokens) { - return undefined - } + // TODO(API-482): we should be able to remove this once the backend bug is fixed. + // There's currently a graphql backend bug where the top tokens query returns different data than the token query for some fields, + // causing each query to override the results of the other query. + // We partially fixed this with #12653, but there can still be other issues when the `topToken` query runs before `token`, + // which then triggers an unnecessary re-render of the `useTopTokensQuery`. + // Given that this hook doesn't care about `feeData` or `protectionInfo`, it's Ok to ignore those values + // and use `useMemoCompare` with a custom comparator. + const formattedData = useMemoCompare( + () => { + if (!data || !data.topTokens) { + return undefined + } - return data.topTokens - .map((token) => { - if (!token) { - return null - } + return data.topTokens + .map((token) => { + if (!token) { + return null + } - return gqlTokenToCurrencyInfo(token) + return gqlTokenToCurrencyInfo(token) + }) + .filter((c): c is CurrencyInfo => Boolean(c)) + }, + (prevData, newData) => { + if (prevData === newData) { + return true + } + + if (!prevData || prevData.length !== newData?.length) { + return false + } + + return prevData.every((prev, i) => { + return prev.currencyId === newData?.[i]?.currencyId }) - .filter((c): c is CurrencyInfo => Boolean(c)) - }, [data]) + }, + ) return useMemo( () => ({ data: formattedData, loading, error: persistedError, refetch }), diff --git a/packages/uniswap/src/features/dataApi/utils.ts b/packages/uniswap/src/features/dataApi/utils.ts index cd09464f920..f12c1858e40 100644 --- a/packages/uniswap/src/features/dataApi/utils.ts +++ b/packages/uniswap/src/features/dataApi/utils.ts @@ -14,7 +14,7 @@ import { import { fromGraphQLChain, toGraphQLChain } from 'uniswap/src/features/chains/utils' import { AttackType, CurrencyInfo, SafetyInfo, TokenList } from 'uniswap/src/features/dataApi/types' import { NativeCurrency } from 'uniswap/src/features/tokens/NativeCurrency' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyId } from 'uniswap/src/types/currency' import { currencyId, @@ -24,7 +24,7 @@ import { } from 'uniswap/src/utils/currencyId' type BuildCurrencyParams = { - chainId?: Nullable + chainId?: Nullable address?: Nullable decimals?: Nullable symbol?: Nullable @@ -37,6 +37,7 @@ type BuildCurrencyParams = { // Converts CurrencyId to ContractInput format for GQL token queries export function currencyIdToContractInput(id: CurrencyId): ContractInput { return { + // TODO: WALL-4919: Remove hardcoded Mainnet chain: toGraphQLChain(currencyIdToChain(id) ?? UniverseChainId.Mainnet) ?? Chain.Ethereum, address: currencyIdToGraphQLAddress(id) ?? undefined, } @@ -44,7 +45,7 @@ export function currencyIdToContractInput(id: CurrencyId): ContractInput { export function tokenProjectToCurrencyInfos( tokenProjects: TokenProjectsQuery['tokenProjects'], - chainFilter?: WalletChainId | null, + chainFilter?: UniverseChainId | null, ): CurrencyInfo[] { return tokenProjects ?.flatMap((project) => @@ -87,7 +88,7 @@ export function tokenProjectToCurrencyInfos( } // use inverse check here (instead of isNativeAddress) so we can typeguard address as must be string if this is true -function isNonNativeAddress(chainId: WalletChainId, address: Maybe): address is string { +function isNonNativeAddress(chainId: UniverseChainId, address: Maybe): address is string { return !isNativeCurrencyAddress(chainId, address) } diff --git a/packages/uniswap/src/features/ens/api.ts b/packages/uniswap/src/features/ens/api.ts index e0b3617bdeb..5b5e3490952 100644 --- a/packages/uniswap/src/features/ens/api.ts +++ b/packages/uniswap/src/features/ens/api.ts @@ -2,7 +2,7 @@ import { skipToken, useQuery } from '@tanstack/react-query' import { providers } from 'ethers/lib/ethers' import { createEthersProvider } from 'uniswap/src/features/providers/createEthersProvider' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { areAddressesEqual } from 'uniswap/src/utils/addresses' import { ONE_MINUTE_MS } from 'utilities/src/time/time' @@ -19,7 +19,7 @@ export enum EnsLookupType { export type EnsLookupParams = { type: EnsLookupType nameOrAddress: string - chainId: WalletChainId + chainId: UniverseChainId } async function getNameFetch(address: string, provider: providers.JsonRpcProvider) { @@ -79,7 +79,7 @@ async function getOnChainEnsFetch(params: EnsLookupParams): Promise({ queryKey: [ONCHAIN_ENS_CACHE_KEY, chainId, type, nameOrAddress], @@ -90,18 +90,18 @@ function useEnsQuery( }) } -export function useENSName(address?: Address, chainId: WalletChainId = UniverseChainId.Mainnet) { +export function useENSName(address?: Address, chainId: UniverseChainId = UniverseChainId.Mainnet) { return useEnsQuery(EnsLookupType.Name, address, chainId) } -export function useAddressFromEns(maybeName: string | null, chainId: WalletChainId = UniverseChainId.Mainnet) { +export function useAddressFromEns(maybeName: string | null, chainId: UniverseChainId = UniverseChainId.Mainnet) { return useEnsQuery(EnsLookupType.Address, maybeName, chainId) } -export function useENSAvatar(address?: string | null, chainId: WalletChainId = UniverseChainId.Mainnet) { +export function useENSAvatar(address?: string | null, chainId: UniverseChainId = UniverseChainId.Mainnet) { return useEnsQuery(EnsLookupType.Avatar, address, chainId) } -export function useENSDescription(name?: string | null, chainId: WalletChainId = UniverseChainId.Mainnet) { +export function useENSDescription(name?: string | null, chainId: UniverseChainId = UniverseChainId.Mainnet) { return useEnsQuery(EnsLookupType.Description, name, chainId) } -export function useENSTwitterUsername(name?: string | null, chainId: WalletChainId = UniverseChainId.Mainnet) { +export function useENSTwitterUsername(name?: string | null, chainId: UniverseChainId = UniverseChainId.Mainnet) { return useEnsQuery(EnsLookupType.TwitterUsername, name, chainId) } diff --git a/packages/uniswap/src/features/ens/useENS.ts b/packages/uniswap/src/features/ens/useENS.ts index 47b96b032d2..c5b3fde72eb 100644 --- a/packages/uniswap/src/features/ens/useENS.ts +++ b/packages/uniswap/src/features/ens/useENS.ts @@ -1,7 +1,7 @@ // Copied from https://github.com/Uniswap/interface/blob/main/src/hooks/useENS.ts import { useAddressFromEns, useENSName } from 'uniswap/src/features/ens/api' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { getValidAddress } from 'uniswap/src/utils/addresses' import { useDebounce } from 'utilities/src/time/timing' @@ -10,7 +10,7 @@ import { useDebounce } from 'utilities/src/time/timing' * @param nameOrAddress ENS name or address */ export function useENS( - chainId: WalletChainId, + chainId: UniverseChainId, nameOrAddress?: string | null, autocompleteDomain?: boolean, ): { diff --git a/packages/uniswap/src/features/favorites/selectors.ts b/packages/uniswap/src/features/favorites/selectors.ts index 4d78f3cb074..1037d235859 100644 --- a/packages/uniswap/src/features/favorites/selectors.ts +++ b/packages/uniswap/src/features/favorites/selectors.ts @@ -3,8 +3,8 @@ import { CurrencyIdToVisibility, NFTKeyToVisibility } from 'uniswap/src/features import { UniswapRootState } from 'uniswap/src/state' import { unique } from 'utilities/src/primitives/array' -export const selectFavoriteTokens = (state: UniswapRootState): string[] => unique(state.favorites.tokens) - +const selectFavoriteTokensWithPossibleDuplicates = (state: UniswapRootState): string[] => state.favorites.tokens +export const selectFavoriteTokens = createSelector(selectFavoriteTokensWithPossibleDuplicates, unique) export const selectHasFavoriteTokens = createSelector(selectFavoriteTokens, (tokens) => Boolean(tokens?.length > 0)) export const makeSelectHasTokenFavorited = (): Selector => diff --git a/packages/uniswap/src/features/fiatOnRamp/FiatOnRampConnectingView.native.tsx b/packages/uniswap/src/features/fiatOnRamp/FiatOnRampConnectingView.native.tsx index 92408066b94..62c309d67a3 100644 --- a/packages/uniswap/src/features/fiatOnRamp/FiatOnRampConnectingView.native.tsx +++ b/packages/uniswap/src/features/fiatOnRamp/FiatOnRampConnectingView.native.tsx @@ -1,7 +1,7 @@ import { useTranslation } from 'react-i18next' import { Image, ImageBackground, StyleSheet } from 'react-native' import { FadeIn, FadeOut } from 'react-native-reanimated' -import { Flex, Text, useDeviceInsets, useIsDarkMode } from 'ui/src' +import { Flex, Text, useIsDarkMode } from 'ui/src' import { FOR_CONNECTING_BACKGROUND_DARK, FOR_CONNECTING_BACKGROUND_LIGHT, UNISWAP_LOGO_LARGE } from 'ui/src/assets' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { iconSizes } from 'ui/src/theme' @@ -9,6 +9,7 @@ import { SERVICE_PROVIDER_ICON_BORDER_RADIUS, ServiceProviderLogoStyles, } from 'uniswap/src/features/fiatOnRamp/constants' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' export function FiatOnRampConnectingView({ isOffRamp, @@ -23,7 +24,7 @@ export function FiatOnRampConnectingView({ serviceProviderName: string serviceProviderLogo?: JSX.Element }): JSX.Element { - const insets = useDeviceInsets() + const insets = useAppInsets() const { t } = useTranslation() const isDarkMode = useIsDarkMode() diff --git a/packages/uniswap/src/features/fiatOnRamp/FiatOnRampConnectingView.tsx b/packages/uniswap/src/features/fiatOnRamp/FiatOnRampConnectingView.tsx index 22486205ad2..fdba2a116ba 100644 --- a/packages/uniswap/src/features/fiatOnRamp/FiatOnRampConnectingView.tsx +++ b/packages/uniswap/src/features/fiatOnRamp/FiatOnRampConnectingView.tsx @@ -1,55 +1,11 @@ -import { useTranslation } from 'react-i18next' -import { AnimatePresence, Flex, Image, Text, useDeviceInsets } from 'ui/src' -import { UNISWAP_LOGO_LARGE } from 'ui/src/assets' -import { iconSizes } from 'ui/src/theme' -import { ServiceProviderLogoStyles } from 'uniswap/src/features/fiatOnRamp/constants' +import { PlatformSplitStubError } from 'utilities/src/errors' -export function FiatOnRampConnectingView({ - isOffRamp, - amount, - quoteCurrencyCode, - serviceProviderName, - serviceProviderLogo, -}: { +export function FiatOnRampConnectingView(_props: { isOffRamp?: boolean amount?: string quoteCurrencyCode?: string serviceProviderName: string serviceProviderLogo?: JSX.Element }): JSX.Element { - const insets = useDeviceInsets() - const { t } = useTranslation() - - return ( - - - - - - - - {serviceProviderLogo} - - - - {t('fiatOnRamp.connection.message', { serviceProvider: serviceProviderName })} - - {quoteCurrencyCode && amount && ( - - {isOffRamp - ? t('fiatOffRamp.connection.quote', { - amount, - currencySymbol: quoteCurrencyCode, - }) - : t('fiatOnRamp.connection.quote', { - amount, - currencySymbol: quoteCurrencyCode, - })} - - )} - - - - - ) + throw new PlatformSplitStubError('FiatOnRampConnectingView') } diff --git a/packages/uniswap/src/features/fiatOnRamp/FiatOnRampConnectingView.web.tsx b/packages/uniswap/src/features/fiatOnRamp/FiatOnRampConnectingView.web.tsx new file mode 100644 index 00000000000..e32df4800c2 --- /dev/null +++ b/packages/uniswap/src/features/fiatOnRamp/FiatOnRampConnectingView.web.tsx @@ -0,0 +1,56 @@ +import { useTranslation } from 'react-i18next' +import { AnimatePresence, Flex, Image, Text } from 'ui/src' +import { UNISWAP_LOGO_LARGE } from 'ui/src/assets' +import { iconSizes } from 'ui/src/theme' +import { ServiceProviderLogoStyles } from 'uniswap/src/features/fiatOnRamp/constants' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' + +export function FiatOnRampConnectingView({ + isOffRamp, + amount, + quoteCurrencyCode, + serviceProviderName, + serviceProviderLogo, +}: { + isOffRamp?: boolean + amount?: string + quoteCurrencyCode?: string + serviceProviderName: string + serviceProviderLogo?: JSX.Element +}): JSX.Element { + const insets = useAppInsets() + const { t } = useTranslation() + + return ( + + + + + + + + {serviceProviderLogo} + + + + {t('fiatOnRamp.connection.message', { serviceProvider: serviceProviderName })} + + {quoteCurrencyCode && amount && ( + + {isOffRamp + ? t('fiatOffRamp.connection.quote', { + amount, + currencySymbol: quoteCurrencyCode, + }) + : t('fiatOnRamp.connection.quote', { + amount, + currencySymbol: quoteCurrencyCode, + })} + + )} + + + + + ) +} diff --git a/packages/uniswap/src/features/fiatOnRamp/hooks.ts b/packages/uniswap/src/features/fiatOnRamp/hooks.ts index 73bfbddecd2..bb5132c468a 100644 --- a/packages/uniswap/src/features/fiatOnRamp/hooks.ts +++ b/packages/uniswap/src/features/fiatOnRamp/hooks.ts @@ -39,7 +39,7 @@ import { TransactionStatus, TransactionType, } from 'uniswap/src/features/transactions/types/transactionDetails' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { getFormattedCurrencyAmount } from 'uniswap/src/utils/currency' import { buildCurrencyId, buildNativeCurrencyId } from 'uniswap/src/utils/currencyId' import { NumberType } from 'utilities/src/format/types' @@ -63,7 +63,7 @@ export function useFormatExactCurrencyAmount(currencyAmount: string, currency: M /** Returns a new externalTransactionId and a callback to store the transaction. */ export function useFiatOnRampTransactionCreator( ownerAddress: string, - chainId: WalletChainId, + chainId: UniverseChainId, serviceProvider?: string, ): { externalTransactionId: string diff --git a/packages/uniswap/src/features/gas/hooks.ts b/packages/uniswap/src/features/gas/hooks.ts index e70de87e44b..02ee00a532f 100644 --- a/packages/uniswap/src/features/gas/hooks.ts +++ b/packages/uniswap/src/features/gas/hooks.ts @@ -21,11 +21,12 @@ import { DynamicConfigs, GasStrategies, GasStrategyType } from 'uniswap/src/feat import { Statsig, useConfig } from 'uniswap/src/features/gating/sdk/statsig' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { useOnChainNativeCurrencyBalance } from 'uniswap/src/features/portfolio/api' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { NativeCurrency } from 'uniswap/src/features/tokens/NativeCurrency' import { ValueType, getCurrencyAmount } from 'uniswap/src/features/tokens/getCurrencyAmount' import { DerivedSendInfo } from 'uniswap/src/features/transactions/send/types' import { usePollingIntervalByChain } from 'uniswap/src/features/transactions/swap/hooks/usePollingIntervalByChain' -import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice' +import { useUSDCValueWithStatus } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice' import { DerivedSwapInfo } from 'uniswap/src/features/transactions/swap/types/derivedSwapInfo' import { UniswapXGasBreakdown } from 'uniswap/src/features/transactions/swap/types/swapTxAndGasInfo' import { UniverseChainId } from 'uniswap/src/types/chains' @@ -113,6 +114,7 @@ export function useTransactionGasFee( const pollingIntervalForChain = usePollingIntervalByChain(tx?.chainId) const activeGasStrategy = useActiveGasStrategy(tx?.chainId, 'general') const shadowGasStrategies = useShadowGasStrategies(tx?.chainId, 'general') + const { defaultChainId } = useEnabledChains() const txWithGasStrategies = useMemo( () => ({ ...tx, gasStrategies: [activeGasStrategy, ...(shadowGasStrategies ?? [])] }), @@ -133,7 +135,7 @@ export function useTransactionGasFee( error: null, }) const account = useAccountMeta() - const provider = useProvider(tx?.chainId ?? UniverseChainId.Mainnet) + const provider = useProvider(tx?.chainId ?? defaultChainId) useEffect(() => { async function calculateClientsideGasEstimate(): Promise { if (skip || !tx) { @@ -201,14 +203,31 @@ export function useTransactionGasFee( }, [clientsideGasEstimate, data, error, isLoading, activeGasStrategy]) } -export function useUSDValue(chainId?: UniverseChainId, ethValueInWei?: string): string | undefined { +export function useUSDValueOfGasFee( + chainId?: UniverseChainId, + feeValueInWei?: string, +): { isLoading: boolean; value: string | undefined } { const currencyAmount = getCurrencyAmount({ - value: ethValueInWei, + value: feeValueInWei, valueType: ValueType.Raw, currency: chainId ? NativeCurrency.onChain(chainId) : undefined, }) + const { value, isLoading } = useUSDCValueWithStatus(currencyAmount) + return { isLoading, value: value?.toExact() } +} - return useUSDCValue(currencyAmount)?.toExact() +// Same as useUSDValueOfGasFee, but returns a CurrencyAmount instead of a string +export function useUSDCurrencyAmountOfGasFee( + chainId?: UniverseChainId, + feeValueInWei?: string, +): CurrencyAmount | null { + const currencyAmount = getCurrencyAmount({ + value: feeValueInWei, + valueType: ValueType.Raw, + currency: chainId ? NativeCurrency.onChain(chainId) : undefined, + }) + const { value } = useUSDCValueWithStatus(currencyAmount) + return value } export function useFormattedUniswapXGasFeeInfo( @@ -217,8 +236,8 @@ export function useFormattedUniswapXGasFeeInfo( ): FormattedUniswapXGasFeeInfo | undefined { const { convertFiatAmountFormatted } = useLocalizationContext() - const approvalCostUsd = useUSDValue(chainId, uniswapXGasBreakdown?.approvalCost) - const wrapCostUsd = useUSDValue(chainId, uniswapXGasBreakdown?.wrapCost) + const { value: approvalCostUsd } = useUSDValueOfGasFee(chainId, uniswapXGasBreakdown?.approvalCost) + const { value: wrapCostUsd } = useUSDValueOfGasFee(chainId, uniswapXGasBreakdown?.wrapCost) return useMemo(() => { if (!uniswapXGasBreakdown) { @@ -323,3 +342,76 @@ function extractGasFeeParams(estimate: GasEstimate): TransactionLegacyFeeParams } } } + +type GasFeeFormattedAmounts = T extends string + ? { gasFeeUSD: string | undefined; gasFeeFormatted: string } + : { gasFeeUSD: string | undefined; gasFeeFormatted: string | null } + +/** + * Returns formatted fiat amounts based on a gas fee. Will format a USD price if a quote + * is available, otherwise will return a formatted native currency amount. + * + * If no placeholder is defined, the response can be null. If a placeholder is defined, + * the gas fee amount will always be a string. + */ +export function useGasFeeFormattedAmounts({ + gasFee, + chainId, + placeholder, +}: { + gasFee: GasFeeResult | undefined + chainId: UniverseChainId + placeholder: T +}): GasFeeFormattedAmounts { + const { convertFiatAmountFormatted, formatNumberOrString } = useLocalizationContext() + const { value: gasFeeUSD, isLoading: gasFeeUSDIsLoading } = useUSDValueOfGasFee(chainId, gasFee?.value) + + // In testnet mode, use native currency values as USD pricing may be unreliable + const { isTestnetModeEnabled } = useEnabledChains() + + const nativeCurrency = NativeCurrency.onChain(chainId) + const nativeCurrencyAmount = getCurrencyAmount({ + currency: nativeCurrency, + value: gasFee?.value, + valueType: ValueType.Raw, + }) + + const fiatAmountFormatted = convertFiatAmountFormatted(gasFeeUSD, NumberType.FiatGasPrice) + + const nativeAmountFormatted = formatNumberOrString({ + value: nativeCurrencyAmount?.toExact(), + type: NumberType.TokenNonTx, + }) + + const emptyState = placeholder ?? null + + const gasFeeFormatted = useMemo(() => { + // Gas fee not available + if (!gasFee?.value) { + return emptyState + } + + // Gas fee available, USD not available - return native currency amount (always do this in testnet mode) + if (!gasFeeUSD || isTestnetModeEnabled) { + return gasFee.isLoading || gasFeeUSDIsLoading ? emptyState : `${nativeAmountFormatted} ${nativeCurrency.symbol}` + } + + // Gas fee and USD both available + return fiatAmountFormatted + }, [ + emptyState, + fiatAmountFormatted, + gasFee?.isLoading, + gasFee?.value, + gasFeeUSD, + gasFeeUSDIsLoading, + isTestnetModeEnabled, + nativeAmountFormatted, + nativeCurrency.symbol, + ]) + + return { + gasFeeUSD, + gasFeeFormatted, + } as GasFeeFormattedAmounts +} diff --git a/packages/uniswap/src/features/gas/useMaxAmountSpend.ts b/packages/uniswap/src/features/gas/useMaxAmountSpend.ts index e616ef48450..db0ad19dae3 100644 --- a/packages/uniswap/src/features/gas/useMaxAmountSpend.ts +++ b/packages/uniswap/src/features/gas/useMaxAmountSpend.ts @@ -54,6 +54,8 @@ function useGetMinAmount(chainId?: UniverseChainId, txType?: TransactionType): J switch (chainId) { case UniverseChainId.Mainnet: return MIN_ETH_FOR_GAS + case UniverseChainId.Sepolia: + return MIN_ETH_FOR_GAS case UniverseChainId.Polygon: return MIN_POLYGON_FOR_GAS case UniverseChainId.Avalanche: @@ -65,8 +67,10 @@ function useGetMinAmount(chainId?: UniverseChainId, txType?: TransactionType): J case UniverseChainId.Base: case UniverseChainId.Bnb: case UniverseChainId.Blast: + case UniverseChainId.WorldChain: case UniverseChainId.Zora: case UniverseChainId.Zksync: + case UniverseChainId.AstrochainSepolia: return MIN_L2_FOR_GAS default: logger.error(new Error('unhandled chain when getting min gas amount'), { diff --git a/packages/uniswap/src/features/gating/configs.ts b/packages/uniswap/src/features/gating/configs.ts index 986da954f16..1b50285b0b5 100644 --- a/packages/uniswap/src/features/gating/configs.ts +++ b/packages/uniswap/src/features/gating/configs.ts @@ -14,6 +14,7 @@ export enum DynamicConfigs { // Web QuickRouteChains = 'quick_route_chains', + AstroChain = 'astro_chain', } // Config values go here for easy access @@ -74,6 +75,10 @@ export enum QuickRouteChainsConfigKey { Chains = 'quick_route_chains', } +export enum AstroChainConfigKey { + Url = 'url', +} + export type DynamicConfigKeys = { // Shared [DynamicConfigs.Swap]: SwapConfigKey @@ -85,4 +90,5 @@ export type DynamicConfigKeys = { // Web [DynamicConfigs.QuickRouteChains]: QuickRouteChainsConfigKey + [DynamicConfigs.AstroChain]: AstroChainConfigKey } diff --git a/packages/uniswap/src/features/gating/flags.ts b/packages/uniswap/src/features/gating/flags.ts index 682d5728378..6007d3c23d7 100644 --- a/packages/uniswap/src/features/gating/flags.ts +++ b/packages/uniswap/src/features/gating/flags.ts @@ -13,6 +13,7 @@ export enum FeatureFlags { SelfReportSpamNFTs, UniswapXPriorityOrders, SharedSwapArbitrumUniswapXExperiment, + V4Swap, // Wallet FlashbotsPrivateRpc, @@ -22,6 +23,7 @@ export enum FeatureFlags { OpenAIAssistant, UnitagsDeviceAttestation, UniswapX, + TestnetMode, // Mobile Datadog, @@ -38,6 +40,7 @@ export enum FeatureFlags { // Web AATestWeb, + AstroChainLaunchModal, UniversalSwap, NavigationHotkeys, Eip6936Enabled, @@ -55,6 +58,7 @@ export enum FeatureFlags { V2Everywhere, V4Everywhere, Zora, + WorldChain, // TODO(WEB-3625): Remove these once we have a generalized system for outage banners. OutageBannerArbitrum, OutageBannerOptimism, @@ -73,6 +77,9 @@ export const WEB_FEATURE_FLAG_NAMES = new Map([ [FeatureFlags.Datadog, 'datadog'], [FeatureFlags.UniswapXPriorityOrders, 'uniswapx_priority_orders'], [FeatureFlags.SharedSwapArbitrumUniswapXExperiment, 'shared_swap_arbitrum_uniswapx_experiment'], + [FeatureFlags.TestnetMode, 'testnet-mode'], + [FeatureFlags.V4Swap, 'v4_swap'], + [FeatureFlags.WorldChain, 'world_chain'], // Web Specific [FeatureFlags.UniversalSwap, 'universal_swap'], @@ -87,6 +94,7 @@ export const WEB_FEATURE_FLAG_NAMES = new Map([ [FeatureFlags.Realtime, 'realtime'], [FeatureFlags.RestExplore, 'rest_explore'], [FeatureFlags.TraceJsonRpc, 'traceJsonRpc'], + [FeatureFlags.AstroChainLaunchModal, 'astro_chain_launch_modal'], [FeatureFlags.UniswapXSyntheticQuote, 'uniswapx_synthetic_quote'], [FeatureFlags.UniswapXv2, 'uniswapx_v2'], [FeatureFlags.V2Everywhere, 'v2_everywhere'], @@ -110,6 +118,9 @@ export const WALLET_FEATURE_FLAG_NAMES = new Map([ [FeatureFlags.SelfReportSpamNFTs, 'self-report-spam-nfts'], [FeatureFlags.UniswapXPriorityOrders, 'uniswapx_priority_orders'], [FeatureFlags.SharedSwapArbitrumUniswapXExperiment, 'shared_swap_arbitrum_uniswapx_experiment'], + [FeatureFlags.TestnetMode, 'testnet-mode'], + [FeatureFlags.V4Swap, 'v4_swap'], + [FeatureFlags.WorldChain, 'world_chain'], // Wallet Specific [FeatureFlags.Datadog, 'datadog'], diff --git a/packages/uniswap/src/features/portfolio/api.ts b/packages/uniswap/src/features/portfolio/api.ts index b62b49a5b21..889aa76cc19 100644 --- a/packages/uniswap/src/features/portfolio/api.ts +++ b/packages/uniswap/src/features/portfolio/api.ts @@ -7,14 +7,14 @@ import { getPollingIntervalByBlocktime } from 'uniswap/src/features/chains/utils import { createEthersProvider } from 'uniswap/src/features/providers/createEthersProvider' import { NativeCurrency } from 'uniswap/src/features/tokens/NativeCurrency' import { ValueType, getCurrencyAmount } from 'uniswap/src/features/tokens/getCurrencyAmount' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { currencyAddress as getCurrencyAddress } from 'uniswap/src/utils/currencyId' const ONCHAIN_BALANCES_CACHE_KEY = 'OnchainBalances' export type BalanceLookupParams = { currencyAddress?: Address - chainId?: WalletChainId + chainId?: UniverseChainId currencyIsNative?: boolean accountAddress?: string } diff --git a/packages/uniswap/src/features/providers/createEthersProvider.ts b/packages/uniswap/src/features/providers/createEthersProvider.ts index f48f773dc6e..3f8a20e974b 100644 --- a/packages/uniswap/src/features/providers/createEthersProvider.ts +++ b/packages/uniswap/src/features/providers/createEthersProvider.ts @@ -3,12 +3,12 @@ import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { FeatureFlags, getFeatureFlagName } from 'uniswap/src/features/gating/flags' import { Statsig } from 'uniswap/src/features/gating/sdk/statsig' import { FlashbotsRpcProvider } from 'uniswap/src/features/providers/FlashbotsRpcProvider' -import { RPCType, UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { RPCType, UniverseChainId } from 'uniswap/src/types/chains' import { logger } from 'utilities/src/logger/logger' // Should use ProviderManager for provider access unless being accessed outside of ProviderManagerContext (e.g., Apollo initialization) export function createEthersProvider( - chainId: WalletChainId, + chainId: UniverseChainId, rpcType: RPCType = RPCType.Public, signer?: Signer, ): ethersProviders.JsonRpcProvider | null { diff --git a/packages/uniswap/src/features/settings/hooks.ts b/packages/uniswap/src/features/settings/hooks.ts index 5b46571a99c..27b1f7708f2 100644 --- a/packages/uniswap/src/features/settings/hooks.ts +++ b/packages/uniswap/src/features/settings/hooks.ts @@ -1,13 +1,72 @@ +import { useMemo } from 'react' import { useSelector } from 'react-redux' +import { CONNECTION_PROVIDER_IDS } from 'uniswap/src/constants/web3' +import { useConnector } from 'uniswap/src/contexts/UniswapContext' +import { getEnabledChains, useFeatureFlaggedChainIds } from 'uniswap/src/features/chains/utils' +import { FeatureFlags } from 'uniswap/src/features/gating/flags' +import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { + selectIsTestnetModeEnabled, selectWalletHideSmallBalancesSetting, selectWalletHideSpamTokensSetting, } from 'uniswap/src/features/settings/selectors' +import { WalletConnectConnector } from 'uniswap/src/features/web3/walletConnect' +import { COMBINED_CHAIN_IDS, InterfaceGqlChain, UniverseChainId } from 'uniswap/src/types/chains' +import { isTestEnv } from 'utilities/src/environment/env' +import { logger } from 'utilities/src/logger/logger' export function useHideSmallBalancesSetting(): boolean { - return useSelector(selectWalletHideSmallBalancesSetting) + const { isTestnetModeEnabled } = useEnabledChains() + + return useSelector(selectWalletHideSmallBalancesSetting) && !isTestnetModeEnabled } export function useHideSpamTokensSetting(): boolean { - return useSelector(selectWalletHideSpamTokensSetting) + const { isTestnetModeEnabled } = useEnabledChains() + + return useSelector(selectWalletHideSpamTokensSetting) && !isTestnetModeEnabled +} + +// Returns the chain ids supported by the user's connected wallet +function useConnectedWalletSupportedChains(): UniverseChainId[] { + try { + const connector = useConnector() + + switch (connector?.type) { + case CONNECTION_PROVIDER_IDS.UNISWAP_WALLET_CONNECT_CONNECTOR_ID: + case CONNECTION_PROVIDER_IDS.WALLET_CONNECT_CONNECTOR_ID: + // Wagmi currently offers no way to discriminate a Connector as a WalletConnect connector providing access to getNamespaceChainsIds. + return (connector as WalletConnectConnector).getNamespaceChainsIds?.().length + ? (connector as WalletConnectConnector).getNamespaceChainsIds?.() + : COMBINED_CHAIN_IDS + default: + return COMBINED_CHAIN_IDS + } + } catch (_e) { + if (!isTestEnv()) { + logger.error(_e, { + tags: { file: 'src/features/settings/hooks', function: 'useConnectedWalletSupportedChains' }, + }) + } + // We're outside the UniswapContext when this hook is used by wallet or extension, so return all chains + return COMBINED_CHAIN_IDS + } +} + +export function useEnabledChains(): { + chains: UniverseChainId[] + gqlChains: InterfaceGqlChain[] + defaultChainId: UniverseChainId + isTestnetModeEnabled: boolean +} { + const featureFlaggedChainIds = useFeatureFlaggedChainIds() + const connectedWalletChainIds = useConnectedWalletSupportedChains() + const isTestnetModeFromState = useSelector(selectIsTestnetModeEnabled) + const isTestnetModeFromFlag = useFeatureFlag(FeatureFlags.TestnetMode) + const isTestnetModeEnabled = isTestnetModeFromState && isTestnetModeFromFlag + + return useMemo( + () => getEnabledChains({ isTestnetModeEnabled, connectedWalletChainIds, featureFlaggedChainIds }), + [isTestnetModeEnabled, connectedWalletChainIds, featureFlaggedChainIds], + ) } diff --git a/packages/uniswap/src/features/settings/saga.ts b/packages/uniswap/src/features/settings/saga.ts new file mode 100644 index 00000000000..687900ee572 --- /dev/null +++ b/packages/uniswap/src/features/settings/saga.ts @@ -0,0 +1,22 @@ +import { call, select } from 'typed-redux-saga' +import { filterChainIdsByFeatureFlag, getEnabledChains } from 'uniswap/src/features/chains/utils' +import { FeatureFlags } from 'uniswap/src/features/gating/flags' +import { getFeatureFlag } from 'uniswap/src/features/gating/hooks' +import { selectIsTestnetModeEnabled } from 'uniswap/src/features/settings/selectors' +import { UniverseChainId } from 'uniswap/src/types/chains' + +export function* getEnabledChainIdsSaga() { + const testnetModeFeatureFlag = getFeatureFlag(FeatureFlags.Datadog) + const testnetModeEnabled = yield* select(selectIsTestnetModeEnabled) + + const worldChainEnabled = getFeatureFlag(FeatureFlags.WorldChain) + + const featureFlaggedChainIds = filterChainIdsByFeatureFlag({ + [UniverseChainId.WorldChain]: worldChainEnabled, + }) + + return yield* call(getEnabledChains, { + isTestnetModeEnabled: testnetModeEnabled && testnetModeFeatureFlag, + featureFlaggedChainIds, + }) +} diff --git a/packages/uniswap/src/features/settings/selectors.ts b/packages/uniswap/src/features/settings/selectors.ts index 4308330aabb..cdf22a8e949 100644 --- a/packages/uniswap/src/features/settings/selectors.ts +++ b/packages/uniswap/src/features/settings/selectors.ts @@ -7,3 +7,6 @@ export const selectWalletHideSmallBalancesSetting = (state: UniswapState): boole export const selectWalletHideSpamTokensSetting = (state: UniswapState): boolean => state.userSettings.hideSpamTokens export const selectCurrentLanguage = (state: UniswapState): Language => state.userSettings.currentLanguage + +export const selectIsTestnetModeEnabled = (state: UniswapState): boolean => + state.userSettings.isTestnetModeEnabled ?? false diff --git a/packages/uniswap/src/features/settings/slice.ts b/packages/uniswap/src/features/settings/slice.ts index 22e70378f80..86965319f13 100644 --- a/packages/uniswap/src/features/settings/slice.ts +++ b/packages/uniswap/src/features/settings/slice.ts @@ -1,12 +1,15 @@ import { createAction, createSlice, PayloadAction } from '@reduxjs/toolkit' import { FiatCurrency } from 'uniswap/src/features/fiatCurrency/constants' import { Language } from 'uniswap/src/features/language/constants' +// eslint-disable-next-line no-restricted-imports +import { analytics } from 'utilities/src/telemetry/analytics/analytics' export interface UserSettingsState { currentLanguage: Language currentCurrency: FiatCurrency hideSmallBalances: boolean hideSpamTokens: boolean + isTestnetModeEnabled?: boolean } export const initialUserSettingsState: UserSettingsState = { @@ -14,6 +17,7 @@ export const initialUserSettingsState: UserSettingsState = { currentCurrency: FiatCurrency.UnitedStatesDollar, hideSmallBalances: true, hideSpamTokens: true, + isTestnetModeEnabled: false, } const slice = createSlice({ @@ -32,11 +36,24 @@ const slice = createSlice({ setCurrentFiatCurrency: (state, action: PayloadAction) => { state.currentCurrency = action.payload }, + /** + * IMPORTANT: minimize and thoroughly vet every usage of this action so that testnets are **never** unintentionally toggled on + */ + setIsTestnetModeEnabled: (state, { payload }: PayloadAction) => { + state.isTestnetModeEnabled = payload + analytics.setTestnetMode(payload) + }, resetSettings: () => initialUserSettingsState, }, }) -export const { setHideSmallBalances, setHideSpamTokens, setCurrentLanguage, setCurrentFiatCurrency } = slice.actions +export const { + setHideSmallBalances, + setHideSpamTokens, + setCurrentLanguage, + setCurrentFiatCurrency, + setIsTestnetModeEnabled, +} = slice.actions export const updateLanguage = createAction('language/updateLanguage') export const syncAppWithDeviceLanguage = (): ReturnType => updateLanguage(null) diff --git a/packages/uniswap/src/features/telemetry/constants/trace.ts b/packages/uniswap/src/features/telemetry/constants/trace.ts index 94318b6db36..7e16cb48b64 100644 --- a/packages/uniswap/src/features/telemetry/constants/trace.ts +++ b/packages/uniswap/src/features/telemetry/constants/trace.ts @@ -19,6 +19,7 @@ export const ModalName = { BuyNativeToken: 'buy-native-token-modal', ChooseProfilePhoto: 'choose-profile-photo-modal', CloudBackupInfo: 'cloud-backup-info-modal', + CreatePosition: 'create-position-modal', DappRequest: 'dapp-request', ENSClaimPeriod: 'ens-claim-period', EnterPassword: 'enter-password-modal', @@ -27,6 +28,7 @@ export const ModalName = { Experiments: 'experiments', Explore: 'explore-modal', FaceIDWarning: 'face-id-warning', + FeeClaim: 'fee-claim-modal', FeeTierSearch: 'fee-tier-search-modal', FOTInfo: 'fee-on-transfer', FiatCurrencySelector: 'fiat-currency-selector', @@ -74,6 +76,7 @@ export const ModalName = { SwapSettings: 'swap-settings-modal', SwapWarning: 'swap-warning-modal', SwapProtection: 'swap-protection-modal', + TestnetMode: 'testnet-mode-modal', TokenSelector: 'token-selector', TokenWarningModal: 'token-warning-modal', TooltipContent: 'tooltip-content', @@ -111,22 +114,19 @@ export const ElementName = { AlreadyHaveWalletSignIn: 'already-have-wallet-sign-in', Buy: 'buy', BuyNativeTokenButton: 'buy-native-token-button', + BridgeNativeTokenButton: 'bridge-native-token-button', Cancel: 'cancel', ChainEthereum: 'chain-ethereum', - ChainEthereumGoerli: 'chain-ethereum-goerli', ChainSepolia: 'chain-sepolia', ChainOptimism: 'chain-optimism', - ChainOptimismGoerli: 'chain-optimism-goerli', ChainArbitrum: 'chain-arbitrum', - ChainArbitrumGoerli: 'chain-arbitrum-goerli', ChainPolygon: 'chain-polygon', - ChainPolygonMumbai: 'chain-polygon-mumbai', ChainCelo: 'chain-celo', - ChainCeloAlfajores: 'chain-celo-alfajores', ChainBNB: 'chain-bnb', ChainAvalanche: 'chain-avalanche', ChainBase: 'chain-base', ChainBlast: 'chain-blast', + ChainWorldChain: 'chain-world-chain', ChainZora: 'chain-zora', ChainZkSync: 'chain-zksync', ChooseInputToken: 'choose-input-token', @@ -180,6 +180,7 @@ export const ElementName = { SwapRoutingPreferenceUniswapX: 'swap-routing-preference-UniswapX', SwapRoutingPreferenceV2: 'swap-routing-preference-v2', SwapRoutingPreferenceV3: 'swap-routing-preference-v3', + SwapRoutingPreferenceV4: 'swap-routing-preference-v4', SwitchCurrenciesButton: 'switch-currencies-button', TimeFrame1H: 'time-frame-1H', TimeFrame1D: 'time-frame-1D', diff --git a/packages/uniswap/src/features/telemetry/constants/wallet.ts b/packages/uniswap/src/features/telemetry/constants/wallet.ts index 8caaee90099..c7b43df1fbd 100644 --- a/packages/uniswap/src/features/telemetry/constants/wallet.ts +++ b/packages/uniswap/src/features/telemetry/constants/wallet.ts @@ -14,6 +14,7 @@ export enum WalletEventName { ShareButtonClicked = 'Share Button Clicked', SwapSubmitted = 'Swap Submitted to Provider', TokenVisibilityChanged = 'Token Visibility Changed', + TestnetModeToggled = 'Testnet Mode Toggled', TransferCompleted = 'Transfer Completed', TransferSubmitted = 'Transfer Submitted', ViewRecoveryPhrase = 'View Recovery Phrase', diff --git a/packages/uniswap/src/features/telemetry/types.ts b/packages/uniswap/src/features/telemetry/types.ts index 5c06d480ec5..2234f1554aa 100644 --- a/packages/uniswap/src/features/telemetry/types.ts +++ b/packages/uniswap/src/features/telemetry/types.ts @@ -40,7 +40,7 @@ import { import { WrapType } from 'uniswap/src/features/transactions/types/wrap' import { UnitagClaimContext } from 'uniswap/src/features/unitags/types' import { RenderPassReport } from 'uniswap/src/types/RenderPassReport' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { LimitsExpiry } from 'uniswap/src/types/limits' import { ImportType } from 'uniswap/src/types/onboarding' @@ -102,6 +102,7 @@ export type SwapRouting = | 'priority_order' | 'bridge' | 'limit_order' + | 'priority' | 'none' export type SwapTradeBaseProperties = { @@ -179,7 +180,7 @@ type BridgeSwapTransactionResultProperties = BaseSwapTransactionResultProperties type FailedUniswapXOrderResultProperties = Omit type TransferProperties = { - chainId: WalletChainId + chainId: UniverseChainId tokenAddress: Address toAddress: Address amountUSD?: number @@ -193,7 +194,7 @@ export type WindowEthereumRequestProperties = { export type DappContextProperties = { dappUrl?: string - chainId?: WalletChainId + chainId?: UniverseChainId activeConnectedAddress?: Address connectedAddresses: Address[] } @@ -747,7 +748,7 @@ export type UniverseEventProperties = { } [WalletEventName.NFTVisibilityChanged]: { tokenId?: string - chainId?: WalletChainId + chainId?: UniverseChainId contractAddress?: Address isSpam?: boolean visible: boolean @@ -785,6 +786,9 @@ export type UniverseEventProperties = { } ) & SwapTradeBaseProperties + [WalletEventName.TestnetModeToggled]: { + enabled: boolean + } [WalletEventName.ViewRecoveryPhrase]: undefined // Please sort new values by EventName type! } diff --git a/packages/uniswap/src/features/testnets/TestnetModeModal.tsx b/packages/uniswap/src/features/testnets/TestnetModeModal.tsx new file mode 100644 index 00000000000..f8e28a270f0 --- /dev/null +++ b/packages/uniswap/src/features/testnets/TestnetModeModal.tsx @@ -0,0 +1,43 @@ +import { useTranslation } from 'react-i18next' +import { Flex } from 'ui/src' +import { Wrench } from 'ui/src/components/icons/Wrench' +import { InfoLinkModal } from 'uniswap/src/components/modals/InfoLinkModal' +import { ModalName } from 'uniswap/src/features/telemetry/constants' + +type TestnetModeModalProps = { + isOpen: boolean + unsupported?: boolean + descriptionCopy?: string + showCloseButton?: boolean + onClose: () => void +} + +export function TestnetModeModal({ + isOpen, + descriptionCopy, + unsupported = false, + showCloseButton = false, + onClose, +}: TestnetModeModalProps): JSX.Element { + const { t } = useTranslation() + return ( + + + + } + showCloseButton={showCloseButton} + onDismiss={onClose} + onButtonPress={onClose} + /> + ) +} diff --git a/packages/uniswap/src/features/tokens/NativeCurrency.ts b/packages/uniswap/src/features/tokens/NativeCurrency.ts index 02f802ba2c8..151cbc4b497 100644 --- a/packages/uniswap/src/features/tokens/NativeCurrency.ts +++ b/packages/uniswap/src/features/tokens/NativeCurrency.ts @@ -3,7 +3,7 @@ import { Currency, NativeCurrency as NativeCurrencyClass, Token } from '@uniswap import { getNativeAddress } from 'uniswap/src/constants/addresses' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { toSupportedChainId } from 'uniswap/src/features/chains/utils' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { wrappedNativeCurrency } from 'uniswap/src/utils/currency' export class NativeCurrency implements NativeCurrencyClass { @@ -27,7 +27,7 @@ export class NativeCurrency implements NativeCurrencyClass { this.address = getNativeAddress(this.chainId) } - chainId: WalletChainId + chainId: UniverseChainId decimals: number name: string symbol: string diff --git a/packages/uniswap/src/features/tokens/hooks.ts b/packages/uniswap/src/features/tokens/hooks.ts index e99d3011053..6c027843411 100644 --- a/packages/uniswap/src/features/tokens/hooks.ts +++ b/packages/uniswap/src/features/tokens/hooks.ts @@ -5,7 +5,7 @@ import { SearchPopularTokensQuery, useSearchPopularTokensQuery, } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' -import { UniverseChainId } from 'uniswap/src/types/chains' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { areAddressesEqual } from 'uniswap/src/utils/addresses' export type TopToken = NonNullable[0]> @@ -17,6 +17,7 @@ export function usePopularTokens(): { } { // Load popular tokens by top Uniswap trading volume const { data, loading } = useSearchPopularTokensQuery() + const { defaultChainId } = useEnabledChains() const popularTokens = useMemo(() => { if (!data || !data.topTokens) { @@ -27,7 +28,7 @@ export function usePopularTokens(): { // eth will be defined only if all the required data is available // when eth data is not fully available, we do not replace weth with eth const eth = data?.eth && data?.eth.length > 0 && data?.eth?.[0]?.project ? data.eth[0] : null - const wethAddress = getWrappedNativeAddress(UniverseChainId.Mainnet) + const wethAddress = getWrappedNativeAddress(defaultChainId) return data.topTokens .map((token) => { @@ -45,7 +46,7 @@ export function usePopularTokens(): { return token }) .filter((t): t is TopToken => Boolean(t)) - }, [data]) + }, [data, defaultChainId]) return { popularTokens, loading } } diff --git a/packages/uniswap/src/features/tokens/useCurrencyInfo.ts b/packages/uniswap/src/features/tokens/useCurrencyInfo.ts index e2a423c094b..5df39b8c4b6 100644 --- a/packages/uniswap/src/features/tokens/useCurrencyInfo.ts +++ b/packages/uniswap/src/features/tokens/useCurrencyInfo.ts @@ -2,7 +2,7 @@ import { useMemo } from 'react' import { useTokenQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { currencyIdToContractInput, gqlTokenToCurrencyInfo } from 'uniswap/src/features/dataApi/utils' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { buildNativeCurrencyId, buildWrappedNativeCurrencyId } from 'uniswap/src/utils/currencyId' export function useCurrencyInfo( @@ -24,12 +24,12 @@ export function useCurrencyInfo( }, [data, _currencyId]) } -export function useNativeCurrencyInfo(chainId: WalletChainId): Maybe { +export function useNativeCurrencyInfo(chainId: UniverseChainId): Maybe { const nativeCurrencyId = buildNativeCurrencyId(chainId) return useCurrencyInfo(nativeCurrencyId) } -export function useWrappedNativeCurrencyInfo(chainId: WalletChainId): Maybe { +export function useWrappedNativeCurrencyInfo(chainId: UniverseChainId): Maybe { const wrappedCurrencyId = buildWrappedNativeCurrencyId(chainId) return useCurrencyInfo(wrappedCurrencyId) } diff --git a/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/BridgeTokenButton.tsx b/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/BridgeTokenButton.tsx new file mode 100644 index 00000000000..7e2dfff6c66 --- /dev/null +++ b/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/BridgeTokenButton.tsx @@ -0,0 +1,80 @@ +import { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import { Button, isWeb } from 'ui/src' +import { opacify, validColor } from 'ui/src/theme' +import { AssetType } from 'uniswap/src/entities/assets' +import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' +import Trace from 'uniswap/src/features/telemetry/Trace' +import { ElementName } from 'uniswap/src/features/telemetry/constants' +import { getDefaultState, useSwapFormContext } from 'uniswap/src/features/transactions/swap/contexts/SwapFormContext' +import { UniverseChainId } from 'uniswap/src/types/chains' +import { useNetworkColors } from 'uniswap/src/utils/colors' +import { currencyIdToAddress } from 'uniswap/src/utils/currencyId' + +export function BridgeTokenButton({ + inputToken, + outputToken, + outputNetworkName, +}: { + inputToken: CurrencyInfo + outputToken: CurrencyInfo + outputNetworkName: string +}): JSX.Element { + const { t } = useTranslation() + const { foreground, background } = useNetworkColors(outputToken.currency?.chainId ?? UniverseChainId.Mainnet) + const primaryColor = validColor(foreground) + const backgroundColor = validColor(background) + const onPressColor = validColor(opacify(50, foreground)) + + const { defaultChainId } = useEnabledChains() + const { updateSwapForm } = useSwapFormContext() + + const onPressBridgeToken = useCallback((): void => { + updateSwapForm({ + ...getDefaultState(defaultChainId), + input: { + address: currencyIdToAddress(inputToken.currencyId), + chainId: inputToken.currency.chainId, + type: AssetType.Currency, + }, + output: { + address: currencyIdToAddress(outputToken.currencyId), + chainId: outputToken.currency.chainId, + type: AssetType.Currency, + }, + }) + }, [ + defaultChainId, + inputToken.currency.chainId, + inputToken.currencyId, + outputToken.currency.chainId, + outputToken.currencyId, + updateSwapForm, + ]) + + if (!outputToken.currency.symbol) { + throw new Error( + 'Unexpected render of `BridgeTokenButton` without a token symbol for currency ' + outputToken.currencyId, + ) + } + + return ( + + + + ) +} diff --git a/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/BuyNativeTokenButton.tsx b/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/BuyNativeTokenButton.tsx index d0b66d0872c..ea82d57a0cb 100644 --- a/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/BuyNativeTokenButton.tsx +++ b/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/BuyNativeTokenButton.tsx @@ -1,17 +1,24 @@ import { useTranslation } from 'react-i18next' -import { Button, Flex, Text, isWeb } from 'ui/src' +import { Button, isWeb } from 'ui/src' import { opacify, validColor } from 'ui/src/theme' import { useUniswapContext } from 'uniswap/src/contexts/UniswapContext' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { useIsSupportedFiatOnRampCurrency } from 'uniswap/src/features/fiatOnRamp/hooks' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import Trace from 'uniswap/src/features/telemetry/Trace' import { ElementName } from 'uniswap/src/features/telemetry/constants' -import { UniverseChainId } from 'uniswap/src/types/chains' import { useNetworkColors } from 'uniswap/src/utils/colors' -export function BuyNativeTokenButton({ nativeCurrencyInfo }: { nativeCurrencyInfo: CurrencyInfo }): JSX.Element { +export function BuyNativeTokenButton({ + nativeCurrencyInfo, + canBridge, +}: { + nativeCurrencyInfo: CurrencyInfo + canBridge: boolean +}): JSX.Element { const { t } = useTranslation() - const { foreground, background } = useNetworkColors(nativeCurrencyInfo.currency?.chainId ?? UniverseChainId.Mainnet) + const { defaultChainId } = useEnabledChains() + const { foreground, background } = useNetworkColors(nativeCurrencyInfo.currency?.chainId ?? defaultChainId) const primaryColor = validColor(foreground) const backgroundColor = validColor(background) const onPressColor = validColor(opacify(50, foreground)) @@ -25,33 +32,23 @@ export function BuyNativeTokenButton({ nativeCurrencyInfo }: { nativeCurrencyInf return ( - {isWeb ? ( - - - {t('swap.warning.insufficientGas.button.buy', { tokenSymbol: nativeCurrencyInfo.currency.symbol })} - - - ) : ( - - )} + ) } diff --git a/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarning.tsx b/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarning.tsx index 0232072b997..b9a34e825da 100644 --- a/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarning.tsx +++ b/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarning.tsx @@ -1,13 +1,49 @@ import { Warning } from 'uniswap/src/components/modals/WarningModal/types' +import { useAccountMeta } from 'uniswap/src/contexts/UniswapContext' import { GasFeeResult } from 'uniswap/src/features/gas/types' -import { PlatformSplitStubError } from 'utilities/src/errors' +import { InsufficientNativeTokenWarningContent } from 'uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarningContent' +import { useInsufficientNativeTokenWarning } from 'uniswap/src/features/transactions/InsufficientNativeTokenWarning/useInsufficientNativeTokenWarning' +import { logger } from 'utilities/src/logger/logger' -export type InsufficientNativeTokenWarningProps = { +export function InsufficientNativeTokenWarning({ + warnings, + flow, + gasFee, +}: { warnings: Warning[] flow: 'send' | 'swap' gasFee: GasFeeResult -} +}): JSX.Element | null { + const parsedInsufficentNativeTokenWarning = useInsufficientNativeTokenWarning({ + warnings, + flow, + gasFee, + }) + + const { nativeCurrency, nativeCurrencyInfo } = parsedInsufficentNativeTokenWarning ?? {} + + const address = useAccountMeta()?.address + + if (!parsedInsufficentNativeTokenWarning || !nativeCurrencyInfo || !nativeCurrency) { + return null + } + + if (!address) { + logger.error(new Error('Unexpected render of `InsufficientNativeTokenWarning` without an active address'), { + tags: { + file: 'InsufficientNativeTokenWarning.tsx', + function: 'InsufficientNativeTokenWarning', + }, + }) + return null + } -export function InsufficientNativeTokenWarning(_: InsufficientNativeTokenWarningProps): JSX.Element | null { - throw new PlatformSplitStubError('InsufficientNativeTokenWarning') + return ( + + ) } diff --git a/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarning.web.tsx b/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarning.web.tsx deleted file mode 100644 index 8b702d79a5c..00000000000 --- a/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarning.web.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { Flex, Text, Tooltip } from 'ui/src' -import { LearnMoreLink } from 'uniswap/src/components/text/LearnMoreLink' -import { uniswapUrls } from 'uniswap/src/constants/urls' -import { BuyNativeTokenButton } from 'uniswap/src/features/transactions/InsufficientNativeTokenWarning/BuyNativeTokenButton' -import { InsufficientNativeTokenBaseComponent } from 'uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenBaseComponent' -import type { InsufficientNativeTokenWarningProps } from 'uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarning' -import { useInsufficientNativeTokenWarning } from 'uniswap/src/features/transactions/InsufficientNativeTokenWarning/useInsufficientNativeTokenWarning' - -export function InsufficientNativeTokenWarning({ - warnings, - flow, - gasFee, -}: InsufficientNativeTokenWarningProps): JSX.Element | null { - const parsedInsufficentNativeTokenWarning = useInsufficientNativeTokenWarning({ - warnings, - flow, - gasFee, - }) - - const { modalOrTooltipMainMessage, nativeCurrencyInfo } = parsedInsufficentNativeTokenWarning ?? {} - - if (!parsedInsufficentNativeTokenWarning || !nativeCurrencyInfo) { - return null - } - - return ( - - - - - - - - - {modalOrTooltipMainMessage} - - - - - - - - - - - - ) -} diff --git a/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarning.native.tsx b/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarningContent.native.tsx similarity index 62% rename from packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarning.native.tsx rename to packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarningContent.native.tsx index 28097312d9a..59707dc4a41 100644 --- a/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarning.native.tsx +++ b/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarningContent.native.tsx @@ -1,3 +1,4 @@ +import { Currency } from '@uniswap/sdk-core' import { useState } from 'react' import { useTranslation } from 'react-i18next' import { Flex, Text, TouchableArea } from 'ui/src' @@ -5,33 +6,39 @@ import { CurrencyLogo } from 'uniswap/src/components/CurrencyLogo/CurrencyLogo' import { WarningModal } from 'uniswap/src/components/modals/WarningModal/WarningModal' import { LearnMoreLink } from 'uniswap/src/components/text/LearnMoreLink' import { uniswapUrls } from 'uniswap/src/constants/urls' +import { useBridgingTokenWithHighestBalance } from 'uniswap/src/features/bridging/hooks/tokens' +import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { ModalName } from 'uniswap/src/features/telemetry/constants' +import { BridgeTokenButton } from 'uniswap/src/features/transactions/InsufficientNativeTokenWarning/BridgeTokenButton' import { BuyNativeTokenButton } from 'uniswap/src/features/transactions/InsufficientNativeTokenWarning/BuyNativeTokenButton' import { InsufficientNativeTokenBaseComponent } from 'uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenBaseComponent' -import type { InsufficientNativeTokenWarningProps } from 'uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarning' import { useInsufficientNativeTokenWarning } from 'uniswap/src/features/transactions/InsufficientNativeTokenWarning/useInsufficientNativeTokenWarning' import { UniverseChainId } from 'uniswap/src/types/chains' +import { currencyIdToAddress } from 'uniswap/src/utils/currencyId' -export function InsufficientNativeTokenWarning({ - warnings, - flow, - gasFee, -}: InsufficientNativeTokenWarningProps): JSX.Element | null { +export function InsufficientNativeTokenWarningContent({ + address, + parsedInsufficentNativeTokenWarning, + nativeCurrencyInfo, + nativeCurrency, +}: { + address: Address + parsedInsufficentNativeTokenWarning: NonNullable> + nativeCurrencyInfo: CurrencyInfo + nativeCurrency: Currency +}): JSX.Element { const { t } = useTranslation() const [showModal, setShowModal] = useState(false) - const parsedInsufficentNativeTokenWarning = useInsufficientNativeTokenWarning({ - warnings, - flow, - gasFee, - }) + const { networkName, modalOrTooltipMainMessage } = parsedInsufficentNativeTokenWarning - const { modalOrTooltipMainMessage, nativeCurrency, nativeCurrencyInfo, networkName } = - parsedInsufficentNativeTokenWarning ?? {} + const currencyAddress = currencyIdToAddress(nativeCurrencyInfo.currencyId) - if (!parsedInsufficentNativeTokenWarning || !nativeCurrencyInfo || !nativeCurrency) { - return null - } + const bridgingTokenWithHighestBalance = useBridgingTokenWithHighestBalance({ + address, + currencyAddress, + currencyChainId: nativeCurrencyInfo.currency.chainId, + }) const shouldShowNetworkName = nativeCurrency.symbol === 'ETH' && nativeCurrency.chainId !== UniverseChainId.Mainnet @@ -66,7 +73,19 @@ export function InsufficientNativeTokenWarning({ {modalOrTooltipMainMessage} - + {bridgingTokenWithHighestBalance && ( + + )} + + + > + nativeCurrencyInfo: CurrencyInfo + nativeCurrency: Currency +} + +export function InsufficientNativeTokenWarningContent( + _: InsufficientNativeTokenWarningContentProps, +): JSX.Element | null { + throw new PlatformSplitStubError('InsufficientNativeTokenWarningContent') +} diff --git a/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarningContent.web.tsx b/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarningContent.web.tsx new file mode 100644 index 00000000000..8db33a40e5a --- /dev/null +++ b/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarningContent.web.tsx @@ -0,0 +1,62 @@ +import { Flex, Text, Tooltip } from 'ui/src' +import { LearnMoreLink } from 'uniswap/src/components/text/LearnMoreLink' +import { uniswapUrls } from 'uniswap/src/constants/urls' +import { useBridgingTokenWithHighestBalance } from 'uniswap/src/features/bridging/hooks/tokens' +import { BridgeTokenButton } from 'uniswap/src/features/transactions/InsufficientNativeTokenWarning/BridgeTokenButton' +import { BuyNativeTokenButton } from 'uniswap/src/features/transactions/InsufficientNativeTokenWarning/BuyNativeTokenButton' +import { InsufficientNativeTokenBaseComponent } from 'uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenBaseComponent' +import type { InsufficientNativeTokenWarningContentProps } from 'uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarningContent' +import { currencyIdToAddress } from 'uniswap/src/utils/currencyId' + +export function InsufficientNativeTokenWarningContent({ + address, + parsedInsufficentNativeTokenWarning, + nativeCurrencyInfo, +}: InsufficientNativeTokenWarningContentProps): JSX.Element | null { + const { networkName, modalOrTooltipMainMessage } = parsedInsufficentNativeTokenWarning + + const currencyAddress = currencyIdToAddress(nativeCurrencyInfo.currencyId) + + const bridgingTokenWithHighestBalance = useBridgingTokenWithHighestBalance({ + address, + currencyAddress, + currencyChainId: nativeCurrencyInfo.currency.chainId, + }) + + return ( + + + + + + + + + {modalOrTooltipMainMessage} + + + + {bridgingTokenWithHighestBalance && ( + + )} + + + + + + + + + + + ) +} diff --git a/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/useInsufficientNativeTokenWarning.tsx b/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/useInsufficientNativeTokenWarning.tsx index ba89656b294..383e048f58b 100644 --- a/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/useInsufficientNativeTokenWarning.tsx +++ b/packages/uniswap/src/features/transactions/InsufficientNativeTokenWarning/useInsufficientNativeTokenWarning.tsx @@ -1,5 +1,5 @@ import { Currency, CurrencyAmount } from '@uniswap/sdk-core' -import { useMemo } from 'react' +import { ComponentProps, useMemo } from 'react' import { Trans } from 'react-i18next' import { Text } from 'ui/src' import { Warning, WarningLabel } from 'uniswap/src/components/modals/WarningModal/types' @@ -7,17 +7,21 @@ import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { toSupportedChainId } from 'uniswap/src/features/chains/utils' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { NativeCurrency } from 'uniswap/src/features/tokens/NativeCurrency' import { ValueType, getCurrencyAmount } from 'uniswap/src/features/tokens/getCurrencyAmount' import { useNativeCurrencyInfo } from 'uniswap/src/features/tokens/useCurrencyInfo' -import { type InsufficientNativeTokenWarningProps } from 'uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarning' +import { InsufficientNativeTokenWarning } from 'uniswap/src/features/transactions/InsufficientNativeTokenWarning/InsufficientNativeTokenWarning' import { INSUFFICIENT_NATIVE_TOKEN_TEXT_VARIANT } from 'uniswap/src/features/transactions/InsufficientNativeTokenWarning/constants' import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice' -import { UniverseChainId } from 'uniswap/src/types/chains' import { useNetworkColors } from 'uniswap/src/utils/colors' import { NumberType } from 'utilities/src/format/types' -export function useInsufficientNativeTokenWarning({ flow, gasFee, warnings }: InsufficientNativeTokenWarningProps): { +export function useInsufficientNativeTokenWarning({ + flow, + gasFee, + warnings, +}: ComponentProps): { gasAmount: CurrencyAmount | null | undefined gasAmountFiatFormatted: string nativeCurrency: Currency @@ -26,12 +30,13 @@ export function useInsufficientNativeTokenWarning({ flow, gasFee, warnings }: In networkName: string modalOrTooltipMainMessage: JSX.Element warning: Warning - flow: InsufficientNativeTokenWarningProps['flow'] + flow: ComponentProps['flow'] } | null { + const { defaultChainId } = useEnabledChains() const { convertFiatAmountFormatted } = useLocalizationContext() const warning = warnings.find((w) => w.type === WarningLabel.InsufficientGasFunds) const nativeCurrency = warning?.currency - const chainId = nativeCurrency?.chainId ?? UniverseChainId.Mainnet + const chainId = nativeCurrency?.chainId ?? defaultChainId const nativeCurrencyInfo = useNativeCurrencyInfo(chainId) diff --git a/packages/uniswap/src/features/transactions/TransactionDetails/TransactionDetails.tsx b/packages/uniswap/src/features/transactions/TransactionDetails/TransactionDetails.tsx index 9a44f950497..56f3a82f692 100644 --- a/packages/uniswap/src/features/transactions/TransactionDetails/TransactionDetails.tsx +++ b/packages/uniswap/src/features/transactions/TransactionDetails/TransactionDetails.tsx @@ -16,7 +16,6 @@ import { FeeOnTransferFeeGroupProps, } from 'uniswap/src/features/transactions/TransactionDetails/FeeOnTransferFee' import { SwapFee } from 'uniswap/src/features/transactions/TransactionDetails/SwapFee' -import { AcrossRoutingInfo } from 'uniswap/src/features/transactions/swap/modals/AcrossRoutingInfo' import { EstimatedTime } from 'uniswap/src/features/transactions/swap/review/EstimatedTime' import { UniswapXGasBreakdown } from 'uniswap/src/features/transactions/swap/types/swapTxAndGasInfo' import { SwapFee as SwapFeeType } from 'uniswap/src/features/transactions/swap/types/trade' @@ -110,8 +109,7 @@ export function TransactionDetails({ transactionUSDValue={transactionUSDValue} uniswapXGasBreakdown={uniswapXGasBreakdown} /> - {isSwap && isBridgeTrade && } - {isSwap && !isBridgeTrade && RoutingInfo} + {isSwap && RoutingInfo} {AccountDetails} {showChildren ? ( diff --git a/packages/uniswap/src/features/transactions/TransactionModal/TransactionModal.native.tsx b/packages/uniswap/src/features/transactions/TransactionModal/TransactionModal.native.tsx index 171aa62bd1a..f2e51c3b1d2 100644 --- a/packages/uniswap/src/features/transactions/TransactionModal/TransactionModal.native.tsx +++ b/packages/uniswap/src/features/transactions/TransactionModal/TransactionModal.native.tsx @@ -9,7 +9,7 @@ import { useDerivedValue, useSharedValue, } from 'react-native-reanimated' -import { Flex, LinearGradient, useDeviceInsets, useSporeColors } from 'ui/src' +import { Flex, LinearGradient, useSporeColors } from 'ui/src' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { useDeviceDimensions } from 'ui/src/hooks/useDeviceDimensions' import { borderRadii, opacify } from 'ui/src/theme' @@ -25,6 +25,7 @@ import { TransactionModalProps, } from 'uniswap/src/features/transactions/TransactionModal/TransactionModalProps' import { TransactionModalUpdateLogger } from 'uniswap/src/features/transactions/TransactionModal/TransactionModalUpdateLogger' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' const HANLDEBAR_HEIGHT = 32 @@ -38,7 +39,7 @@ export function TransactionModal({ const fullscreen = screen === TransactionScreen.Form const colors = useSporeColors() - const insets = useDeviceInsets() + const insets = useAppInsets() const dimensions = useDeviceDimensions() const backgroundColorValue = colors.surface1.get() @@ -103,7 +104,7 @@ export function TransactionModalInnerContainer({ fullscreen, children, }: TransactionModalInnerContainerProps): JSX.Element { - const insets = useDeviceInsets() + const insets = useAppInsets() const { animatedFooterHeight } = useBottomSheetInternal() @@ -129,7 +130,7 @@ export function TransactionModalInnerContainer({ } export function TransactionModalFooterContainer({ children }: TransactionModalFooterContainerProps): JSX.Element { - const insets = useDeviceInsets() + const insets = useAppInsets() const colors = useSporeColors() // Most of this logic is based on the `BottomSheetFooterContainer` component from `@gorhom/bottom-sheet`. diff --git a/packages/uniswap/src/features/transactions/hooks/useUSDTokenUpdater.ts b/packages/uniswap/src/features/transactions/hooks/useUSDTokenUpdater.ts index db77f1b613e..34712831895 100644 --- a/packages/uniswap/src/features/transactions/hooks/useUSDTokenUpdater.ts +++ b/packages/uniswap/src/features/transactions/hooks/useUSDTokenUpdater.ts @@ -25,7 +25,7 @@ export function useUSDTokenUpdater({ onFiatAmountUpdated, onTokenAmountUpdated, }: USDTokenUpdaterProps): void { - const price = useUSDCPrice(currency) + const { price } = useUSDCPrice(currency) const shouldUseUSDRef = useRef(isFiatInput) const { convertFiatAmount, formatCurrencyAmount } = useLocalizationContext() const conversionRate = convertFiatAmount(1).amount diff --git a/packages/uniswap/src/features/transactions/liquidity/types.ts b/packages/uniswap/src/features/transactions/liquidity/types.ts index bc7fb31ac41..bc570c76189 100644 --- a/packages/uniswap/src/features/transactions/liquidity/types.ts +++ b/packages/uniswap/src/features/transactions/liquidity/types.ts @@ -7,11 +7,17 @@ import { ValidatedPermit, ValidatedTransactionRequest } from 'uniswap/src/featur export interface LiquidityAction { currency0Amount: CurrencyAmount currency1Amount: CurrencyAmount - liquidityToken: Token + liquidityToken?: Token } -export type LiquidityTxAndGasInfo = IncreasePositionTxAndGasInfo -export type ValidatedLiquidityTxContext = ValidatedIncreasePositionTxAndGasInfo +export type LiquidityTxAndGasInfo = + | IncreasePositionTxAndGasInfo + | DecreasePositionTxAndGasInfo + | CreatePositionTxAndGasInfo +export type ValidatedLiquidityTxContext = + | ValidatedIncreasePositionTxAndGasInfo + | ValidatedDecreasePositionTxAndGasInfo + | ValidatedCreatePositionTxAndGasInfo export function isValidLiquidityTxContext( liquidityTxContext: LiquidityTxAndGasInfo | unknown, @@ -20,29 +26,52 @@ export function isValidLiquidityTxContext( return validateLiquidityTxContext(liquidityTxContext) !== undefined } -interface BaseRequiredLiquidityTxContextFields { - approvalError: false -} - interface BaseLiquidityTxAndGasInfo { protocolVersion: ProtocolVersion action: LiquidityAction - approvalError: boolean approveToken0Request: ValidatedTransactionRequest | undefined approveToken1Request: ValidatedTransactionRequest | undefined approvePositionTokenRequest: ValidatedTransactionRequest | undefined permit: ValidatedPermit | undefined revocationTxRequest: ValidatedTransactionRequest | undefined + txRequest: ValidatedTransactionRequest | undefined } export interface IncreasePositionTxAndGasInfo extends BaseLiquidityTxAndGasInfo { + type: 'increase' unsigned: boolean increasePositionRequestArgs: IncreaseLPPositionRequest | undefined - txRequest: ValidatedTransactionRequest | undefined +} + +export interface DecreasePositionTxAndGasInfo extends BaseLiquidityTxAndGasInfo { + type: 'decrease' +} + +export interface CreatePositionTxAndGasInfo extends BaseLiquidityTxAndGasInfo { + type: 'create' + unsigned: boolean + createPositionRequestArgs: IncreaseLPPositionRequest | undefined } export type ValidatedIncreasePositionTxAndGasInfo = Required & - BaseRequiredLiquidityTxContextFields & + ( + | { + unsigned: true + permit: ValidatedPermit + txRequest: undefined + } + | { + unsigned: false + permit: undefined + txRequest: ValidatedTransactionRequest + } + ) + +export type ValidatedDecreasePositionTxAndGasInfo = Required & { + txRequest: ValidatedTransactionRequest +} + +export type ValidatedCreatePositionTxAndGasInfo = Required & ( | { unsigned: true @@ -63,17 +92,17 @@ function validateLiquidityTxContext( return undefined } - if (!liquidityTxContext.approvalError && liquidityTxContext.action) { - const { approvalError } = liquidityTxContext - const { action, txRequest, unsigned, permit } = liquidityTxContext - + if (liquidityTxContext.action) { + const { action, txRequest, permit } = liquidityTxContext + const unsigned = + (liquidityTxContext.type === 'increase' || liquidityTxContext.type === 'create') && liquidityTxContext.unsigned if (unsigned) { if (!permit) { return undefined } - return { ...liquidityTxContext, action, approvalError, unsigned, txRequest: undefined, permit } + return { ...liquidityTxContext, action, unsigned, txRequest: undefined, permit } } else if (txRequest) { - return { ...liquidityTxContext, action, approvalError, unsigned, txRequest, permit: undefined } + return { ...liquidityTxContext, action, unsigned, txRequest, permit: undefined } } } diff --git a/packages/uniswap/src/features/transactions/selectors.ts b/packages/uniswap/src/features/transactions/selectors.ts index 9ed4d5fc1e2..44c256315a5 100644 --- a/packages/uniswap/src/features/transactions/selectors.ts +++ b/packages/uniswap/src/features/transactions/selectors.ts @@ -6,7 +6,7 @@ import { uniqueAddressesOnly } from 'uniswap/src/features/address/utils' import { selectTokensVisibility } from 'uniswap/src/features/favorites/selectors' import { CurrencyIdToVisibility } from 'uniswap/src/features/favorites/slice' import { TransactionsState } from 'uniswap/src/features/transactions/slice' -import { isClassic, isUniswapX } from 'uniswap/src/features/transactions/swap/utils/routing' +import { isBridge, isClassic, isUniswapX } from 'uniswap/src/features/transactions/swap/utils/routing' import { isFinalizedTx, SendTokenTransactionInfo, @@ -15,7 +15,7 @@ import { UniswapXOrderDetails, } from 'uniswap/src/features/transactions/types/transactionDetails' import { UniswapState } from 'uniswap/src/state/uniswapReducer' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { buildCurrencyId } from 'uniswap/src/utils/currencyId' import { unique } from 'utilities/src/primitives/array' import { flattenObjectOfObjects } from 'utilities/src/primitives/objects' @@ -65,8 +65,8 @@ export const makeSelectAddressTransactions = (): Selector< const duplicate = self.find( (tx2) => tx2.id !== tx.id && - isClassic(tx) && - isClassic(tx2) && + (isClassic(tx) || isBridge(tx)) && + (isClassic(tx2) || isBridge(tx2)) && tx2.options.request.chainId && tx2.options.request.chainId === tx.options.request.chainId && tx.options.request.nonce && @@ -129,7 +129,7 @@ const makeSelectTokenVisibilityFromLocalTxs = (): Selector { it('updates a transaction that was previoulsy added', () => { const id = '19' - const chainId = UniverseChainId.Polygon as WalletChainId + const chainId = UniverseChainId.Polygon as UniverseChainId const transaction = { routing: Routing.CLASSIC, chainId, @@ -206,7 +206,7 @@ describe('transaction reducer', () => { store.dispatch( cancelTransaction({ address: '0xaddress', - chainId: UniverseChainId.Goerli, + chainId: UniverseChainId.Optimism, cancelRequest: {}, id, }), @@ -250,7 +250,7 @@ describe('transaction reducer', () => { store.dispatch( replaceTransaction({ address: '0xaddress', - chainId: UniverseChainId.Goerli, + chainId: UniverseChainId.Optimism, id, newTxParams, }), @@ -263,7 +263,7 @@ describe('transaction reducer', () => { it('replaces a transaction that was previously added', () => { const id = '101' - const chainId = UniverseChainId.Optimism as WalletChainId + const chainId = UniverseChainId.Optimism as UniverseChainId const transaction = { routing: Routing.CLASSIC, chainId, @@ -289,7 +289,7 @@ describe('transaction reducer', () => { const address1 = '0x123' const address2 = '0xabc' const chainId1 = UniverseChainId.Mainnet - const chainId2 = UniverseChainId.Goerli + const chainId2 = UniverseChainId.Optimism store.dispatch( addTransaction({ routing: Routing.CLASSIC, diff --git a/packages/uniswap/src/features/transactions/swap/contexts/SwapFormContext.tsx b/packages/uniswap/src/features/transactions/swap/contexts/SwapFormContext.tsx index 362a248720c..c9585c26abf 100644 --- a/packages/uniswap/src/features/transactions/swap/contexts/SwapFormContext.tsx +++ b/packages/uniswap/src/features/transactions/swap/contexts/SwapFormContext.tsx @@ -3,6 +3,7 @@ import { getNativeAddress } from 'uniswap/src/constants/addresses' import { AssetType, TradeableAsset } from 'uniswap/src/entities/assets' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { useSwapAnalytics } from 'uniswap/src/features/transactions/swap/analytics' import { useDerivedSwapInfo } from 'uniswap/src/features/transactions/swap/hooks/useDerivedSwapInfo' import { DEFAULT_CUSTOM_DEADLINE } from 'uniswap/src/features/transactions/swap/settings/useDeadlineSettings' @@ -58,19 +59,19 @@ function getDefaultInputCurrency(chainId: UniverseChainId): TradeableAsset { } } -const DEFAULT_STATE: Readonly> = { +export const getDefaultState = (defaultChainId: UniverseChainId): Readonly> => ({ exactAmountFiat: undefined, exactAmountToken: '', exactCurrencyField: CurrencyField.INPUT, focusOnCurrencyField: CurrencyField.INPUT, filteredChainIds: {}, - input: getDefaultInputCurrency(UniverseChainId.Mainnet), + input: getDefaultInputCurrency(defaultChainId), output: undefined, isFiatMode: false, isSubmitting: false, selectedProtocols: DEFAULT_PROTOCOL_OPTIONS, customDeadline: DEFAULT_CUSTOM_DEADLINE, -} +}) export const SwapFormContext = createContext(undefined) @@ -88,7 +89,9 @@ export function SwapFormContextProvider({ const amountUpdatedTimeRef = useRef(0) const exactAmountFiatRef = useRef('') const exactAmountTokenRef = useRef('') - const [swapForm, setSwapForm] = useState(prefilledState ?? DEFAULT_STATE) + const { defaultChainId } = useEnabledChains() + const defaultState = useMemo(() => getDefaultState(defaultChainId), [defaultChainId]) + const [swapForm, setSwapForm] = useState(prefilledState ?? defaultState) const datadogEnabled = useFeatureFlag(FeatureFlags.Datadog) // prefilled state may load in -- i.e. `outputCurrency` URL param pulling from gql @@ -102,9 +105,9 @@ export function SwapFormContextProvider({ previousInputCurrencyId !== (prefilledState?.input && currencyId(prefilledState.input)) || previousOutputCurrencyId !== (prefilledState?.output && currencyId(prefilledState.output)) ) { - setSwapForm(prefilledState ?? DEFAULT_STATE) + setSwapForm(prefilledState ?? defaultState) } - }, [prefilledState, previousInitialInputCurrency, previousInitialOutputCurrency]) + }, [prefilledState, previousInitialInputCurrency, previousInitialOutputCurrency, defaultState]) // Enable launching the output token selector through a change to the prefilled state useEffect(() => { diff --git a/packages/uniswap/src/features/transactions/swap/form/SwapFormScreen.tsx b/packages/uniswap/src/features/transactions/swap/form/SwapFormScreen.tsx index c86fefb5c29..e128990c614 100644 --- a/packages/uniswap/src/features/transactions/swap/form/SwapFormScreen.tsx +++ b/packages/uniswap/src/features/transactions/swap/form/SwapFormScreen.tsx @@ -21,6 +21,7 @@ import { CurrencyInputPanel, CurrencyInputPanelRef } from 'uniswap/src/component import { getAlertColor } from 'uniswap/src/components/modals/WarningModal/getAlertColor' import { RouterLabel } from 'uniswap/src/components/RouterLabel/RouterLabel' import { MAX_FIAT_INPUT_DECIMALS } from 'uniswap/src/constants/transactions' +import { usePrefetchSwappableTokens } from 'uniswap/src/data/apiClients/tradingApi/useTradingApiSwappableTokensQuery' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { ElementName, SectionName } from 'uniswap/src/features/telemetry/constants' import Trace from 'uniswap/src/features/telemetry/Trace' @@ -41,6 +42,7 @@ import { useExactOutputWillFail } from 'uniswap/src/features/transactions/swap/h import { useSwapNetworkNotification } from 'uniswap/src/features/transactions/swap/hooks/useSwapNetworkNotification' import { useParsedSwapWarnings } from 'uniswap/src/features/transactions/swap/hooks/useSwapWarnings' import { useSyncFiatAndTokenAmountUpdater } from 'uniswap/src/features/transactions/swap/hooks/useSyncFiatAndTokenAmountUpdater' +import { AcrossRoutingInfo } from 'uniswap/src/features/transactions/swap/modals/AcrossRoutingInfo' import { MarketPriceImpactWarning } from 'uniswap/src/features/transactions/swap/modals/MarketPriceImpactWarning' import { RoutingInfo } from 'uniswap/src/features/transactions/swap/modals/RoutingInfo' import { MaxSlippageRow } from 'uniswap/src/features/transactions/swap/review/MaxSlippageRow' @@ -151,6 +153,8 @@ function SwapFormContent({ wrapCallback }: { wrapCallback?: WrapCallback }): JSX outputChainId: output?.chainId, }) + usePrefetchSwappableTokens(input) + const onRestorePress = (): void => { if (!openWalletRestoreModal) { throw new Error('Invalid call to `onRestorePress` with missing `openWalletRestoreModal`') @@ -796,7 +800,9 @@ function ExpandableRows({ isBridge }: { isBridge?: boolean }): JSX.Element | nul transactionUSDValue={derivedSwapInfo.currencyAmountsUSDValue[CurrencyField.OUTPUT]} uniswapXGasBreakdown={uniswapXGasBreakdown} RoutingInfo={ - isWeb ? ( + isBridge ? ( + + ) : ( @@ -811,7 +817,7 @@ function ExpandableRows({ isBridge }: { isBridge?: boolean }): JSX.Element | nul - ) : undefined + ) } RateInfo={ showPriceImpactWarning && trade.trade ? ( diff --git a/packages/uniswap/src/features/transactions/swap/form/SwapFormSettings.tsx b/packages/uniswap/src/features/transactions/swap/form/SwapFormSettings.tsx index c865eba08c8..9b046d5c830 100644 --- a/packages/uniswap/src/features/transactions/swap/form/SwapFormSettings.tsx +++ b/packages/uniswap/src/features/transactions/swap/form/SwapFormSettings.tsx @@ -11,6 +11,7 @@ import { ViewOnlyModal } from 'uniswap/src/features/transactions/modals/ViewOnly import { useSwapFormContext } from 'uniswap/src/features/transactions/swap/contexts/SwapFormContext' import { SwapSettingsModal } from 'uniswap/src/features/transactions/swap/settings/SwapSettingsModal' import { SwapSettingConfig } from 'uniswap/src/features/transactions/swap/settings/configs/types' +import { BridgeTrade } from 'uniswap/src/features/transactions/swap/types/trade' import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { dismissNativeKeyboard } from 'utilities/src/device/keyboard' import { isInterface, isMobileApp } from 'utilities/src/platform' @@ -21,7 +22,8 @@ export function SwapFormSettings({ customSettings }: { customSettings: SwapSetti const colors = useSporeColors() const account = useAccountMeta() - const { customSlippageTolerance } = useSwapFormContext() + const { customSlippageTolerance, derivedSwapInfo } = useSwapFormContext() + const isBridgeTrade = derivedSwapInfo.trade.trade instanceof BridgeTrade const [showSwapSettingsModal, setShowSettingsModal] = useState(false) const [showViewOnlyModal, setShowViewOnlyModal] = useState(false) @@ -42,6 +44,8 @@ export function SwapFormSettings({ customSettings }: { customSettings: SwapSetti const topAlignment = isInterface ? -34 : 6 const rightAlignment = isMobileApp ? 24 : 4 + const showCustomSlippage = customSlippageTolerance && !isBridgeTrade + return ( setShowViewOnlyModal(false)} /> @@ -79,13 +83,13 @@ export function SwapFormSettings({ customSettings }: { customSettings: SwapSetti - {customSlippageTolerance ? ( + {showCustomSlippage ? ( {formatPercent(customSlippageTolerance)} diff --git a/packages/uniswap/src/features/transactions/swap/form/SwapTokenSelector.tsx b/packages/uniswap/src/features/transactions/swap/form/SwapTokenSelector.tsx index f5ee7cb9186..04c306f9ec0 100644 --- a/packages/uniswap/src/features/transactions/swap/form/SwapTokenSelector.tsx +++ b/packages/uniswap/src/features/transactions/swap/form/SwapTokenSelector.tsx @@ -9,8 +9,10 @@ import { TokenSelectorFlow } from 'uniswap/src/components/TokenSelector/types' import { useAccountMeta, useUniswapContext } from 'uniswap/src/contexts/UniswapContext' import { AssetType, TradeableAsset } from 'uniswap/src/entities/assets' import { useTokenProjects } from 'uniswap/src/features/dataApi/tokenProjects' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { SwapFormState, useSwapFormContext } from 'uniswap/src/features/transactions/swap/contexts/SwapFormContext' import { maybeLogFirstSwapAction } from 'uniswap/src/features/transactions/swap/utils/maybeLogFirstSwapAction' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { areCurrencyIdsEqual, currencyAddress, currencyId } from 'uniswap/src/utils/currencyId' import { useTrace } from 'utilities/src/telemetry/trace/TraceContext' @@ -18,6 +20,7 @@ import { useTrace } from 'utilities/src/telemetry/trace/TraceContext' export function SwapTokenSelector({ isModalOpen }: { isModalOpen: boolean }): JSX.Element { const trace = useTrace() const account = useAccountMeta() + const { isTestnetModeEnabled, defaultChainId } = useEnabledChains() const swapContext = useSwapFormContext() const { setIsSwapTokenSelectorOpen } = useUniswapContext() const { updateSwapForm, exactCurrencyField, selectingCurrencyField, output, input, filteredChainIds } = swapContext @@ -110,10 +113,22 @@ export function SwapTokenSelector({ isModalOpen }: { isModalOpen: boolean }): JS ], ) + const getChainId = (): UniverseChainId | undefined => { + const selectedChainId = filteredChainIds[selectingCurrencyField ?? CurrencyField.INPUT] + + // allow undefined for prod networks + if (selectedChainId || !isTestnetModeEnabled) { + return selectedChainId + } + + // should never be undefined for testnets + return filteredChainIds[CurrencyField.INPUT] ?? input?.chainId ?? defaultChainId + } + const props: TokenSelectorProps = { isModalOpen, activeAccountAddress, - chainId: filteredChainIds[selectingCurrencyField ?? CurrencyField.INPUT], + chainId: getChainId(), input, // token selector modal will only open on currency field selection; casting to satisfy typecheck here - we should consider refactoring the types here to avoid casting currencyField: selectingCurrencyField as CurrencyField, diff --git a/packages/uniswap/src/features/transactions/swap/form/footer/GasTradeRow.tsx b/packages/uniswap/src/features/transactions/swap/form/footer/GasTradeRow.tsx index 37f20c6912c..e83592f3ab8 100644 --- a/packages/uniswap/src/features/transactions/swap/form/footer/GasTradeRow.tsx +++ b/packages/uniswap/src/features/transactions/swap/form/footer/GasTradeRow.tsx @@ -9,11 +9,11 @@ import { getAlertColor } from 'uniswap/src/components/modals/WarningModal/getAle import { Warning } from 'uniswap/src/components/modals/WarningModal/types' import { useFormattedUniswapXGasFeeInfo, + useGasFeeFormattedAmounts, useGasFeeHighRelativeToValue, - useUSDValue, } from 'uniswap/src/features/gas/hooks' import { FormattedUniswapXGasFeeInfo, GasFeeResult } from 'uniswap/src/features/gas/types' -import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { useSwapFormContext } from 'uniswap/src/features/transactions/swap/contexts/SwapFormContext' import { useSwapTxContext } from 'uniswap/src/features/transactions/swap/contexts/SwapTxContext' import { NetworkFeeWarning } from 'uniswap/src/features/transactions/swap/modals/NetworkFeeWarning' @@ -22,7 +22,6 @@ import { SwapRateRatio } from 'uniswap/src/features/transactions/swap/review/Swa import { IndicativeTrade, Trade } from 'uniswap/src/features/transactions/swap/types/trade' import { isUniswapX } from 'uniswap/src/features/transactions/swap/utils/routing' import { CurrencyField } from 'uniswap/src/types/currency' -import { NumberType } from 'utilities/src/format/types' import { isInterface, isMobileApp } from 'utilities/src/platform' import { usePrevious } from 'utilities/src/react/hooks' @@ -35,7 +34,6 @@ type DebouncedGasInfo = { } export function useDebouncedGasInfo(): DebouncedGasInfo { - const { convertFiatAmountFormatted } = useLocalizationContext() const { derivedSwapInfo: { chainId, currencyAmountsUSDValue, trade, currencyAmounts, exactCurrencyField }, } = useSwapFormContext() @@ -49,14 +47,19 @@ export function useDebouncedGasInfo(): DebouncedGasInfo { chainId, ) - const usdPrice = useUSDValue(chainId, gasFee?.value) - const isHighRelativeToValue = useGasFeeHighRelativeToValue(usdPrice, outputUSDValue ?? inputUSDValue) + const { gasFeeFormatted, gasFeeUSD } = useGasFeeFormattedAmounts({ + gasFee, + chainId, + placeholder: undefined, + }) + + const isHighRelativeToValue = useGasFeeHighRelativeToValue(gasFeeUSD, outputUSDValue ?? inputUSDValue) const amountChanged = usePrevious(currencyAmounts[exactCurrencyField]) !== currencyAmounts[exactCurrencyField] const tradeChanged = usePrevious(trade.trade) !== trade.trade && Boolean(trade.trade) const tradeLoadingOrRefetching = Boolean(trade.isLoading || trade.isFetching) - const gasLoading = Boolean(gasFee.isLoading || (gasFee.value && !usdPrice)) + const gasLoading = Boolean(gasFee.isLoading || (gasFee.value && !gasFeeUSD)) const isLoading = tradeLoadingOrRefetching || gasLoading || amountChanged || tradeChanged @@ -66,10 +69,15 @@ export function useDebouncedGasInfo(): DebouncedGasInfo { if (isLoading) { setInfo((prev) => ({ ...prev, isLoading })) } else { - const fiatPriceFormatted = usdPrice ? convertFiatAmountFormatted(usdPrice, NumberType.FiatGasPrice) : undefined - setInfo({ gasFee, fiatPriceFormatted, isHighRelativeToValue, uniswapXGasFeeInfo, isLoading }) + setInfo({ + gasFee, + fiatPriceFormatted: gasFeeFormatted ?? undefined, + isHighRelativeToValue, + uniswapXGasFeeInfo, + isLoading, + }) } - }, [convertFiatAmountFormatted, gasFee, isHighRelativeToValue, isLoading, uniswapXGasFeeInfo, usdPrice]) + }, [gasFee, gasFeeFormatted, isHighRelativeToValue, isLoading, uniswapXGasFeeInfo]) return info } @@ -143,10 +151,15 @@ export function GasTradeRow({ gasInfo: DebouncedGasInfo showPriceImpactWarning?: boolean priceImpactWarning?: Warning -}): JSX.Element { +}): JSX.Element | null { // Debounce the trade to prevent flickering on input const debouncedTrade = useDebouncedTrade() const warningColor = getAlertColor(priceImpactWarning?.severity) + const { isTestnetModeEnabled } = useEnabledChains() + + if (isTestnetModeEnabled) { + return null + } if (isMobileApp) { return diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useDerivedSwapInfo.ts b/packages/uniswap/src/features/transactions/swap/hooks/useDerivedSwapInfo.ts index 9c5e5190cb5..c5fd98fe793 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/useDerivedSwapInfo.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/useDerivedSwapInfo.ts @@ -4,6 +4,7 @@ import { useAccountMeta } from 'uniswap/src/contexts/UniswapContext' import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { useOnChainCurrencyBalance } from 'uniswap/src/features/portfolio/api' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { ValueType, getCurrencyAmount } from 'uniswap/src/features/tokens/getCurrencyAmount' import { useCurrencyInfo } from 'uniswap/src/features/tokens/useCurrencyInfo' import { useSetTradeSlippage } from 'uniswap/src/features/transactions/swap/hooks/useSetTradeSlippage' @@ -12,7 +13,6 @@ import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUS import { DerivedSwapInfo } from 'uniswap/src/features/transactions/swap/types/derivedSwapInfo' import { getWrapType, isWrapAction } from 'uniswap/src/features/transactions/swap/utils/wrap' import { TransactionState } from 'uniswap/src/features/transactions/types/transactionState' -import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { buildCurrencyId } from 'uniswap/src/utils/currencyId' @@ -36,6 +36,7 @@ export function useDerivedSwapInfo({ } = state const account = useAccountMeta() + const { defaultChainId } = useEnabledChains() const currencyInInfo = useCurrencyInfo( currencyAssetIn ? buildCurrencyId(currencyAssetIn.chainId, currencyAssetIn.address) : undefined, @@ -57,7 +58,7 @@ export function useDerivedSwapInfo({ const currencyIn = currencyInInfo?.currency const currencyOut = currencyOutInfo?.currency - const chainId = currencyIn?.chainId ?? currencyOut?.chainId ?? UniverseChainId.Mainnet + const chainId = currencyIn?.chainId ?? currencyOut?.chainId ?? defaultChainId const { balance: tokenInBalance } = useOnChainCurrencyBalance(currencyIn, account?.address) const { balance: tokenOutBalance } = useOnChainCurrencyBalance(currencyOut, account?.address) diff --git a/packages/uniswap/src/features/transactions/swap/hooks/usePollingIntervalByChain.ts b/packages/uniswap/src/features/transactions/swap/hooks/usePollingIntervalByChain.ts index d3b10fc710d..7d7080cf0ba 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/usePollingIntervalByChain.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/usePollingIntervalByChain.ts @@ -1,12 +1,12 @@ import { isMainnetChainId } from 'uniswap/src/features/chains/utils' import { DynamicConfigs, SwapConfigKey } from 'uniswap/src/features/gating/configs' import { useDynamicConfigValue } from 'uniswap/src/features/gating/hooks' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' const FALLBACK_L1_BLOCK_TIME_MS = 12000 const FALLBACK_L2_BLOCK_TIME_MS = 3000 -export function usePollingIntervalByChain(chainId?: WalletChainId): number { +export function usePollingIntervalByChain(chainId?: UniverseChainId): number { const averageL1BlockTimeMs = useDynamicConfigValue( DynamicConfigs.Swap, SwapConfigKey.AverageL1BlockTimeMs, diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useSwapPrefilledState.ts b/packages/uniswap/src/features/transactions/swap/hooks/useSwapPrefilledState.ts index 5d77fb25ffc..e692511fe51 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/useSwapPrefilledState.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/useSwapPrefilledState.ts @@ -4,7 +4,7 @@ import { AssetType, CurrencyAsset } from 'uniswap/src/entities/assets' import { SwapFormState } from 'uniswap/src/features/transactions/swap/contexts/SwapFormContext' import { DEFAULT_PROTOCOL_OPTIONS } from 'uniswap/src/features/transactions/swap/utils/protocols' import { TransactionState } from 'uniswap/src/features/transactions/types/transactionState' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { areAddressesEqual } from 'uniswap/src/utils/addresses' @@ -61,7 +61,7 @@ export function getSwapPrefilledState({ currencyField, }: { currencyAddress: Address - currencyChainId: WalletChainId + currencyChainId: UniverseChainId currencyField: CurrencyField }): TransactionState { const nativeTokenAddress = getNativeAddress(currencyChainId) diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useSwapWarnings.tsx b/packages/uniswap/src/features/transactions/swap/hooks/useSwapWarnings.tsx index 5f4b5fcf563..459ea393e38 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/useSwapWarnings.tsx +++ b/packages/uniswap/src/features/transactions/swap/hooks/useSwapWarnings.tsx @@ -9,6 +9,7 @@ import { Warning, WarningAction, WarningLabel, WarningSeverity } from 'uniswap/s import { uniswapUrls } from 'uniswap/src/constants/urls' import { useAccountMeta } from 'uniswap/src/contexts/UniswapContext' import { FetchError, isRateLimitFetchError } from 'uniswap/src/data/apiClients/FetchError' +import { Err404 } from 'uniswap/src/data/tradingApi/__generated__' import { selectHasDismissedBridgingWarning } from 'uniswap/src/features/behaviorHistory/selectors' import { useTransactionGasWarning } from 'uniswap/src/features/gas/hooks' import { LocalizationContextState, useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' @@ -89,6 +90,14 @@ export function getSwapWarnings( title: t('swap.warning.rateLimit.title'), message: t('swap.warning.rateLimit.message'), }) + } else if (error instanceof FetchError && error?.data?.errorCode === Err404.errorCode.QUOTE_AMOUNT_TOO_LOW_ERROR) { + warnings.push({ + type: WarningLabel.EnterLargerAmount, + severity: WarningSeverity.Low, + action: WarningAction.DisableReview, + title: t('swap.warning.enterLargerAmount.title'), + message: '', + }) } else { // catch all other router errors in a generic swap router error message warnings.push({ diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useSyncFiatAndTokenAmountUpdater.tsx b/packages/uniswap/src/features/transactions/swap/hooks/useSyncFiatAndTokenAmountUpdater.tsx index f982382531d..e831e32e744 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/useSyncFiatAndTokenAmountUpdater.tsx +++ b/packages/uniswap/src/features/transactions/swap/hooks/useSyncFiatAndTokenAmountUpdater.tsx @@ -22,7 +22,7 @@ export function useSyncFiatAndTokenAmountUpdater({ skip = false }: { skip?: bool const exactCurrency = derivedSwapInfo.currencies[exactCurrencyField] - const usdPriceOfCurrency = useUSDCPrice(skip ? undefined : exactCurrency?.currency ?? undefined) + const { price: usdPriceOfCurrency } = useUSDCPrice(skip ? undefined : exactCurrency?.currency ?? undefined) const { convertFiatAmount } = useLocalizationContext() const conversionRate = convertFiatAmount(1).amount const chainId = currencyIdToChain(exactCurrency?.currencyId ?? '') diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useTokenApprovalInfo.ts b/packages/uniswap/src/features/transactions/swap/hooks/useTokenApprovalInfo.ts index cd441a67bca..9d5aa15c41d 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/useTokenApprovalInfo.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/useTokenApprovalInfo.ts @@ -12,12 +12,12 @@ import { } from 'uniswap/src/features/transactions/swap/utils/tradingApi' import { GasFeeEstimates } from 'uniswap/src/features/transactions/types/transactionDetails' import { WrapType } from 'uniswap/src/features/transactions/types/wrap' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { logger } from 'utilities/src/logger/logger' import { ONE_MINUTE_MS, ONE_SECOND_MS } from 'utilities/src/time/time' export interface TokenApprovalInfoParams { - chainId: WalletChainId + chainId: UniverseChainId wrapType: WrapType currencyInAmount: Maybe> currencyOutAmount?: Maybe> diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useTrade.ts b/packages/uniswap/src/features/transactions/swap/hooks/useTrade.ts index 7fc5869b068..6e9c5c8b629 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/useTrade.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/useTrade.ts @@ -1,6 +1,7 @@ import { TradeType } from '@uniswap/sdk-core' import { useMemo, useRef } from 'react' 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 { QuoteRequest, TradeType as TradingApiTradeType } from 'uniswap/src/data/tradingApi/__generated__/index' import { useActiveGasStrategy, useShadowGasStrategies } from 'uniswap/src/features/gas/hooks' @@ -85,12 +86,13 @@ export function useTrade({ !amount || currencyInEqualsCurrencyOut - const quoteRequestArgs: QuoteRequest | undefined = useMemo(() => { + const v4Enabled = useFeatureFlag(FeatureFlags.V4Swap) + + const quoteRequestArgs = useMemo((): WithV4Flag | undefined => { if (skipQuery) { return undefined } - - const quoteArgs: QuoteRequest = { + return { type: requestTradeType, amount: amount.quotient.toString(), swapper: activeAccountAddress ?? UNCONNECTED_ADDRESS, @@ -101,9 +103,8 @@ export function useTrade({ slippageTolerance: customSlippageTolerance, ...routingParams, gasStrategies: [activeGasStrategy, ...(shadowGasStrategies ?? [])], + v4Enabled, } - - return quoteArgs }, [ activeAccountAddress, amount, @@ -117,6 +118,7 @@ export function useTrade({ tokenInChainId, tokenOutAddress, tokenOutChainId, + v4Enabled, ]) /***** Fetch quote from trading API ******/ diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useTransactionRequestInfo.test.ts b/packages/uniswap/src/features/transactions/swap/hooks/useTransactionRequestInfo.test.ts index 2e647b4a06b..bd06759c011 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/useTransactionRequestInfo.test.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/useTransactionRequestInfo.test.ts @@ -18,6 +18,7 @@ jest.mock('uniswap/src/features/transactions/swap/hooks/useWrapTransactionReques jest.mock('uniswap/src/features/gas/hooks') jest.mock('uniswap/src/features/gating/hooks', () => { return { + ...jest.requireActual('uniswap/src/features/gating/hooks'), useDynamicConfigValue: jest.fn().mockImplementation((config: unknown, key: unknown, defaultVal: unknown) => { return defaultVal }), diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useTransactionRequestInfo.ts b/packages/uniswap/src/features/transactions/swap/hooks/useTransactionRequestInfo.ts index c97bb8a8f88..acf2d4afb0e 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/useTransactionRequestInfo.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/useTransactionRequestInfo.ts @@ -1,6 +1,7 @@ import { SwapEventName } from '@uniswap/analytics-events' import { providers } from 'ethers/lib/ethers' import { useEffect, useMemo, useRef } from 'react' +import { WithV4Flag } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient' import { useTradingApiSwapQuery } from 'uniswap/src/data/apiClients/tradingApi/useTradingApiSwapQuery' import { CreateSwapRequest, @@ -12,7 +13,8 @@ import { AccountMeta } from 'uniswap/src/features/accounts/types' import { useActiveGasStrategy, useShadowGasStrategies, useTransactionGasFee } from 'uniswap/src/features/gas/hooks' import { GasFeeResult, areEqualGasStrategies } from 'uniswap/src/features/gas/types' import { DynamicConfigs, SwapConfigKey } from 'uniswap/src/features/gating/configs' -import { useDynamicConfigValue } from 'uniswap/src/features/gating/hooks' +import { FeatureFlags } from 'uniswap/src/features/gating/flags' +import { useDynamicConfigValue, useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { getBaseTradeAnalyticsPropertiesFromSwapInfo } from 'uniswap/src/features/transactions/swap/analytics' @@ -64,6 +66,7 @@ export function useTransactionRequestInfo({ const formatter = useLocalizationContext() const activeGasStrategy = useActiveGasStrategy(derivedSwapInfo.chainId, 'general') const shadowGasStrategies = useShadowGasStrategies(derivedSwapInfo.chainId, 'general') + const v4Enabled = useFeatureFlag(FeatureFlags.V4Swap) const { trade: tradeWithStatus, customDeadline } = derivedSwapInfo const { trade } = tradeWithStatus || { trade: undefined } @@ -88,7 +91,7 @@ export function useTransactionRequestInfo({ const missingSig = requiresPermit2Sig && !signatureInfo.signature // Format request args - const swapRequestArgs: CreateSwapRequest | undefined = useMemo(() => { + const swapRequestArgs: WithV4Flag | undefined = useMemo(() => { // TODO: MOB(2438) https://linear.app/uniswap/issue/MOB-2438/uniswap-x-clean-old-trading-api-code if (!swapQuote) { return undefined @@ -108,7 +111,7 @@ export function useTransactionRequestInfo({ const deadlineSeconds = (customDeadline ?? 0) * 60 const deadline = customDeadline ? Math.floor(Date.now() / 1000) + deadlineSeconds : undefined - const swapArgs: CreateSwapRequest = { + const swapArgs: WithV4Flag = { quote, permitData: permitData ?? undefined, signature: signatureInfo.signature, @@ -116,6 +119,7 @@ export function useTransactionRequestInfo({ deadline, refreshGasPrice: true, gasStrategies: [activeGasStrategy, ...(shadowGasStrategies ?? [])], + v4Enabled, } return swapArgs @@ -129,6 +133,7 @@ export function useTransactionRequestInfo({ signatureInfo.signature, swapQuote, tradeWithStatus, + v4Enabled, ]) // Wrap transaction request diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useUSDCPrice.ts b/packages/uniswap/src/features/transactions/swap/hooks/useUSDCPrice.ts index a1eaad6e4ee..335874137bf 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/useUSDCPrice.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/useUSDCPrice.ts @@ -5,12 +5,14 @@ import { USDB_BLAST, USDC, USDC_ARBITRUM, + USDC_ASTROCHAIN_SEPOLIA, USDC_AVALANCHE, USDC_BASE, USDC_CELO, - USDC_GOERLI, USDC_OPTIMISM, USDC_POLYGON, + USDC_SEPOLIA, + USDC_WORLD_CHAIN, USDC_ZKSYNC, USDC_ZORA, USDT_BNB, @@ -24,7 +26,7 @@ import { areCurrencyIdsEqual, currencyId } from 'uniswap/src/utils/currencyId' // The amount is large enough to filter low liquidity pairs. export const STABLECOIN_AMOUNT_OUT: { [chainId: number]: CurrencyAmount } = { [UniverseChainId.Mainnet]: CurrencyAmount.fromRawAmount(USDC, 100_000e6), - [UniverseChainId.Goerli]: CurrencyAmount.fromRawAmount(USDC_GOERLI, 100_000e6), + [UniverseChainId.Sepolia]: CurrencyAmount.fromRawAmount(USDC_SEPOLIA, 100_000e6), [UniverseChainId.ArbitrumOne]: CurrencyAmount.fromRawAmount(USDC_ARBITRUM, 10_000e6), [UniverseChainId.Base]: CurrencyAmount.fromRawAmount(USDC_BASE, 10_000e6), [UniverseChainId.Bnb]: CurrencyAmount.fromRawAmount(USDT_BNB, 10_000e18), @@ -33,15 +35,20 @@ export const STABLECOIN_AMOUNT_OUT: { [chainId: number]: CurrencyAmount } [UniverseChainId.Blast]: CurrencyAmount.fromRawAmount(USDB_BLAST, 10_000e18), [UniverseChainId.Avalanche]: CurrencyAmount.fromRawAmount(USDC_AVALANCHE, 10_000e6), [UniverseChainId.Celo]: CurrencyAmount.fromRawAmount(USDC_CELO, 10_000e18), + [UniverseChainId.WorldChain]: CurrencyAmount.fromRawAmount(USDC_WORLD_CHAIN, 10_000e6), [UniverseChainId.Zora]: CurrencyAmount.fromRawAmount(USDC_ZORA, 10_000e6), [UniverseChainId.Zksync]: CurrencyAmount.fromRawAmount(USDC_ZKSYNC, 10_000e6), + [UniverseChainId.AstrochainSepolia]: CurrencyAmount.fromRawAmount(USDC_ASTROCHAIN_SEPOLIA, 10_000e6), } /** * Returns the price in USDC of the input currency * @param currency currency to compute the USDC price of */ -export function useUSDCPrice(currency?: Currency): Price | undefined { +export function useUSDCPrice(currency?: Currency): { + price: Price | undefined + isLoading: boolean +} { const chainId = currency?.chainId const quoteAmount = chainId ? STABLECOIN_AMOUNT_OUT[chainId] : undefined @@ -53,7 +60,7 @@ export function useUSDCPrice(currency?: Currency): Price | u ) const amountSpecified = currencyIsStablecoin ? undefined : quoteAmount - const { trade } = useTrade({ + const { trade, isLoading } = useTrade({ amountSpecified, otherCurrency: currency, tradeType: TradeType.EXACT_OUTPUT, @@ -63,27 +70,27 @@ export function useUSDCPrice(currency?: Currency): Price | u return useMemo(() => { if (!stablecoin) { - return undefined + return { price: undefined, isLoading: false } } if (currencyIsStablecoin) { // handle stablecoin - return new Price(stablecoin, stablecoin, '1', '1') + return { price: new Price(stablecoin, stablecoin, '1', '1'), isLoading: false } } if (!trade || !isClassic(trade) || !trade.routes?.[0] || !quoteAmount || !currency) { - return undefined + return { price: undefined, isLoading } } const { numerator, denominator } = trade.routes[0].midPrice - return new Price(currency, stablecoin, denominator, numerator) - }, [currency, stablecoin, currencyIsStablecoin, quoteAmount, trade]) + return { price: new Price(currency, stablecoin, denominator, numerator), isLoading } + }, [currency, stablecoin, currencyIsStablecoin, quoteAmount, trade, isLoading]) } export function useUSDCValue( currencyAmount: CurrencyAmount | undefined | null, ): CurrencyAmount | null { - const price = useUSDCPrice(currencyAmount?.currency) + const { price } = useUSDCPrice(currencyAmount?.currency) return useMemo(() => { if (!price || !currencyAmount) { @@ -96,3 +103,28 @@ export function useUSDCValue( } }, [currencyAmount, price]) } + +/** + * @param currencyAmount + * @returns Returns fiat value of the currency amount, and loading status of the currency<->stable quote + */ +export function useUSDCValueWithStatus(currencyAmount: CurrencyAmount | undefined | null): { + value: CurrencyAmount | null + isLoading: boolean +} { + const { price, isLoading } = useUSDCPrice(currencyAmount?.currency) + + return useMemo(() => { + if (!price || !currencyAmount) { + return { value: null, isLoading } + } + try { + return { value: price.quote(currencyAmount), isLoading } + } catch (error) { + return { + value: null, + isLoading: false, + } + } + }, [currencyAmount, isLoading, price]) +} diff --git a/packages/uniswap/src/features/transactions/swap/hooks/useWrapTransactionRequest.ts b/packages/uniswap/src/features/transactions/swap/hooks/useWrapTransactionRequest.ts index 532dd4bbf83..d340087e432 100644 --- a/packages/uniswap/src/features/transactions/swap/hooks/useWrapTransactionRequest.ts +++ b/packages/uniswap/src/features/transactions/swap/hooks/useWrapTransactionRequest.ts @@ -9,10 +9,10 @@ import { AccountMeta } from 'uniswap/src/features/accounts/types' import { DerivedSwapInfo } from 'uniswap/src/features/transactions/swap/types/derivedSwapInfo' import { isUniswapX } from 'uniswap/src/features/transactions/swap/utils/routing' import { WrapType } from 'uniswap/src/features/transactions/types/wrap' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { useAsyncData } from 'utilities/src/react/hooks' -export async function getWethContract(chainId: WalletChainId, provider: providers.Provider): Promise { +export async function getWethContract(chainId: UniverseChainId, provider: providers.Provider): Promise { return new Contract(getWrappedNativeAddress(chainId), WETH_ABI, provider) as Weth } @@ -36,7 +36,7 @@ export function useWrapTransactionRequest( const getWrapTransactionRequest = async ( provider: providers.Provider | undefined, isUniswapXWrap: boolean, - chainId: WalletChainId, + chainId: UniverseChainId, address: Address | undefined, wrapType: WrapType, currencyAmountIn: Maybe>, diff --git a/packages/uniswap/src/features/transactions/swap/modals/AcrossRoutingInfo.tsx b/packages/uniswap/src/features/transactions/swap/modals/AcrossRoutingInfo.tsx index 12471dd1b6a..d7f0cb9e6eb 100644 --- a/packages/uniswap/src/features/transactions/swap/modals/AcrossRoutingInfo.tsx +++ b/packages/uniswap/src/features/transactions/swap/modals/AcrossRoutingInfo.tsx @@ -1,8 +1,9 @@ +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { Flex, Text, UniversalImage } from 'ui/src' -import { ACROSS_LOGO } from 'ui/src/assets' -import { InfoCircle } from 'ui/src/components/icons/InfoCircle' -import { iconSizes } from 'ui/src/theme' +import { Flex, Text } from 'ui/src' +import AcrossLogoFull from 'ui/src/assets/logos/svg/across-logo-full.svg' +import { OrderRouting } from 'ui/src/components/icons/OrderRouting' +import { AcrossLogo } from 'ui/src/components/logos/AcrossLogo' import { WarningInfo } from 'uniswap/src/components/modals/WarningModal/WarningInfo' import { WarningSeverity } from 'uniswap/src/components/modals/WarningModal/types' import { LearnMoreLink } from 'uniswap/src/components/text/LearnMoreLink' @@ -13,62 +14,75 @@ import { isMobileApp } from 'utilities/src/platform' export function AcrossRoutingInfo(): JSX.Element { const { t } = useTranslation() - return ( - - - - {t('swap.details.orderRouting')} + const commonModalProps = useMemo( + () => ({ + caption: t('swap.details.orderRoutingInfo'), + rejectText: t('common.button.close'), + modalName: ModalName.AcrossRoutingInfo, + severity: WarningSeverity.None, + title: t('swap.details.orderRouting'), + icon: , + }), + [t], + ) + + const commonTooltipProps = useMemo( + () => ({ + text: ( + + {t('swap.details.orderRoutingInfo')} + + ), + placement: 'top' as const, + }), + [t], + ) + + const commonInfoButton = useMemo( + () => + isMobileApp ? ( + + + + + {t('swap.details.poweredBy')} - + + + + + ) : undefined, + [t], + ) + + return ( + + + {t('swap.details.orderRouting')} + + } + infoButton={commonInfoButton} + modalProps={commonModalProps} + tooltipProps={commonTooltipProps} + triggerPlacement="end" + /> + + - + Across API - - } - infoButton={ - isMobileApp ? ( - - ) : undefined - } - modalProps={{ - caption: t('swap.details.orderRoutingInfo'), - rejectText: t('common.button.close'), - modalName: ModalName.AcrossRoutingInfo, - severity: WarningSeverity.None, - title: t('swap.details.orderRouting'), - icon: ( - - ), - }} - tooltipProps={{ - text: ( - - {t('swap.details.orderRoutingInfo')} - - ), - placement: 'top', - }} - trigger={null} - /> + } + /> + ) } diff --git a/packages/uniswap/src/features/transactions/swap/modals/BridgingModal.tsx b/packages/uniswap/src/features/transactions/swap/modals/BridgingModal.tsx index ab0f2c9d20c..a8b5cecf286 100644 --- a/packages/uniswap/src/features/transactions/swap/modals/BridgingModal.tsx +++ b/packages/uniswap/src/features/transactions/swap/modals/BridgingModal.tsx @@ -45,10 +45,10 @@ export function BridgingModal({ const toNetwork = toNetworkRaw ? toSupportedChainId(toNetworkRaw) : null const icon = ( - - + + - + ) diff --git a/packages/uniswap/src/features/transactions/swap/modals/RoutingInfo.tsx b/packages/uniswap/src/features/transactions/swap/modals/RoutingInfo.tsx index 2d7683c0a70..a1f49893260 100644 --- a/packages/uniswap/src/features/transactions/swap/modals/RoutingInfo.tsx +++ b/packages/uniswap/src/features/transactions/swap/modals/RoutingInfo.tsx @@ -5,7 +5,7 @@ import { Flex, Text, TouchableArea, UniswapXText, isWeb } from 'ui/src' import RoutingDiagram from 'uniswap/src/components/RoutingDiagram/RoutingDiagram' import { WarningInfo } from 'uniswap/src/components/modals/WarningModal/WarningInfo' import { uniswapUrls } from 'uniswap/src/constants/urls' -import { useUSDValue } from 'uniswap/src/features/gas/hooks' +import { useUSDValueOfGasFee } from 'uniswap/src/features/gas/hooks' import { GasFeeResult } from 'uniswap/src/features/gas/types' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { ModalName } from 'uniswap/src/features/telemetry/constants' @@ -27,7 +27,7 @@ export function RoutingInfo({ }>): JSX.Element | null { const { trade } = useSwapTxContext() const { convertFiatAmountFormatted } = useLocalizationContext() - const gasFeeUSD = useUSDValue(chainId, gasFee.value ?? undefined) + const { value: gasFeeUSD } = useUSDValueOfGasFee(chainId, gasFee.value ?? undefined) const gasFeeFormatted = convertFiatAmountFormatted(gasFeeUSD, NumberType.FiatGasPrice) const routes = useMemo(() => (trade && isClassic(trade) ? getRoutingDiagramEntries(trade) : []), [trade]) diff --git a/packages/uniswap/src/features/transactions/swap/review/SwapDetails.tsx b/packages/uniswap/src/features/transactions/swap/review/SwapDetails.tsx index e7b842de309..74683b6783d 100644 --- a/packages/uniswap/src/features/transactions/swap/review/SwapDetails.tsx +++ b/packages/uniswap/src/features/transactions/swap/review/SwapDetails.tsx @@ -9,6 +9,7 @@ import Trace from 'uniswap/src/features/telemetry/Trace' import { ElementName } from 'uniswap/src/features/telemetry/constants' import { FeeOnTransferFeeGroupProps } from 'uniswap/src/features/transactions/TransactionDetails/FeeOnTransferFee' import { TransactionDetails } from 'uniswap/src/features/transactions/TransactionDetails/TransactionDetails' +import { AcrossRoutingInfo } from 'uniswap/src/features/transactions/swap/modals/AcrossRoutingInfo' import { EstimatedTime } from 'uniswap/src/features/transactions/swap/review/EstimatedTime' import { MaxSlippageRow } from 'uniswap/src/features/transactions/swap/review/MaxSlippageRow' import { SwapRateRatio } from 'uniswap/src/features/transactions/swap/review/SwapRateRatio' @@ -128,6 +129,7 @@ export function SwapDetails({ {isBridgeTrade && } + {isBridgeTrade && } {!isBridgeTrade && ( { setIsDefault(!isDefault) if (!isDefault) { @@ -86,6 +88,15 @@ export const ProtocolPreference: SwapSettingConfig = { onSelect={() => toggleProtocol(ProtocolItems.UNISWAPX_V2)} /> )} + {v4Enabled && ( + toggleProtocol(ProtocolItems.V4)} + /> + )} } @@ -149,7 +162,7 @@ function OptionRow({ cantDisable, onSelect, }: { - title: JSX.Element + title: JSX.Element | string active: boolean elementName: ElementNameType cantDisable: boolean diff --git a/packages/uniswap/src/features/transactions/swap/settings/configs/Slippage.native.tsx b/packages/uniswap/src/features/transactions/swap/settings/configs/Slippage.native.tsx index 35f64f2eff8..87d1328526b 100644 --- a/packages/uniswap/src/features/transactions/swap/settings/configs/Slippage.native.tsx +++ b/packages/uniswap/src/features/transactions/swap/settings/configs/Slippage.native.tsx @@ -1,6 +1,7 @@ import { TradeType } from '@uniswap/sdk-core' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { Flex, Text, TouchableArea, isWeb, useSporeColors } from 'ui/src' +import { ColorTokens, Flex, Text, TouchableArea, isWeb, useSporeColors } from 'ui/src' import { PlusMinusButton, PlusMinusButtonType } from 'ui/src/components/button/PlusMinusButton' import { AlertTriangleFilled } from 'ui/src/components/icons' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' @@ -32,9 +33,15 @@ export const Slippage: SwapSettingConfig = { currentSlippage = autoSlippageTolerance } + const isBridgeTrade = derivedSwapInfo.trade.trade instanceof BridgeTrade + + if (isBridgeTrade) { + currentSlippage = 0 + } + return ( - {!isCustomSlippage ? ( + {!isCustomSlippage && !isBridgeTrade ? ( {t('swap.settings.slippage.control.auto')} @@ -72,29 +79,42 @@ export const Slippage: SwapSettingConfig = { onPressPlusMinusButton, } = useSlippageSettings() - if (trade instanceof BridgeTrade) { - // Check exists to make sure trade conforms to TradeWithSlippage, - // since this component should not be rendered for bridge trades which don't have slippage - return null - } + const isBridgeTrade = trade instanceof BridgeTrade + + const slippageMessage = useMemo(() => { + if (isBridgeTrade) { + return + } else { + return ( + + ) + } + }, [currentSlippageTolerance, inputWarning, isBridgeTrade, showSlippageWarning, t, trade]) return ( - - {t('swap.settings.slippage.description')} - - + {!isBridgeTrade && ( + + {t('swap.settings.slippage.description')} + + )} + {!isBridgeTrade && } - + {t('swap.settings.slippage.control.auto')} - + {isBridgeTrade ? ( + + 0.00 + + ) : ( + + )} + % @@ -131,17 +158,12 @@ export const Slippage: SwapSettingConfig = { - + {slippageMessage} ) @@ -167,14 +189,7 @@ function SlippageMessage({ const slippageTolerancePercent = slippageToleranceToPercent(slippageTolerance) if (inputWarning) { - return ( - - - - {inputWarning} - - - ) + return } return trade ? ( @@ -206,3 +221,22 @@ function SlippageMessage({ ) : null } + +function WarningMessage({ + text, + color, + showAlert = false, +}: { + text: string + color: ColorTokens + showAlert?: boolean +}): JSX.Element { + return ( + + {showAlert ?? } + + {text} + + + ) +} diff --git a/packages/uniswap/src/features/transactions/swap/settings/configs/Slippage.web.tsx b/packages/uniswap/src/features/transactions/swap/settings/configs/Slippage.web.tsx index de533d1ac02..ce85ecc7067 100644 --- a/packages/uniswap/src/features/transactions/swap/settings/configs/Slippage.web.tsx +++ b/packages/uniswap/src/features/transactions/swap/settings/configs/Slippage.web.tsx @@ -52,6 +52,7 @@ export const Slippage: SwapSettingConfig = { borderWidth={1} gap="$spacing8" p="$spacing4" + pr="$spacing8" style={inputAnimatedStyle} > = BaseDerivedInfo & { - chainId: WalletChainId + chainId: UniverseChainId currencies: BaseDerivedInfo['currencies'] & { [CurrencyField.OUTPUT]: Maybe } diff --git a/packages/uniswap/src/features/transactions/swap/types/trade.ts b/packages/uniswap/src/features/transactions/swap/types/trade.ts index ecf4a45fec5..dc4e24ea8a5 100644 --- a/packages/uniswap/src/features/transactions/swap/types/trade.ts +++ b/packages/uniswap/src/features/transactions/swap/types/trade.ts @@ -3,6 +3,7 @@ import { Currency, CurrencyAmount, Percent, Price, TradeType } from '@uniswap/sd import { UnsignedV2DutchOrderInfo, V2DutchOrderTrade, PriorityOrderTrade as IPriorityOrderTrade, UnsignedPriorityOrderInfo } from '@uniswap/uniswapx-sdk' import { Route as V2RouteSDK } from '@uniswap/v2-sdk' import { Route as V3RouteSDK } from '@uniswap/v3-sdk' +import { Route as V4RouteSDK } from '@uniswap/v4-sdk' import { AxiosError } from 'axios' import { BridgeQuoteResponse, ClassicQuoteResponse, DiscriminatedQuoteResponse, DutchQuoteResponse, PriorityQuoteResponse } from 'uniswap/src/data/apiClients/tradingApi/TradingApiClient' import { BigNumber, providers } from 'ethers/lib/ethers' @@ -148,6 +149,11 @@ export class ClassicTrade< inputAmount: CurrencyAmount outputAmount: CurrencyAmount }[] + readonly v4Routes: { + routev4: V4RouteSDK + inputAmount: CurrencyAmount + outputAmount: CurrencyAmount + }[] readonly tradeType: TTradeType }) { super(routes) diff --git a/packages/uniswap/src/features/transactions/swap/utils/generateTransactionSteps.ts b/packages/uniswap/src/features/transactions/swap/utils/generateTransactionSteps.ts index 23b4adf2a44..05837bb0778 100644 --- a/packages/uniswap/src/features/transactions/swap/utils/generateTransactionSteps.ts +++ b/packages/uniswap/src/features/transactions/swap/utils/generateTransactionSteps.ts @@ -28,6 +28,7 @@ export enum TransactionStepType { UniswapXSignature = 'UniswapXSignature', IncreasePositionTransaction = 'IncreasePositionTransaction', IncreasePositionTransactionAsync = 'IncreasePositionTransactionAsync', + DecreasePositionTransaction = 'DecreasePositionTransaction', } type UniswapXSwapSteps = @@ -49,8 +50,10 @@ type IncreasePositionSteps = | IncreasePositionTransactionStep | IncreasePositionTransactionStepAsync +type DecreasePositionSteps = TokenApprovalTransactionStep | DecreasePositionTransactionStep + // TODO: add v4 lp flow -export type TransactionStep = ClassicSwapSteps | UniswapXSwapSteps | IncreasePositionSteps +export type TransactionStep = ClassicSwapSteps | UniswapXSwapSteps | IncreasePositionSteps | DecreasePositionSteps export type OnChainTransactionStep = TransactionStep & OnChainTransactionFields export type SignatureTransactionStep = TransactionStep & SignTypedDataStepFields @@ -108,6 +111,11 @@ export interface IncreasePositionTransactionStepAsync { getTxRequest(signature: string): Promise // fetches tx request from trading api with signature } +export interface DecreasePositionTransactionStep extends OnChainTransactionFields { + // Doesn't require permit + type: TransactionStepType.DecreasePositionTransaction +} + type ClassicSwapFlow = | { revocation?: TokenRevocationTransactionStep @@ -138,6 +146,11 @@ type IncreasePositionFlow = increasePosition: IncreasePositionTransactionStepAsync } +type DecreasePositionFlow = { + approvalPositionToken?: TokenApprovalTransactionStep + decreasePosition: DecreasePositionTransactionStep +} + function orderSwapSteps(flow: ClassicSwapFlow): ClassicSwapSteps[] { const steps: ClassicSwapSteps[] = [] @@ -158,7 +171,7 @@ function orderSwapSteps(flow: ClassicSwapFlow): ClassicSwapSteps[] { return steps } -function orderLiquiditySteps(flow: IncreasePositionFlow): IncreasePositionSteps[] { +function orderIncreaseLiquiditySteps(flow: IncreasePositionFlow): IncreasePositionSteps[] { const steps: IncreasePositionSteps[] = [] if (flow.approvalToken0) { steps.push(flow.approvalToken0) @@ -181,6 +194,18 @@ function orderLiquiditySteps(flow: IncreasePositionFlow): IncreasePositionSteps[ return steps } +function orderDecreaseLiquiditySteps(flow: DecreasePositionFlow): DecreasePositionSteps[] { + const steps: DecreasePositionSteps[] = [] + + if (flow.approvalPositionToken) { + steps.push(flow.approvalPositionToken) + } + + steps.push(flow.decreasePosition) + + return steps +} + // UniswapX export interface UniswapXSignatureStep extends SignTypedDataStepFields { type: TransactionStepType.UniswapXSignature @@ -317,7 +342,10 @@ function createSwapTransactionStep(txRequest: ValidatedTransactionRequest): Swap } } -function createSwapTransactionAsyncStep(swapRequestArgs: CreateSwapRequest | undefined): SwapTransactionStepAsync { +function createSwapTransactionAsyncStep( + swapRequestArgs: CreateSwapRequest | undefined, + v4Enabled: boolean, +): SwapTransactionStepAsync { return { type: TransactionStepType.SwapTransactionAsync, getTxRequest: async (signature: string): Promise => { @@ -330,6 +358,7 @@ function createSwapTransactionAsyncStep(swapRequestArgs: CreateSwapRequest | und signature, /* simulating transaction provides a more accurate gas limit, and the simulation will succeed because async swap step will only occur after approval has been confirmed. */ simulateTransaction: true, + v4Enabled, }) return validateTransactionRequest(swap) @@ -350,15 +379,15 @@ function createIncreasePositionAsyncStep( return { type: TransactionStepType.IncreasePositionTransactionAsync, - getTxRequest: async (/* TODO(WEB-5084): accept the signature here*/): Promise< - ValidatedTransactionRequest | undefined - > => { + getTxRequest: async (signature: string): Promise => { if (!increasePositionRequestArgs) { return undefined } const { increase } = await increaseLpPosition({ - ...increasePositionRequestArgs /** TODO(WEB-5084): add the signature here */, + ...increasePositionRequestArgs, + signature, + simulateTransaction: true, }) return validateTransactionRequest(increase) @@ -366,52 +395,69 @@ function createIncreasePositionAsyncStep( } } -export function generateTransactionSteps(swapTxContext: SwapTxAndGasInfo | LiquidityTxAndGasInfo): TransactionStep[] { - const isValidSwap = isValidSwapTxContext(swapTxContext) - const isValidLP = isValidLiquidityTxContext(swapTxContext) +function createDecreasePositionStep(txRequest: ValidatedTransactionRequest): DecreasePositionTransactionStep { + return { + type: TransactionStepType.DecreasePositionTransaction, + txRequest, + } +} + +export function generateTransactionSteps( + txContext: SwapTxAndGasInfo | LiquidityTxAndGasInfo, + v4Enabled = false, +): TransactionStep[] { + const isValidSwap = isValidSwapTxContext(txContext) + const isValidLP = isValidLiquidityTxContext(txContext) if (isValidLP) { - const { - action, - approveToken0Request, - approveToken1Request, - approvePositionTokenRequest, - increasePositionRequestArgs, - } = swapTxContext + const { action, approveToken0Request, approveToken1Request, approvePositionTokenRequest } = txContext const approvalToken0 = createLPApprovalTransactionStep(approveToken0Request, action.currency0Amount.currency) const approvalToken1 = createLPApprovalTransactionStep(approveToken1Request, action.currency1Amount.currency) const approvalPositionToken = createLPApprovalTransactionStep(approvePositionTokenRequest, action.liquidityToken) - if (swapTxContext.unsigned) { - return orderLiquiditySteps({ - approvalToken0, - approvalToken1, - approvalPositionToken, - permit: createPermit2SignatureStep(swapTxContext.permit, action.currency0Amount.currency), // TODO: what about for multiple tokens - increasePosition: createIncreasePositionAsyncStep(increasePositionRequestArgs), - }) + switch (txContext.type) { + case 'decrease': + return orderDecreaseLiquiditySteps({ + approvalPositionToken, + decreasePosition: createDecreasePositionStep(txContext.txRequest), + }) + case 'create': + case 'increase': + if (txContext.unsigned) { + return orderIncreaseLiquiditySteps({ + approvalToken0, + approvalToken1, + approvalPositionToken, + permit: createPermit2SignatureStep(txContext.permit, action.currency0Amount.currency), // TODO: what about for multiple tokens + increasePosition: createIncreasePositionAsyncStep( + txContext.type === 'increase' + ? txContext.increasePositionRequestArgs + : txContext.createPositionRequestArgs, + ), + }) + } else { + return orderIncreaseLiquiditySteps({ + approvalToken0, + approvalToken1, + approvalPositionToken, + permit: undefined, + increasePosition: createIncreasePositionStep(txContext.txRequest), + }) + } } - - return orderLiquiditySteps({ - approvalToken0, - approvalToken1, - approvalPositionToken, - permit: undefined, - increasePosition: createIncreasePositionStep(swapTxContext.txRequest), - }) } else if (isValidSwap) { - const { trade, approveTxRequest, revocationTxRequest } = swapTxContext + const { trade, approveTxRequest, revocationTxRequest } = txContext - if (isClassic(swapTxContext)) { - const { swapRequestArgs } = swapTxContext + if (isClassic(txContext)) { + const { swapRequestArgs } = txContext - if (swapTxContext.unsigned) { + if (txContext.unsigned) { return orderSwapSteps({ revocation: createRevocationTransactionStep(revocationTxRequest, trade), approval: createSwapApprovalTransactionStep(approveTxRequest, trade), - permit: createPermit2SignatureStep(swapTxContext.permit, trade.inputAmount.currency), - swap: createSwapTransactionAsyncStep(swapRequestArgs), + permit: createPermit2SignatureStep(txContext.permit, trade.inputAmount.currency), + swap: createSwapTransactionAsyncStep(swapRequestArgs, v4Enabled), }) } @@ -419,31 +465,31 @@ export function generateTransactionSteps(swapTxContext: SwapTxAndGasInfo | Liqui revocation: createRevocationTransactionStep(revocationTxRequest, trade), approval: createSwapApprovalTransactionStep(approveTxRequest, trade), permit: undefined, - swap: createSwapTransactionStep(swapTxContext.txRequest), + swap: createSwapTransactionStep(txContext.txRequest), }) - } else if (isUniswapX(swapTxContext)) { + } else if (isUniswapX(txContext)) { return orderUniswapXSteps({ revocation: createRevocationTransactionStep(revocationTxRequest, trade), - wrap: createWrapTransactionStep(swapTxContext.wrapTxRequest, trade), + wrap: createWrapTransactionStep(txContext.wrapTxRequest, trade), approval: createSwapApprovalTransactionStep(approveTxRequest, trade), - signOrder: createSignOrderUniswapXStep(swapTxContext.permit, swapTxContext.trade.quote.quote), + signOrder: createSignOrderUniswapXStep(txContext.permit, txContext.trade.quote.quote), }) - } else if (isBridge(swapTxContext)) { - const { swapRequestArgs } = swapTxContext + } else if (isBridge(txContext)) { + const { swapRequestArgs } = txContext - if (swapTxContext.unsigned) { + if (txContext.unsigned) { return orderSwapSteps({ revocation: createRevocationTransactionStep(revocationTxRequest, trade), approval: createSwapApprovalTransactionStep(approveTxRequest, trade), - permit: createPermit2SignatureStep(swapTxContext.permit, trade.inputAmount.currency), - swap: createSwapTransactionAsyncStep(swapRequestArgs), + permit: createPermit2SignatureStep(txContext.permit, trade.inputAmount.currency), + swap: createSwapTransactionAsyncStep(swapRequestArgs, v4Enabled), }) } return orderSwapSteps({ revocation: createRevocationTransactionStep(revocationTxRequest, trade), approval: createSwapApprovalTransactionStep(approveTxRequest, trade), permit: undefined, - swap: createSwapTransactionStep(swapTxContext.txRequest), + swap: createSwapTransactionStep(txContext.txRequest), }) } } diff --git a/packages/uniswap/src/features/transactions/swap/utils/protocols.ts b/packages/uniswap/src/features/transactions/swap/utils/protocols.ts index e9ee4830865..9eaa4fe68ba 100644 --- a/packages/uniswap/src/features/transactions/swap/utils/protocols.ts +++ b/packages/uniswap/src/features/transactions/swap/utils/protocols.ts @@ -6,7 +6,7 @@ import { UniverseChainId } from 'uniswap/src/types/chains' export const DEFAULT_PROTOCOL_OPTIONS = [ // `as const` allows us to derive a type narrower than ProtocolItems, and the `...` spread removes readonly, allowing DEFAULT_PROTOCOL_OPTIONS to be passed around as an argument without `readonly` - ...([ProtocolItems.UNISWAPX_V2 /* ProtocolItems.V4 */, ProtocolItems.V3, ProtocolItems.V2] as const), + ...([ProtocolItems.UNISWAPX_V2, ProtocolItems.V4, ProtocolItems.V3, ProtocolItems.V2] as const), ] export type FrontendSupportedProtocol = (typeof DEFAULT_PROTOCOL_OPTIONS)[number] @@ -24,7 +24,7 @@ export function useProtocolsForChain( const uniswapXAllowedForChain = (chainId && LAUNCHED_UNISWAPX_CHAINS.includes(chainId)) || priorityOrdersAllowed || arbUniswapXAllowed - // const v4SwapAllowed = useFeatureFlag(FeatureFlags.V4Swap) + const v4SwapAllowed = useFeatureFlag(FeatureFlags.V4Swap) return useMemo(() => { let protocols = [...userSelectedProtocols] @@ -34,10 +34,10 @@ export function useProtocolsForChain( protocols = protocols.filter((protocol) => protocol !== ProtocolItems.UNISWAPX_V2) } - // if (!v4SwapAllowed) { - // protocols = protocols.filter((protocol) => protocol !== ProtocolItems.V4) - // } + if (!v4SwapAllowed) { + protocols = protocols.filter((protocol) => protocol !== ProtocolItems.V4) + } return protocols - }, [uniswapXAllowedForChain, uniswapXEnabled, userSelectedProtocols]) + }, [uniswapXAllowedForChain, uniswapXEnabled, userSelectedProtocols, v4SwapAllowed]) } diff --git a/packages/uniswap/src/features/transactions/swap/utils/trade.test.ts b/packages/uniswap/src/features/transactions/swap/utils/trade.test.ts index d8ae0c07199..aca1154e562 100644 --- a/packages/uniswap/src/features/transactions/swap/utils/trade.test.ts +++ b/packages/uniswap/src/features/transactions/swap/utils/trade.test.ts @@ -1,7 +1,8 @@ import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core' import { FeeAmount, Pool, Route } from '@uniswap/v3-sdk' import { UNI, WBTC } from 'uniswap/src/constants/tokens' -import { ClassicTrade } from 'uniswap/src/features/transactions/swap/types/trade' +import { Routing } from 'uniswap/src/data/tradingApi/__generated__' +import { BridgeTrade, ClassicTrade } from 'uniswap/src/features/transactions/swap/types/trade' import { requireAcceptNewTrade } from 'uniswap/src/features/transactions/swap/utils/trade' import { UniverseChainId } from 'uniswap/src/types/chains' @@ -15,29 +16,14 @@ export const mockPool = new Pool( ) describe(requireAcceptNewTrade, () => { - const oldTrade = new ClassicTrade({ - v3Routes: [ - { - routev3: new Route([mockPool], UNI[UniverseChainId.Mainnet], WBTC), - inputAmount: CurrencyAmount.fromRawAmount(UNI[UniverseChainId.Mainnet], 1000), - outputAmount: CurrencyAmount.fromRawAmount(WBTC, 1000), - }, - ], - v2Routes: [], - mixedRoutes: [], - tradeType: TradeType.EXACT_INPUT, - slippageTolerance: 0.5, - deadline: Date.now() + 60 * 30 * 1000, - }) - - it('returns false when prices are within threshold', () => { - const newTrade = new ClassicTrade({ + describe('ClassicTrade', () => { + const oldTrade = new ClassicTrade({ + v4Routes: [], v3Routes: [ { routev3: new Route([mockPool], UNI[UniverseChainId.Mainnet], WBTC), inputAmount: CurrencyAmount.fromRawAmount(UNI[UniverseChainId.Mainnet], 1000), - // Update this number if `ACCEPT_NEW_TRADE_THRESHOLD` changes - outputAmount: CurrencyAmount.fromRawAmount(WBTC, 990), + outputAmount: CurrencyAmount.fromRawAmount(WBTC, 1000), }, ], v2Routes: [], @@ -46,43 +32,151 @@ describe(requireAcceptNewTrade, () => { slippageTolerance: 0.5, deadline: Date.now() + 60 * 30 * 1000, }) - expect(requireAcceptNewTrade(oldTrade, newTrade)).toBe(false) + + it('returns false when prices are within threshold', () => { + const newTrade = new ClassicTrade({ + v4Routes: [], + v3Routes: [ + { + routev3: new Route([mockPool], UNI[UniverseChainId.Mainnet], WBTC), + inputAmount: CurrencyAmount.fromRawAmount(UNI[UniverseChainId.Mainnet], 1000), + // Update this number if `ACCEPT_NEW_TRADE_THRESHOLD` changes + outputAmount: CurrencyAmount.fromRawAmount(WBTC, 990), + }, + ], + v2Routes: [], + mixedRoutes: [], + tradeType: TradeType.EXACT_INPUT, + slippageTolerance: 0.5, + deadline: Date.now() + 60 * 30 * 1000, + }) + expect(requireAcceptNewTrade(oldTrade, newTrade)).toBe(false) + }) + + it('returns true when prices move above threshold', () => { + const newTrade = new ClassicTrade({ + v4Routes: [], + v3Routes: [ + { + routev3: new Route([mockPool], UNI[UniverseChainId.Mainnet], WBTC), + inputAmount: CurrencyAmount.fromRawAmount(UNI[UniverseChainId.Mainnet], 1000), + // Update this number if `ACCEPT_NEW_TRADE_THRESHOLD` changes + outputAmount: CurrencyAmount.fromRawAmount(WBTC, 979), + }, + ], + v2Routes: [], + mixedRoutes: [], + tradeType: TradeType.EXACT_INPUT, + slippageTolerance: 0.5, + deadline: Date.now() + 60 * 30 * 1000, + }) + expect(requireAcceptNewTrade(oldTrade, newTrade)).toBe(true) + }) + + it('returns false when new price is better', () => { + const newTrade = new ClassicTrade({ + v4Routes: [], + v3Routes: [ + { + routev3: new Route([mockPool], UNI[UniverseChainId.Mainnet], WBTC), + inputAmount: CurrencyAmount.fromRawAmount(UNI[UniverseChainId.Mainnet], 1000), + outputAmount: CurrencyAmount.fromRawAmount(WBTC, 2000000), + }, + ], + v2Routes: [], + mixedRoutes: [], + tradeType: TradeType.EXACT_INPUT, + slippageTolerance: 0.5, + deadline: Date.now() + 60 * 30 * 1000, + }) + expect(requireAcceptNewTrade(oldTrade, newTrade)).toBe(false) + }) }) - it('returns true when prices move above threshold', () => { - const newTrade = new ClassicTrade({ - v3Routes: [ - { - routev3: new Route([mockPool], UNI[UniverseChainId.Mainnet], WBTC), - inputAmount: CurrencyAmount.fromRawAmount(UNI[UniverseChainId.Mainnet], 1000), - // Update this number if `ACCEPT_NEW_TRADE_THRESHOLD` changes - outputAmount: CurrencyAmount.fromRawAmount(WBTC, 979), + describe('BridgeTrade', () => { + const oldTrade = new BridgeTrade({ + quote: { + quote: { + input: { + amount: '1000', + }, + output: { + amount: '990', + }, }, - ], - v2Routes: [], - mixedRoutes: [], + requestId: '123', + routing: Routing.BRIDGE, + permitData: null, + }, + currencyIn: UNI[UniverseChainId.Mainnet], + currencyOut: WBTC, tradeType: TradeType.EXACT_INPUT, - slippageTolerance: 0.5, - deadline: Date.now() + 60 * 30 * 1000, }) - expect(requireAcceptNewTrade(oldTrade, newTrade)).toBe(true) - }) - it('returns false when new price is better', () => { - const newTrade = new ClassicTrade({ - v3Routes: [ - { - routev3: new Route([mockPool], UNI[UniverseChainId.Mainnet], WBTC), - inputAmount: CurrencyAmount.fromRawAmount(UNI[UniverseChainId.Mainnet], 1000), - outputAmount: CurrencyAmount.fromRawAmount(WBTC, 2000000), + it('returns false when output amount is within threshold', () => { + const newTrade = new BridgeTrade({ + quote: { + quote: { + input: { + amount: '1000', + }, + output: { + amount: '981', + }, + }, + requestId: '123', + routing: Routing.BRIDGE, + permitData: null, }, - ], - v2Routes: [], - mixedRoutes: [], - tradeType: TradeType.EXACT_INPUT, - slippageTolerance: 0.5, - deadline: Date.now() + 60 * 30 * 1000, + currencyIn: UNI[UniverseChainId.Mainnet], + currencyOut: WBTC, + tradeType: TradeType.EXACT_INPUT, + }) + expect(requireAcceptNewTrade(oldTrade, newTrade)).toBe(false) + }) + + it('returns true when output amount is below threshold', () => { + const newTrade = new BridgeTrade({ + quote: { + quote: { + input: { + amount: '1000', + }, + output: { + amount: '979', + }, + }, + requestId: '123', + routing: Routing.BRIDGE, + permitData: null, + }, + currencyIn: UNI[UniverseChainId.Mainnet], + currencyOut: WBTC, + tradeType: TradeType.EXACT_INPUT, + }) + expect(requireAcceptNewTrade(oldTrade, newTrade)).toBe(true) + }) + + it('returns false when new trade is better', () => { + const newTrade = new BridgeTrade({ + quote: { + quote: { + input: { + amount: '1000', + }, + output: { + amount: '1001', + }, + }, + requestId: '123', + routing: Routing.BRIDGE, + permitData: null, + }, + currencyIn: UNI[UniverseChainId.Mainnet], + currencyOut: WBTC, + tradeType: TradeType.EXACT_INPUT, + }) + expect(requireAcceptNewTrade(oldTrade, newTrade)).toBe(false) }) - expect(requireAcceptNewTrade(oldTrade, newTrade)).toBe(false) }) }) diff --git a/packages/uniswap/src/features/transactions/swap/utils/trade.ts b/packages/uniswap/src/features/transactions/swap/utils/trade.ts index e65833c2d54..9286c01ffc7 100644 --- a/packages/uniswap/src/features/transactions/swap/utils/trade.ts +++ b/packages/uniswap/src/features/transactions/swap/utils/trade.ts @@ -1,6 +1,6 @@ import providers from '@ethersproject/providers' -import { Protocol } from '@uniswap/router-sdk' -import { Percent, TradeType } from '@uniswap/sdk-core' +import { ONE, Protocol } from '@uniswap/router-sdk' +import { Fraction, Percent, TradeType } from '@uniswap/sdk-core' import { NullablePermit, Permit } from 'uniswap/src/data/tradingApi/__generated__/index' import { LocalizationContextState } from 'uniswap/src/features/language/LocalizationContext' import { IndicativeTrade, Trade } from 'uniswap/src/features/transactions/swap/types/trade' @@ -84,11 +84,18 @@ export function requireAcceptNewTrade(oldTrade: Maybe, newTrade: Maybe (r?.routev2 ? { ...r, routev2: r.routev2 } : [])) ?? [], v3Routes: routes?.flatMap((r) => (r?.routev3 ? { ...r, routev3: r.routev3 } : [])) ?? [], + v4Routes: routes?.flatMap((r) => (r?.routev4 ? { ...r, routev4: r.routev4 } : [])) ?? [], mixedRoutes: routes?.flatMap((r) => (r?.mixedRoute ? { ...r, mixedRoute: r.mixedRoute } : [])) ?? [], tradeType, }) @@ -110,6 +114,7 @@ export function computeRoutes( quoteResponse?: QuoteResponse, ): | { + routev4: V4Route | null routev3: V3Route | null routev2: V2Route | null mixedRoute: MixedRouteSDK | null @@ -172,12 +177,22 @@ export function computeRoutes( const isOnlyV2 = isV2OnlyRouteApi(route) const isOnlyV3 = isV3OnlyRouteApi(route) + const isOnlyV4 = isV4OnlyRouteApi(route) + + const v4Routes = route.filter((r): r is V4PoolInRoute => r.type === 'v4-pool') return { + routev4: isOnlyV4 + ? new V4Route( + v4Routes.map((r) => parseV4PoolApi(r, parsedCurrencyIn, parsedCurrencyOut)), + parsedCurrencyIn, + parsedCurrencyOut, + ) + : null, routev3: isOnlyV3 ? new V3Route(route.map(parseV3PoolApi), parsedCurrencyIn, parsedCurrencyOut) : null, routev2: isOnlyV2 ? new V2Route(route.map(parseV2PairApi), parsedCurrencyIn, parsedCurrencyOut) : null, mixedRoute: - !isOnlyV3 && !isOnlyV2 + !isOnlyV3 && !isOnlyV2 && !isOnlyV4 ? new MixedRouteSDK(route.map(parseMixedRouteApi), parsedCurrencyIn, parsedCurrencyOut) : null, inputAmount, @@ -206,6 +221,23 @@ function parseTokenApi(token: TradingApiTokenInRoute): Token { ) } +function parseV4PoolApi( + { fee, sqrtRatioX96, liquidity, tickCurrent, tickSpacing, hooks }: TradingApiV4PoolInRoute, + tokenIn: Currency, + tokenOut: Currency, +): V4Pool { + return new V4Pool( + tokenIn, + tokenOut, + Number(fee), + Number(tickSpacing), + hooks, + sqrtRatioX96, + liquidity, + Number(tickCurrent), + ) +} + function parseV3PoolApi({ fee, sqrtRatioX96, @@ -213,11 +245,11 @@ function parseV3PoolApi({ tickCurrent, tokenIn, tokenOut, -}: TradingApiV3PoolInRoute): Pool { +}: TradingApiV3PoolInRoute): V3Pool { if (!tokenIn || !tokenOut || !fee || !sqrtRatioX96 || !liquidity || !tickCurrent) { throw new Error('Expected pool values to be present') } - return new Pool( + return new V3Pool( parseTokenApi(tokenIn), parseTokenApi(tokenOut), parseInt(fee, 10) as FeeAmount, @@ -237,18 +269,23 @@ function parseV2PairApi({ reserve0, reserve1 }: TradingApiV2PoolInRoute): Pair { ) } -function parseMixedRouteApi(pool: TradingApiV2PoolInRoute | TradingApiV3PoolInRoute): Pair | Pool { +type ClassicPoolInRoute = TradingApiV2PoolInRoute | TradingApiV3PoolInRoute | TradingApiV4PoolInRoute +function parseMixedRouteApi(pool: ClassicPoolInRoute): Pair | V3Pool { return pool.type === 'v2-pool' ? parseV2PairApi(pool) : parseV3PoolApi(pool) } -function isV2OnlyRouteApi(route: (TradingApiV2PoolInRoute | TradingApiV3PoolInRoute)[]): boolean { +function isV2OnlyRouteApi(route: ClassicPoolInRoute[]): boolean { return route.every((pool) => pool.type === 'v2-pool') } -function isV3OnlyRouteApi(route: (TradingApiV2PoolInRoute | TradingApiV3PoolInRoute)[]): boolean { +function isV3OnlyRouteApi(route: ClassicPoolInRoute[]): boolean { return route.every((pool) => pool.type === 'v3-pool') } +function isV4OnlyRouteApi(route: ClassicPoolInRoute[]): boolean { + return route.every((pool) => pool.type === 'v4-pool') +} + export function getTokenAddressFromChainForTradingApi(address: Address, chainId: UniverseChainId): string { // For native currencies, we need to map to 0x0000000000000000000000000000000000000000 if (address === UNIVERSE_CHAIN_INFO[chainId].nativeCurrency.address) { diff --git a/packages/uniswap/src/features/transactions/swap/utils/wrap.ts b/packages/uniswap/src/features/transactions/swap/utils/wrap.ts index 3dcc93b80fb..cc1e63aa4fa 100644 --- a/packages/uniswap/src/features/transactions/swap/utils/wrap.ts +++ b/packages/uniswap/src/features/transactions/swap/utils/wrap.ts @@ -1,6 +1,6 @@ import { Currency } from '@uniswap/sdk-core' import { WrapType } from 'uniswap/src/features/transactions/types/wrap' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { areCurrencyIdsEqual, buildWrappedNativeCurrencyId, currencyId } from 'uniswap/src/utils/currencyId' export function getWrapType( @@ -11,7 +11,7 @@ export function getWrapType( return WrapType.NotApplicable } - const inputChainId = inputCurrency.chainId as WalletChainId + const inputChainId = inputCurrency.chainId as UniverseChainId const wrappedCurrencyId = buildWrappedNativeCurrencyId(inputChainId) if (inputCurrency.isNative && areCurrencyIdsEqual(currencyId(outputCurrency), wrappedCurrencyId)) { diff --git a/packages/uniswap/src/features/transactions/types/transactionDetails.ts b/packages/uniswap/src/features/transactions/types/transactionDetails.ts index 1ccac0f61fa..bb33a25b464 100644 --- a/packages/uniswap/src/features/transactions/types/transactionDetails.ts +++ b/packages/uniswap/src/features/transactions/types/transactionDetails.ts @@ -7,7 +7,7 @@ import { TransactionListQuery } from 'uniswap/src/data/graphql/uniswap-data-api/ import { Routing } from 'uniswap/src/data/tradingApi/__generated__/index' import { GasEstimate } from 'uniswap/src/data/tradingApi/types' import { AssetType } from 'uniswap/src/entities/assets' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { DappInfo } from 'uniswap/src/types/walletConnect' export type WarningWithStyle = { @@ -27,11 +27,11 @@ export type ParsedWarnings = { warnings: Warning[] } -export type ChainIdToTxIdToDetails = Partial> +export type ChainIdToTxIdToDetails = Partial> // Basic identifying info for a transaction export interface TransactionId { - chainId: WalletChainId + chainId: UniverseChainId // moonpay externalTransactionId id: string } @@ -81,7 +81,7 @@ export type TransactionNetworkFee = { quantity: string tokenSymbol: string tokenAddress: string - chainId: WalletChainId + chainId: UniverseChainId } export type GasFeeEstimates = { @@ -114,6 +114,8 @@ export interface BridgeTransactionDetails extends BaseTransactionDetails { // Info for submitting the tx options: TransactionOptions + + sendConfirmed?: boolean } export type TransactionDetails = UniswapXOrderDetails | ClassicTransactionDetails | BridgeTransactionDetails diff --git a/packages/uniswap/src/features/transactions/types/transactionState.ts b/packages/uniswap/src/features/transactions/types/transactionState.ts index 7c8c36289d6..48e1c098568 100644 --- a/packages/uniswap/src/features/transactions/types/transactionState.ts +++ b/packages/uniswap/src/features/transactions/types/transactionState.ts @@ -1,6 +1,6 @@ import { AssetType, TradeableAsset } from 'uniswap/src/entities/assets' import { FrontendSupportedProtocol } from 'uniswap/src/features/transactions/swap/utils/protocols' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField, CurrencyId } from 'uniswap/src/types/currency' import { currencyIdToAddress, currencyIdToChain } from 'uniswap/src/utils/currencyId' @@ -23,8 +23,10 @@ export interface TransactionState { export const prepareSwapFormState = ({ inputCurrencyId, + defaultChainId, }: { inputCurrencyId?: CurrencyId + defaultChainId: UniverseChainId }): TransactionState | undefined => { if (!inputCurrencyId) { return undefined @@ -35,7 +37,7 @@ export const prepareSwapFormState = ({ exactAmountToken: '', [CurrencyField.INPUT]: { address: currencyIdToAddress(inputCurrencyId), - chainId: (currencyIdToChain(inputCurrencyId) as WalletChainId) ?? UniverseChainId.Mainnet, + chainId: (currencyIdToChain(inputCurrencyId) as UniverseChainId) ?? defaultChainId, type: AssetType.Currency, }, [CurrencyField.OUTPUT]: null, diff --git a/packages/uniswap/src/features/web3/walletConnect.ts b/packages/uniswap/src/features/web3/walletConnect.ts new file mode 100644 index 00000000000..9bbeab0c34d --- /dev/null +++ b/packages/uniswap/src/features/web3/walletConnect.ts @@ -0,0 +1,9 @@ +import { CONNECTION_PROVIDER_IDS } from 'uniswap/src/constants/web3' +import { UniverseChainId } from 'uniswap/src/types/chains' +import { Connector } from 'wagmi' + +export interface WalletConnectConnector extends Connector { + type: typeof CONNECTION_PROVIDER_IDS.UNISWAP_WALLET_CONNECT_CONNECTOR_ID + getNamespaceChainsIds: () => UniverseChainId[] + getProvider(): Promise<{ modal: { setTheme: ({ themeMode }: { themeMode: 'dark' | 'light' }) => void } }> +} diff --git a/packages/uniswap/src/hooks/useAppInsets.tsx b/packages/uniswap/src/hooks/useAppInsets.tsx new file mode 100644 index 00000000000..76a1a102999 --- /dev/null +++ b/packages/uniswap/src/hooks/useAppInsets.tsx @@ -0,0 +1,18 @@ +// eslint-disable-next-line no-restricted-imports +import { useDeviceInsets } from 'ui/src/hooks/useDeviceInsets' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' +import { isMobileApp } from 'utilities/src/platform' + +export const TESTNET_MODE_BANNER_HEIGHT = 44 +export const useAppInsets = (): { + top: number + right: number + bottom: number + left: number +} => { + const { isTestnetModeEnabled } = useEnabledChains() + const insets = useDeviceInsets() + + const extraTop = isTestnetModeEnabled && isMobileApp ? TESTNET_MODE_BANNER_HEIGHT : 0 + return { ...insets, top: insets.top + extraTop } +} diff --git a/packages/uniswap/src/i18n/locales/@types/i18next.ts b/packages/uniswap/src/i18n/locales/@types/i18next.ts index f494d92bcfa..d471ee8a406 100644 --- a/packages/uniswap/src/i18n/locales/@types/i18next.ts +++ b/packages/uniswap/src/i18n/locales/@types/i18next.ts @@ -1,15 +1,13 @@ import 'i18next' -// This is only required because importing json as const is not yet supported -// The json file can be directly imported instead of generating an interface when that is available -import Resources from 'uniswap/src/i18n/locales/@types/resources' -interface CustomResources { - translation: Resources['en-US'] -} +const en = require('uniswap/src/i18n/locales/source/en-US.json') as Record; + +const resources = { + translation: en, +} as const; declare module 'i18next' { interface CustomTypeOptions { - defaultNS: 'translation' - resources: CustomResources + resources: typeof resources; } } diff --git a/packages/uniswap/src/i18n/locales/source/en-US.json b/packages/uniswap/src/i18n/locales/source/en-US.json index 9cdd51bfb38..ddb83284e60 100644 --- a/packages/uniswap/src/i18n/locales/source/en-US.json +++ b/packages/uniswap/src/i18n/locales/source/en-US.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Deposit tokens to the {{label}} network.", "common.deposited": "Deposited", "common.depositing": "Depositing", + "common.depositTokens": "Deposit tokens", "common.detailed.label": "Detailed", "common.detected": "Detected", "common.developers": "Developers", @@ -430,6 +431,7 @@ "common.migrate.position": "Migrate position", "common.migrated.liquidity": "Migrated liquidity", "common.migrating.liquidity": "Migrating liquidity", + "common.min": "Min", "common.mint.cancelled": "Mint cancelled", "common.mint.failed": "Mint failed", "common.minted": "Minted", @@ -450,6 +452,7 @@ "common.noResults": "No results found.", "common.notAvailableInRegion.error": "Not available in your region", "common.notCreated.label": "Not created", + "common.notSupported": "Not supported", "common.oneDay": "1 day", "common.oneHour": "1 hour", "common.oneMonth": "1 month", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}, and other options", "fiatOnRamp.quote.type.other": "Other options", "fiatOnRamp.quote.type.recent": "Recently used", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "You can receive tokens & NFTs on Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora, and ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "From an account", "fiatOnRamp.receiveCrypto.title": "Receive crypto", "fiatOnRamp.receiveCrypto.transferFunds": "Fund your wallet by transferring crypto from another wallet or account", @@ -901,6 +903,8 @@ "home.activity.error.load": "Couldn’t load activity", "home.activity.title": "Activity", "home.banner.offline": "You are in offline mode", + "home.banner.testnetMode": "You are in testnet mode", + "home.banner.testnetMode.nav": "You are in testnet mode. Toggle this off in settings.", "home.explore.footer": "Tap “search” to explore more", "home.explore.title": "Explore tokens", "home.extension.error": "Error loading accounts", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Create position", "nav.tabs.createV2Position": "Create V2 position", "nav.tabs.createV3Position": "Create V3 position", + "nav.tabs.createV4Position": "Create V4 position", "nav.tabs.viewPosition": "View position", "network.lostConnection": "You may have lost your network connection.", "network.mightBeDown": "{{network}} might be down right now, or you may have lost your network connection.", @@ -1331,6 +1336,7 @@ "pool.back": "Back to Pool", "pool.balances": "Pool balances", "pool.claimFees": "Claim fees", + "pool.claimFees.button.label": "Claim", "pool.collectAs": "Collect as {{nativeWrappedSymbol}}", "pool.collected": " Collected", "pool.collecting": "Collecting", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Manage liquidity in rewards pool", "pool.max.label": "Max:", "pool.maxPrice": "Max price", + "pool.migrateToV4": "Migrate to V4", "pool.min.label": "Min:", "pool.minPrice": "Min price", "pool.mustBeInitialized": "This pool must be initialized before you can add liquidity. To initialize, select a starting price for the pool. Then, enter your liquidity price range and deposit amount. Gas fees will be higher than usual due to the initialization transaction.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Tip: Use this tool to find v2 pools that don’t automatically appear in the interface.", "pools.approving.amount": "Approving {{amount}}", "position.addHook": "Add a Hook", + "position.addHook.tooltip": "Hooks are an advanced feature that enable pools to interact with smart contracts, unlocking various capabilities. Exercise caution when adding hooks, as some may be malicious or cause unintended consequences.", "position.appearHere": "Your position will appear here.", + "position.create.modal.header": "Creating position", "position.currentValue": "Current position value", + "position.deposit.description": "Specify the token amounts for your liquidity contribution.", "position.depositedCurrency": "Deposited {{currencySymbol}}", "position.migrate.liquidity": "When migrating positions, you cannot change your token pair, but you can add a hook to enhance functionality.", "position.new": "New Position", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Connecting...", "qrScanner.status.loading": "Loading...", "qrScanner.title": "Scan a QR code", - "qrScanner.wallet.title": "You can receive tokens & NFTs on Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast, and BNB Chain.", + "qrScanner.wallet.networks": "Supported Networks", + "qrScanner.wallet.title": "You can send and receive tokens and NFTs on all of our {{numOfNetworks}} supported networks.", "removeLiquidity.collectFees": "You will also collect fees earned from this position.", "removeLiquidity.outputEstimated": "Output is estimated. If the price changes by more than {{allowed}}% your transaction will revert.", "removeLiquidity.pendingText": "Removing {{amtA}} {{symA}} and {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Nickname", "settings.setting.wallet.notifications.title": "Notifications", "settings.setting.wallet.preferences.title": "Wallet preferences", - "settings.showTestNets": "Show testnets", + "settings.setting.wallet.testnetMode.description": "This turns on testnets for developers to try out features and transactions without using real assets. Tokens on testnets do not hold any real value.", + "settings.setting.wallet.testnetMode.title": "Testnet mode", "settings.switchNetwork.warning": "To use Uniswap on {{label}}, switch the network in your wallet’s settings.", "settings.title": "Settings", "settings.version": "Version {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "New input", "swap.details.newQuote.output": "New output", "swap.details.orderRouting": "Order routing", - "swap.details.orderRoutingInfo": "Your price already includes network costs on the destination network and a 0.05% Across fee.", + "swap.details.orderRoutingInfo": "This swap is routed via Across, a decentralized protocol that moves assets across networks while prioritizing safety, fast execution, and low prices.", + "swap.details.poweredBy": "Powered by", "swap.details.rate": "Rate", "swap.details.slippage": "Max slippage", "swap.details.uniswapFee": "Fee", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "The Uniswap client selects the cheapest trade option factoring price and network costs.", "swap.settings.routingPreference.option.v2.title": "v2 pools", "swap.settings.routingPreference.option.v3.title": "v3 pools", + "swap.settings.routingPreference.option.v4.title": "v4 pools", "swap.settings.routingPreference.title": "Trade options", "swap.settings.slippage.control.auto": "Auto", "swap.settings.slippage.description": "Your transaction will revert if the price changes more than the slippage percentage.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Your transaction will revert if the price changes unfavorably by more than this percentage.", "swap.signAndSwap": "Sign and swap", "swap.slippage.amt": "{{amt}} slippage", + "swap.slippage.bridging": "No slippage when swapping across networks", "swap.slippage.settings.title": "Max slippage", "swap.slippage.tooltip": "The maximum price movement before your transaction will revert.", "swap.slippageBelow.warning": "Slippage below {{amt}} may result in a failed transaction", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Transaction deadline", "swap.transaction.revertAfter": "Your transaction will revert if it is pending for more than this period of time.", "swap.unsupportedAssets.readMore": "Read more about unsupported assets", + "swap.warning.enterLargerAmount.title": "Enter a larger amount", "swap.warning.expectedFailure": "This transaction is expected to fail", "swap.warning.feeOnTransfer.message": "Some tokens take a fee when they are bought or sold, which is set by the token issuer. Uniswap does not receive any share of these fees.", "swap.warning.feeOnTransfer.title": "Why is there an additional fee?", "swap.warning.insufficientBalance.title": "You don’t have enough {{currencySymbol}}", "swap.warning.insufficientGas.button": "Not enough {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Swap for {{ tokenSymbol }} on {{networkName}}", "swap.warning.insufficientGas.button.buy": "Buy {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Buy with card", "swap.warning.insufficientGas.message.withNetwork": "Not enough {{currencySymbol}} on {{networkName}} to swap", "swap.warning.insufficientGas.message.withoutNetwork": "Not enough {{currencySymbol}} to swap", "swap.warning.insufficientGas.title": "You don’t have enough {{currencySymbol}} to cover the network cost", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "token data for on {{chainName}}", "tdp.nameNotFound": "Name not found", "tdp.noInfoAvailable": "No token information available", + "tdp.noTestnetSupportDescription": "Some testnets do not support swapping, sending, or buying tokens.", "tdp.stats.unsupportedChainDescription": "Token stats and charts for {{chain}} are available on {{infoLink}}", "tdp.symbolNotFound": "Symbol not found", + "testnet.unsupported": "This functionality is not supported in testnet mode.", "themeToggle.theme": "Theme", "title.betterPricesMoreListings": "Better prices. More listings. Buy, sell, and trade NFTs across top marketplaces like OpenSea. Explore trending collections.", "title.buySellTradeEthereum": "Buy, sell & trade Ethereum and other top tokens on Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "{{ownerAddress}}’s balance", "token.bridge": "{{label}} token bridge", "token.chart.tooltip": "Fees: {{amount}}", + "token.details.testnet.unsupported": "Token details are unavailable for testnet tokens.", "token.error.unknown": "Unknown token", "token.fee.buy.label": "buy fee", "token.fee.label": "fee", @@ -2109,6 +2130,8 @@ "transaction.warning.insufficientGas.modal.title.withoutNetwork": "Not enough {{tokenSymbol}}", "transaction.watcher.error.cancel": "Unable to cancel transaction", "transaction.watcher.error.status": "Error while checking transaction status", + "unichain.launch.modal.description": "An Ethereum L2 designed for DeFi, built by Uniswap Labs. Testnet goes live today.", + "unichain.launch.modal.title": "Introducing Unichain", "uniswapX.aggregatesLiquidity": " aggregates liquidity sources for better prices and gas free swaps.", "uniswapx.description": "UniswapX aggregates liquidity sources for better prices and gas free swaps.", "uniswapx.included": "Includes UniswapX ", @@ -2212,7 +2235,6 @@ "walletConnect.pending.switchAccount": "Switch Account", "walletConnect.pending.switchNetwork": "Switch Network", "walletConnect.pending.title": "Connect to {{dappName}}", - "walletConnect.permissions.networks": "Networks", "walletConnect.permissions.option.transferAssets": "Transfer your assets without consent", "walletConnect.permissions.option.viewTokenBalances": "View your token balances", "walletConnect.permissions.option.viewWalletAddress": "View your wallet address", diff --git a/packages/uniswap/src/i18n/locales/translations/af-ZA.json b/packages/uniswap/src/i18n/locales/translations/af-ZA.json index d577189a9f6..cf24e7846fe 100644 --- a/packages/uniswap/src/i18n/locales/translations/af-ZA.json +++ b/packages/uniswap/src/i18n/locales/translations/af-ZA.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Deponeer tokens na die {{label}} netwerk.", "common.deposited": "Gedeponeer", "common.depositing": "Deponeer", + "common.depositTokens": "Deposito tokens", "common.detailed.label": "Gedetailleerd", "common.detected": "Bespeur", "common.developers": "Ontwikkelaars", @@ -430,6 +431,7 @@ "common.migrate.position": "Migreer posisie", "common.migrated.liquidity": "Gemigreerde likiditeit", "common.migrating.liquidity": "Migreer likiditeit", + "common.min": "Min", "common.mint.cancelled": "Munt gekanselleer", "common.mint.failed": "Munt het misluk", "common.minted": "Gemunt", @@ -450,6 +452,7 @@ "common.noResults": "Geen resultate gevind.", "common.notAvailableInRegion.error": "Nie beskikbaar in jou streek nie", "common.notCreated.label": "Nie geskep nie", + "common.notSupported": "Nie ondersteun nie", "common.oneDay": "1 dag", "common.oneHour": "1 uur", "common.oneMonth": "1 maand", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}, en ander opsies", "fiatOnRamp.quote.type.other": "Ander opsies", "fiatOnRamp.quote.type.recent": "Onlangs gebruik", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "U kan tokens en NFT's op Ethereum, Polygon, Arbitrum, Optimisme, Base, BNB, Blast, Avalanche, Zora en ZKsync ontvang.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Van 'n rekening", "fiatOnRamp.receiveCrypto.title": "Kry kripto", "fiatOnRamp.receiveCrypto.transferFunds": "Befonds jou beursie deur kripto van 'n ander beursie of rekening oor te dra", @@ -901,6 +903,8 @@ "home.activity.error.load": "Kon nie aktiwiteit laai nie", "home.activity.title": "Aktiwiteit", "home.banner.offline": "Jy is in vanlyn modus", + "home.banner.testnetMode": "Jy is in die toetsnetmodus", + "home.banner.testnetMode.nav": "Jy is in die toetsnet-modus. Skakel dit af in instellings.", "home.explore.footer": "Tik hier om duisende tekens, NFT's en meer te verken", "home.explore.title": "Verken tokens", "home.extension.error": "Kon nie rekeninge laai nie", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Skep posisie", "nav.tabs.createV2Position": "Skep V2 posisie", "nav.tabs.createV3Position": "Skep V3 posisie", + "nav.tabs.createV4Position": "Skep V4 posisie", "nav.tabs.viewPosition": "Bekyk posisie", "network.lostConnection": "Jy het dalk jou netwerkverbinding verloor.", "network.mightBeDown": "{{network}} is dalk op die oomblik af, of jy het dalk jou netwerkverbinding verloor.", @@ -1331,6 +1336,7 @@ "pool.back": "Terug na swembad", "pool.balances": "Poolsaldo's", "pool.claimFees": "Eis fooie", + "pool.claimFees.button.label": "Eis", "pool.collectAs": "Versamel as {{nativeWrappedSymbol}}", "pool.collected": " Afgehaal", "pool.collecting": "Versamel", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Bestuur likiditeit in beloningspoel", "pool.max.label": "Maks:", "pool.maxPrice": "Maksimum prys", + "pool.migrateToV4": "Migreer na V4", "pool.min.label": "Min:", "pool.minPrice": "Min prys", "pool.mustBeInitialized": "Hierdie poel moet geïnisialiseer word voordat jy likiditeit kan byvoeg. Om te inisialiseer, kies 'n beginprys vir die swembad. Voer dan jou likiditeitsprysklas en depositobedrag in. Gasfooie sal hoër wees as gewoonlik as gevolg van die inisialiseringstransaksie.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Wenk: Gebruik hierdie hulpmiddel om v2-poele te vind wat nie outomaties in die koppelvlak verskyn nie.", "pools.approving.amount": "Keur tans {{amount}}goed", "position.addHook": "Voeg 'n haak by", + "position.addHook.tooltip": "Hakies is 'n gevorderde kenmerk wat poele in staat stel om met slim kontrakte te kommunikeer, wat verskeie vermoëns ontsluit. Wees versigtig wanneer jy hake byvoeg, aangesien sommige kwaadwillig kan wees of onbedoelde gevolge kan veroorsaak.", "position.appearHere": "Jou posisie sal hier verskyn.", + "position.create.modal.header": "Skep posisie", "position.currentValue": "Huidige posisie waarde", + "position.deposit.description": "Spesifiseer die tekenbedrae vir jou likiditeitsbydrae.", "position.depositedCurrency": "Gedeponeer {{currencySymbol}}", "position.migrate.liquidity": "Wanneer jy posisies migreer, kan jy nie jou tokenpaar verander nie, maar jy kan 'n haak byvoeg om funksionaliteit te verbeter.", "position.new": "Nuwe Posisie", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Koppel tans …", "qrScanner.status.loading": "Laai tans...", "qrScanner.title": "Skandeer 'n QR-kode", - "qrScanner.wallet.title": "Jy kan tokens en NFT's op Ethereum, Polygon, Arbitrum, Optimisme, Base, ZKsync, Zora, Avalanche, Celo, Blast en BNB Chain ontvang.", + "qrScanner.wallet.networks": "Ondersteunde netwerke", + "qrScanner.wallet.title": "Jy kan tokens en NFT's op al ons {{numOfNetworks}} ondersteunde netwerke stuur en ontvang.", "removeLiquidity.collectFees": "Jy sal ook fooie wat uit hierdie posisie verdien word, invorder.", "removeLiquidity.outputEstimated": "Uitset word geskat. As die prys met meer as {{allowed}}% verander, sal jou transaksie terugkeer.", "removeLiquidity.pendingText": "Verwyder tans {{amtA}} {{symA}} en {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Bynaam", "settings.setting.wallet.notifications.title": "Kennisgewings", "settings.setting.wallet.preferences.title": "Wallet-voorkeure", - "settings.showTestNets": "Wys toetsnette", + "settings.setting.wallet.testnetMode.description": "Dit skakel toetsnette aan vir ontwikkelaars om kenmerke en transaksies uit te probeer sonder om werklike bates te gebruik. Tokens op toetsnette hou geen werklike waarde in nie.", + "settings.setting.wallet.testnetMode.title": "Toetsnet-modus", "settings.switchNetwork.warning": "Om Uniswap op {{label}}te gebruik, verander die netwerk in jou beursie se instellings.", "settings.title": "Instellings", "settings.version": "Weergawe {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Nuwe insette", "swap.details.newQuote.output": "Nuwe uitset", "swap.details.orderRouting": "Bestel roetering", - "swap.details.orderRoutingInfo": "Jou prys sluit reeds netwerkkoste op die bestemmingsnetwerk en 'n 0.05% Across fooi in.", + "swap.details.orderRoutingInfo": "Hierdie ruil word gelei via Across, 'n gedesentraliseerde protokol wat bates oor netwerke beweeg terwyl veiligheid, vinnige uitvoering en lae pryse geprioritiseer word.", + "swap.details.poweredBy": "Aangedryf deur", "swap.details.rate": "Koers", "swap.details.slippage": "Maksimum gly", "swap.details.uniswapFee": "Fooi", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Die Uniswap-kliënt kies die goedkoopste handelsopsie wat prys en netwerkkoste faktoriseer.", "swap.settings.routingPreference.option.v2.title": "v2 swembaddens", "swap.settings.routingPreference.option.v3.title": "v3 swembaddens", + "swap.settings.routingPreference.option.v4.title": "v4 swembaddens", "swap.settings.routingPreference.title": "Handel opsies", "swap.settings.slippage.control.auto": "Outo", "swap.settings.slippage.description": "Jou transaksie sal terugdraai as die prys meer as die glippersentasie verander.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Jou transaksie sal terugdraai as die prys ongunstig verander met meer as hierdie persentasie.", "swap.signAndSwap": "Teken en ruil", "swap.slippage.amt": "{{amt}} glip", + "swap.slippage.bridging": "Geen gly wanneer oor netwerke geruil word nie", "swap.slippage.settings.title": "Glip instellings", "swap.slippage.tooltip": "Die maksimum prysbeweging voor jou transaksie sal terugkeer.", "swap.slippageBelow.warning": "Glip onder {{amt}} kan lei tot 'n mislukte transaksie", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Transaksie sperdatum", "swap.transaction.revertAfter": "Jou transaksie sal terugdraai as dit vir langer as hierdie tydperk hangende is.", "swap.unsupportedAssets.readMore": "Lees meer oor nie-ondersteunde bates", + "swap.warning.enterLargerAmount.title": "Voer 'n groter bedrag in", "swap.warning.expectedFailure": "Hierdie transaksie sal na verwagting misluk", "swap.warning.feeOnTransfer.message": "Sommige tokens neem 'n fooi wanneer dit gekoop of verkoop word, wat deur die token-uitreiker bepaal word. Uniswap ontvang geen deel van hierdie fooie nie.", "swap.warning.feeOnTransfer.title": "Hoekom is daar 'n bykomende fooi?", "swap.warning.insufficientBalance.title": "Jy het nie genoeg {{currencySymbol}}nie", "swap.warning.insufficientGas.button": "Nie genoeg {{currencySymbol}}nie", + "swap.warning.insufficientGas.button.bridge": "Ruil vir {{ tokenSymbol }} op {{networkName}}", "swap.warning.insufficientGas.button.buy": "Koop {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Koop met kaart", "swap.warning.insufficientGas.message.withNetwork": "Nie genoeg {{currencySymbol}} op {{networkName}} om te ruil", "swap.warning.insufficientGas.message.withoutNetwork": "Nie genoeg {{currencySymbol}} om te ruil nie", "swap.warning.insufficientGas.title": "Jy het nie genoeg {{currencySymbol}} om die netwerkkoste te dek nie", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "tekendata vir op {{chainName}}", "tdp.nameNotFound": "Naam nie gevind nie", "tdp.noInfoAvailable": "Geen tekeninligting beskikbaar nie", + "tdp.noTestnetSupportDescription": "Sommige toetsnette ondersteun nie ruil, stuur of koop van tekens nie.", "tdp.stats.unsupportedChainDescription": "Tekenstatistieke en kaarte vir {{chain}} is beskikbaar op {{infoLink}}", "tdp.symbolNotFound": "Simbool nie gevind nie", + "testnet.unsupported": "Hierdie funksionaliteit word nie in die toetsnetmodus ondersteun nie.", "themeToggle.theme": "Tema", "title.betterPricesMoreListings": "Beter pryse. Meer inskrywings. Koop, verkoop en verhandel NFT's oor topmarkplekke soos OpenSea. Verken gewilde versamelings.", "title.buySellTradeEthereum": "Koop, verkoop en verhandel Ethereum en ander top-tokens op Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "{{ownerAddress}}se balans", "token.bridge": "{{label}} tekenbrug", "token.chart.tooltip": "Fooie: {{amount}}", + "token.details.testnet.unsupported": "Tokenbesonderhede is nie beskikbaar vir toetsnet-tokens nie.", "token.error.unknown": "Onbekende teken", "token.fee.buy.label": "koop fooi", "token.fee.label": "fooi", @@ -2212,7 +2233,6 @@ "walletConnect.pending.switchAccount": "Wissel rekening", "walletConnect.pending.switchNetwork": "Skakel netwerk", "walletConnect.pending.title": "Koppel aan {{dappName}}", - "walletConnect.permissions.networks": "Netwerke", "walletConnect.permissions.option.transferAssets": "Dra jou bates oor sonder toestemming", "walletConnect.permissions.option.viewTokenBalances": "Kyk na jou tokensaldo's", "walletConnect.permissions.option.viewWalletAddress": "Kyk na jou beursie-adres", diff --git a/packages/uniswap/src/i18n/locales/translations/ar-SA.json b/packages/uniswap/src/i18n/locales/translations/ar-SA.json index 4c1c6287aa5..93718c839ee 100644 --- a/packages/uniswap/src/i18n/locales/translations/ar-SA.json +++ b/packages/uniswap/src/i18n/locales/translations/ar-SA.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "قم بإيداع الرموز المميزة في شبكة {{label}} .", "common.deposited": "المودعة", "common.depositing": "الإيداع", + "common.depositTokens": "إيداع الرموز", "common.detailed.label": "مفصلة", "common.detected": "مُكتَشَف", "common.developers": "المطورين", @@ -430,6 +431,7 @@ "common.migrate.position": "نقل الموقع", "common.migrated.liquidity": "السيولة المهاجرة", "common.migrating.liquidity": "هجرة السيولة", + "common.min": "الحد الأدنى", "common.mint.cancelled": "تم إلغاء النعناع", "common.mint.failed": "فشل النعناع", "common.minted": "سكت", @@ -450,6 +452,7 @@ "common.noResults": "لم يتم العثور على نتائج.", "common.notAvailableInRegion.error": "غير متوفر في منطقتك", "common.notCreated.label": "لم يتم إنشاؤه", + "common.notSupported": "غير مدعوم", "common.oneDay": "يوم 1", "common.oneHour": "1 ساعة", "common.oneMonth": "شهر واحد", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}وخيارات أخرى", "fiatOnRamp.quote.type.other": "خيارات أخرى", "fiatOnRamp.quote.type.recent": "مستخدم حديثا", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "يمكنك الحصول على الرموز المميزة والرموز غير القابلة للاستبدال (NFTs) على Ethereum وPolygon وArbitrum وOptimism وBase وBNB وBlast وAvalanche وZora وZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "من حساب", "fiatOnRamp.receiveCrypto.title": "تلقي التشفير", "fiatOnRamp.receiveCrypto.transferFunds": "قم بتمويل محفظتك عن طريق تحويل العملات المشفرة من محفظة أو حساب آخر", @@ -901,6 +903,8 @@ "home.activity.error.load": "تعذر تحميل النشاط", "home.activity.title": "نشاط", "home.banner.offline": "أنت في وضع غير متصل بالشبكة", + "home.banner.testnetMode": "أنت في وضع الشبكة التجريبية", + "home.banner.testnetMode.nav": "أنت في وضع الشبكة التجريبية. قم بإيقاف تشغيله في الإعدادات.", "home.explore.footer": "انقر هنا لاستكشاف الآلاف من الرموز المميزة والرموز غير القابلة للاستبدال (NFTs) والمزيد", "home.explore.title": "استكشاف الرموز", "home.extension.error": "حدث خطأ أثناء تحميل الحسابات", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "إنشاء موقف", "nav.tabs.createV2Position": "إنشاء موضع V2", "nav.tabs.createV3Position": "إنشاء موضع V3", + "nav.tabs.createV4Position": "إنشاء موضع V4", "nav.tabs.viewPosition": "عرض الموقف", "network.lostConnection": "ربما تكون قد فقدت اتصالك بالشبكة.", "network.mightBeDown": "ربما يكون {{network}} معطلاً الآن، أو ربما فقدت اتصالك بالشبكة.", @@ -1331,6 +1336,7 @@ "pool.back": "العودة إلى بركة", "pool.balances": "أرصدة المجمع", "pool.claimFees": "رسوم المطالبة", + "pool.claimFees.button.label": "مطالبة", "pool.collectAs": "اجمع كـ {{nativeWrappedSymbol}}", "pool.collected": " تم جمعها", "pool.collecting": "جمع", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "إدارة السيولة في مجمع المكافآت", "pool.max.label": "الأعلى:", "pool.maxPrice": "السعر الأقصى", + "pool.migrateToV4": "الانتقال إلى الإصدار 4", "pool.min.label": "الحد الأدنى:", "pool.minPrice": "سعر دقيقة", "pool.mustBeInitialized": "يجب تهيئة هذا التجمع قبل أن تتمكن من إضافة السيولة. للتهيئة، حدد سعر البداية للمجمع. ثم أدخل النطاق السعري للسيولة ومبلغ الإيداع. ستكون رسوم الغاز أعلى من المعتاد بسبب معاملة التهيئة.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "نصيحة: استخدم هذه الأداة للعثور على تجمعات v2 التي لا تظهر تلقائيًا في الواجهة.", "pools.approving.amount": "الموافقة {{amount}}", "position.addHook": "أضف خطافًا", + "position.addHook.tooltip": "تعتبر الخطافات ميزة متقدمة تتيح للمجموعات التفاعل مع العقود الذكية، مما يفتح المجال أمام قدرات مختلفة. توخ الحذر عند إضافة الخطافات، حيث قد تكون بعضها ضارة أو تسبب عواقب غير مقصودة.", "position.appearHere": "سوف يظهر موقفك هنا.", + "position.create.modal.header": "إنشاء موقف", "position.currentValue": "قيمة الموضع الحالي", + "position.deposit.description": "قم بتحديد المبالغ الرمزية لمساهمتك في السيولة.", "position.depositedCurrency": "تم الإيداع {{currencySymbol}}", "position.migrate.liquidity": "عند ترحيل المواضع، لا يمكنك تغيير زوج الرمز الخاص بك، ولكن يمكنك إضافة خطاف لتحسين الوظائف.", "position.new": "منصب جديد", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "توصيل...", "qrScanner.status.loading": "تحميل...", "qrScanner.title": "مسح رمز الاستجابة السريعة", - "qrScanner.wallet.title": "يمكنك الحصول على الرموز المميزة وNFTs على Ethereum وPolygon وArbitrum وOptimism وBase وZKsync وZora وAvalanche وCelo وBlast وBNB Chain.", + "qrScanner.wallet.networks": "الشبكات المدعومة", + "qrScanner.wallet.title": "يمكنك إرسال واستقبال الرموز والرموز غير القابلة للاستبدال على جميع شبكاتنا المدعومة {{numOfNetworks}} .", "removeLiquidity.collectFees": "ستقوم أيضًا بتحصيل الرسوم المكتسبة من هذا المنصب.", "removeLiquidity.outputEstimated": "ويقدر الناتج. إذا تغير السعر بأكثر من {{allowed}}% فسيتم إرجاع معاملتك.", "removeLiquidity.pendingText": "إزالة {{amtA}} {{symA}} و {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "كنية", "settings.setting.wallet.notifications.title": "إشعارات", "settings.setting.wallet.preferences.title": "تفضيلات المحفظة", - "settings.showTestNets": "عرض شبكات الاختبار", + "settings.setting.wallet.testnetMode.description": "يؤدي هذا إلى تشغيل شبكات الاختبار للمطورين لتجربة الميزات والمعاملات دون استخدام أصول حقيقية. لا تحمل الرموز الموجودة على شبكات الاختبار أي قيمة حقيقية.", + "settings.setting.wallet.testnetMode.title": "وضع الشبكة الاختبارية", "settings.switchNetwork.warning": "لاستخدام Uniswap على {{label}}، قم بتبديل الشبكة في إعدادات محفظتك.", "settings.title": "إعدادات", "settings.version": "الإصدار {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "مدخلات جديدة", "swap.details.newQuote.output": "إخراج جديد", "swap.details.orderRouting": "توجيه الطلب", - "swap.details.orderRoutingInfo": "يتضمن السعر الخاص بك بالفعل تكاليف الشبكة على شبكة الوجهة ورسومًا بنسبة 0.05%.", + "swap.details.orderRoutingInfo": "يتم توجيه هذا التبادل عبر Across، وهو بروتوكول لامركزي ينقل الأصول عبر الشبكات مع إعطاء الأولوية للسلامة والتنفيذ السريع والأسعار المنخفضة.", + "swap.details.poweredBy": "مدعوم بواسطة", "swap.details.rate": "معدل", "swap.details.slippage": "أقصى انزلاق", "swap.details.uniswapFee": "مصاريف", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "يقوم عميل Uniswap باختيار أرخص سعر لخيارات التداول وتكاليف الشبكة.", "swap.settings.routingPreference.option.v2.title": "حمامات v2", "swap.settings.routingPreference.option.v3.title": "حمامات v3", + "swap.settings.routingPreference.option.v4.title": "حمامات سباحة v4", "swap.settings.routingPreference.title": "خيارات التجارة", "swap.settings.slippage.control.auto": "آلي", "swap.settings.slippage.description": "سيتم إرجاع معاملتك إذا تغير السعر بأكثر من نسبة الانزلاق.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "سيتم إرجاع معاملتك إذا تغير السعر بشكل غير مناسب بأكثر من هذه النسبة.", "swap.signAndSwap": "التوقيع والمبادلة", "swap.slippage.amt": "{{amt}} الإنزلاق", + "swap.slippage.bridging": "لا يوجد انزلاق عند التبديل عبر الشبكات", "swap.slippage.settings.title": "إعدادات الانزلاق", "swap.slippage.tooltip": "سيتم إرجاع الحد الأقصى لحركة السعر قبل معاملتك.", "swap.slippageBelow.warning": "قد يؤدي الانزلاق تحت {{amt}} إلى فشل الصفقة", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "الموعد النهائي للمعاملة", "swap.transaction.revertAfter": "سيتم إرجاع معاملتك إذا كانت معلقة لأكثر من هذه الفترة الزمنية.", "swap.unsupportedAssets.readMore": "اقرأ المزيد عن الأصول غير المدعومة", + "swap.warning.enterLargerAmount.title": "أدخل مبلغًا أكبر", "swap.warning.expectedFailure": "من المتوقع أن تفشل هذه المعاملة", "swap.warning.feeOnTransfer.message": "تأخذ بعض الرموز المميزة رسومًا عند شرائها أو بيعها، والتي يحددها مصدر الرمز المميز. لا يحصل Uniswap على أي حصة من هذه الرسوم.", "swap.warning.feeOnTransfer.title": "لماذا هناك رسوم إضافية؟", "swap.warning.insufficientBalance.title": "ليس لديك ما يكفي {{currencySymbol}}", "swap.warning.insufficientGas.button": "لا يكفي {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "قم بالتبديل إلى {{ tokenSymbol }} على {{networkName}}", "swap.warning.insufficientGas.button.buy": "شراء {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "شراء بالبطاقة", "swap.warning.insufficientGas.message.withNetwork": "لا يكفي {{currencySymbol}} على {{networkName}} للتبديل", "swap.warning.insufficientGas.message.withoutNetwork": "لا يكفي {{currencySymbol}} للتبديل", "swap.warning.insufficientGas.title": "ليس لديك ما يكفي {{currencySymbol}} لتغطية تكلفة الشبكة", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "بيانات الرمز المميز لـ على {{chainName}}", "tdp.nameNotFound": "لم يتم العثور على الاسم", "tdp.noInfoAvailable": "لا توجد معلومات رمزية متاحة", + "tdp.noTestnetSupportDescription": "بعض شبكات الاختبار لا تدعم تبادل الرموز أو إرسالها أو شرائها.", "tdp.stats.unsupportedChainDescription": "إحصائيات ومخططات الرمز المميز لـ {{chain}} متاحة على {{infoLink}}", "tdp.symbolNotFound": "لم يتم العثور على الرمز", + "testnet.unsupported": "لا يتم دعم هذه الوظيفة في وضع الشبكة التجريبية.", "themeToggle.theme": "سمة", "title.betterPricesMoreListings": "افضل اسعار. المزيد من القوائم. قم بشراء وبيع وتداول NFTs عبر أفضل الأسواق مثل OpenSea. استكشاف مجموعات تتجه.", "title.buySellTradeEthereum": "قم بشراء وبيع وتداول Ethereum وغيرها من الرموز المميزة على Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "{{ownerAddress}}رصيد", "token.bridge": "{{label}} جسر رمزي", "token.chart.tooltip": "الرسوم: {{amount}}", + "token.details.testnet.unsupported": "تفاصيل الرمز غير متوفرة لرموز الشبكة التجريبية.", "token.error.unknown": "رمز غير معروف", "token.fee.buy.label": "رسوم الشراء", "token.fee.label": "مصاريف", @@ -2212,7 +2233,6 @@ "walletConnect.pending.switchAccount": "تبديل الحساب", "walletConnect.pending.switchNetwork": "تبديل الشبكة", "walletConnect.pending.title": "اتصل بـ {{dappName}}", - "walletConnect.permissions.networks": "الشبكات", "walletConnect.permissions.option.transferAssets": "نقل الأصول الخاصة بك دون موافقة", "walletConnect.permissions.option.viewTokenBalances": "عرض أرصدة الرمز المميز الخاص بك", "walletConnect.permissions.option.viewWalletAddress": "عرض عنوان محفظتك", diff --git a/packages/uniswap/src/i18n/locales/translations/ca-ES.json b/packages/uniswap/src/i18n/locales/translations/ca-ES.json index f17c0b25eaf..d7a5148e229 100644 --- a/packages/uniswap/src/i18n/locales/translations/ca-ES.json +++ b/packages/uniswap/src/i18n/locales/translations/ca-ES.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Diposita fitxes a la xarxa {{label}} .", "common.deposited": "Dipositat", "common.depositing": "Dipositant", + "common.depositTokens": "Dipòsit de fitxes", "common.detailed.label": "Detallada", "common.detected": "Detectat", "common.developers": "Desenvolupadors", @@ -430,6 +431,7 @@ "common.migrate.position": "Migrar posició", "common.migrated.liquidity": "Liquiditat migrada", "common.migrating.liquidity": "Liquiditat migratòria", + "common.min": "Min", "common.mint.cancelled": "Menta cancel·lada", "common.mint.failed": "Mint ha fallat", "common.minted": "Encunyat", @@ -450,6 +452,7 @@ "common.noResults": "Sense resultats.", "common.notAvailableInRegion.error": "No disponible a la teva regió", "common.notCreated.label": "No creat", + "common.notSupported": "No s'admet", "common.oneDay": "1 dia", "common.oneHour": "1 hora", "common.oneMonth": "1 mes", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}, i altres opcions", "fiatOnRamp.quote.type.other": "Altres opcions", "fiatOnRamp.quote.type.recent": "Usat recentment", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Podeu rebre fitxes i NFT a Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora i ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Des d'un compte", "fiatOnRamp.receiveCrypto.title": "Rebre cripto", "fiatOnRamp.receiveCrypto.transferFunds": "Finança la teva cartera transferint criptografia des d'una altra cartera o compte", @@ -901,6 +903,8 @@ "home.activity.error.load": "No s'ha pogut carregar l'activitat", "home.activity.title": "Activitat", "home.banner.offline": "Esteu en mode fora de línia", + "home.banner.testnetMode": "Esteu en mode testnet", + "home.banner.testnetMode.nav": "Esteu en mode testnet. Desactiva aquesta opció a la configuració.", "home.explore.footer": "Toca aquí per explorar milers de fitxes, NFT i molt més", "home.explore.title": "Explora fitxes", "home.extension.error": "S'ha produït un error en carregar els comptes", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Crear posició", "nav.tabs.createV2Position": "Crea la posició V2", "nav.tabs.createV3Position": "Crea una posició V3", + "nav.tabs.createV4Position": "Crea una posició V4", "nav.tabs.viewPosition": "Visualització de la posició", "network.lostConnection": "És possible que hàgiu perdut la connexió a la xarxa.", "network.mightBeDown": "És possible que {{network}} estigui inactiva en aquest moment o que hagis perdut la connexió de xarxa.", @@ -1331,6 +1336,7 @@ "pool.back": "Tornar a la piscina", "pool.balances": "Balanços de piscina", "pool.claimFees": "Reclamació de taxes", + "pool.claimFees.button.label": "Reclamació", "pool.collectAs": "Recolliu com a {{nativeWrappedSymbol}}", "pool.collected": " Recollida", "pool.collecting": "Col·leccionant", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Gestioneu la liquiditat del grup de recompenses", "pool.max.label": "Màx:", "pool.maxPrice": "Preu màxim", + "pool.migrateToV4": "Migrar a V4", "pool.min.label": "Min:", "pool.minPrice": "Preu mínim", "pool.mustBeInitialized": "Aquest grup s'ha d'inicialitzar abans de poder afegir liquiditat. Per inicialitzar, seleccioneu un preu inicial per a la piscina. A continuació, introduïu el vostre rang de preus de liquiditat i l'import del dipòsit. Les tarifes del gas seran més altes de l'habitual a causa de la transacció d'inicialització.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Consell: utilitzeu aquesta eina per trobar grups v2 que no apareixen automàticament a la interfície.", "pools.approving.amount": "S'està aprovant {{amount}}", "position.addHook": "Afegiu un ganxo", + "position.addHook.tooltip": "Els ganxos són una funció avançada que permet als grups interactuar amb contractes intel·ligents, desbloquejant diverses capacitats. Aneu amb compte quan afegiu ganxos, ja que alguns poden ser maliciosos o causar conseqüències no desitjades.", "position.appearHere": "La teva posició apareixerà aquí.", + "position.create.modal.header": "Creació de posició", "position.currentValue": "Valor de la posició actual", + "position.deposit.description": "Especifiqueu les quantitats testimonials per a la vostra aportació de liquiditat.", "position.depositedCurrency": "Dipositat {{currencySymbol}}", "position.migrate.liquidity": "Quan migreu posicions, no podeu canviar el vostre parell de fitxes, però podeu afegir un ganxo per millorar la funcionalitat.", "position.new": "Nova Posició", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Connectant...", "qrScanner.status.loading": "Carregant...", "qrScanner.title": "Escaneja un codi QR", - "qrScanner.wallet.title": "Podeu rebre fitxes i NFT a Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast i BNB Chain.", + "qrScanner.wallet.networks": "Xarxes suportades", + "qrScanner.wallet.title": "Podeu enviar i rebre fitxes i NFT a totes les nostres xarxes compatibles amb {{numOfNetworks}} .", "removeLiquidity.collectFees": "També cobraràs les comissions obtingudes amb aquesta posició.", "removeLiquidity.outputEstimated": "S'estima la sortida. Si el preu canvia en més del {{allowed}}%, la transacció es revertirà.", "removeLiquidity.pendingText": "S'està eliminant {{amtA}} {{symA}} i {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Pseudònim", "settings.setting.wallet.notifications.title": "Notificacions", "settings.setting.wallet.preferences.title": "Preferències de cartera", - "settings.showTestNets": "Mostra les xarxes de prova", + "settings.setting.wallet.testnetMode.description": "Això activa les xarxes de prova perquè els desenvolupadors puguin provar funcions i transaccions sense utilitzar actius reals. Els testimonis de les xarxes de prova no tenen cap valor real.", + "settings.setting.wallet.testnetMode.title": "Mode Testnet", "settings.switchNetwork.warning": "Per utilitzar Uniswap a {{label}}, canvieu la xarxa a la configuració de la cartera.", "settings.title": "Configuració", "settings.version": "Versió {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Nova entrada", "swap.details.newQuote.output": "Sortida nova", "swap.details.orderRouting": "Encaminament de comandes", - "swap.details.orderRoutingInfo": "El vostre preu ja inclou els costos de xarxa a la xarxa de destinació i una tarifa del 0,05%.", + "swap.details.orderRoutingInfo": "Aquest intercanvi s'encamina mitjançant Across, un protocol descentralitzat que mou els actius a través de les xarxes alhora que prioritza la seguretat, l'execució ràpida i els preus baixos.", + "swap.details.poweredBy": "Desenvolupat per", "swap.details.rate": "Taxa", "swap.details.slippage": "Lliscament màxim", "swap.details.uniswapFee": "Quota", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "El client d'Uniswap selecciona l'opció comercial més barata tenint en compte el preu i els costos de xarxa.", "swap.settings.routingPreference.option.v2.title": "piscines v2", "swap.settings.routingPreference.option.v3.title": "piscines v3", + "swap.settings.routingPreference.option.v4.title": "piscines v4", "swap.settings.routingPreference.title": "Opcions comercials", "swap.settings.slippage.control.auto": "Automàtic", "swap.settings.slippage.description": "La vostra transacció es revertirà si el preu canvia més que el percentatge de lliscament.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "La vostra transacció es revertirà si el preu canvia desfavorablement en més d'aquest percentatge.", "swap.signAndSwap": "Signar i intercanviar", "swap.slippage.amt": "{{amt}} lliscament", + "swap.slippage.bridging": "Cap lliscament quan es canvia entre xarxes", "swap.slippage.settings.title": "Configuració de lliscament", "swap.slippage.tooltip": "El moviment màxim del preu abans de la transacció es revertirà.", "swap.slippageBelow.warning": "El lliscament per sota de {{amt}} pot donar lloc a una transacció fallida", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Termini de transacció", "swap.transaction.revertAfter": "La transacció es revertirà si està pendent durant més d'aquest període de temps.", "swap.unsupportedAssets.readMore": "Obteniu més informació sobre els recursos no compatibles", + "swap.warning.enterLargerAmount.title": "Introduïu una quantitat més gran", "swap.warning.expectedFailure": "S'espera que aquesta transacció fracassi", "swap.warning.feeOnTransfer.message": "Algunes fitxes cobren una tarifa quan es compren o venen, que l'estableix l'emissor del token. Uniswap no rep cap part d'aquestes tarifes.", "swap.warning.feeOnTransfer.title": "Per què hi ha una tarifa addicional?", "swap.warning.insufficientBalance.title": "No en tens prou {{currencySymbol}}", "swap.warning.insufficientGas.button": "No n'hi ha prou {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Canvia per {{ tokenSymbol }} a {{networkName}}", "swap.warning.insufficientGas.button.buy": "Compra {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Compra amb targeta", "swap.warning.insufficientGas.message.withNetwork": "No hi ha prou {{currencySymbol}} a {{networkName}} per canviar", "swap.warning.insufficientGas.message.withoutNetwork": "No hi ha prou {{currencySymbol}} per intercanviar", "swap.warning.insufficientGas.title": "No teniu prou {{currencySymbol}} per cobrir el cost de la xarxa", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "dades de testimoni per a el {{chainName}}", "tdp.nameNotFound": "No s'ha trobat el nom", "tdp.noInfoAvailable": "No hi ha informació de testimoni disponible", + "tdp.noTestnetSupportDescription": "Algunes xarxes de prova no admeten l'intercanvi, l'enviament o la compra de fitxes.", "tdp.stats.unsupportedChainDescription": "Les estadístiques i els gràfics de testimonis per a {{chain}} estan disponibles a {{infoLink}}", "tdp.symbolNotFound": "No s'ha trobat el símbol", + "testnet.unsupported": "Aquesta funcionalitat no és compatible amb el mode Testnet.", "themeToggle.theme": "Tema", "title.betterPricesMoreListings": "Millors preus. Més llistats. Compreu, veneu i comercialitzeu NFT als principals mercats com OpenSea. Exploreu les col·leccions de tendències.", "title.buySellTradeEthereum": "Compreu, veneu i comercialitzeu Ethereum i altres fitxes principals a Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "El saldo de {{ownerAddress}}", "token.bridge": "{{label}} pont de fitxes", "token.chart.tooltip": "Tarifes: {{amount}}", + "token.details.testnet.unsupported": "Els detalls del testimoni no estan disponibles per als testimonis de la xarxa de prova.", "token.error.unknown": "Fitxa desconeguda", "token.fee.buy.label": "quota de compra", "token.fee.label": "quota", @@ -2212,7 +2233,6 @@ "walletConnect.pending.switchAccount": "Canvia de compte", "walletConnect.pending.switchNetwork": "Canvia de xarxa", "walletConnect.pending.title": "Connecta't a {{dappName}}", - "walletConnect.permissions.networks": "Xarxes", "walletConnect.permissions.option.transferAssets": "Transfereix els teus actius sense consentiment", "walletConnect.permissions.option.viewTokenBalances": "Consulteu els vostres saldos de testimonis", "walletConnect.permissions.option.viewWalletAddress": "Consulta l'adreça de la teva cartera", diff --git a/packages/uniswap/src/i18n/locales/translations/cs-CZ.json b/packages/uniswap/src/i18n/locales/translations/cs-CZ.json index 759e4ccc174..9bb0add2d52 100644 --- a/packages/uniswap/src/i18n/locales/translations/cs-CZ.json +++ b/packages/uniswap/src/i18n/locales/translations/cs-CZ.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Vložte tokeny do sítě {{label}} .", "common.deposited": "Uloženo", "common.depositing": "Ukládání", + "common.depositTokens": "Vkladové tokeny", "common.detailed.label": "Detailní", "common.detected": "Zjištěno", "common.developers": "Vývojáři", @@ -430,6 +431,7 @@ "common.migrate.position": "Migrovat pozici", "common.migrated.liquidity": "Migrovaná likvidita", "common.migrating.liquidity": "Migrace likvidity", + "common.min": "Min", "common.mint.cancelled": "Mincovna zrušena", "common.mint.failed": "Mincovna selhala", "common.minted": "Ražené", @@ -450,6 +452,7 @@ "common.noResults": "Nebyly nalezeny žádné výsledky.", "common.notAvailableInRegion.error": "Není k dispozici ve vaší oblasti", "common.notCreated.label": "Nevytvořeno", + "common.notSupported": "Není podporováno", "common.oneDay": "1 den", "common.oneHour": "1 hodina", "common.oneMonth": "1 měsíc", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}a další možnosti", "fiatOnRamp.quote.type.other": "Jiné možnosti", "fiatOnRamp.quote.type.recent": "Nedávno použité", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Tokeny a NFT můžete přijímat na Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora a ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Z účtu", "fiatOnRamp.receiveCrypto.title": "Přijímat kryptoměny", "fiatOnRamp.receiveCrypto.transferFunds": "Financujte svou peněženku převodem kryptoměn z jiné peněženky nebo účtu", @@ -901,6 +903,8 @@ "home.activity.error.load": "Aktivitu se nepodařilo načíst", "home.activity.title": "Aktivita", "home.banner.offline": "Jste v režimu offline", + "home.banner.testnetMode": "Jste v režimu testovací sítě", + "home.banner.testnetMode.nav": "Jste v režimu testovací sítě. Vypněte to v nastavení.", "home.explore.footer": "Klepnutím sem prozkoumáte tisíce tokenů, NFT a další", "home.explore.title": "Prozkoumejte tokeny", "home.extension.error": "Chyba při načítání účtů", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Vytvořte pozici", "nav.tabs.createV2Position": "Vytvořte pozici V2", "nav.tabs.createV3Position": "Vytvořte pozici V3", + "nav.tabs.createV4Position": "Vytvořte pozici V4", "nav.tabs.viewPosition": "Zobrazit pozici", "network.lostConnection": "Možná jste ztratili připojení k síti.", "network.mightBeDown": "{{network}} může být právě teď mimo provoz nebo jste možná ztratili připojení k síti.", @@ -1331,6 +1336,7 @@ "pool.back": "Zpět k bazénu", "pool.balances": "Bazénové zůstatky", "pool.claimFees": "Poplatky za reklamaci", + "pool.claimFees.button.label": "Tvrzení", "pool.collectAs": "Sbírejte jako {{nativeWrappedSymbol}}", "pool.collected": " Shromážděno", "pool.collecting": "Sbírání", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Spravujte likviditu ve fondu odměn", "pool.max.label": "Max:", "pool.maxPrice": "Max cena", + "pool.migrateToV4": "Migrujte na V4", "pool.min.label": "min:", "pool.minPrice": "Cena min", "pool.mustBeInitialized": "Tento fond musí být inicializován, než budete moci přidat likviditu. Pro inicializaci vyberte počáteční cenu fondu. Poté zadejte své cenové rozpětí likvidity a částku vkladu. Poplatky za plyn budou kvůli inicializační transakci vyšší než obvykle.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Tip: Pomocí tohoto nástroje můžete najít fondy verze 2, které se automaticky nezobrazují v rozhraní.", "pools.approving.amount": "Schvalování {{amount}}", "position.addHook": "Přidejte háček", + "position.addHook.tooltip": "Háky jsou pokročilou funkcí, která umožňuje fondům komunikovat s chytrými smlouvami a odemykat různé možnosti. Při přidávání háčků buďte opatrní, protože některé mohou být škodlivé nebo způsobit nezamýšlené následky.", "position.appearHere": "Zde se zobrazí vaše pozice.", + "position.create.modal.header": "Vytváření pozice", "position.currentValue": "Hodnota aktuální pozice", + "position.deposit.description": "Uveďte symbolické částky pro váš příspěvek likvidity.", "position.depositedCurrency": "Vloženo {{currencySymbol}}", "position.migrate.liquidity": "Při migraci pozic nemůžete změnit svůj pár tokenů, ale můžete přidat háček pro vylepšení funkčnosti.", "position.new": "Nová pozice", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Spojovací...", "qrScanner.status.loading": "Načítání...", "qrScanner.title": "Naskenujte QR kód", - "qrScanner.wallet.title": "Můžete přijímat tokeny a NFT na Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast a BNB Chain.", + "qrScanner.wallet.networks": "Podporované sítě", + "qrScanner.wallet.title": "Můžete odesílat a přijímat tokeny a NFT ve všech našich {{numOfNetworks}} podporovaných sítích.", "removeLiquidity.collectFees": "Budete také vybírat poplatky získané z této pozice.", "removeLiquidity.outputEstimated": "Výkon se odhaduje. Pokud se cena změní o více než {{allowed}}% vaše transakce se vrátí zpět.", "removeLiquidity.pendingText": "Odebírání {{amtA}} {{symA}} a {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Přezdívka", "settings.setting.wallet.notifications.title": "Oznámení", "settings.setting.wallet.preferences.title": "Předvolby peněženky", - "settings.showTestNets": "Zobrazit testovací sítě", + "settings.setting.wallet.testnetMode.description": "To zapíná testovací sítě pro vývojáře, aby mohli vyzkoušet funkce a transakce bez použití skutečných aktiv. Tokeny na testovacích sítích nemají žádnou skutečnou hodnotu.", + "settings.setting.wallet.testnetMode.title": "Režim Testnet", "settings.switchNetwork.warning": "Chcete-li používat Uniswap na {{label}}, přepněte síť v nastavení peněženky.", "settings.title": "Nastavení", "settings.version": "Verze {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Nový vstup", "swap.details.newQuote.output": "Nový výstup", "swap.details.orderRouting": "Směrování objednávky", - "swap.details.orderRoutingInfo": "Vaše cena již zahrnuje síťové náklady v cílové síti a poplatek 0,05 % Across.", + "swap.details.orderRoutingInfo": "Tento swap je směrován přes Across, decentralizovaný protokol, který přesouvá aktiva napříč sítěmi a přitom upřednostňuje bezpečnost, rychlé provedení a nízké ceny.", + "swap.details.poweredBy": "Provozováno na", "swap.details.rate": "Hodnotit", "swap.details.slippage": "Maximální prokluz", "swap.details.uniswapFee": "Poplatek", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Klient Uniswap vybere nejlevnější faktoringovou cenu obchodní opce a síťové náklady.", "swap.settings.routingPreference.option.v2.title": "v2 bazény", "swap.settings.routingPreference.option.v3.title": "v3 bazény", + "swap.settings.routingPreference.option.v4.title": "v4 bazény", "swap.settings.routingPreference.title": "Obchodní opce", "swap.settings.slippage.control.auto": "Auto", "swap.settings.slippage.description": "Vaše transakce se vrátí, pokud se cena změní více než procento skluzu.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Vaše transakce se vrátí zpět, pokud se cena nepříznivě změní o více než toto procento.", "swap.signAndSwap": "Podepište a vyměňte", "swap.slippage.amt": "{{amt}} skluz", + "swap.slippage.bridging": "Žádné prokluzování při výměně mezi sítěmi", "swap.slippage.settings.title": "Nastavení skluzu", "swap.slippage.tooltip": "Maximální pohyb ceny před vaší transakcí se vrátí.", "swap.slippageBelow.warning": "Skluz pod {{amt}} může vést k neúspěšné transakci", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Termín transakce", "swap.transaction.revertAfter": "Vaše transakce se vrátí zpět, pokud čeká na vyřízení déle než toto časové období.", "swap.unsupportedAssets.readMore": "Přečtěte si další informace o nepodporovaných aktivech", + "swap.warning.enterLargerAmount.title": "Zadejte větší částku", "swap.warning.expectedFailure": "Očekává se, že tato transakce selže", "swap.warning.feeOnTransfer.message": "Některé tokeny si při nákupu nebo prodeji účtují poplatek, který nastavuje vydavatel tokenu. Uniswap nedostává žádný podíl z těchto poplatků.", "swap.warning.feeOnTransfer.title": "Proč se platí další poplatek?", "swap.warning.insufficientBalance.title": "Nemáte dost {{currencySymbol}}", "swap.warning.insufficientGas.button": "Nestačí {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Vyměňte za {{ tokenSymbol }} na {{networkName}}", "swap.warning.insufficientGas.button.buy": "Koupit {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Koupit kartou", "swap.warning.insufficientGas.message.withNetwork": "Nedostatek {{currencySymbol}} na {{networkName}} k výměně", "swap.warning.insufficientGas.message.withoutNetwork": "Na výměnu nestačí {{currencySymbol}}", "swap.warning.insufficientGas.title": "Nemáte dostatek {{currencySymbol}} na pokrytí nákladů na síť", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "data tokenu pro na {{chainName}}", "tdp.nameNotFound": "Jméno nenalezeno", "tdp.noInfoAvailable": "Nejsou k dispozici žádné informace o tokenu", + "tdp.noTestnetSupportDescription": "Některé testovací sítě nepodporují výměnu, odesílání nebo nákup tokenů.", "tdp.stats.unsupportedChainDescription": "Statistiky tokenů a grafy pro {{chain}} jsou k dispozici na {{infoLink}}", "tdp.symbolNotFound": "Symbol nenalezen", + "testnet.unsupported": "Tato funkce není podporována v režimu testovací sítě.", "themeToggle.theme": "Téma", "title.betterPricesMoreListings": "Lepší ceny. Další výpisy. Nakupujte, prodávejte a obchodujte s NFT na špičkových tržištích, jako je OpenSea. Prozkoumejte trendy kolekce.", "title.buySellTradeEthereum": "Nakupujte, prodávejte a obchodujte s Ethereem a dalšími špičkovými tokeny na Uniswapu", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "Zůstatek {{ownerAddress}}", "token.bridge": "{{label}} token bridge", "token.chart.tooltip": "Poplatky: {{amount}}", + "token.details.testnet.unsupported": "Podrobnosti o tokenu nejsou pro tokeny testnet k dispozici.", "token.error.unknown": "Neznámý token", "token.fee.buy.label": "koupit poplatek", "token.fee.label": "poplatek", @@ -2212,7 +2233,6 @@ "walletConnect.pending.switchAccount": "Přepnout účet", "walletConnect.pending.switchNetwork": "Přepnout síť", "walletConnect.pending.title": "Připojte se k {{dappName}}", - "walletConnect.permissions.networks": "sítě", "walletConnect.permissions.option.transferAssets": "Převeďte svá aktiva bez souhlasu", "walletConnect.permissions.option.viewTokenBalances": "Prohlédněte si zůstatky tokenů", "walletConnect.permissions.option.viewWalletAddress": "Zobrazit adresu peněženky", diff --git a/packages/uniswap/src/i18n/locales/translations/da-DK.json b/packages/uniswap/src/i18n/locales/translations/da-DK.json index 7788ed083d1..761ae1b9b74 100644 --- a/packages/uniswap/src/i18n/locales/translations/da-DK.json +++ b/packages/uniswap/src/i18n/locales/translations/da-DK.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Indbetal tokens til {{label}} netværket.", "common.deposited": "Deponeret", "common.depositing": "Indbetaling", + "common.depositTokens": "Indbetalingspoletter", "common.detailed.label": "Detaljeret", "common.detected": "Opdaget", "common.developers": "Udviklere", @@ -430,6 +431,7 @@ "common.migrate.position": "Migrer stilling", "common.migrated.liquidity": "Migreret likviditet", "common.migrating.liquidity": "Migrering af likviditet", + "common.min": "Min", "common.mint.cancelled": "Mint annulleret", "common.mint.failed": "Mint mislykkedes", "common.minted": "Udmøntet", @@ -450,6 +452,7 @@ "common.noResults": "Ingen resultater fundet.", "common.notAvailableInRegion.error": "Ikke tilgængelig i dit område", "common.notCreated.label": "Ikke oprettet", + "common.notSupported": "Ikke understøttet", "common.oneDay": "1 dag", "common.oneHour": "1 time", "common.oneMonth": "1 måned", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}og andre muligheder", "fiatOnRamp.quote.type.other": "Andre muligheder", "fiatOnRamp.quote.type.recent": "Nyligt brugt", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Du kan modtage tokens og NFT'er på Ethereum, Polygon, Arbitrum, Optimisme, Base, BNB, Blast, Avalanche, Zora og ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Fra en konto", "fiatOnRamp.receiveCrypto.title": "Modtag krypto", "fiatOnRamp.receiveCrypto.transferFunds": "Finansier din tegnebog ved at overføre krypto fra en anden tegnebog eller konto", @@ -901,6 +903,8 @@ "home.activity.error.load": "Aktiviteten kunne ikke indlæses", "home.activity.title": "Aktivitet", "home.banner.offline": "Du er i offlinetilstand", + "home.banner.testnetMode": "Du er i testnet-tilstand", + "home.banner.testnetMode.nav": "Du er i testnet-tilstand. Slå dette fra i indstillingerne.", "home.explore.footer": "Tryk her for at udforske tusindvis af tokens, NFT'er og mere", "home.explore.title": "Udforsk tokens", "home.extension.error": "Fejl ved indlæsning af konti", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Opret stilling", "nav.tabs.createV2Position": "Opret V2-position", "nav.tabs.createV3Position": "Opret V3-position", + "nav.tabs.createV4Position": "Opret V4-position", "nav.tabs.viewPosition": "Se position", "network.lostConnection": "Du har muligvis mistet din netværksforbindelse.", "network.mightBeDown": "{{network}} er muligvis nede lige nu, eller du kan have mistet din netværksforbindelse.", @@ -1331,6 +1336,7 @@ "pool.back": "Tilbage til Pool", "pool.balances": "Pool saldi", "pool.claimFees": "Krav gebyrer", + "pool.claimFees.button.label": "Påstand", "pool.collectAs": "Saml som {{nativeWrappedSymbol}}", "pool.collected": " Indsamlet", "pool.collecting": "Indsamling", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Administrer likviditet i belønningspuljen", "pool.max.label": "Maks:", "pool.maxPrice": "Max pris", + "pool.migrateToV4": "Migrer til V4", "pool.min.label": "Min.:", "pool.minPrice": "Min pris", "pool.mustBeInitialized": "Denne pulje skal initialiseres, før du kan tilføje likviditet. For at initialisere skal du vælge en startpris for puljen. Indtast derefter dit likviditetsprisinterval og indbetalingsbeløb. Gasgebyrer vil være højere end normalt på grund af initialiseringstransaktionen.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Tip: Brug dette værktøj til at finde v2-puljer, der ikke automatisk vises i grænsefladen.", "pools.approving.amount": "Godkender {{amount}}", "position.addHook": "Tilføj en krog", + "position.addHook.tooltip": "Hooks er en avanceret funktion, der gør det muligt for pools at interagere med smarte kontrakter, der låser op for forskellige muligheder. Vær forsigtig, når du tilføjer kroge, da nogle kan være ondsindede eller forårsage utilsigtede konsekvenser.", "position.appearHere": "Din position vises her.", + "position.create.modal.header": "Oprettelse af position", "position.currentValue": "Nuværende positionsværdi", + "position.deposit.description": "Angiv tokenbeløbene for dit likviditetsbidrag.", "position.depositedCurrency": "Deponeret {{currencySymbol}}", "position.migrate.liquidity": "Når du migrerer positioner, kan du ikke ændre dit token-par, men du kan tilføje en hook for at forbedre funktionaliteten.", "position.new": "Ny stilling", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Tilslutning...", "qrScanner.status.loading": "Indlæser...", "qrScanner.title": "Scan en QR-kode", - "qrScanner.wallet.title": "Du kan modtage tokens og NFT'er på Ethereum, Polygon, Arbitrum, Optimisme, Base, ZKsync, Zora, Avalanche, Celo, Blast og BNB Chain.", + "qrScanner.wallet.networks": "Understøttede netværk", + "qrScanner.wallet.title": "Du kan sende og modtage tokens og NFT'er på alle vores {{numOfNetworks}} understøttede netværk.", "removeLiquidity.collectFees": "Du vil også opkræve gebyrer optjent fra denne stilling.", "removeLiquidity.outputEstimated": "Output er estimeret. Hvis prisen ændres med mere end {{allowed}}% vil din transaktion vende tilbage.", "removeLiquidity.pendingText": "Fjerner {{amtA}} {{symA}} og {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Kaldenavn", "settings.setting.wallet.notifications.title": "Meddelelser", "settings.setting.wallet.preferences.title": "Tegnebogspræferencer", - "settings.showTestNets": "Vis testnet", + "settings.setting.wallet.testnetMode.description": "Dette aktiverer testnet, så udviklere kan prøve funktioner og transaktioner uden at bruge rigtige aktiver. Tokens på testnet har ikke nogen reel værdi.", + "settings.setting.wallet.testnetMode.title": "Testnet-tilstand", "settings.switchNetwork.warning": "For at bruge Uniswap på {{label}}skal du skifte netværk i din tegnebogs indstillinger.", "settings.title": "Indstillinger", "settings.version": "Version {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Nyt input", "swap.details.newQuote.output": "Nyt output", "swap.details.orderRouting": "Ordredirigering", - "swap.details.orderRoutingInfo": "Din pris inkluderer allerede netværksomkostninger på destinationsnetværket og et 0,05 % tværgående gebyr.", + "swap.details.orderRoutingInfo": "Denne swap rutes via Across, en decentral protokol, der flytter aktiver på tværs af netværk, mens sikkerhed, hurtig eksekvering og lave priser prioriteres.", + "swap.details.poweredBy": "Drevet af", "swap.details.rate": "Sats", "swap.details.slippage": "Max glidning", "swap.details.uniswapFee": "Betaling", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Uniswap-klienten vælger den billigste handelsoption med hensyn til pris og netværksomkostninger.", "swap.settings.routingPreference.option.v2.title": "v2 puljer", "swap.settings.routingPreference.option.v3.title": "v3 puljer", + "swap.settings.routingPreference.option.v4.title": "v4 puljer", "swap.settings.routingPreference.title": "Handelsmuligheder", "swap.settings.slippage.control.auto": "Auto", "swap.settings.slippage.description": "Din transaktion går tilbage, hvis prisen ændrer sig mere end slipprocenten.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Din transaktion vil vende tilbage, hvis prisen ændres ugunstigt med mere end denne procentdel.", "swap.signAndSwap": "Signer og byt", "swap.slippage.amt": "{{amt}} glidning", + "swap.slippage.bridging": "Ingen glidning ved udveksling på tværs af netværk", "swap.slippage.settings.title": "Indstillinger for glidning", "swap.slippage.tooltip": "Den maksimale prisbevægelse før din transaktion vil vende tilbage.", "swap.slippageBelow.warning": "Skridning under {{amt}} kan resultere i en mislykket transaktion", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Transaktionsfrist", "swap.transaction.revertAfter": "Din transaktion vil vende tilbage, hvis den er afventende i mere end denne periode.", "swap.unsupportedAssets.readMore": "Læs mere om ikke-understøttede aktiver", + "swap.warning.enterLargerAmount.title": "Indtast et større beløb", "swap.warning.expectedFailure": "Denne transaktion forventes at mislykkes", "swap.warning.feeOnTransfer.message": "Nogle tokens tager et gebyr, når de købes eller sælges, som fastsættes af tokenudstederen. Uniswap modtager ingen andel af disse gebyrer.", "swap.warning.feeOnTransfer.title": "Hvorfor er der et ekstra gebyr?", "swap.warning.insufficientBalance.title": "Du har ikke nok {{currencySymbol}}", "swap.warning.insufficientGas.button": "Ikke nok {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Byt til {{ tokenSymbol }} på {{networkName}}", "swap.warning.insufficientGas.button.buy": "Køb {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Køb med kort", "swap.warning.insufficientGas.message.withNetwork": "Ikke nok {{currencySymbol}} på {{networkName}} til at bytte", "swap.warning.insufficientGas.message.withoutNetwork": "Ikke nok {{currencySymbol}} til at bytte", "swap.warning.insufficientGas.title": "Du har ikke nok {{currencySymbol}} til at dække netværksomkostningerne", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "tokendata for på {{chainName}}", "tdp.nameNotFound": "Navn blev ikke fundet", "tdp.noInfoAvailable": "Ingen token-information tilgængelig", + "tdp.noTestnetSupportDescription": "Nogle testnet understøtter ikke bytte, afsendelse eller køb af tokens.", "tdp.stats.unsupportedChainDescription": "Tokenstatistik og diagrammer for {{chain}} er tilgængelige på {{infoLink}}", "tdp.symbolNotFound": "Symbol ikke fundet", + "testnet.unsupported": "Denne funktionalitet understøttes ikke i testnet-tilstand.", "themeToggle.theme": "Tema", "title.betterPricesMoreListings": "Bedre priser. Flere opslag. Køb, sælg og byt NFT'er på tværs af topmarkedspladser som OpenSea. Udforsk populære samlinger.", "title.buySellTradeEthereum": "Køb, sælg og byt Ethereum og andre top-tokens på Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "{{ownerAddress}}'s saldo", "token.bridge": "{{label}} symbolsk bro", "token.chart.tooltip": "Gebyrer: {{amount}}", + "token.details.testnet.unsupported": "Tokendetaljer er ikke tilgængelige for testnet-tokens.", "token.error.unknown": "Ukendt token", "token.fee.buy.label": "købe gebyr", "token.fee.label": "betaling", @@ -2212,7 +2233,6 @@ "walletConnect.pending.switchAccount": "Skift konto", "walletConnect.pending.switchNetwork": "Skift netværk", "walletConnect.pending.title": "Opret forbindelse til {{dappName}}", - "walletConnect.permissions.networks": "Netværk", "walletConnect.permissions.option.transferAssets": "Overfør dine aktiver uden samtykke", "walletConnect.permissions.option.viewTokenBalances": "Se dine symbolske saldi", "walletConnect.permissions.option.viewWalletAddress": "Se din tegnebogsadresse", diff --git a/packages/uniswap/src/i18n/locales/translations/de-DE.json b/packages/uniswap/src/i18n/locales/translations/de-DE.json index 66211b0e8cb..5c7bd613e99 100644 --- a/packages/uniswap/src/i18n/locales/translations/de-DE.json +++ b/packages/uniswap/src/i18n/locales/translations/de-DE.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Zahlen Sie Token im {{label}} -Netzwerk ein.", "common.deposited": "Hinterlegt", "common.depositing": "Einzahlung", + "common.depositTokens": "Einzahlungstoken", "common.detailed.label": "Detaillierte", "common.detected": "Erkannt", "common.developers": "Entwickler", @@ -430,6 +431,7 @@ "common.migrate.position": "Position migrieren", "common.migrated.liquidity": "Migrierte Liquidität", "common.migrating.liquidity": "Liquiditätsmigration", + "common.min": "Mindest", "common.mint.cancelled": "Postfrisch gestempelt", "common.mint.failed": "Mint ist fehlgeschlagen", "common.minted": "Geprägt", @@ -450,6 +452,7 @@ "common.noResults": "keine Ergebnisse gefunden.", "common.notAvailableInRegion.error": "In Ihrer Region nicht verfügbar", "common.notCreated.label": "Nicht erstellt", + "common.notSupported": "Wird nicht unterstützt", "common.oneDay": "1 Tag", "common.oneHour": "1 Stunde", "common.oneMonth": "1 Monat", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}und andere Optionen", "fiatOnRamp.quote.type.other": "Andere Optionen", "fiatOnRamp.quote.type.recent": "Kürzlich benutzt", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Sie können Token und NFTs auf Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora und ZKsync erhalten.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Von einem Konto", "fiatOnRamp.receiveCrypto.title": "Krypto empfangen", "fiatOnRamp.receiveCrypto.transferFunds": "Finanzieren Sie Ihr Wallet, indem Sie Kryptowährungen von einem anderen Wallet oder Konto übertragen", @@ -901,6 +903,8 @@ "home.activity.error.load": "Aktivität konnte nicht geladen werden", "home.activity.title": "Aktivität", "home.banner.offline": "Sie befinden sich im Offlinemodus", + "home.banner.testnetMode": "Sie befinden sich im Testnet-Modus", + "home.banner.testnetMode.nav": "Sie befinden sich im Testnetzmodus. Deaktivieren Sie dies in den Einstellungen.", "home.explore.footer": "Tippen Sie hier, um Tausende von Token, NFTs und mehr zu entdecken", "home.explore.title": "Token erkunden", "home.extension.error": "Fehler beim Laden der Konten", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Position anlegen", "nav.tabs.createV2Position": "V2-Position erstellen", "nav.tabs.createV3Position": "V3-Position erstellen", + "nav.tabs.createV4Position": "V4-Position erstellen", "nav.tabs.viewPosition": "Position anzeigen", "network.lostConnection": "Möglicherweise haben Sie Ihre Netzwerkverbindung verloren.", "network.mightBeDown": "{{network}} ist möglicherweise gerade ausgefallen oder Ihre Netzwerkverbindung ist verloren gegangen.", @@ -1331,6 +1336,7 @@ "pool.back": "Zurück zum Pool", "pool.balances": "Poolguthaben", "pool.claimFees": "Gebühren geltend machen", + "pool.claimFees.button.label": "Beanspruchen", "pool.collectAs": "Sammeln als {{nativeWrappedSymbol}}", "pool.collected": " Gesammelt", "pool.collecting": "Sammeln", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Verwalten Sie die Liquidität im Belohnungspool", "pool.max.label": "Max:", "pool.maxPrice": "Max Preis", + "pool.migrateToV4": "Migration auf V4", "pool.min.label": "Mindest:", "pool.minPrice": "Mindestpreis", "pool.mustBeInitialized": "Dieser Pool muss initialisiert werden, bevor Sie Liquidität hinzufügen können. Wählen Sie zum Initialisieren einen Startpreis für den Pool aus. Geben Sie dann Ihren Liquiditätspreisbereich und den Einzahlungsbetrag ein. Aufgrund der Initialisierungstransaktion sind die Gasgebühren höher als üblich.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Tipp: Verwenden Sie dieses Tool, um v2-Pools zu finden, die nicht automatisch in der Benutzeroberfläche angezeigt werden.", "pools.approving.amount": "Genehmigen {{amount}}", "position.addHook": "Einen Haken hinzufügen", + "position.addHook.tooltip": "Hooks sind eine erweiterte Funktion, die es Pools ermöglicht, mit Smart Contracts zu interagieren und so verschiedene Funktionen freizuschalten. Gehen Sie beim Hinzufügen von Hooks vorsichtig vor, da einige davon bösartig sein oder unbeabsichtigte Folgen haben können.", "position.appearHere": "Ihre Position wird hier angezeigt.", + "position.create.modal.header": "Position erstellen", "position.currentValue": "Aktueller Positionswert", + "position.deposit.description": "Geben Sie die Token-Beträge für Ihren Liquiditätsbeitrag an.", "position.depositedCurrency": "Eingezahlt {{currencySymbol}}", "position.migrate.liquidity": "Beim Migrieren von Positionen können Sie Ihr Token-Paar nicht ändern, aber Sie können einen Hook hinzufügen, um die Funktionalität zu verbessern.", "position.new": "Neue Position", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Verbinden...", "qrScanner.status.loading": "Wird geladen...", "qrScanner.title": "Scannen Sie einen QR-Code", - "qrScanner.wallet.title": "Sie können Token und NFTs auf Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast und BNB Chain erhalten.", + "qrScanner.wallet.networks": "Unterstützte Netzwerke", + "qrScanner.wallet.title": "Sie können Token und NFTs in allen unseren {{numOfNetworks}} unterstützten Netzwerken senden und empfangen.", "removeLiquidity.collectFees": "Sie erhalten außerdem das Honorar, das Sie in dieser Position verdienen.", "removeLiquidity.outputEstimated": "Die Ausgabe ist geschätzt. Wenn sich der Preis um mehr als {{allowed}}% ändert, wird Ihre Transaktion rückgängig gemacht.", "removeLiquidity.pendingText": "Entfernen von {{amtA}} {{symA}} und {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Spitzname", "settings.setting.wallet.notifications.title": "Benachrichtigungen", "settings.setting.wallet.preferences.title": "Persönliche Einstellungen", - "settings.showTestNets": "Testnetze anzeigen", + "settings.setting.wallet.testnetMode.description": "Dadurch werden Testnetze aktiviert, damit Entwickler Funktionen und Transaktionen ausprobieren können, ohne echte Vermögenswerte zu verwenden. Token in Testnetzen haben keinen echten Wert.", + "settings.setting.wallet.testnetMode.title": "Testnet-Modus", "settings.switchNetwork.warning": "Um Uniswap auf {{label}}zu verwenden, wechseln Sie das Netzwerk in den Einstellungen Ihres Wallets.", "settings.title": "Einstellungen", "settings.version": "Version {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Neuer Input", "swap.details.newQuote.output": "Neue Ausgabe", "swap.details.orderRouting": "Auftragsrouting", - "swap.details.orderRoutingInfo": "In Ihrem Preis sind bereits die Netzwerkkosten im Zielnetz und eine Across-Gebühr von 0,05 % enthalten.", + "swap.details.orderRoutingInfo": "Dieser Austausch wird über Across geleitet, ein dezentrales Protokoll, das Vermögenswerte über Netzwerke hinweg verschiebt und dabei Sicherheit, schnelle Ausführung und niedrige Preise priorisiert.", + "swap.details.poweredBy": "Angetrieben von", "swap.details.rate": "Rate", "swap.details.slippage": "Maximaler Schlupf", "swap.details.uniswapFee": "Gebühr", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Der Uniswap-Kunde wählt unter Berücksichtigung von Preis und Netzwerkkosten die günstigste Handelsoption aus.", "swap.settings.routingPreference.option.v2.title": "V2-Pools", "swap.settings.routingPreference.option.v3.title": "V3-Pools", + "swap.settings.routingPreference.option.v4.title": "v4-Pools", "swap.settings.routingPreference.title": "Handelsoptionen", "swap.settings.slippage.control.auto": "Auto", "swap.settings.slippage.description": "Ihre Transaktion wird rückgängig gemacht, wenn sich der Preis um mehr als den Slippage-Prozentsatz ändert.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Bei einer ungünstigen Preisänderung um mehr als diesen Prozentsatz wird Ihre Transaktion rückgängig gemacht.", "swap.signAndSwap": "Unterschreiben und tauschen", "swap.slippage.amt": "{{amt}} Schlupf", + "swap.slippage.bridging": "Kein Schlupf beim Wechsel zwischen Netzwerken", "swap.slippage.settings.title": "Slippage-Einstellungen", "swap.slippage.tooltip": "Die maximale Preisbewegung vor Ihrer Transaktion wird wiederhergestellt.", "swap.slippageBelow.warning": "Ein Slippage unter {{amt}} kann zu einer fehlgeschlagenen Transaktion führen", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Transaktionsfrist", "swap.transaction.revertAfter": "Ihre Transaktion wird rückgängig gemacht, wenn sie länger als diesen Zeitraum aussteht.", "swap.unsupportedAssets.readMore": "Mehr zu nicht unterstützten Assets", + "swap.warning.enterLargerAmount.title": "Geben Sie einen größeren Betrag ein", "swap.warning.expectedFailure": "Diese Transaktion wird voraussichtlich fehlschlagen", "swap.warning.feeOnTransfer.message": "Für einige Token wird beim Kauf oder Verkauf eine Gebühr erhoben, die vom Token-Emittenten festgelegt wird. Uniswap erhält keinen Anteil an diesen Gebühren.", "swap.warning.feeOnTransfer.title": "Warum gibt es eine zusätzliche Gebühr?", "swap.warning.insufficientBalance.title": "Du hast nicht genug {{currencySymbol}}", "swap.warning.insufficientGas.button": "Nicht genug {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Tausche {{ tokenSymbol }} gegen {{networkName}}aus", "swap.warning.insufficientGas.button.buy": "Kaufen {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Mit Karte kaufen", "swap.warning.insufficientGas.message.withNetwork": "Nicht genug {{currencySymbol}} auf {{networkName}} zum Tauschen", "swap.warning.insufficientGas.message.withoutNetwork": "Nicht genug {{currencySymbol}} zum Tauschen", "swap.warning.insufficientGas.title": "Sie haben nicht genug {{currencySymbol}} , um die Netzwerkkosten zu decken", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "Token-Daten für am {{chainName}}", "tdp.nameNotFound": "Name nicht gefunden", "tdp.noInfoAvailable": "Keine Token-Informationen verfügbar", + "tdp.noTestnetSupportDescription": "Einige Testnetze unterstützen das Tauschen, Senden oder Kaufen von Token nicht.", "tdp.stats.unsupportedChainDescription": "Token-Statistiken und Diagramme für {{chain}} sind verfügbar auf {{infoLink}}", "tdp.symbolNotFound": "Symbol nicht gefunden", + "testnet.unsupported": "Diese Funktionalität wird im Testnet-Modus nicht unterstützt.", "themeToggle.theme": "Thema", "title.betterPricesMoreListings": "Bessere Preise. Mehr Angebote. Kaufen, verkaufen und handeln Sie NFTs auf Top-Marktplätzen wie OpenSea. Entdecken Sie trendige Sammlungen.", "title.buySellTradeEthereum": "Kaufen, verkaufen und handeln Sie Ethereum und andere Top-Token auf Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "{{ownerAddress}}Guthaben", "token.bridge": "{{label}} Token-Brücke", "token.chart.tooltip": "Gebühren: {{amount}}", + "token.details.testnet.unsupported": "Für Testnet-Token sind keine Tokendetails verfügbar.", "token.error.unknown": "Unbekanntes Token", "token.fee.buy.label": "Kaufgebühr", "token.fee.label": "Gebühr", @@ -2212,7 +2233,6 @@ "walletConnect.pending.switchAccount": "Benutzer wechseln", "walletConnect.pending.switchNetwork": "Netzwerk wechseln", "walletConnect.pending.title": "Verbinden mit {{dappName}}", - "walletConnect.permissions.networks": "Netzwerke", "walletConnect.permissions.option.transferAssets": "Übertragen Sie Ihr Vermögen ohne Zustimmung", "walletConnect.permissions.option.viewTokenBalances": "Zeigen Sie Ihr Token-Guthaben an", "walletConnect.permissions.option.viewWalletAddress": "Zeigen Sie Ihre Wallet-Adresse an", diff --git a/packages/uniswap/src/i18n/locales/translations/el-GR.json b/packages/uniswap/src/i18n/locales/translations/el-GR.json index b9b898bd9e7..7e8c2a58280 100644 --- a/packages/uniswap/src/i18n/locales/translations/el-GR.json +++ b/packages/uniswap/src/i18n/locales/translations/el-GR.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Καταθέστε διακριτικά στο δίκτυο {{label}} .", "common.deposited": "Κατατίθεται", "common.depositing": "Κατάθεση", + "common.depositTokens": "μάρκες κατάθεσης", "common.detailed.label": "Λεπτομερής", "common.detected": "Εντοπίστηκε", "common.developers": "προγραμματιστές", @@ -430,6 +431,7 @@ "common.migrate.position": "Θέση μετεγκατάστασης", "common.migrated.liquidity": "Μεταναστευτική ρευστότητα", "common.migrating.liquidity": "Μεταναστευτική ρευστότητα", + "common.min": "Ελάχ", "common.mint.cancelled": "Το νομισματοκοπείο ακυρώθηκε", "common.mint.failed": "Το Mint απέτυχε", "common.minted": "Κοπή", @@ -450,6 +452,7 @@ "common.noResults": "Δεν βρέθηκαν αποτελέσματα.", "common.notAvailableInRegion.error": "Δεν είναι διαθέσιμο στην περιοχή σας", "common.notCreated.label": "Δεν δημιουργήθηκε", + "common.notSupported": "Δεν υποστηρίζεται", "common.oneDay": "1 ημέρα", "common.oneHour": "1 ώρα", "common.oneMonth": "1 μήνα", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}και άλλες επιλογές", "fiatOnRamp.quote.type.other": "Αλλες επιλογές", "fiatOnRamp.quote.type.recent": "Πρόσφατα χρησιμοποιημένο", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Μπορείτε να λάβετε διακριτικά και NFT σε Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora και ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Από λογαριασμό", "fiatOnRamp.receiveCrypto.title": "Λήψη κρυπτογράφησης", "fiatOnRamp.receiveCrypto.transferFunds": "Χρηματοδοτήστε το πορτοφόλι σας μεταφέροντας κρυπτογράφηση από άλλο πορτοφόλι ή λογαριασμό", @@ -901,6 +903,8 @@ "home.activity.error.load": "Δεν ήταν δυνατή η φόρτωση της δραστηριότητας", "home.activity.title": "Δραστηριότητα", "home.banner.offline": "Είστε σε λειτουργία εκτός σύνδεσης", + "home.banner.testnetMode": "Είστε σε λειτουργία testnet", + "home.banner.testnetMode.nav": "Είστε σε λειτουργία testnet. Απενεργοποιήστε αυτό στις ρυθμίσεις.", "home.explore.footer": "Πατήστε εδώ για να εξερευνήσετε χιλιάδες διακριτικά, NFT και άλλα", "home.explore.title": "Εξερευνήστε τα διακριτικά", "home.extension.error": "Σφάλμα φόρτωσης λογαριασμών", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Δημιουργία θέσης", "nav.tabs.createV2Position": "Δημιουργία θέσης V2", "nav.tabs.createV3Position": "Δημιουργία θέσης V3", + "nav.tabs.createV4Position": "Δημιουργία θέσης V4", "nav.tabs.viewPosition": "Θέση προβολής", "network.lostConnection": "Μπορεί να έχετε χάσει τη σύνδεση δικτύου σας.", "network.mightBeDown": "Το {{network}} μπορεί να είναι απενεργοποιημένο αυτήν τη στιγμή ή μπορεί να έχετε χάσει τη σύνδεσή σας στο δίκτυο.", @@ -1331,6 +1336,7 @@ "pool.back": "Επιστροφή στην πισίνα", "pool.balances": "Υπόλοιπα πισίνας", "pool.claimFees": "Διεκδικήστε τέλη", + "pool.claimFees.button.label": "Αξίωση", "pool.collectAs": "Συλλέξτε ως {{nativeWrappedSymbol}}", "pool.collected": " Συγκεντρωμένος", "pool.collecting": "Περισυλλογή", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Διαχειριστείτε τη ρευστότητα στην ομάδα ανταμοιβών", "pool.max.label": "Μέγιστη:", "pool.maxPrice": "Μέγιστη τιμή", + "pool.migrateToV4": "Μεταφορά στο V4", "pool.min.label": "Ελάχ.", "pool.minPrice": "Ελάχιστη τιμή", "pool.mustBeInitialized": "Αυτή η ομάδα πρέπει να προετοιμαστεί για να μπορέσετε να προσθέσετε ρευστότητα. Για να αρχικοποιήσετε, επιλέξτε μια αρχική τιμή για την πισίνα. Στη συνέχεια, εισαγάγετε το εύρος τιμών ρευστότητας και το ποσό κατάθεσης. Τα τέλη φυσικού αερίου θα είναι υψηλότερα από το συνηθισμένο λόγω της συναλλαγής αρχικοποίησης.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Συμβουλή: Χρησιμοποιήστε αυτό το εργαλείο για να βρείτε ομάδες v2 που δεν εμφανίζονται αυτόματα στη διεπαφή.", "pools.approving.amount": "Έγκριση {{amount}}", "position.addHook": "Προσθέστε ένα άγκιστρο", + "position.addHook.tooltip": "Τα Hooks είναι μια προηγμένη λειτουργία που επιτρέπει στις πισίνες να αλληλεπιδρούν με έξυπνα συμβόλαια, ξεκλειδώνοντας διάφορες δυνατότητες. Να είστε προσεκτικοί όταν προσθέτετε άγκιστρα, καθώς ορισμένα μπορεί να είναι κακόβουλα ή να προκαλέσουν ανεπιθύμητες συνέπειες.", "position.appearHere": "Η θέση σας θα εμφανιστεί εδώ.", + "position.create.modal.header": "Δημιουργία θέσης", "position.currentValue": "Τιμή τρέχουσας θέσης", + "position.deposit.description": "Καθορίστε τα συμβολικά ποσά για τη συνεισφορά ρευστότητάς σας.", "position.depositedCurrency": "Κατατέθηκε {{currencySymbol}}", "position.migrate.liquidity": "Κατά τη μετεγκατάσταση θέσεων, δεν μπορείτε να αλλάξετε το ζεύγος διακριτικών, αλλά μπορείτε να προσθέσετε ένα άγκιστρο για να βελτιώσετε τη λειτουργικότητα.", "position.new": "Νέα Θέση", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Συνδετικός...", "qrScanner.status.loading": "Φόρτωση...", "qrScanner.title": "Σαρώστε έναν κωδικό QR", - "qrScanner.wallet.title": "Μπορείτε να λάβετε διακριτικά και NFT σε Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast και BNB Chain.", + "qrScanner.wallet.networks": "Υποστηριζόμενα Δίκτυα", + "qrScanner.wallet.title": "Μπορείτε να στέλνετε και να λαμβάνετε διακριτικά και NFT σε όλα τα δίκτυά μας που υποστηρίζονται {{numOfNetworks}} .", "removeLiquidity.collectFees": "Θα εισπράξετε επίσης αμοιβές που κερδίζετε από αυτή τη θέση.", "removeLiquidity.outputEstimated": "Εκτιμάται η παραγωγή. Εάν η τιμή αλλάξει κατά περισσότερο από {{allowed}}% η συναλλαγή σας θα επανέλθει.", "removeLiquidity.pendingText": "Αφαίρεση {{amtA}} {{symA}} και {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Παρατσούκλι", "settings.setting.wallet.notifications.title": "Ειδοποιήσεις", "settings.setting.wallet.preferences.title": "Προτιμήσεις πορτοφολιού", - "settings.showTestNets": "Εμφάνιση δοκιμαστικών δικτύων", + "settings.setting.wallet.testnetMode.description": "Αυτό ενεργοποιεί τα δοκιμαστικά δίκτυα ώστε οι προγραμματιστές να δοκιμάσουν λειτουργίες και συναλλαγές χωρίς να χρησιμοποιούν πραγματικά στοιχεία. Τα διακριτικά στα δοκιμαστικά δίκτυα δεν έχουν καμία πραγματική αξία.", + "settings.setting.wallet.testnetMode.title": "Λειτουργία δοκιμαστικού δικτύου", "settings.switchNetwork.warning": "Για να χρησιμοποιήσετε το Uniswap στο {{label}}, αλλάξτε το δίκτυο στις ρυθμίσεις του πορτοφολιού σας.", "settings.title": "Ρυθμίσεις", "settings.version": "Έκδοση {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Νέα εισαγωγή", "swap.details.newQuote.output": "Νέα έξοδος", "swap.details.orderRouting": "Δρομολόγηση παραγγελίας", - "swap.details.orderRoutingInfo": "Η τιμή σας περιλαμβάνει ήδη το κόστος δικτύου στο δίκτυο προορισμού και 0,05% συνολική χρέωση.", + "swap.details.orderRoutingInfo": "Αυτή η ανταλλαγή δρομολογείται μέσω του Across, ενός αποκεντρωμένου πρωτοκόλλου που μετακινεί στοιχεία στα δίκτυα, ενώ δίνει προτεραιότητα στην ασφάλεια, τη γρήγορη εκτέλεση και τις χαμηλές τιμές.", + "swap.details.poweredBy": "Τροφοδοτείται από", "swap.details.rate": "Τιμή", "swap.details.slippage": "Μέγιστη ολίσθηση", "swap.details.uniswapFee": "Τέλη", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Ο πελάτης Uniswap επιλέγει τη φθηνότερη επιλογή συναλλαγών με παραγοντοποίηση τιμής και κόστους δικτύου.", "swap.settings.routingPreference.option.v2.title": "v2 πισίνες", "swap.settings.routingPreference.option.v3.title": "v3 πισίνες", + "swap.settings.routingPreference.option.v4.title": "v4 πισίνες", "swap.settings.routingPreference.title": "Εμπορικές επιλογές", "swap.settings.slippage.control.auto": "Αυτο", "swap.settings.slippage.description": "Η συναλλαγή σας θα επανέλθει εάν η τιμή αλλάξει περισσότερο από το ποσοστό ολίσθησης.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Η συναλλαγή σας θα επανέλθει εάν η τιμή αλλάξει δυσμενώς περισσότερο από αυτό το ποσοστό.", "swap.signAndSwap": "Υπογράψτε και ανταλλάξτε", "swap.slippage.amt": "{{amt}} ολίσθηση", + "swap.slippage.bridging": "Χωρίς ολίσθηση κατά την εναλλαγή μεταξύ δικτύων", "swap.slippage.settings.title": "Ρυθμίσεις ολίσθησης", "swap.slippage.tooltip": "Η μέγιστη κίνηση της τιμής πριν από τη συναλλαγή σας θα επανέλθει.", "swap.slippageBelow.warning": "Η ολίσθηση κάτω από το {{amt}} μπορεί να οδηγήσει σε αποτυχημένη συναλλαγή", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Προθεσμία συναλλαγής", "swap.transaction.revertAfter": "Η συναλλαγή σας θα επανέλθει εάν είναι σε εκκρεμότητα για περισσότερο από αυτό το χρονικό διάστημα.", "swap.unsupportedAssets.readMore": "Διαβάστε περισσότερα σχετικά με τα μη υποστηριζόμενα στοιχεία", + "swap.warning.enterLargerAmount.title": "Εισαγάγετε μεγαλύτερο ποσό", "swap.warning.expectedFailure": "Αυτή η συναλλαγή αναμένεται να αποτύχει", "swap.warning.feeOnTransfer.message": "Ορισμένα μάρκες λαμβάνουν ένα τέλος όταν αγοράζονται ή πωλούνται, το οποίο ορίζεται από τον εκδότη του κουπόνι. Η Uniswap δεν λαμβάνει κανένα μερίδιο από αυτές τις χρεώσεις.", "swap.warning.feeOnTransfer.title": "Γιατί υπάρχει επιπλέον χρέωση;", "swap.warning.insufficientBalance.title": "Δεν σου φτάνουν {{currencySymbol}}", "swap.warning.insufficientGas.button": "Δεν αρκεί {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Ανταλλάξτε για {{ tokenSymbol }} στο {{networkName}}", "swap.warning.insufficientGas.button.buy": "Αγοράστε {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Αγορά με κάρτα", "swap.warning.insufficientGas.message.withNetwork": "Δεν αρκεί {{currencySymbol}} στο {{networkName}} για εναλλαγή", "swap.warning.insufficientGas.message.withoutNetwork": "Δεν αρκεί {{currencySymbol}} για εναλλαγή", "swap.warning.insufficientGas.title": "Δεν έχετε αρκετό {{currencySymbol}} για να καλύψετε το κόστος του δικτύου", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "δεδομένα διακριτικού για στο {{chainName}}", "tdp.nameNotFound": "Το όνομα δεν βρέθηκε", "tdp.noInfoAvailable": "Δεν υπάρχουν διαθέσιμες πληροφορίες συμβολικών", + "tdp.noTestnetSupportDescription": "Ορισμένα δοκιμαστικά δίκτυα δεν υποστηρίζουν την ανταλλαγή, την αποστολή ή την αγορά κουπονιών.", "tdp.stats.unsupportedChainDescription": "Τα στατιστικά στοιχεία και τα γραφήματα για {{chain}} είναι διαθέσιμα στο {{infoLink}}", "tdp.symbolNotFound": "Το σύμβολο δεν βρέθηκε", + "testnet.unsupported": "Αυτή η λειτουργία δεν υποστηρίζεται σε λειτουργία δοκιμαστικού δικτύου.", "themeToggle.theme": "Θέμα", "title.betterPricesMoreListings": "Καλύτερες τιμές. Περισσότερες καταχωρίσεις. Αγοράστε, πουλήστε και ανταλλάξτε NFT σε κορυφαίες αγορές όπως το OpenSea. Εξερευνήστε τις τάσεις συλλογές.", "title.buySellTradeEthereum": "Αγοράστε, πουλήστε και ανταλλάξτε Ethereum και άλλα κορυφαία διακριτικά στο Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "ισορροπία {{ownerAddress}}", "token.bridge": "{{label}} συμβολική γέφυρα", "token.chart.tooltip": "Τέλη: {{amount}}", + "token.details.testnet.unsupported": "Οι λεπτομέρειες διακριτικού δεν είναι διαθέσιμες για μάρκες δοκιμαστικού δικτύου.", "token.error.unknown": "Άγνωστο διακριτικό", "token.fee.buy.label": "προμήθεια αγοράς", "token.fee.label": "αμοιβή", @@ -2212,7 +2233,6 @@ "walletConnect.pending.switchAccount": "Αλλαγή λογαριασμού", "walletConnect.pending.switchNetwork": "Εναλλαγή δικτύου", "walletConnect.pending.title": "Συνδεθείτε στο {{dappName}}", - "walletConnect.permissions.networks": "Δίκτυα", "walletConnect.permissions.option.transferAssets": "Μεταφέρετε τα περιουσιακά σας στοιχεία χωρίς συναίνεση", "walletConnect.permissions.option.viewTokenBalances": "Δείτε τα υπόλοιπα των συμβολικών σας", "walletConnect.permissions.option.viewWalletAddress": "Δείτε τη διεύθυνση του πορτοφολιού σας", diff --git a/packages/uniswap/src/i18n/locales/translations/es-419.json b/packages/uniswap/src/i18n/locales/translations/es-419.json index 9cdd51bfb38..ddb83284e60 100644 --- a/packages/uniswap/src/i18n/locales/translations/es-419.json +++ b/packages/uniswap/src/i18n/locales/translations/es-419.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Deposit tokens to the {{label}} network.", "common.deposited": "Deposited", "common.depositing": "Depositing", + "common.depositTokens": "Deposit tokens", "common.detailed.label": "Detailed", "common.detected": "Detected", "common.developers": "Developers", @@ -430,6 +431,7 @@ "common.migrate.position": "Migrate position", "common.migrated.liquidity": "Migrated liquidity", "common.migrating.liquidity": "Migrating liquidity", + "common.min": "Min", "common.mint.cancelled": "Mint cancelled", "common.mint.failed": "Mint failed", "common.minted": "Minted", @@ -450,6 +452,7 @@ "common.noResults": "No results found.", "common.notAvailableInRegion.error": "Not available in your region", "common.notCreated.label": "Not created", + "common.notSupported": "Not supported", "common.oneDay": "1 day", "common.oneHour": "1 hour", "common.oneMonth": "1 month", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}, and other options", "fiatOnRamp.quote.type.other": "Other options", "fiatOnRamp.quote.type.recent": "Recently used", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "You can receive tokens & NFTs on Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora, and ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "From an account", "fiatOnRamp.receiveCrypto.title": "Receive crypto", "fiatOnRamp.receiveCrypto.transferFunds": "Fund your wallet by transferring crypto from another wallet or account", @@ -901,6 +903,8 @@ "home.activity.error.load": "Couldn’t load activity", "home.activity.title": "Activity", "home.banner.offline": "You are in offline mode", + "home.banner.testnetMode": "You are in testnet mode", + "home.banner.testnetMode.nav": "You are in testnet mode. Toggle this off in settings.", "home.explore.footer": "Tap “search” to explore more", "home.explore.title": "Explore tokens", "home.extension.error": "Error loading accounts", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Create position", "nav.tabs.createV2Position": "Create V2 position", "nav.tabs.createV3Position": "Create V3 position", + "nav.tabs.createV4Position": "Create V4 position", "nav.tabs.viewPosition": "View position", "network.lostConnection": "You may have lost your network connection.", "network.mightBeDown": "{{network}} might be down right now, or you may have lost your network connection.", @@ -1331,6 +1336,7 @@ "pool.back": "Back to Pool", "pool.balances": "Pool balances", "pool.claimFees": "Claim fees", + "pool.claimFees.button.label": "Claim", "pool.collectAs": "Collect as {{nativeWrappedSymbol}}", "pool.collected": " Collected", "pool.collecting": "Collecting", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Manage liquidity in rewards pool", "pool.max.label": "Max:", "pool.maxPrice": "Max price", + "pool.migrateToV4": "Migrate to V4", "pool.min.label": "Min:", "pool.minPrice": "Min price", "pool.mustBeInitialized": "This pool must be initialized before you can add liquidity. To initialize, select a starting price for the pool. Then, enter your liquidity price range and deposit amount. Gas fees will be higher than usual due to the initialization transaction.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Tip: Use this tool to find v2 pools that don’t automatically appear in the interface.", "pools.approving.amount": "Approving {{amount}}", "position.addHook": "Add a Hook", + "position.addHook.tooltip": "Hooks are an advanced feature that enable pools to interact with smart contracts, unlocking various capabilities. Exercise caution when adding hooks, as some may be malicious or cause unintended consequences.", "position.appearHere": "Your position will appear here.", + "position.create.modal.header": "Creating position", "position.currentValue": "Current position value", + "position.deposit.description": "Specify the token amounts for your liquidity contribution.", "position.depositedCurrency": "Deposited {{currencySymbol}}", "position.migrate.liquidity": "When migrating positions, you cannot change your token pair, but you can add a hook to enhance functionality.", "position.new": "New Position", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Connecting...", "qrScanner.status.loading": "Loading...", "qrScanner.title": "Scan a QR code", - "qrScanner.wallet.title": "You can receive tokens & NFTs on Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast, and BNB Chain.", + "qrScanner.wallet.networks": "Supported Networks", + "qrScanner.wallet.title": "You can send and receive tokens and NFTs on all of our {{numOfNetworks}} supported networks.", "removeLiquidity.collectFees": "You will also collect fees earned from this position.", "removeLiquidity.outputEstimated": "Output is estimated. If the price changes by more than {{allowed}}% your transaction will revert.", "removeLiquidity.pendingText": "Removing {{amtA}} {{symA}} and {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Nickname", "settings.setting.wallet.notifications.title": "Notifications", "settings.setting.wallet.preferences.title": "Wallet preferences", - "settings.showTestNets": "Show testnets", + "settings.setting.wallet.testnetMode.description": "This turns on testnets for developers to try out features and transactions without using real assets. Tokens on testnets do not hold any real value.", + "settings.setting.wallet.testnetMode.title": "Testnet mode", "settings.switchNetwork.warning": "To use Uniswap on {{label}}, switch the network in your wallet’s settings.", "settings.title": "Settings", "settings.version": "Version {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "New input", "swap.details.newQuote.output": "New output", "swap.details.orderRouting": "Order routing", - "swap.details.orderRoutingInfo": "Your price already includes network costs on the destination network and a 0.05% Across fee.", + "swap.details.orderRoutingInfo": "This swap is routed via Across, a decentralized protocol that moves assets across networks while prioritizing safety, fast execution, and low prices.", + "swap.details.poweredBy": "Powered by", "swap.details.rate": "Rate", "swap.details.slippage": "Max slippage", "swap.details.uniswapFee": "Fee", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "The Uniswap client selects the cheapest trade option factoring price and network costs.", "swap.settings.routingPreference.option.v2.title": "v2 pools", "swap.settings.routingPreference.option.v3.title": "v3 pools", + "swap.settings.routingPreference.option.v4.title": "v4 pools", "swap.settings.routingPreference.title": "Trade options", "swap.settings.slippage.control.auto": "Auto", "swap.settings.slippage.description": "Your transaction will revert if the price changes more than the slippage percentage.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Your transaction will revert if the price changes unfavorably by more than this percentage.", "swap.signAndSwap": "Sign and swap", "swap.slippage.amt": "{{amt}} slippage", + "swap.slippage.bridging": "No slippage when swapping across networks", "swap.slippage.settings.title": "Max slippage", "swap.slippage.tooltip": "The maximum price movement before your transaction will revert.", "swap.slippageBelow.warning": "Slippage below {{amt}} may result in a failed transaction", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Transaction deadline", "swap.transaction.revertAfter": "Your transaction will revert if it is pending for more than this period of time.", "swap.unsupportedAssets.readMore": "Read more about unsupported assets", + "swap.warning.enterLargerAmount.title": "Enter a larger amount", "swap.warning.expectedFailure": "This transaction is expected to fail", "swap.warning.feeOnTransfer.message": "Some tokens take a fee when they are bought or sold, which is set by the token issuer. Uniswap does not receive any share of these fees.", "swap.warning.feeOnTransfer.title": "Why is there an additional fee?", "swap.warning.insufficientBalance.title": "You don’t have enough {{currencySymbol}}", "swap.warning.insufficientGas.button": "Not enough {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Swap for {{ tokenSymbol }} on {{networkName}}", "swap.warning.insufficientGas.button.buy": "Buy {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Buy with card", "swap.warning.insufficientGas.message.withNetwork": "Not enough {{currencySymbol}} on {{networkName}} to swap", "swap.warning.insufficientGas.message.withoutNetwork": "Not enough {{currencySymbol}} to swap", "swap.warning.insufficientGas.title": "You don’t have enough {{currencySymbol}} to cover the network cost", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "token data for on {{chainName}}", "tdp.nameNotFound": "Name not found", "tdp.noInfoAvailable": "No token information available", + "tdp.noTestnetSupportDescription": "Some testnets do not support swapping, sending, or buying tokens.", "tdp.stats.unsupportedChainDescription": "Token stats and charts for {{chain}} are available on {{infoLink}}", "tdp.symbolNotFound": "Symbol not found", + "testnet.unsupported": "This functionality is not supported in testnet mode.", "themeToggle.theme": "Theme", "title.betterPricesMoreListings": "Better prices. More listings. Buy, sell, and trade NFTs across top marketplaces like OpenSea. Explore trending collections.", "title.buySellTradeEthereum": "Buy, sell & trade Ethereum and other top tokens on Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "{{ownerAddress}}’s balance", "token.bridge": "{{label}} token bridge", "token.chart.tooltip": "Fees: {{amount}}", + "token.details.testnet.unsupported": "Token details are unavailable for testnet tokens.", "token.error.unknown": "Unknown token", "token.fee.buy.label": "buy fee", "token.fee.label": "fee", @@ -2109,6 +2130,8 @@ "transaction.warning.insufficientGas.modal.title.withoutNetwork": "Not enough {{tokenSymbol}}", "transaction.watcher.error.cancel": "Unable to cancel transaction", "transaction.watcher.error.status": "Error while checking transaction status", + "unichain.launch.modal.description": "An Ethereum L2 designed for DeFi, built by Uniswap Labs. Testnet goes live today.", + "unichain.launch.modal.title": "Introducing Unichain", "uniswapX.aggregatesLiquidity": " aggregates liquidity sources for better prices and gas free swaps.", "uniswapx.description": "UniswapX aggregates liquidity sources for better prices and gas free swaps.", "uniswapx.included": "Includes UniswapX ", @@ -2212,7 +2235,6 @@ "walletConnect.pending.switchAccount": "Switch Account", "walletConnect.pending.switchNetwork": "Switch Network", "walletConnect.pending.title": "Connect to {{dappName}}", - "walletConnect.permissions.networks": "Networks", "walletConnect.permissions.option.transferAssets": "Transfer your assets without consent", "walletConnect.permissions.option.viewTokenBalances": "View your token balances", "walletConnect.permissions.option.viewWalletAddress": "View your wallet address", diff --git a/packages/uniswap/src/i18n/locales/translations/es-ES.json b/packages/uniswap/src/i18n/locales/translations/es-ES.json index 05367776ca0..a79ad9c8564 100644 --- a/packages/uniswap/src/i18n/locales/translations/es-ES.json +++ b/packages/uniswap/src/i18n/locales/translations/es-ES.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Depositar tokens en la red {{label}}.", "common.deposited": "Se depositó", "common.depositing": "Depositando", + "common.depositTokens": "Fichas de depósito", "common.detailed.label": "Se detalló", "common.detected": "Se detectó", "common.developers": "Desarrolladores", @@ -430,6 +431,7 @@ "common.migrate.position": "Migrar posición", "common.migrated.liquidity": "Liquidez migrada", "common.migrating.liquidity": "Migrando liquidez", + "common.min": "Mínimo", "common.mint.cancelled": "Se canceló la acuñación", "common.mint.failed": "No se pudo acuñar", "common.minted": "Se acuñó", @@ -450,6 +452,7 @@ "common.noResults": "No se encontraron resultados.", "common.notAvailableInRegion.error": "No está disponible en tu región", "common.notCreated.label": "No se creó", + "common.notSupported": "No soportado", "common.oneDay": "1 día", "common.oneHour": "1 hora", "common.oneMonth": "1 mes", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}} y otras opciones", "fiatOnRamp.quote.type.other": "Otras opciones", "fiatOnRamp.quote.type.recent": "Usadas recientemente", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Puedes recibir tokens y NFT en Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora y ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Desde una cuenta", "fiatOnRamp.receiveCrypto.title": "Recibir cripto", "fiatOnRamp.receiveCrypto.transferFunds": "Deposita fondos en tu wallet al transferir cripto desde otra wallet o cuenta", @@ -901,6 +903,8 @@ "home.activity.error.load": "No se pudo cargar la actividad", "home.activity.title": "Actividad", "home.banner.offline": "Estás en modo fuera de línea", + "home.banner.testnetMode": "Estás en modo de red de prueba", + "home.banner.testnetMode.nav": "Estás en modo de red de prueba. Desactiva esta opción en la configuración.", "home.explore.footer": "Toca “Buscar” para explorar más", "home.explore.title": "Explorar tokens", "home.extension.error": "Error al cargar cuentas", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Crear posición", "nav.tabs.createV2Position": "Crear posición V2", "nav.tabs.createV3Position": "Crear posición V3", + "nav.tabs.createV4Position": "Crear posición V4", "nav.tabs.viewPosition": "Ver posición", "network.lostConnection": "Puede que hayas perdido tu conexión de red.", "network.mightBeDown": "Puede que {{network}} no esté funcionando en este momento o que hayas perdido la conexión de red.", @@ -1331,6 +1336,7 @@ "pool.back": "Volver al pool", "pool.balances": "Saldos de pool", "pool.claimFees": "Reclamar tarifas", + "pool.claimFees.button.label": "Afirmar", "pool.collectAs": "Cobrar como {{nativeWrappedSymbol}}", "pool.collected": " Cobrado", "pool.collecting": "Cobrando", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Gestionar liquidez en pool de recompensas", "pool.max.label": "Máx.:", "pool.maxPrice": "Precio máx.", + "pool.migrateToV4": "Migrar a V4", "pool.min.label": "Mín.:", "pool.minPrice": "Precio mín.", "pool.mustBeInitialized": "Este pool se debe inicializar antes de poder agregar liquidez. Para inicializarlo, selecciona un precio inicial para el pool. Luego, introduce tu rango de precio de liquidez y el monto del depósito. Las tarifas de gas serán más altas de lo habitual debido a la transacción de inicialización.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Sugerencia: Usa esta herramienta para buscar pools v2 que no aparezcan automáticamente en la interfaz.", "pools.approving.amount": "Aprobando {{amount}}", "position.addHook": "Agregar un gancho", + "position.addHook.tooltip": "Los hooks son una función avanzada que permite que los pools interactúen con contratos inteligentes, desbloqueando varias capacidades. Tenga cuidado al agregar hooks, ya que algunos pueden ser maliciosos o causar consecuencias no deseadas.", "position.appearHere": "Tu posición aparecerá aquí.", + "position.create.modal.header": "Creando posición", "position.currentValue": "Valor de la posición actual", + "position.deposit.description": "Especifique las cantidades de tokens para su contribución de liquidez.", "position.depositedCurrency": "Depositado {{currencySymbol}}", "position.migrate.liquidity": "Al migrar posiciones, no puedes cambiar tu par de tokens, pero puedes agregar un gancho para mejorar la funcionalidad.", "position.new": "Nueva posición", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Conectando…", "qrScanner.status.loading": "Cargando…", "qrScanner.title": "Escanear un código QR", - "qrScanner.wallet.title": "Puedes recibir tokens y NFT en Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast y BNB Chain.", + "qrScanner.wallet.networks": "Redes compatibles", + "qrScanner.wallet.title": "Puede enviar y recibir tokens y NFT en todas nuestras redes compatibles con {{numOfNetworks}} .", "removeLiquidity.collectFees": "También cobrarás las tarifas que se generen por esta posición.", "removeLiquidity.outputEstimated": "El resultado es estimado. Si el precio cambia en más del {{allowed}} %, se revertirá tu transacción.", "removeLiquidity.pendingText": "Eliminando {{amtA}} {{symA}} y {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Apodo", "settings.setting.wallet.notifications.title": "Notificaciones", "settings.setting.wallet.preferences.title": "Preferencias de wallet", - "settings.showTestNets": "Mostrar testnets", + "settings.setting.wallet.testnetMode.description": "Esto activa redes de prueba para que los desarrolladores prueben funciones y transacciones sin usar activos reales. Los tokens en redes de prueba no tienen ningún valor real.", + "settings.setting.wallet.testnetMode.title": "Modo de red de prueba", "settings.switchNetwork.warning": "Para usar Uniswap en {{label}}, cambia la red en la configuración de tu wallet.", "settings.title": "Configuración", "settings.version": "Versión {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Nueva entrada", "swap.details.newQuote.output": "Nueva salida", "swap.details.orderRouting": "Direccionamiento de orden", - "swap.details.orderRoutingInfo": "Su precio ya incluye los costos de red en la red de destino y una tarifa Across del 0,05%.", + "swap.details.orderRoutingInfo": "Este intercambio se enruta a través de Across, un protocolo descentralizado que mueve activos a través de redes priorizando la seguridad, la ejecución rápida y los precios bajos.", + "swap.details.poweredBy": "Desarrollado por", "swap.details.rate": "Tasa", "swap.details.slippage": "Deslizamiento máx.", "swap.details.uniswapFee": "Tarifa", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "El cliente de Uniswap selecciona la opción de operación más barata, teniendo en cuenta el precio y los costos de red.", "swap.settings.routingPreference.option.v2.title": "Pools v2", "swap.settings.routingPreference.option.v3.title": "Pools v3", + "swap.settings.routingPreference.option.v4.title": "Pools v4", "swap.settings.routingPreference.title": "Opciones de operación", "swap.settings.slippage.control.auto": "Automático", "swap.settings.slippage.description": "Tu transacción se revertirá si el precio cambia más que el porcentaje de deslizamiento.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Tu transacción se revertirá si el precio cambia de manera desfavorable en un porcentaje superior al indicado.", "swap.signAndSwap": "Firmar e intercambiar", "swap.slippage.amt": "Deslizamiento de {{amt}}", + "swap.slippage.bridging": "Sin deslizamiento al cambiar entre redes", "swap.slippage.settings.title": "Deslizamiento máx.", "swap.slippage.tooltip": "El movimiento de precio máximo antes de que se revierta tu transacción.", "swap.slippageBelow.warning": "Un deslizamiento por debajo de {{amt}} puede provocar una transacción fallida", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Fecha límite de transacción", "swap.transaction.revertAfter": "Tu transacción se revertirá si está pendiente por más tiempo que el indicado.", "swap.unsupportedAssets.readMore": "Leer más sobre los activos no compatibles", + "swap.warning.enterLargerAmount.title": "Introduzca una cantidad mayor", "swap.warning.expectedFailure": "Se prevé que esta transacción fallará", "swap.warning.feeOnTransfer.message": "Algunos tokens cobran una tarifa cuando se compran o venden. El emisor del token fija esta tarifa. Uniswap no recibe ninguna parte de estas tarifas.", "swap.warning.feeOnTransfer.title": "¿Por qué hay una tarifa adicional?", "swap.warning.insufficientBalance.title": "No tienes suficientes {{currencySymbol}}", "swap.warning.insufficientGas.button": "No tienes suficientes {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Intercambio de {{ tokenSymbol }} en {{networkName}}", "swap.warning.insufficientGas.button.buy": "Comprar {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Comprar con tarjeta", "swap.warning.insufficientGas.message.withNetwork": "No hay suficientes {{currencySymbol}} en {{networkName}} para intercambiar", "swap.warning.insufficientGas.message.withoutNetwork": "No hay suficientes {{currencySymbol}} para intercambiar", "swap.warning.insufficientGas.title": "No tienes suficientes {{currencySymbol}} para cubrir el costo de red", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "datos de token para en {{chainName}}", "tdp.nameNotFound": "No se encontró el nombre", "tdp.noInfoAvailable": "No hay información sobre el token disponible", + "tdp.noTestnetSupportDescription": "Algunas redes de prueba no admiten el intercambio, el envío o la compra de tokens.", "tdp.stats.unsupportedChainDescription": "Las estadísticas y gráficos del token para {{chain}} están disponibles en {{infoLink}}", "tdp.symbolNotFound": "No se encontró el símbolo", + "testnet.unsupported": "Esta funcionalidad no es compatible con el modo testnet.", "themeToggle.theme": "Tema", "title.betterPricesMoreListings": "Mejores precios. Más cotizaciones. Compra, vende e intercambia NFT en los principales mercados, como OpenSea. Explora colecciones populares.", "title.buySellTradeEthereum": "Compra, vende e intercambia Ethereum y otros tokens importantes en Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "Saldo de {{ownerAddress}}", "token.bridge": "Puente de token de {{label}}", "token.chart.tooltip": "Tarifas: {{amount}}", + "token.details.testnet.unsupported": "Los detalles del token no están disponibles para los tokens de red de prueba.", "token.error.unknown": "Token desconocido", "token.fee.buy.label": "Tarifa de compra", "token.fee.label": "tarifa", @@ -2109,6 +2130,8 @@ "transaction.warning.insufficientGas.modal.title.withoutNetwork": "No hay suficientes {{tokenSymbol}}", "transaction.watcher.error.cancel": "No se puede cancelar la transacción", "transaction.watcher.error.status": "Error al verificar el estado de la transacción", + "unichain.launch.modal.description": "Una segunda capa de Ethereum diseñada para DeFi, creada por Uniswap Labs. La red de prueba se pone en marcha hoy.", + "unichain.launch.modal.title": "Presentando Unichain", "uniswapX.aggregatesLiquidity": " combina fuentes de liquidez para obtener mejores precios e intercambios sin gas.", "uniswapx.description": "UniswapX combina fuentes de liquidez para obtener mejores precios e intercambios sin gas.", "uniswapx.included": "Incluye UniswapX ", @@ -2212,7 +2235,6 @@ "walletConnect.pending.switchAccount": "Cambiar cuenta", "walletConnect.pending.switchNetwork": "Cambiar de red", "walletConnect.pending.title": "Conectar a {{dappName}}", - "walletConnect.permissions.networks": "Redes", "walletConnect.permissions.option.transferAssets": "Transferir tus activos sin consentimiento", "walletConnect.permissions.option.viewTokenBalances": "Ver tus saldos de tokens", "walletConnect.permissions.option.viewWalletAddress": "Ver la dirección de tu wallet", diff --git a/packages/uniswap/src/i18n/locales/translations/fi-FI.json b/packages/uniswap/src/i18n/locales/translations/fi-FI.json index 47f8e9d5320..8a5e2983d26 100644 --- a/packages/uniswap/src/i18n/locales/translations/fi-FI.json +++ b/packages/uniswap/src/i18n/locales/translations/fi-FI.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Talletustunnukset {{label}} verkkoon.", "common.deposited": "Talletettu", "common.depositing": "Tallettaminen", + "common.depositTokens": "Talletustunnukset", "common.detailed.label": "Yksityiskohtainen", "common.detected": "Havaittu", "common.developers": "Kehittäjät", @@ -430,6 +431,7 @@ "common.migrate.position": "Siirrä sijainti", "common.migrated.liquidity": "Siirretty likviditeetti", "common.migrating.liquidity": "Likviditeetin siirto", + "common.min": "Min", "common.mint.cancelled": "Minttu peruttu", "common.mint.failed": "Minttu epäonnistui", "common.minted": "Mintettu", @@ -450,6 +452,7 @@ "common.noResults": "Ei tuloksia.", "common.notAvailableInRegion.error": "Ei saatavilla alueellasi", "common.notCreated.label": "Ei luotu", + "common.notSupported": "Ei tuettu", "common.oneDay": "1 päivä", "common.oneHour": "1 tunti", "common.oneMonth": "1 kuukausi", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}ja muita vaihtoehtoja", "fiatOnRamp.quote.type.other": "Muita vaihtoehtoja", "fiatOnRamp.quote.type.recent": "Äskettäin käytetty", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Voit vastaanottaa tokeneja ja NFT:itä Ethereumissa, Polygonissa, Arbitrumissa, Optimismissa, Basessa, BNB:ssä, Blastissa, Avalanchessa, Zorassa ja ZKsyncissä.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Tililtä", "fiatOnRamp.receiveCrypto.title": "Vastaanota krypto", "fiatOnRamp.receiveCrypto.transferFunds": "Rahoita lompakkoosi siirtämällä kryptoa toisesta lompakosta tai tililtä", @@ -901,6 +903,8 @@ "home.activity.error.load": "Toimintoa ei voitu ladata", "home.activity.title": "Toiminta", "home.banner.offline": "Olet offline-tilassa", + "home.banner.testnetMode": "Olet testiverkkotilassa", + "home.banner.testnetMode.nav": "Olet testiverkkotilassa. Kytke tämä pois päältä asetuksista.", "home.explore.footer": "Napauta tätä tutkiaksesi tuhansia tokeneita, NFT:itä ja paljon muuta", "home.explore.title": "Tutustu tokeneihin", "home.extension.error": "Virhe ladattaessa tilejä", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Luo asema", "nav.tabs.createV2Position": "Luo V2-paikka", "nav.tabs.createV3Position": "Luo V3-sijainti", + "nav.tabs.createV4Position": "Luo V4-asema", "nav.tabs.viewPosition": "Näytä sijainti", "network.lostConnection": "Olet ehkä menettänyt verkkoyhteytesi.", "network.mightBeDown": "{{network}} saattaa olla poissa juuri nyt tai olet ehkä menettänyt verkkoyhteytesi.", @@ -1331,6 +1336,7 @@ "pool.back": "Takaisin Pooliin", "pool.balances": "Allas saldot", "pool.claimFees": "Korvausmaksut", + "pool.claimFees.button.label": "Väite", "pool.collectAs": "Kerää muodossa {{nativeWrappedSymbol}}", "pool.collected": " Kerätty", "pool.collecting": "Kerätä", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Hallitse palkintopoolin likviditeettiä", "pool.max.label": "Max:", "pool.maxPrice": "Max hinta", + "pool.migrateToV4": "Siirrä V4:ään", "pool.min.label": "Min:", "pool.minPrice": "Min hinta", "pool.mustBeInitialized": "Tämä pooli on alustettava ennen kuin voit lisätä likviditeettiä. Alustus valitsemalla poolille aloitushinta. Syötä sitten likviditeettihintaluokkasi ja talletussummasi. Kaasumaksut ovat normaalia korkeammat alustustapahtuman vuoksi.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Vihje: Käytä tätä työkalua löytääksesi v2-varastot, jotka eivät näy automaattisesti käyttöliittymässä.", "pools.approving.amount": "Hyväksytään {{amount}}", "position.addHook": "Lisää koukku", + "position.addHook.tooltip": "Koukut ovat edistyksellinen ominaisuus, jonka avulla poolit voivat olla vuorovaikutuksessa älykkäiden sopimusten kanssa, mikä vapauttaa erilaisia ominaisuuksia. Ole varovainen lisääessäsi koukkuja, koska jotkut niistä voivat olla haitallisia tai aiheuttaa ei-toivottuja seurauksia.", "position.appearHere": "Sijaintisi näkyy täällä.", + "position.create.modal.header": "Aseman luominen", "position.currentValue": "Nykyisen sijainnin arvo", + "position.deposit.description": "Määritä maksuvalmiusosuutesi tunnusmäärät.", "position.depositedCurrency": "Talletettu {{currencySymbol}}", "position.migrate.liquidity": "Kun siirrät paikkoja, et voi vaihtaa merkkiparia, mutta voit lisätä koukun toiminnallisuuden parantamiseksi.", "position.new": "Uusi asema", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Yhdistetään...", "qrScanner.status.loading": "Ladataan...", "qrScanner.title": "Skannaa QR-koodi", - "qrScanner.wallet.title": "Voit vastaanottaa tokeneja ja NFT:itä Ethereumissa, Polygonissa, Arbitrumissa, Optimismissa, Basessa, ZKsyncissä, Zorassa, Avalanchessa, Celossa, Blastissa ja BNB Chainissa.", + "qrScanner.wallet.networks": "Tuetut verkot", + "qrScanner.wallet.title": "Voit lähettää ja vastaanottaa tunnuksia ja NFT:itä kaikissa {{numOfNetworks}} tuetuissa verkoissamme.", "removeLiquidity.collectFees": "Saat myös tästä paikasta ansaitut palkkiot.", "removeLiquidity.outputEstimated": "Tuotos on arvioitu. Jos hinta muuttuu enemmän kuin {{allowed}}%, tapahtumasi palautetaan.", "removeLiquidity.pendingText": "Poistetaan {{amtA}} {{symA}} ja {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Nimimerkki", "settings.setting.wallet.notifications.title": "Ilmoitukset", "settings.setting.wallet.preferences.title": "Lompakon asetukset", - "settings.showTestNets": "Näytä testiverkot", + "settings.setting.wallet.testnetMode.description": "Tämä ottaa testiverkot käyttöön, jotta kehittäjät voivat kokeilla ominaisuuksia ja tapahtumia ilman todellista omaisuutta. Testiverkkojen tokeneilla ei ole todellista arvoa.", + "settings.setting.wallet.testnetMode.title": "Testnet-tila", "settings.switchNetwork.warning": "Jos haluat käyttää Uniswap-toimintoa {{label}}, vaihda verkkoa lompakkosi asetuksista.", "settings.title": "asetukset", "settings.version": "Versio {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Uusi tulo", "swap.details.newQuote.output": "Uusi lähtö", "swap.details.orderRouting": "Tilauksen reititys", - "swap.details.orderRoutingInfo": "Hintasi sisältää jo verkkokulut kohdeverkossa ja 0,05 % Across-maksun.", + "swap.details.orderRoutingInfo": "Tämä vaihto reititetään Across-protokollan kautta, joka on hajautettu protokolla, joka siirtää omaisuutta verkkojen välillä ja asettaa etusijalle turvallisuuden, nopean suorituskyvyn ja alhaiset hinnat.", + "swap.details.poweredBy": "Voimanlähteenä", "swap.details.rate": "Rate", "swap.details.slippage": "Max liukuminen", "swap.details.uniswapFee": "Maksu", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Uniswap-asiakas valitsee edullisimman kauppavaihtoehdon, joka laskee huomioon hinnan ja verkkokustannukset.", "swap.settings.routingPreference.option.v2.title": "v2 poolit", "swap.settings.routingPreference.option.v3.title": "v3 poolit", + "swap.settings.routingPreference.option.v4.title": "v4 poolit", "swap.settings.routingPreference.title": "Kaupan vaihtoehdot", "swap.settings.slippage.control.auto": "Auto", "swap.settings.slippage.description": "Tapahtumasi palautuu, jos hinta muuttuu enemmän kuin lipsumaprosentti.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Tapahtumasi palautuu, jos hinta muuttuu epäsuotuisasti enemmän kuin tämä prosenttiosuus.", "swap.signAndSwap": "Allekirjoita ja vaihda", "swap.slippage.amt": "{{amt}} lipsahdus", + "swap.slippage.bridging": "Ei liukumista vaihdettaessa verkkojen välillä", "swap.slippage.settings.title": "Liukastumisen asetukset", "swap.slippage.tooltip": "Maksutapahtumaa edeltänyt enimmäishinnan muutos palautuu.", "swap.slippageBelow.warning": "Liukuminen alle {{amt}} voi johtaa epäonnistuneeseen tapahtumaan", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Kaupan määräaika", "swap.transaction.revertAfter": "Tapahtumasi palautetaan, jos se on vireillä yli tämän ajanjakson.", "swap.unsupportedAssets.readMore": "Lue lisää tukemattomista sisällöistä", + "swap.warning.enterLargerAmount.title": "Syötä suurempi summa", "swap.warning.expectedFailure": "Tämän kaupan odotetaan epäonnistuvan", "swap.warning.feeOnTransfer.message": "Jotkin rahakkeet veloittavat osto- tai myyntimaksun, jonka tunnuksen myöntäjä määrittää. Uniswap ei saa osuutta näistä maksuista.", "swap.warning.feeOnTransfer.title": "Miksi on lisämaksu?", "swap.warning.insufficientBalance.title": "Sinulla ei ole tarpeeksi {{currencySymbol}}", "swap.warning.insufficientGas.button": "Ei tarpeeksi {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Vaihda arvoon {{ tokenSymbol }} , {{networkName}}", "swap.warning.insufficientGas.button.buy": "Osta {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Osta kortilla", "swap.warning.insufficientGas.message.withNetwork": "Ei tarpeeksi {{currencySymbol}} {{networkName}} vaihtamiseen", "swap.warning.insufficientGas.message.withoutNetwork": "Ei tarpeeksi {{currencySymbol}} vaihtamiseen", "swap.warning.insufficientGas.title": "Sinulla ei ole tarpeeksi {{currencySymbol}} kattamaan verkkokustannuksia", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "merkkidata osoitteessa {{chainName}}", "tdp.nameNotFound": "Nimeä ei löydy", "tdp.noInfoAvailable": "Tunnustietoja ei ole saatavilla", + "tdp.noTestnetSupportDescription": "Jotkut testiverkot eivät tue tunnuksien vaihtamista, lähettämistä tai ostamista.", "tdp.stats.unsupportedChainDescription": "Token-tilastot ja kaaviot kohteelle {{chain}} ovat saatavilla osoitteessa {{infoLink}}", "tdp.symbolNotFound": "Symbolia ei löydy", + "testnet.unsupported": "Tätä toimintoa ei tueta testnet-tilassa.", "themeToggle.theme": "Teema", "title.betterPricesMoreListings": "Paremmat hinnat. Lisää listauksia. Osta, myy ja vaihda NFT-tuotteita parhailla markkinapaikoilla, kuten OpenSea. Tutustu trendikkäisiin kokoelmiin.", "title.buySellTradeEthereum": "Osta, myy ja vaihda Ethereumia ja muita huipputokeneita Uniswapissa", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "{{ownerAddress}}:n saldo", "token.bridge": "{{label}} merkkisilta", "token.chart.tooltip": "Maksut: {{amount}}", + "token.details.testnet.unsupported": "Tokenin tiedot eivät ole saatavilla testnet-tunnuksille.", "token.error.unknown": "Tuntematon tunnus", "token.fee.buy.label": "ostaa maksu", "token.fee.label": "maksu", @@ -2212,7 +2233,6 @@ "walletConnect.pending.switchAccount": "Vaihda tiliä", "walletConnect.pending.switchNetwork": "Vaihda verkkoa", "walletConnect.pending.title": "Yhdistä {{dappName}}", - "walletConnect.permissions.networks": "Verkot", "walletConnect.permissions.option.transferAssets": "Siirrä omaisuuttasi ilman lupaa", "walletConnect.permissions.option.viewTokenBalances": "Tarkastele token-saldoasi", "walletConnect.permissions.option.viewWalletAddress": "Katso lompakkosi osoite", diff --git a/packages/uniswap/src/i18n/locales/translations/fil-PH.json b/packages/uniswap/src/i18n/locales/translations/fil-PH.json index 48d911880ab..0fe88f08993 100644 --- a/packages/uniswap/src/i18n/locales/translations/fil-PH.json +++ b/packages/uniswap/src/i18n/locales/translations/fil-PH.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Magdeposito ng mga token sa {{label}} network.", "common.deposited": "Nakadeposito", "common.depositing": "Pagdedeposito", + "common.depositTokens": "Mga token ng deposito", "common.detailed.label": "Detalyadong", "common.detected": "Natukoy", "common.developers": "Mga developer", @@ -430,6 +431,7 @@ "common.migrate.position": "Lumipat ng posisyon", "common.migrated.liquidity": "Lumipat na pagkatubig", "common.migrating.liquidity": "Ang paglipat ng pagkatubig", + "common.min": "Min", "common.mint.cancelled": "Kinansela ang Mint", "common.mint.failed": "Nabigo ang Mint", "common.minted": "Minted", @@ -450,6 +452,7 @@ "common.noResults": "Walang nakitang resulta.", "common.notAvailableInRegion.error": "Hindi available sa iyong rehiyon", "common.notCreated.label": "Hindi nilikha", + "common.notSupported": "Hindi suportado", "common.oneDay": "1 araw", "common.oneHour": "1 oras", "common.oneMonth": "1 buwan", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}, at iba pang mga opsyon", "fiatOnRamp.quote.type.other": "Iba pang mga pagpipilian", "fiatOnRamp.quote.type.recent": "Ginamit kamakailan", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Maaari kang makatanggap ng mga token at NFT sa Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora, at ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Mula sa isang account", "fiatOnRamp.receiveCrypto.title": "Tumanggap ng crypto", "fiatOnRamp.receiveCrypto.transferFunds": "Pondohan ang iyong wallet sa pamamagitan ng paglilipat ng crypto mula sa ibang wallet o account", @@ -901,6 +903,8 @@ "home.activity.error.load": "Hindi ma-load ang aktibidad", "home.activity.title": "Aktibidad", "home.banner.offline": "Nasa offline mode ka", + "home.banner.testnetMode": "Nasa testnet mode ka", + "home.banner.testnetMode.nav": "Nasa testnet mode ka. I-toggle ito sa mga setting.", "home.explore.footer": "I-tap ang “search” para mag-explore pa", "home.explore.title": "I-explore ang mga token", "home.extension.error": "Error sa paglo-load ng mga account", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Lumikha ng posisyon", "nav.tabs.createV2Position": "Lumikha ng V2 na posisyon", "nav.tabs.createV3Position": "Lumikha ng V3 na posisyon", + "nav.tabs.createV4Position": "Lumikha ng V4 na posisyon", "nav.tabs.viewPosition": "Tingnan ang posisyon", "network.lostConnection": "Maaaring nawala ang iyong koneksyon sa network.", "network.mightBeDown": "{{network}} maaaring down ngayon, o maaaring nawala ang iyong koneksyon sa network.", @@ -1331,6 +1336,7 @@ "pool.back": "Bumalik sa Pool", "pool.balances": "Mga balanse ng pool", "pool.claimFees": "Mga bayarin sa pag-claim", + "pool.claimFees.button.label": "Claim", "pool.collectAs": "Kolektahin bilang {{nativeWrappedSymbol}}", "pool.collected": " Nakolekta", "pool.collecting": "Nangongolekta", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Pamahalaan ang pagkatubig sa pool ng mga reward", "pool.max.label": "Max:", "pool.maxPrice": "Pinakamataas na presyo", + "pool.migrateToV4": "Lumipat sa V4", "pool.min.label": "Min:", "pool.minPrice": "Min na presyo", "pool.mustBeInitialized": "Dapat masimulan ang pool na ito bago ka makapagdagdag ng liquidity. Upang magsimula, pumili ng panimulang presyo para sa pool. Pagkatapos, ilagay ang iyong hanay ng presyo ng pagkatubig at halaga ng deposito. Ang mga bayarin sa gas ay mas mataas kaysa karaniwan dahil sa transaksyon sa pagsisimula.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Tip: Gamitin ang tool na ito upang maghanap ng mga v2 pool na hindi awtomatikong lumalabas sa interface.", "pools.approving.amount": "Inaprubahan ang {{amount}}", "position.addHook": "Magdagdag ng Hook", + "position.addHook.tooltip": "Ang Hooks ay isang advanced na feature na nagbibigay-daan sa mga pool na makipag-ugnayan sa mga smart contract, na nag-a-unlock ng iba't ibang kakayahan. Mag-ingat kapag nagdaragdag ng mga kawit, dahil ang ilan ay maaaring malisyoso o magdulot ng hindi sinasadyang mga kahihinatnan.", "position.appearHere": "Ang iyong posisyon ay lilitaw dito.", + "position.create.modal.header": "Paglikha ng posisyon", "position.currentValue": "Kasalukuyang halaga ng posisyon", + "position.deposit.description": "Tukuyin ang mga halaga ng token para sa iyong kontribusyon sa pagkatubig.", "position.depositedCurrency": "Na-deposito {{currencySymbol}}", "position.migrate.liquidity": "Kapag naglilipat ng mga posisyon, hindi mo mababago ang iyong pares ng token, ngunit maaari kang magdagdag ng hook upang mapahusay ang pagpapagana.", "position.new": "Bagong Posisyon", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Kumokonekta...", "qrScanner.status.loading": "Naglo-load...", "qrScanner.title": "Mag-scan ng QR code", - "qrScanner.wallet.title": "Maaari kang makatanggap ng mga token at NFT sa Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast, at BNB Chain.", + "qrScanner.wallet.networks": "Mga suportadong Network", + "qrScanner.wallet.title": "Maaari kang magpadala at tumanggap ng mga token at NFT sa lahat ng aming {{numOfNetworks}} suportadong network.", "removeLiquidity.collectFees": "Mangongolekta ka rin ng mga bayad na nakuha mula sa posisyong ito.", "removeLiquidity.outputEstimated": "Tinatantya ang output. Kung magbago ang presyo ng higit sa {{allowed}}% ang iyong transaksyon ay babalik.", "removeLiquidity.pendingText": "Tinatanggal ang {{amtA}} {{symA}} at {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "palayaw", "settings.setting.wallet.notifications.title": "Mga abiso", "settings.setting.wallet.preferences.title": "Mga kagustuhan sa pitaka", - "settings.showTestNets": "Ipakita ang mga testnet", + "settings.setting.wallet.testnetMode.description": "Ino-on nito ang mga testnet para sa mga developer na subukan ang mga feature at transaksyon nang hindi gumagamit ng mga totoong asset. Ang mga token sa testnets ay walang tunay na halaga.", + "settings.setting.wallet.testnetMode.title": "Testnet mode", "settings.switchNetwork.warning": "Upang gamitin ang Uniswap sa {{label}}, ilipat ang network sa mga setting ng iyong wallet.", "settings.title": "Mga setting", "settings.version": "Bersyon {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Bagong input", "swap.details.newQuote.output": "Bagong output", "swap.details.orderRouting": "Pagruruta ng order", - "swap.details.orderRoutingInfo": "Kasama na sa iyong presyo ang mga gastos sa network sa patutunguhang network at isang 0.05% Across fee.", + "swap.details.orderRoutingInfo": "Ang swap na ito ay niruruta sa pamamagitan ng Across, isang desentralisadong protocol na naglilipat ng mga asset sa mga network habang inuuna ang kaligtasan, mabilis na pagpapatupad, at mababang presyo.", + "swap.details.poweredBy": "Pinapatakbo ng", "swap.details.rate": "Rate", "swap.details.slippage": "Max slippage", "swap.details.uniswapFee": "Bayad", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Pinipili ng Uniswap client ang pinakamurang trade option factoring price at mga gastos sa network.", "swap.settings.routingPreference.option.v2.title": "v2 pool", "swap.settings.routingPreference.option.v3.title": "v3 pool", + "swap.settings.routingPreference.option.v4.title": "v4 na pool", "swap.settings.routingPreference.title": "Mga pagpipilian sa kalakalan", "swap.settings.slippage.control.auto": "Auto", "swap.settings.slippage.description": "Babalik ang iyong transaksyon kung magbago ang presyo nang higit sa porsyento ng slippage.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Ang iyong transaksyon ay babalik kung ang presyo ay nagbago nang hindi maganda ng higit sa porsyentong ito.", "swap.signAndSwap": "Pumirma at magpalit", "swap.slippage.amt": "{{amt}} pagkadulas", + "swap.slippage.bridging": "Walang slippage kapag nagpapalitan sa mga network", "swap.slippage.settings.title": "Max slippage", "swap.slippage.tooltip": "Ang pinakamataas na paggalaw ng presyo bago ang iyong transaksyon ay babalik.", "swap.slippageBelow.warning": "Ang slippage sa ibaba {{amt}} ay maaaring magresulta sa isang nabigong transaksyon", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Deadline ng transaksyon", "swap.transaction.revertAfter": "Ang iyong transaksyon ay babalik kung ito ay nakabinbin nang higit sa panahong ito.", "swap.unsupportedAssets.readMore": "Magbasa pa tungkol sa mga hindi sinusuportahang asset", + "swap.warning.enterLargerAmount.title": "Maglagay ng mas malaking halaga", "swap.warning.expectedFailure": "Inaasahang mabibigo ang transaksyong ito", "swap.warning.feeOnTransfer.message": "May bayad ang ilang token kapag binili o ibinenta ang mga ito, na itinakda ng nagbigay ng token. Ang Uniswap ay hindi tumatanggap ng anumang bahagi ng mga bayarin na ito.", "swap.warning.feeOnTransfer.title": "Bakit may karagdagang bayad?", "swap.warning.insufficientBalance.title": "Wala kang sapat na {{currencySymbol}}", "swap.warning.insufficientGas.button": "Hindi sapat {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Magpalit ng {{ tokenSymbol }} sa {{networkName}}", "swap.warning.insufficientGas.button.buy": "Bumili ng {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Bumili gamit ang card", "swap.warning.insufficientGas.message.withNetwork": "Hindi sapat {{currencySymbol}} sa {{networkName}} para magpalit", "swap.warning.insufficientGas.message.withoutNetwork": "Hindi sapat {{currencySymbol}} para magpalit", "swap.warning.insufficientGas.title": "Wala kang sapat na {{currencySymbol}} para mabayaran ang halaga ng network", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "data ng token para sa sa {{chainName}}", "tdp.nameNotFound": "Hindi nahanap ang pangalan", "tdp.noInfoAvailable": "Walang magagamit na impormasyon ng token", + "tdp.noTestnetSupportDescription": "Ang ilang mga testnet ay hindi sumusuporta sa pagpapalit, pagpapadala, o pagbili ng mga token.", "tdp.stats.unsupportedChainDescription": "Ang mga istatistika at chart ng token para sa {{chain}} ay available sa {{infoLink}}", "tdp.symbolNotFound": "Hindi natagpuan ang simbolo", + "testnet.unsupported": "Hindi sinusuportahan ang functionality na ito sa testnet mode.", "themeToggle.theme": "Tema", "title.betterPricesMoreListings": "Mas magandang presyo. Higit pang mga listahan. Bumili, magbenta, at mag-trade ng mga NFT sa mga nangungunang marketplace tulad ng OpenSea. I-explore ang mga trending na koleksyon.", "title.buySellTradeEthereum": "Bumili, magbenta at mag-trade ng Ethereum at iba pang nangungunang token sa Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "Balanse ni {{ownerAddress}}", "token.bridge": "{{label}} token bridge", "token.chart.tooltip": "Mga Bayarin: {{amount}}", + "token.details.testnet.unsupported": "Hindi available ang mga detalye ng token para sa mga testnet token.", "token.error.unknown": "Hindi kilalang token", "token.fee.buy.label": "bayad sa pagbili", "token.fee.label": "bayad", @@ -2109,6 +2130,8 @@ "transaction.warning.insufficientGas.modal.title.withoutNetwork": "Hindi sapat {{tokenSymbol}}", "transaction.watcher.error.cancel": "Hindi makansela ang transaksyon", "transaction.watcher.error.status": "Error habang sinusuri ang status ng transaksyon", + "unichain.launch.modal.description": "Isang Ethereum L2 na idinisenyo para sa DeFi, na binuo ng Uniswap Labs. Ang Testnet ay magiging live ngayon.", + "unichain.launch.modal.title": "Ipinapakilala ang Unichain", "uniswapX.aggregatesLiquidity": "Pinagsasama-sama ng ang mga mapagkukunan ng pagkatubig para sa mas magandang presyo at mga palitan ng libreng gas.", "uniswapx.description": "Pinagsasama-sama ng UniswapX ang mga mapagkukunan ng pagkatubig para sa mas mahusay na mga presyo at mga libreng pagpapalit ng gas.", "uniswapx.included": "May kasamang UniswapX ", @@ -2212,7 +2235,6 @@ "walletConnect.pending.switchAccount": "Lumipat ng Account", "walletConnect.pending.switchNetwork": "Lumipat ng Network", "walletConnect.pending.title": "Kumonekta sa {{dappName}}", - "walletConnect.permissions.networks": "Mga network", "walletConnect.permissions.option.transferAssets": "Ilipat ang iyong mga asset nang walang pahintulot", "walletConnect.permissions.option.viewTokenBalances": "Tingnan ang iyong mga balanse ng token", "walletConnect.permissions.option.viewWalletAddress": "Tingnan ang iyong wallet address", diff --git a/packages/uniswap/src/i18n/locales/translations/fr-FR.json b/packages/uniswap/src/i18n/locales/translations/fr-FR.json index b347698f08e..e5216070049 100644 --- a/packages/uniswap/src/i18n/locales/translations/fr-FR.json +++ b/packages/uniswap/src/i18n/locales/translations/fr-FR.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Déposer les tokens sur le réseau {{label}}.", "common.deposited": "Déposé", "common.depositing": "Dépôt en cours", + "common.depositTokens": "Jetons de dépôt", "common.detailed.label": "Détaillé", "common.detected": "Détecté", "common.developers": "Développeurs", @@ -430,6 +431,7 @@ "common.migrate.position": "Migrer position", "common.migrated.liquidity": "Liquidités migrées", "common.migrating.liquidity": "Migration des liquidités", + "common.min": "Min", "common.mint.cancelled": "Frappe annulée", "common.mint.failed": "Échec de la frappe", "common.minted": "Frappée", @@ -450,6 +452,7 @@ "common.noResults": "Aucun résultat trouvé.", "common.notAvailableInRegion.error": "Non disponible dans votre région", "common.notCreated.label": "Non créé", + "common.notSupported": "Non pris en charge", "common.oneDay": "1 jour", "common.oneHour": "1 heure", "common.oneMonth": "1 mois", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}, et d’autres options", "fiatOnRamp.quote.type.other": "Autres options", "fiatOnRamp.quote.type.recent": "Utilisé récemment", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Vous pouvez recevoir des tokens et des NFT sur Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora et ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Depuis un compte", "fiatOnRamp.receiveCrypto.title": "Recevoir des cryptos", "fiatOnRamp.receiveCrypto.transferFunds": "Alimentez votre wallet en transférant des cryptos depuis un autre wallet ou compte", @@ -901,6 +903,8 @@ "home.activity.error.load": "Impossible de charger l’activité", "home.activity.title": "Activité", "home.banner.offline": "Vous êtes en mode hors ligne", + "home.banner.testnetMode": "Vous êtes en mode testnet", + "home.banner.testnetMode.nav": "Vous êtes en mode testnet. Désactivez cette option dans les paramètres.", "home.explore.footer": "Appuyer sur « Rechercher » pour explorer davantage", "home.explore.title": "Découvrir les tokens", "home.extension.error": "Erreur lors du chargement des comptes", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Créer une position", "nav.tabs.createV2Position": "Créer une position V2", "nav.tabs.createV3Position": "Créer une position V3", + "nav.tabs.createV4Position": "Créer une position V4", "nav.tabs.viewPosition": "Voir la position", "network.lostConnection": "Vous avez peut-être perdu votre connexion réseau.", "network.mightBeDown": "Il est possible que {{network}} ne fonctionne pas en ce moment ou que vous ayez perdu votre connexion réseau.", @@ -1331,6 +1336,7 @@ "pool.back": "Retour au pool", "pool.balances": "Soldes des pools", "pool.claimFees": "Demander les frais", + "pool.claimFees.button.label": "Réclamer", "pool.collectAs": "Collecter en tant que {{nativeWrappedSymbol}}", "pool.collected": " Collecté", "pool.collecting": "Collecte en cours", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Gérer les liquidités du pool de récompenses", "pool.max.label": "Max :", "pool.maxPrice": "Prix max", + "pool.migrateToV4": "Migrer vers V4", "pool.min.label": "Min :", "pool.minPrice": "Prix min", "pool.mustBeInitialized": "Ce pool doit être initialisé avant de pouvoir ajouter des liquidités. Pour l'initialiser, sélectionnez un prix de départ pour le pool. Ensuite, entrez votre fourchette de prix de liquidités et le montant du dépôt. Les frais de gaz seront plus élevés que d’habitude en raison de la transaction d’initialisation.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Astuce : utilisez cet outil pour rechercher les pools v2 qui n’apparaissent pas automatiquement dans l’interface.", "pools.approving.amount": "Approbation de {{amount}}", "position.addHook": "Ajouter un crochet", + "position.addHook.tooltip": "Les hooks sont une fonctionnalité avancée qui permet aux pools d'interagir avec les contrats intelligents, débloquant ainsi diverses fonctionnalités. Soyez prudent lorsque vous ajoutez des hooks, car certains peuvent être malveillants ou entraîner des conséquences imprévues.", "position.appearHere": "Votre position apparaîtra ici.", + "position.create.modal.header": "Création de poste", "position.currentValue": "Valeur de la position actuelle", + "position.deposit.description": "Précisez les montants symboliques de votre contribution en liquidité.", "position.depositedCurrency": "Déposé {{currencySymbol}}", "position.migrate.liquidity": "Lors de la migration de positions, vous ne pouvez pas modifier votre paire de jetons, mais vous pouvez ajouter un hook pour améliorer les fonctionnalités.", "position.new": "Nouveau poste", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Connexion en cours…", "qrScanner.status.loading": "Chargement en cours…", "qrScanner.title": "Scanner un code QR", - "qrScanner.wallet.title": "Vous pouvez recevoir des tokens et des NFT sur Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast et BNB Chain.", + "qrScanner.wallet.networks": "Réseaux pris en charge", + "qrScanner.wallet.title": "Vous pouvez envoyer et recevoir des jetons et des NFT sur tous nos {{numOfNetworks}} réseaux pris en charge.", "removeLiquidity.collectFees": "Vous collecterez également les frais gagnés pour cette position.", "removeLiquidity.outputEstimated": "La sortie est une estimation. Si le prix change de plus de {{allowed}} %, votre transaction sera annulée.", "removeLiquidity.pendingText": "Retrait de {{amtA}} {{symA}} et {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Pseudo", "settings.setting.wallet.notifications.title": "Notifications", "settings.setting.wallet.preferences.title": "Préférences du wallet", - "settings.showTestNets": "Afficher les réseaux de test", + "settings.setting.wallet.testnetMode.description": "Cela active les réseaux de test pour que les développeurs puissent tester des fonctionnalités et des transactions sans utiliser de ressources réelles. Les jetons sur les réseaux de test n'ont aucune valeur réelle.", + "settings.setting.wallet.testnetMode.title": "Mode réseau de test", "settings.switchNetwork.warning": "Pour utiliser Uniswap sur {{label}}, changez le réseau dans les paramètres de votre wallet.", "settings.title": "Paramètres", "settings.version": "Version {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Nouvelle entrée", "swap.details.newQuote.output": "Nouvelle sortie", "swap.details.orderRouting": "Routage des commandes", - "swap.details.orderRoutingInfo": "Votre prix inclut déjà les coûts de réseau sur le réseau de destination et des frais Across de 0,05 %.", + "swap.details.orderRoutingInfo": "Cet échange est acheminé via Across, un protocole décentralisé qui déplace les actifs sur les réseaux tout en privilégiant la sécurité, l'exécution rapide et les prix bas.", + "swap.details.poweredBy": "Alimenté par", "swap.details.rate": "Taux", "swap.details.slippage": "Slippage max", "swap.details.uniswapFee": "Frais", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Le client Uniswap sélectionne l’option de transaction la moins chère en tenant compte du prix et des frais de réseau.", "swap.settings.routingPreference.option.v2.title": "Pools v2", "swap.settings.routingPreference.option.v3.title": "Pools v3", + "swap.settings.routingPreference.option.v4.title": "Pools v4", "swap.settings.routingPreference.title": "Options d’échange", "swap.settings.slippage.control.auto": "Automatique", "swap.settings.slippage.description": "Votre transaction sera annulée si le prix change plus que le pourcentage de slippage.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Votre transaction sera annulée si le prix change de façon défavorable d’un montant supérieur à ce pourcentage.", "swap.signAndSwap": "Signer et échanger", "swap.slippage.amt": "Slippage de {{amt}}", + "swap.slippage.bridging": "Aucun glissement lors de l'échange entre réseaux", "swap.slippage.settings.title": "Slippage max", "swap.slippage.tooltip": "Mouvement de prix maximum avant que votre transaction ne soit annulée.", "swap.slippageBelow.warning": "Un slippage inférieur à {{amt}} peut entraîner l’échec d’une transaction", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Date limite de transaction", "swap.transaction.revertAfter": "Votre transaction sera annulée si elle est en attente pendant une durée supérieure à cette période.", "swap.unsupportedAssets.readMore": "En savoir plus sur les actifs non pris en charge", + "swap.warning.enterLargerAmount.title": "Entrez un montant plus grand", "swap.warning.expectedFailure": "Cette transaction devrait échouer", "swap.warning.feeOnTransfer.message": "Certains tokens prélèvent des frais lors de leur achat ou de leur vente, qui sont fixés par leur émetteur. Uniswap ne reçoit aucune part de ces frais.", "swap.warning.feeOnTransfer.title": "Pourquoi des frais supplémentaires sont-ils facturés ?", "swap.warning.insufficientBalance.title": "Vous n’avez pas assez de {{currencySymbol}}", "swap.warning.insufficientGas.button": "Pas assez de {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Échangez contre {{ tokenSymbol }} sur {{networkName}}", "swap.warning.insufficientGas.button.buy": "Acheter {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Acheter avec une carte", "swap.warning.insufficientGas.message.withNetwork": "Pas assez de {{currencySymbol}} sur {{networkName}} pour échanger", "swap.warning.insufficientGas.message.withoutNetwork": "Pas assez {{currencySymbol}} pour échanger", "swap.warning.insufficientGas.title": "Vous n’avez pas assez de {{currencySymbol}} pour couvrir les frais de réseau", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "données de token pour sur {{chainName}}", "tdp.nameNotFound": "Nom introuvable", "tdp.noInfoAvailable": "Aucune information sur le token disponible", + "tdp.noTestnetSupportDescription": "Certains réseaux de test ne prennent pas en charge l'échange, l'envoi ou l'achat de jetons.", "tdp.stats.unsupportedChainDescription": "Les statistiques et graphiques des tokens pour {{chain}} sont disponibles sur {{infoLink}}", "tdp.symbolNotFound": "Symbole introuvable", + "testnet.unsupported": "Cette fonctionnalité n'est pas prise en charge en mode testnet.", "themeToggle.theme": "Thème", "title.betterPricesMoreListings": "Meilleurs prix. Autres listes. Achetez, vendez et échangez des NFT sur les principales places de marché comme OpenSea. Découvrez les collections tendance.", "title.buySellTradeEthereum": "Achetez, vendez et échangez Ethereum et d’autres tokens de premier plan sur Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "Solde de {{ownerAddress}}", "token.bridge": "Pont de tokens {{label}}", "token.chart.tooltip": "Frais : {{amount}}", + "token.details.testnet.unsupported": "Les détails des jetons ne sont pas disponibles pour les jetons de testnet.", "token.error.unknown": "Token inconnu", "token.fee.buy.label": "Frais d'achat", "token.fee.label": "frais", @@ -2212,7 +2233,6 @@ "walletConnect.pending.switchAccount": "Changer de compte", "walletConnect.pending.switchNetwork": "Changer de réseau", "walletConnect.pending.title": "Se connecter à {{dappName}}", - "walletConnect.permissions.networks": "Réseaux", "walletConnect.permissions.option.transferAssets": "Transférer vos actifs sans consentement", "walletConnect.permissions.option.viewTokenBalances": "Consulter vos soldes de tokens", "walletConnect.permissions.option.viewWalletAddress": "Afficher l’adresse de votre wallet", diff --git a/packages/uniswap/src/i18n/locales/translations/he-IL.json b/packages/uniswap/src/i18n/locales/translations/he-IL.json index ba8f32bbb3a..2e92c2be1e3 100644 --- a/packages/uniswap/src/i18n/locales/translations/he-IL.json +++ b/packages/uniswap/src/i18n/locales/translations/he-IL.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "הפקד אסימונים לרשת {{label}} .", "common.deposited": "הופקד", "common.depositing": "הפקדה", + "common.depositTokens": "אסימוני הפקדה", "common.detailed.label": "מְפוֹרָט", "common.detected": "זוהה", "common.developers": "מפתחים", @@ -430,6 +431,7 @@ "common.migrate.position": "העבר עמדה", "common.migrated.liquidity": "נזילות עברה", "common.migrating.liquidity": "העברת נזילות", + "common.min": "מינימום", "common.mint.cancelled": "המנטה בוטלה", "common.mint.failed": "מנטה נכשלה", "common.minted": "טָבוּעַ", @@ -450,6 +452,7 @@ "common.noResults": "לא נמצאו תוצאות.", "common.notAvailableInRegion.error": "לא זמין באזור שלך", "common.notCreated.label": "לא נוצר", + "common.notSupported": "לא נתמך", "common.oneDay": "יום 1", "common.oneHour": "1 שעה", "common.oneMonth": "1 חודש", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}ואפשרויות אחרות", "fiatOnRamp.quote.type.other": "אפשרויות אחרות", "fiatOnRamp.quote.type.recent": "שומש לאחרונה", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "אתה יכול לקבל אסימונים ו-NFT על Ethereum, Polygon, Arbitrum, אופטימיות, Base, BNB, Blast, Avalanche, Zora ו-ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "מתוך חשבון", "fiatOnRamp.receiveCrypto.title": "קבל קריפטו", "fiatOnRamp.receiveCrypto.transferFunds": "ממן את הארנק שלך על ידי העברת קריפטו מארנק או חשבון אחר", @@ -901,6 +903,8 @@ "home.activity.error.load": "לא ניתן היה לטעון את הפעילות", "home.activity.title": "פעילות", "home.banner.offline": "אתה במצב לא מקוון", + "home.banner.testnetMode": "אתה במצב testnet", + "home.banner.testnetMode.nav": "אתה במצב testnet. כבה את זה בהגדרות.", "home.explore.footer": "הקש כאן כדי לחקור אלפי אסימונים, NFTs ועוד", "home.explore.title": "חקור אסימונים", "home.extension.error": "שגיאה בטעינת חשבונות", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "צור עמדה", "nav.tabs.createV2Position": "צור עמדת V2", "nav.tabs.createV3Position": "צור מיקום V3", + "nav.tabs.createV4Position": "צור עמדת V4", "nav.tabs.viewPosition": "צפה במיקום", "network.lostConnection": "ייתכן שאיבדת את החיבור לרשת.", "network.mightBeDown": "ייתכן ש-{{network}} מושבת כרגע, או שאיבדת את החיבור לרשת.", @@ -1331,6 +1336,7 @@ "pool.back": "חזרה לבריכה", "pool.balances": "יתרות בריכה", "pool.claimFees": "דמי תביעה", + "pool.claimFees.button.label": "תְבִיעָה", "pool.collectAs": "איסוף בתור {{nativeWrappedSymbol}}", "pool.collected": " נאסף", "pool.collecting": "איסוף", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "נהל נזילות במאגר התגמולים", "pool.max.label": "מקסימום:", "pool.maxPrice": "מחיר מקסימלי", + "pool.migrateToV4": "העבר ל-V4", "pool.min.label": "מינימום:", "pool.minPrice": "מחיר מינימלי", "pool.mustBeInitialized": "יש לאתחל את המאגר הזה לפני שתוכל להוסיף נזילות. לאתחול, בחר מחיר התחלתי לבריכה. לאחר מכן, הזן את טווח מחירי הנזילות ואת סכום ההפקדה. עמלות הגז יהיו גבוהות מהרגיל עקב עסקת האתחול.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "טיפ: השתמש בכלי זה כדי למצוא בריכות v2 שאינן מופיעות אוטומטית בממשק.", "pools.approving.amount": "מאשר {{amount}}", "position.addHook": "הוסף הוק", + "position.addHook.tooltip": "הוקס הם תכונה מתקדמת המאפשרת לבריכות לקיים אינטראקציה עם חוזים חכמים, ולפתוח יכולות שונות. היזהר בעת הוספת ווים, מכיוון שחלקם עלולים להיות זדוניים או לגרום לתוצאות לא רצויות.", "position.appearHere": "עמדתך תופיע כאן.", + "position.create.modal.header": "יצירת עמדה", "position.currentValue": "ערך המיקום הנוכחי", + "position.deposit.description": "ציין את הסכומים הסמליים עבור תרומת הנזילות שלך.", "position.depositedCurrency": "הופקד {{currencySymbol}}", "position.migrate.liquidity": "בעת העברת עמדות, אינך יכול לשנות את צמד האסימונים שלך, אך אתה יכול להוסיף וו כדי לשפר את הפונקציונליות.", "position.new": "עמדה חדשה", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "מְקַשֵׁר...", "qrScanner.status.loading": "טוען...", "qrScanner.title": "סרוק קוד QR", - "qrScanner.wallet.title": "אתה יכול לקבל אסימונים ו-NFT על Ethereum, Polygon, Arbitrum, אופטימיות, Base, ZKsync, Zora, Avalanche, Celo, Blast ו-BNB Chain.", + "qrScanner.wallet.networks": "רשתות נתמכות", + "qrScanner.wallet.title": "אתה יכול לשלוח ולקבל אסימונים ו-NFT בכל הרשתות הנתמכות {{numOfNetworks}} שלנו.", "removeLiquidity.collectFees": "כמו כן, תגבה עמלות שנצברו מתפקיד זה.", "removeLiquidity.outputEstimated": "התפוקה מוערכת. אם המחיר ישתנה ביותר מ-% {{allowed}}% העסקה שלך תחזור.", "removeLiquidity.pendingText": "מסיר את {{amtA}} {{symA}} ו- {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "כינוי", "settings.setting.wallet.notifications.title": "התראות", "settings.setting.wallet.preferences.title": "העדפות ארנק", - "settings.showTestNets": "הצג רשתות בדיקה", + "settings.setting.wallet.testnetMode.description": "זה מפעיל רשתות בדיקה למפתחים כדי לנסות תכונות ועסקאות מבלי להשתמש בנכסים אמיתיים. לאסימונים ברשתות בדיקות אין ערך אמיתי.", + "settings.setting.wallet.testnetMode.title": "מצב Testnet", "settings.switchNetwork.warning": "כדי להשתמש ב-Uniswap ב- {{label}}, החלף את הרשת בהגדרות הארנק שלך.", "settings.title": "הגדרות", "settings.version": "גרסה {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "קלט חדש", "swap.details.newQuote.output": "פלט חדש", "swap.details.orderRouting": "ניתוב הזמנה", - "swap.details.orderRoutingInfo": "המחיר שלך כבר כולל עלויות רשת ברשת היעד ועמלה של 0.05%.", + "swap.details.orderRoutingInfo": "ההחלפה הזו מנותבת דרך Across, פרוטוקול מבוזר שמעביר נכסים בין רשתות תוך מתן עדיפות לבטיחות, ביצוע מהיר ומחירים נמוכים.", + "swap.details.poweredBy": "מופעל על ידי", "swap.details.rate": "ציון", "swap.details.slippage": "החלקה מקסימלית", "swap.details.uniswapFee": "תַשְׁלוּם", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "לקוח Uniswap בוחר את אפשרות הסחר הזולה ביותר תוך התחשבות במחיר ועלויות רשת.", "swap.settings.routingPreference.option.v2.title": "v2 בריכות", "swap.settings.routingPreference.option.v3.title": "v3 בריכות", + "swap.settings.routingPreference.option.v4.title": "v4 בריכות", "swap.settings.routingPreference.title": "אפשרויות סחר", "swap.settings.slippage.control.auto": "אוטומטי", "swap.settings.slippage.description": "העסקה שלך תחזור אם המחיר ישתנה יותר מאחוז ההחלקה.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "העסקה שלך תחזור אם המחיר ישתנה לרעה ביותר מאחוז זה.", "swap.signAndSwap": "חתמו והחליפו", "swap.slippage.amt": "{{amt}} החלקה", + "swap.slippage.bridging": "אין החלקה בעת החלפה בין רשתות", "swap.slippage.settings.title": "הגדרות החלקה", "swap.slippage.tooltip": "תנועת המחיר המקסימלית לפני העסקה שלך תחזור.", "swap.slippageBelow.warning": "סטייה מתחת ל-{{amt}} עלולה לגרום לכישלון בעסקה", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "מועד אחרון לעסקה", "swap.transaction.revertAfter": "העסקה שלך תחזור אם היא בהמתנה במשך יותר מתקופת זמן זו.", "swap.unsupportedAssets.readMore": "קרא עוד על נכסים לא נתמכים", + "swap.warning.enterLargerAmount.title": "הזן כמות גדולה יותר", "swap.warning.expectedFailure": "עסקה זו צפויה להיכשל", "swap.warning.feeOnTransfer.message": "חלק מהאסימונים גובים עמלה כאשר הם נרכשים או נמכרים, אשר נקבעת על ידי מנפיק האסימונים. Uniswap אינה מקבלת כל חלק בעמלות אלו.", "swap.warning.feeOnTransfer.title": "למה יש תשלום נוסף?", "swap.warning.insufficientBalance.title": "אין לך מספיק {{currencySymbol}}", "swap.warning.insufficientGas.button": "לא מספיק {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "החלף עבור {{ tokenSymbol }} ב- {{networkName}}", "swap.warning.insufficientGas.button.buy": "קנה {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "קנה עם כרטיס", "swap.warning.insufficientGas.message.withNetwork": "לא מספיק {{currencySymbol}} על {{networkName}} כדי להחליף", "swap.warning.insufficientGas.message.withoutNetwork": "לא מספיק {{currencySymbol}} כדי להחליף", "swap.warning.insufficientGas.title": "אין לך מספיק {{currencySymbol}} כדי לכסות את עלות הרשת", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "נתוני אסימון עבור ב- {{chainName}}", "tdp.nameNotFound": "השם לא נמצא", "tdp.noInfoAvailable": "אין מידע אסימון זמין", + "tdp.noTestnetSupportDescription": "רשתות בדיקה מסוימות אינן תומכות בהחלפה, שליחה או קנייה של אסימונים.", "tdp.stats.unsupportedChainDescription": "סטטיסטיקות אסימונים ותרשימים עבור {{chain}} זמינים ב- {{infoLink}}", "tdp.symbolNotFound": "הסמל לא נמצא", + "testnet.unsupported": "פונקציונליות זו אינה נתמכת במצב testnet.", "themeToggle.theme": "נושא", "title.betterPricesMoreListings": "מחירים טובים יותר. רישומים נוספים. קנה, מכור וסחר ב-NFT במקומות שווקים מובילים כמו OpenSea. חקור אוספים פופולריים.", "title.buySellTradeEthereum": "קנה, מכור וסחר ב-Ethereum ואסימונים מובילים אחרים ב-Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "המאזן של {{ownerAddress}}", "token.bridge": "{{label}} גשר אסימונים", "token.chart.tooltip": "עמלות: {{amount}}", + "token.details.testnet.unsupported": "פרטי אסימון אינם זמינים עבור אסימוני testnet.", "token.error.unknown": "אסימון לא ידוע", "token.fee.buy.label": "עמלת קנייה", "token.fee.label": "תַשְׁלוּם", @@ -2212,7 +2233,6 @@ "walletConnect.pending.switchAccount": "החלף חשבון", "walletConnect.pending.switchNetwork": "החלף רשת", "walletConnect.pending.title": "התחבר אל {{dappName}}", - "walletConnect.permissions.networks": "רשתות", "walletConnect.permissions.option.transferAssets": "העבר את הנכסים שלך ללא הסכמה", "walletConnect.permissions.option.viewTokenBalances": "הצג את יתרות האסימונים שלך", "walletConnect.permissions.option.viewWalletAddress": "הצג את כתובת הארנק שלך", diff --git a/packages/uniswap/src/i18n/locales/translations/hi-IN.json b/packages/uniswap/src/i18n/locales/translations/hi-IN.json index efc80e8a490..5adb616eebc 100644 --- a/packages/uniswap/src/i18n/locales/translations/hi-IN.json +++ b/packages/uniswap/src/i18n/locales/translations/hi-IN.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "{{label}} नेटवर्क पर टोकन जमा करें।", "common.deposited": "जमा किया", "common.depositing": "जमा", + "common.depositTokens": "टोकन जमा करें", "common.detailed.label": "विस्तृत", "common.detected": "का पता चला", "common.developers": "डेवलपर्स", @@ -430,6 +431,7 @@ "common.migrate.position": "स्थान परिवर्तन", "common.migrated.liquidity": "माइग्रेटेड लिक्विडिटी", "common.migrating.liquidity": "तरलता का स्थानांतरण", + "common.min": "मिन", "common.mint.cancelled": "टकसाल रद्द", "common.mint.failed": "मिंट विफल", "common.minted": "ढाला", @@ -450,6 +452,7 @@ "common.noResults": "कोई परिणाम नहीं मिला।", "common.notAvailableInRegion.error": "आपके क्षेत्र में उपलब्ध नहीं है", "common.notCreated.label": "नहीं बनाया गया", + "common.notSupported": "समर्थित नहीं", "common.oneDay": "1 दिन", "common.oneHour": "1 घंटा", "common.oneMonth": "1 महीना", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}, और अन्य विकल्प", "fiatOnRamp.quote.type.other": "अन्य विकल्प", "fiatOnRamp.quote.type.recent": "हाल ही में उपयोग किया गया", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "आप एथेरियम, पॉलीगॉन, आर्बिट्रम, ऑप्टिमिज़्म, बेस, बीएनबी, ब्लास्ट, एवलांच, ज़ोरा और जेडकेसिंक पर टोकन और एनएफटी प्राप्त कर सकते हैं।", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "किसी खाते से", "fiatOnRamp.receiveCrypto.title": "क्रिप्टो प्राप्त करें", "fiatOnRamp.receiveCrypto.transferFunds": "किसी अन्य वॉलेट या खाते से क्रिप्टो ट्रांसफर करके अपने वॉलेट को फंड करें", @@ -901,6 +903,8 @@ "home.activity.error.load": "गतिविधि लोड नहीं हो सकी", "home.activity.title": "गतिविधि", "home.banner.offline": "आप ऑफ़लाइन मोड में हैं", + "home.banner.testnetMode": "आप टेस्टनेट मोड में हैं", + "home.banner.testnetMode.nav": "आप टेस्टनेट मोड में हैं। सेटिंग्स में जाकर इसे बंद करें।", "home.explore.footer": "हज़ारों टोकन, NFT और बहुत कुछ देखने के लिए यहां टैप करें", "home.explore.title": "टोकन एक्सप्लोर करें", "home.extension.error": "खाते लोड करने में त्रुटि", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "स्थिति बनाएं", "nav.tabs.createV2Position": "V2 स्थिति बनाएं", "nav.tabs.createV3Position": "V3 स्थिति बनाएं", + "nav.tabs.createV4Position": "V4 स्थिति बनाएं", "nav.tabs.viewPosition": "स्थिति देखें", "network.lostConnection": "हो सकता है कि आपका नेटवर्क कनेक्शन टूट गया हो।", "network.mightBeDown": "{{network}} शायद अभी डाउन है, या हो सकता है कि आपने अपना नेटवर्क कनेक्शन खो दिया हो।", @@ -1331,6 +1336,7 @@ "pool.back": "पूल पर वापस जाएँ", "pool.balances": "पूल संतुलन", "pool.claimFees": "दावा शुल्क", + "pool.claimFees.button.label": "दावा", "pool.collectAs": "{{nativeWrappedSymbol}}के रूप में एकत्रित करें", "pool.collected": " एकत्र किया हुआ", "pool.collecting": "एकत्रित", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "पुरस्कार पूल में तरलता का प्रबंधन करें", "pool.max.label": "अधिकतम:", "pool.maxPrice": "अधिकतम मूल्य", + "pool.migrateToV4": "V4 पर माइग्रेट करें", "pool.min.label": "न्यूनतम:", "pool.minPrice": "न्यूनतम मूल्य", "pool.mustBeInitialized": "इससे पहले कि आप लिक्विडिटी जोड़ सकें, इस पूल को आरंभीकृत किया जाना चाहिए। आरंभीकृत करने के लिए, पूल के लिए एक आरंभिक मूल्य चुनें। फिर, अपनी लिक्विडिटी मूल्य सीमा और जमा राशि दर्ज करें। आरंभीकरण लेनदेन के कारण गैस शुल्क सामान्य से अधिक होगा।", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "सुझाव: इस टूल का उपयोग उन v2 पूल को खोजने के लिए करें जो इंटरफ़ेस में स्वचालित रूप से दिखाई नहीं देते हैं।", "pools.approving.amount": "अनुमोदन {{amount}}", "position.addHook": "एक हुक जोड़ें", + "position.addHook.tooltip": "हुक एक उन्नत सुविधा है जो पूल को स्मार्ट कॉन्ट्रैक्ट के साथ इंटरैक्ट करने में सक्षम बनाती है, जिससे विभिन्न क्षमताएँ अनलॉक होती हैं। हुक जोड़ते समय सावधानी बरतें, क्योंकि कुछ दुर्भावनापूर्ण हो सकते हैं या अनपेक्षित परिणाम पैदा कर सकते हैं।", "position.appearHere": "आपकी स्थिति यहां दिखाई देगी.", + "position.create.modal.header": "स्थिति बनाना", "position.currentValue": "वर्तमान स्थिति मान", + "position.deposit.description": "अपने तरलता योगदान के लिए टोकन राशि निर्दिष्ट करें.", "position.depositedCurrency": "जमा {{currencySymbol}}", "position.migrate.liquidity": "पोजीशन्स को माइग्रेट करते समय, आप अपने टोकन जोड़े को बदल नहीं सकते हैं, लेकिन आप कार्यक्षमता बढ़ाने के लिए एक हुक जोड़ सकते हैं।", "position.new": "नई स्थिति", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "कनेक्ट हो रहा है...", "qrScanner.status.loading": "लोड हो रहा है...", "qrScanner.title": "QR कोड स्कैन करें", - "qrScanner.wallet.title": "आप एथेरियम, पॉलीगॉन, आर्बिट्रम, ऑप्टिमिज़्म, बेस, जेडकेसिंक, ज़ोरा, एवलांच, सेलो, ब्लास्ट और बीएनबी चेन पर टोकन और एनएफटी प्राप्त कर सकते हैं।", + "qrScanner.wallet.networks": "समर्थित नेटवर्क", + "qrScanner.wallet.title": "आप हमारे सभी {{numOfNetworks}} समर्थित नेटवर्क पर टोकन और NFT भेज और प्राप्त कर सकते हैं।", "removeLiquidity.collectFees": "आप इस पद से अर्जित शुल्क भी एकत्र करेंगे।", "removeLiquidity.outputEstimated": "आउटपुट अनुमानित है। यदि कीमत {{allowed}}% से अधिक बदलती है तो आपका लेनदेन वापस हो जाएगा।", "removeLiquidity.pendingText": "{{amtA}} {{symA}} और {{amtB}} {{symB}}को हटाना", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "उपनाम", "settings.setting.wallet.notifications.title": "सूचनाएं", "settings.setting.wallet.preferences.title": "वॉलेट प्राथमिकताएँ", - "settings.showTestNets": "टेस्टनेट दिखाएं", + "settings.setting.wallet.testnetMode.description": "यह डेवलपर्स के लिए टेस्टनेट चालू करता है ताकि वे वास्तविक परिसंपत्तियों का उपयोग किए बिना सुविधाओं और लेनदेन को आज़मा सकें। टेस्टनेट पर टोकन का कोई वास्तविक मूल्य नहीं होता है।", + "settings.setting.wallet.testnetMode.title": "टेस्टनेट मोड", "settings.switchNetwork.warning": "{{label}}पर Uniswap का उपयोग करने के लिए, अपने वॉलेट की सेटिंग में नेटवर्क स्विच करें।", "settings.title": "समायोजन", "settings.version": "संस्करण {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "नया इनपुट", "swap.details.newQuote.output": "नया आउटपुट", "swap.details.orderRouting": "ऑर्डर रूटिंग", - "swap.details.orderRoutingInfo": "आपके मूल्य में पहले से ही गंतव्य नेटवर्क पर नेटवर्क लागत और 0.05% एक्रॉस शुल्क शामिल है।", + "swap.details.orderRoutingInfo": "यह स्वैप एक्रॉस के माध्यम से किया जाता है, जो एक विकेन्द्रीकृत प्रोटोकॉल है जो सुरक्षा, तीव्र निष्पादन और कम कीमतों को प्राथमिकता देते हुए परिसंपत्तियों को नेटवर्क के पार ले जाता है।", + "swap.details.poweredBy": "द्वारा संचालित", "swap.details.rate": "दर", "swap.details.slippage": "अधिकतम फिसलन", "swap.details.uniswapFee": "शुल्क", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "यूनिस्वैप क्लाइंट मूल्य और नेटवर्क लागत को ध्यान में रखते हुए सबसे सस्ता व्यापार विकल्प चुनता है।", "swap.settings.routingPreference.option.v2.title": "v2 पूल", "swap.settings.routingPreference.option.v3.title": "v3 पूल", + "swap.settings.routingPreference.option.v4.title": "v4 पूल", "swap.settings.routingPreference.title": "व्यापार विकल्प", "swap.settings.slippage.control.auto": "ऑटो", "swap.settings.slippage.description": "यदि कीमत में स्लिपेज प्रतिशत से अधिक परिवर्तन होता है तो आपका लेनदेन वापस कर दिया जाएगा।", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "यदि मूल्य में इस प्रतिशत से अधिक प्रतिकूल परिवर्तन होता है तो आपका लेनदेन पूर्ववत हो जाएगा।", "swap.signAndSwap": "हस्ताक्षर करें और अदला-बदली करें", "swap.slippage.amt": "{{amt}} फिसलन", + "swap.slippage.bridging": "नेटवर्कों के बीच स्वैपिंग करते समय कोई फिसलन नहीं", "swap.slippage.settings.title": "फिसलन सेटिंग्स", "swap.slippage.tooltip": "आपके लेन-देन से पहले अधिकतम मूल्य परिवर्तन पूर्ववत हो जाएगा।", "swap.slippageBelow.warning": "{{amt}} से नीचे स्लिपेज के परिणामस्वरूप लेनदेन विफल हो सकता है", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "लेन-देन की समय सीमा", "swap.transaction.revertAfter": "यदि आपका लेन-देन इस अवधि से अधिक समय तक लंबित रहता है तो उसे वापस कर दिया जाएगा।", "swap.unsupportedAssets.readMore": "असमर्थित परिसंपत्तियों के बारे में अधिक पढ़ें", + "swap.warning.enterLargerAmount.title": "अधिक राशि दर्ज करें", "swap.warning.expectedFailure": "इस लेन-देन के विफल होने की आशंका है", "swap.warning.feeOnTransfer.message": "कुछ टोकन खरीदे या बेचे जाने पर शुल्क लेते हैं, जो टोकन जारीकर्ता द्वारा निर्धारित किया जाता है। Uniswap को इन शुल्कों का कोई हिस्सा नहीं मिलता है।", "swap.warning.feeOnTransfer.title": "अतिरिक्त शुल्क क्यों है?", "swap.warning.insufficientBalance.title": "आपके पास पर्याप्त {{currencySymbol}}नहीं है", "swap.warning.insufficientGas.button": "पर्याप्त नहीं {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "{{ tokenSymbol }} को {{networkName}}पर बदलें", "swap.warning.insufficientGas.button.buy": "खरीदें {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "कार्ड से खरीदें", "swap.warning.insufficientGas.message.withNetwork": "स्वैप करने के लिए {{networkName}} पर पर्याप्त {{currencySymbol}} नहीं है", "swap.warning.insufficientGas.message.withoutNetwork": "स्वैप करने के लिए पर्याप्त {{currencySymbol}} नहीं", "swap.warning.insufficientGas.title": "आपके पास नेटवर्क लागत को कवर करने के लिए पर्याप्त {{currencySymbol}} नहीं है", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": " के लिए टोकन डेटा {{chainName}}पर", "tdp.nameNotFound": "नाम नहीं मिला", "tdp.noInfoAvailable": "कोई टोकन जानकारी उपलब्ध नहीं", + "tdp.noTestnetSupportDescription": "कुछ टेस्टनेट टोकन की अदला-बदली, भेजने या खरीदने का समर्थन नहीं करते हैं।", "tdp.stats.unsupportedChainDescription": "{{chain}} के लिए टोकन आँकड़े और चार्ट {{infoLink}}पर उपलब्ध हैं", "tdp.symbolNotFound": "प्रतीक नहीं मिला", + "testnet.unsupported": "यह कार्यक्षमता टेस्टनेट मोड में समर्थित नहीं है।", "themeToggle.theme": "विषय", "title.betterPricesMoreListings": "बेहतर कीमतें। ज़्यादा लिस्टिंग। OpenSea जैसे शीर्ष मार्केटप्लेस पर NFT खरीदें, बेचें और ट्रेड करें। ट्रेंडिंग कलेक्शन एक्सप्लोर करें।", "title.buySellTradeEthereum": "Uniswap पर Ethereum और अन्य शीर्ष टोकन खरीदें, बेचें और व्यापार करें", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "{{ownerAddress}}का संतुलन", "token.bridge": "{{label}} टोकन ब्रिज", "token.chart.tooltip": "शुल्क: {{amount}}", + "token.details.testnet.unsupported": "टेस्टनेट टोकन के लिए टोकन विवरण उपलब्ध नहीं हैं।", "token.error.unknown": "अज्ञात टोकन", "token.fee.buy.label": "खरीद शुल्क", "token.fee.label": "शुल्क", @@ -2212,7 +2233,6 @@ "walletConnect.pending.switchAccount": "खाता स्थानांतरित करें", "walletConnect.pending.switchNetwork": "नेटवर्क स्विच करें", "walletConnect.pending.title": "{{dappName}}से कनेक्ट करें", - "walletConnect.permissions.networks": "नेटवर्क", "walletConnect.permissions.option.transferAssets": "बिना सहमति के अपनी संपत्ति हस्तांतरित करें", "walletConnect.permissions.option.viewTokenBalances": "अपने टोकन शेष देखें", "walletConnect.permissions.option.viewWalletAddress": "अपना बटुआ पता देखें", diff --git a/packages/uniswap/src/i18n/locales/translations/hu-HU.json b/packages/uniswap/src/i18n/locales/translations/hu-HU.json index 3244605c121..9d4e3437f72 100644 --- a/packages/uniswap/src/i18n/locales/translations/hu-HU.json +++ b/packages/uniswap/src/i18n/locales/translations/hu-HU.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Befizetési tokenek a {{label}} hálózatba.", "common.deposited": "Letétbe helyezve", "common.depositing": "Befizetés", + "common.depositTokens": "Befizetési tokenek", "common.detailed.label": "Részletes", "common.detected": "Észlelve", "common.developers": "Fejlesztők", @@ -430,6 +431,7 @@ "common.migrate.position": "Pozíció áttelepítése", "common.migrated.liquidity": "Migrált likviditás", "common.migrating.liquidity": "Likviditás migrációja", + "common.min": "Min", "common.mint.cancelled": "Mint törölték", "common.mint.failed": "Mint nem sikerült", "common.minted": "Verés", @@ -450,6 +452,7 @@ "common.noResults": "Nincs találat.", "common.notAvailableInRegion.error": "Nem érhető el az Ön régiójában", "common.notCreated.label": "Nem jött létre", + "common.notSupported": "Nem támogatott", "common.oneDay": "1 nap", "common.oneHour": "1 óra", "common.oneMonth": "1 hónap", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}és egyéb lehetőségek", "fiatOnRamp.quote.type.other": "Egyéb opciók", "fiatOnRamp.quote.type.recent": "Nemrég használt", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Tokeneket és NFT-ket kaphat az Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora és ZKsync platformokon.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Egy fiókból", "fiatOnRamp.receiveCrypto.title": "Kap kriptot", "fiatOnRamp.receiveCrypto.transferFunds": "Finanszírozza pénztárcáját egy másik pénztárcáról vagy számláról történő kriptográfiai átutalással", @@ -901,6 +903,8 @@ "home.activity.error.load": "Nem sikerült betölteni a tevékenységet", "home.activity.title": "Tevékenység", "home.banner.offline": "Ön offline módban van", + "home.banner.testnetMode": "Testnet módban van", + "home.banner.testnetMode.nav": "Testnet módban van. Kapcsolja ki ezt a beállításokban.", "home.explore.footer": "Koppintson ide több ezer token, NFT és egyebek felfedezéséhez", "home.explore.title": "Fedezze fel a tokeneket", "home.extension.error": "Hiba a fiókok betöltésekor", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Pozíció létrehozása", "nav.tabs.createV2Position": "V2 pozíció létrehozása", "nav.tabs.createV3Position": "V3 pozíció létrehozása", + "nav.tabs.createV4Position": "V4 pozíció létrehozása", "nav.tabs.viewPosition": "Pozíció megtekintése", "network.lostConnection": "Lehet, hogy megszakadt a hálózati kapcsolat.", "network.mightBeDown": "Lehet, hogy a {{network}} jelenleg nem működik, vagy megszakadt a hálózati kapcsolat.", @@ -1331,6 +1336,7 @@ "pool.back": "Vissza a medencébe", "pool.balances": "Pool egyenlegek", "pool.claimFees": "Díjak követelése", + "pool.claimFees.button.label": "Követelés", "pool.collectAs": "Gyűjtés {{nativeWrappedSymbol}}-ként", "pool.collected": " Összegyűjtött", "pool.collecting": "Gyűjtő", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Likviditás kezelése a jutalom poolban", "pool.max.label": "Max:", "pool.maxPrice": "Max ár", + "pool.migrateToV4": "Migráció a V4-re", "pool.min.label": "Min.:", "pool.minPrice": "Minimális ár", "pool.mustBeInitialized": "Ezt a készletet inicializálni kell, mielőtt likviditást adhatna hozzá. Az inicializáláshoz válasszon kikiáltási árat a készlethez. Ezután adja meg likviditási ártartományát és a betét összegét. A gázdíjak az inicializálási tranzakció miatt a szokásosnál magasabbak lesznek.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Tipp: Ezzel az eszközzel olyan v2-készleteket kereshet, amelyek nem jelennek meg automatikusan a felületen.", "pools.approving.amount": "{{amount}}jóváhagyása", "position.addHook": "Adj hozzá egy horgot", + "position.addHook.tooltip": "A horgok egy olyan fejlett funkció, amely lehetővé teszi a poolok számára, hogy kölcsönhatásba léphessenek az intelligens szerződésekkel, felszabadítva ezzel a különféle képességeket. Legyen körültekintő a horgok felszerelésekor, mivel egyesek rosszindulatúak lehetnek, vagy nem kívánt következményekkel járhatnak.", "position.appearHere": "Az Ön pozíciója itt fog megjelenni.", + "position.create.modal.header": "Pozíció létrehozása", "position.currentValue": "Aktuális pozíció értéke", + "position.deposit.description": "Adja meg a likviditási hozzájárulás jelképes összegét.", "position.depositedCurrency": "Letétbe helyezve {{currencySymbol}}", "position.migrate.liquidity": "Pozíciók áttelepítésekor nem módosíthatja a tokenpárt, de hozzáadhat egy horogot a funkcionalitás javítása érdekében.", "position.new": "Új pozíció", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Csatlakozás...", "qrScanner.status.loading": "Betöltés...", "qrScanner.title": "QR-kód beolvasása", - "qrScanner.wallet.title": "Tokeneket és NFT-ket kaphat az Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast és BNB Chain platformokon.", + "qrScanner.wallet.networks": "Támogatott hálózatok", + "qrScanner.wallet.title": "Tokeneket és NFT-ket küldhet és fogadhat az összes {{numOfNetworks}} támogatott hálózatunkon.", "removeLiquidity.collectFees": "Az ebből a pozícióból szerzett díjakat is beszed.", "removeLiquidity.outputEstimated": "A kimenet becsült. Ha az ár több mint {{allowed}}%-kal változik, a tranzakció visszaáll.", "removeLiquidity.pendingText": "{{amtA}} {{symA}} és {{amtB}} {{symB}}eltávolítása", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Becenév", "settings.setting.wallet.notifications.title": "Értesítések", "settings.setting.wallet.preferences.title": "Pénztárca-beállítások", - "settings.showTestNets": "Testnetek megjelenítése", + "settings.setting.wallet.testnetMode.description": "Ez bekapcsolja a teszthálózatokat a fejlesztők számára, hogy valódi eszközök használata nélkül próbálhassák ki a funkciókat és a tranzakciókat. A teszthálózatokon lévő tokenek nem rendelkeznek valós értékkel.", + "settings.setting.wallet.testnetMode.title": "Testnet mód", "settings.switchNetwork.warning": "Az Uniswap használatához {{label}}esetén váltson hálózatot a pénztárca beállításaiban.", "settings.title": "Beállítások", "settings.version": "{{appVersion}}verzió", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Új bemenet", "swap.details.newQuote.output": "Új kimenet", "swap.details.orderRouting": "Rendelési útválasztás", - "swap.details.orderRoutingInfo": "Az Ön ára már tartalmazza a célhálózat hálózati költségeit és a 0,05% Across díjat.", + "swap.details.orderRoutingInfo": "Ez a csere az Acrosson keresztül történik, egy decentralizált protokollon, amely az eszközöket a hálózatok között mozgatja, miközben előnyben részesíti a biztonságot, a gyors végrehajtást és az alacsony árakat.", + "swap.details.poweredBy": "Powered by", "swap.details.rate": "Mérték", "swap.details.slippage": "Max csúszás", "swap.details.uniswapFee": "Díj", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Az Uniswap kliens a legolcsóbb kereskedési opciót választja, figyelembe véve az árat és a hálózati költségeket.", "swap.settings.routingPreference.option.v2.title": "v2 poolok", "swap.settings.routingPreference.option.v3.title": "v3 poolok", + "swap.settings.routingPreference.option.v4.title": "v4 poolok", "swap.settings.routingPreference.title": "Kereskedelmi lehetőségek", "swap.settings.slippage.control.auto": "Auto", "swap.settings.slippage.description": "A tranzakció visszaáll, ha az ár a csúszási százaléknál nagyobb mértékben változik.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "A tranzakció visszaáll, ha az ár ennél a százaléknál nagyobb mértékben változik kedvezőtlenül.", "swap.signAndSwap": "Írd alá és cseréld", "swap.slippage.amt": "{{amt}} csúszás", + "swap.slippage.bridging": "Nincs csúszás a hálózatok közötti csere során", "swap.slippage.settings.title": "Csúszási beállítások", "swap.slippage.tooltip": "A tranzakció előtti maximális ármozgás visszaáll.", "swap.slippageBelow.warning": "A {{amt}} alatti elcsúszás sikertelen tranzakcióhoz vezethet", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Tranzakció határideje", "swap.transaction.revertAfter": "A tranzakció visszaáll, ha az ennél hosszabb ideig függőben van.", "swap.unsupportedAssets.readMore": "További információ a nem támogatott eszközökről", + "swap.warning.enterLargerAmount.title": "Adjon meg nagyobb összeget", "swap.warning.expectedFailure": "Ez a tranzakció várhatóan meghiúsul", "swap.warning.feeOnTransfer.message": "Egyes tokenek vásárlásukkor vagy eladásukkor díjat számítanak fel, amelyet a token kibocsátója határoz meg. Az Uniswap nem részesül ezekből a díjakból.", "swap.warning.feeOnTransfer.title": "Miért van plusz díj?", "swap.warning.insufficientBalance.title": "Nincs elég {{currencySymbol}}", "swap.warning.insufficientGas.button": "Nem elég {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Csere {{ tokenSymbol }} értékre {{networkName}}-n", "swap.warning.insufficientGas.button.buy": "Vásároljon {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Vásároljon kártyával", "swap.warning.insufficientGas.message.withNetwork": "Nem elég {{currencySymbol}} {{networkName}} a cseréhez", "swap.warning.insufficientGas.message.withoutNetwork": "Nem elég {{currencySymbol}} a cseréhez", "swap.warning.insufficientGas.title": "Nincs elég {{currencySymbol}} a hálózati költségek fedezésére", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": " tokenadatok a {{chainName}}-n", "tdp.nameNotFound": "A név nem található", "tdp.noInfoAvailable": "Nem áll rendelkezésre token információ", + "tdp.noTestnetSupportDescription": "Egyes teszthálózatok nem támogatják a tokenek cseréjét, küldését vagy vásárlását.", "tdp.stats.unsupportedChainDescription": "A {{chain}} tokenstatisztikái és diagramjai a {{infoLink}}oldalon érhetők el", "tdp.symbolNotFound": "A szimbólum nem található", + "testnet.unsupported": "Ez a funkció nem támogatott tesztnet módban.", "themeToggle.theme": "Téma", "title.betterPricesMoreListings": "Jobb árak. További listák. Vásároljon, adjon el és keressen NFT-ket olyan vezető piacokon, mint az OpenSea. Fedezze fel a felkapott kollekciókat.", "title.buySellTradeEthereum": "Vásárolj, adj el és cserélj Ethereumot és más legjobb tokeneket az Uniswap-on", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "{{ownerAddress}}egyenlege", "token.bridge": "{{label}} token híd", "token.chart.tooltip": "Díjak: {{amount}}", + "token.details.testnet.unsupported": "A token részletei nem érhetők el a testnet tokenek esetében.", "token.error.unknown": "Ismeretlen token", "token.fee.buy.label": "vásárlási díj", "token.fee.label": "díj", @@ -2212,7 +2233,6 @@ "walletConnect.pending.switchAccount": "Fiók váltása", "walletConnect.pending.switchNetwork": "Hálózat váltása", "walletConnect.pending.title": "Csatlakozás ehhez: {{dappName}}", - "walletConnect.permissions.networks": "Hálózatok", "walletConnect.permissions.option.transferAssets": "Vigye át eszközeit beleegyezés nélkül", "walletConnect.permissions.option.viewTokenBalances": "Tekintse meg token egyenlegét", "walletConnect.permissions.option.viewWalletAddress": "Tekintse meg pénztárcája címét", diff --git a/packages/uniswap/src/i18n/locales/translations/id-ID.json b/packages/uniswap/src/i18n/locales/translations/id-ID.json index 851edaa6d82..13263611cab 100644 --- a/packages/uniswap/src/i18n/locales/translations/id-ID.json +++ b/packages/uniswap/src/i18n/locales/translations/id-ID.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Setor token ke jaringan {{label}} .", "common.deposited": "Disimpan", "common.depositing": "Menyetorkan", + "common.depositTokens": "Setor token", "common.detailed.label": "Terperinci", "common.detected": "Terdeteksi", "common.developers": "Pengembang", @@ -430,6 +431,7 @@ "common.migrate.position": "Migrasi posisi", "common.migrated.liquidity": "Likuiditas yang dimigrasi", "common.migrating.liquidity": "Migrasi likuiditas", + "common.min": "menit", "common.mint.cancelled": "Mint dibatalkan", "common.mint.failed": "Mint gagal", "common.minted": "dicetak", @@ -450,6 +452,7 @@ "common.noResults": "Tidak ada hasil yang ditemukan.", "common.notAvailableInRegion.error": "Tidak tersedia di wilayah Anda", "common.notCreated.label": "Tidak dibuat", + "common.notSupported": "Tidak didukung", "common.oneDay": "1 hari", "common.oneHour": "1 jam", "common.oneMonth": "1 bulan", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}, dan opsi lainnya", "fiatOnRamp.quote.type.other": "Pilihan lain", "fiatOnRamp.quote.type.recent": "Baru - baru ini digunakan", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Anda dapat menerima token & NFT di Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora, dan ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Dari sebuah akun", "fiatOnRamp.receiveCrypto.title": "Terima kripto", "fiatOnRamp.receiveCrypto.transferFunds": "Danai dompet Anda dengan mentransfer kripto dari dompet atau akun lain", @@ -901,6 +903,8 @@ "home.activity.error.load": "Tidak dapat memuat aktivitas", "home.activity.title": "Aktivitas", "home.banner.offline": "Anda berada dalam mode offline", + "home.banner.testnetMode": "Anda berada dalam mode testnet", + "home.banner.testnetMode.nav": "Anda berada dalam mode testnet. Nonaktifkan ini di pengaturan.", "home.explore.footer": "Ketuk di sini untuk menjelajahi ribuan token, NFT, dan banyak lagi", "home.explore.title": "Jelajahi token", "home.extension.error": "Terjadi kesalahan saat memuat akun", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Buat posisi", "nav.tabs.createV2Position": "Buat posisi V2", "nav.tabs.createV3Position": "Buat posisi V3", + "nav.tabs.createV4Position": "Buat posisi V4", "nav.tabs.viewPosition": "Lihat posisi", "network.lostConnection": "Anda mungkin kehilangan koneksi jaringan.", "network.mightBeDown": "{{network}} mungkin sedang down saat ini, atau Anda mungkin kehilangan koneksi jaringan.", @@ -1331,6 +1336,7 @@ "pool.back": "Kembali ke Kolam Renang", "pool.balances": "Saldo kumpulan", "pool.claimFees": "Biaya klaim", + "pool.claimFees.button.label": "Mengeklaim", "pool.collectAs": "Kumpulkan sebagai {{nativeWrappedSymbol}}", "pool.collected": " Dikumpulkan", "pool.collecting": "Mengumpulkan", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Kelola likuiditas di kumpulan hadiah", "pool.max.label": "Maks:", "pool.maxPrice": "Harga maksimal", + "pool.migrateToV4": "Bermigrasi ke V4", "pool.min.label": "Min:", "pool.minPrice": "Harga minimal", "pool.mustBeInitialized": "Kumpulan ini harus diinisialisasi sebelum Anda dapat menambahkan likuiditas. Untuk melakukan inisialisasi, pilih harga awal untuk kumpulan tersebut. Kemudian, masukkan kisaran harga likuiditas dan jumlah deposit Anda. Biaya bahan bakar akan lebih tinggi dari biasanya karena transaksi inisialisasi.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Tip: Gunakan alat ini untuk menemukan kumpulan v2 yang tidak muncul secara otomatis di antarmuka.", "pools.approving.amount": "Menyetujui {{amount}}", "position.addHook": "Tambahkan Hook", + "position.addHook.tooltip": "Hook merupakan fitur canggih yang memungkinkan pool berinteraksi dengan kontrak pintar, membuka berbagai kemampuan. Berhati-hatilah saat menambahkan hook, karena beberapa hook mungkin berbahaya atau menyebabkan konsekuensi yang tidak diinginkan.", "position.appearHere": "Posisi Anda akan muncul di sini.", + "position.create.modal.header": "Membuat posisi", "position.currentValue": "Nilai posisi saat ini", + "position.deposit.description": "Tentukan jumlah token untuk kontribusi likuiditas Anda.", "position.depositedCurrency": "Disetorkan {{currencySymbol}}", "position.migrate.liquidity": "Saat melakukan migrasi posisi, Anda tidak dapat mengubah pasangan token Anda, tetapi Anda dapat menambahkan kait untuk meningkatkan fungsionalitas.", "position.new": "Posisi Baru", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Menghubungkan...", "qrScanner.status.loading": "Memuat...", "qrScanner.title": "Pindai kode QR", - "qrScanner.wallet.title": "Anda dapat menerima token & NFT di Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast, dan BNB Chain.", + "qrScanner.wallet.networks": "Jaringan yang Didukung", + "qrScanner.wallet.title": "Anda dapat mengirim dan menerima token dan NFT di semua {{numOfNetworks}} jaringan kami yang didukung.", "removeLiquidity.collectFees": "Anda juga akan memungut biaya yang diperoleh dari posisi ini.", "removeLiquidity.outputEstimated": "Output diperkirakan. Jika harga berubah lebih dari {{allowed}}% transaksi Anda akan dikembalikan.", "removeLiquidity.pendingText": "Menghapus {{amtA}} {{symA}} dan {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Nama panggilan", "settings.setting.wallet.notifications.title": "Pemberitahuan", "settings.setting.wallet.preferences.title": "Preferensi dompet", - "settings.showTestNets": "Tampilkan testnet", + "settings.setting.wallet.testnetMode.description": "Hal ini mengaktifkan testnet agar pengembang dapat mencoba fitur dan transaksi tanpa menggunakan aset riil. Token pada testnet tidak memiliki nilai riil apa pun.", + "settings.setting.wallet.testnetMode.title": "Mode uji coba", "settings.switchNetwork.warning": "Untuk menggunakan Uniswap di {{label}}, alihkan jaringan di pengaturan dompet Anda.", "settings.title": "Pengaturan", "settings.version": "Versi {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Masukan baru", "swap.details.newQuote.output": "Keluaran baru", "swap.details.orderRouting": "Perutean pesanan", - "swap.details.orderRoutingInfo": "Harga Anda sudah termasuk biaya jaringan pada jaringan tujuan dan biaya Across sebesar 0,05%.", + "swap.details.orderRoutingInfo": "Pertukaran ini disalurkan melalui Across, protokol terdesentralisasi yang memindahkan aset lintas jaringan dengan mengutamakan keamanan, eksekusi cepat, dan harga rendah.", + "swap.details.poweredBy": "Didukung oleh", "swap.details.rate": "Kecepatan", "swap.details.slippage": "Slip maksimum", "swap.details.uniswapFee": "Biaya", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Klien Uniswap memilih opsi perdagangan termurah dengan memperhitungkan harga dan biaya jaringan.", "swap.settings.routingPreference.option.v2.title": "kolam v2", "swap.settings.routingPreference.option.v3.title": "kolam v3", + "swap.settings.routingPreference.option.v4.title": "kolam renang v4", "swap.settings.routingPreference.title": "Opsi perdagangan", "swap.settings.slippage.control.auto": "Mobil", "swap.settings.slippage.description": "Transaksi Anda akan kembali jika harga berubah lebih dari persentase slippage.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Transaksi Anda akan dikembalikan jika harga berubah secara tidak menguntungkan lebih dari persentase ini.", "swap.signAndSwap": "Tanda tangani dan tukar", "swap.slippage.amt": "{{amt}} selip", + "swap.slippage.bridging": "Tidak ada selip saat berpindah antar jaringan", "swap.slippage.settings.title": "Pengaturan selip", "swap.slippage.tooltip": "Pergerakan harga maksimum sebelum transaksi Anda akan kembali.", "swap.slippageBelow.warning": "Slippage di bawah {{amt}} dapat mengakibatkan transaksi gagal", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Batas waktu transaksi", "swap.transaction.revertAfter": "Transaksi Anda akan dikembalikan jika tertunda lebih dari jangka waktu ini.", "swap.unsupportedAssets.readMore": "Baca selengkapnya tentang aset yang tidak didukung", + "swap.warning.enterLargerAmount.title": "Masukkan jumlah yang lebih besar", "swap.warning.expectedFailure": "Transaksi ini diperkirakan gagal", "swap.warning.feeOnTransfer.message": "Beberapa token mengenakan biaya saat dibeli atau dijual, yang ditetapkan oleh penerbit token. Uniswap tidak menerima bagian apa pun dari biaya ini.", "swap.warning.feeOnTransfer.title": "Mengapa ada biaya tambahan?", "swap.warning.insufficientBalance.title": "Anda tidak punya cukup {{currencySymbol}}", "swap.warning.insufficientGas.button": "Tidak cukup {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Tukar dengan {{ tokenSymbol }} pada {{networkName}}", "swap.warning.insufficientGas.button.buy": "Beli {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Beli dengan kartu", "swap.warning.insufficientGas.message.withNetwork": "Tidak cukup {{currencySymbol}} pada {{networkName}} untuk bertukar", "swap.warning.insufficientGas.message.withoutNetwork": "Tidak cukup {{currencySymbol}} untuk bertukar", "swap.warning.insufficientGas.title": "Anda tidak memiliki cukup {{currencySymbol}} untuk menutupi biaya jaringan", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "data token untuk pada {{chainName}}", "tdp.nameNotFound": "Nama tidak ditemukan", "tdp.noInfoAvailable": "Tidak ada informasi token yang tersedia", + "tdp.noTestnetSupportDescription": "Beberapa testnet tidak mendukung pertukaran, pengiriman, atau pembelian token.", "tdp.stats.unsupportedChainDescription": "Statistik dan grafik token untuk {{chain}} tersedia di {{infoLink}}", "tdp.symbolNotFound": "Simbol tidak ditemukan", + "testnet.unsupported": "Fungsionalitas ini tidak didukung dalam mode testnet.", "themeToggle.theme": "Tema", "title.betterPricesMoreListings": "Harga yang lebih baik. Lebih banyak daftar. Beli, jual, dan perdagangkan NFT di pasar terkemuka seperti OpenSea. Jelajahi koleksi yang sedang tren.", "title.buySellTradeEthereum": "Beli, jual & perdagangkan Ethereum dan token teratas lainnya di Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "saldo {{ownerAddress}}", "token.bridge": "{{label}} jembatan token", "token.chart.tooltip": "Biaya: {{amount}}", + "token.details.testnet.unsupported": "Rincian token tidak tersedia untuk token testnet.", "token.error.unknown": "Tanda tidak diketahui", "token.fee.buy.label": "biaya pembelian", "token.fee.label": "biaya", @@ -2212,7 +2233,6 @@ "walletConnect.pending.switchAccount": "Ganti akun", "walletConnect.pending.switchNetwork": "Ganti Jaringan", "walletConnect.pending.title": "Hubungkan ke {{dappName}}", - "walletConnect.permissions.networks": "Jaringan", "walletConnect.permissions.option.transferAssets": "Mentransfer aset Anda tanpa persetujuan", "walletConnect.permissions.option.viewTokenBalances": "Lihat saldo token Anda", "walletConnect.permissions.option.viewWalletAddress": "Lihat alamat dompet Anda", diff --git a/packages/uniswap/src/i18n/locales/translations/it-IT.json b/packages/uniswap/src/i18n/locales/translations/it-IT.json index a86c52d2214..4da5cd7cd57 100644 --- a/packages/uniswap/src/i18n/locales/translations/it-IT.json +++ b/packages/uniswap/src/i18n/locales/translations/it-IT.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Deposita token sulla rete {{label}} .", "common.deposited": "Depositato", "common.depositing": "Depositare", + "common.depositTokens": "Deposita token", "common.detailed.label": "Dettagliato", "common.detected": "Rilevato", "common.developers": "Sviluppatori", @@ -430,6 +431,7 @@ "common.migrate.position": "Migrare la posizione", "common.migrated.liquidity": "Liquidità migrata", "common.migrating.liquidity": "Migrazione della liquidità", + "common.min": "Minimo", "common.mint.cancelled": "Nuovo annullato", "common.mint.failed": "La menta è fallita", "common.minted": "Coniato", @@ -450,6 +452,7 @@ "common.noResults": "nessun risultato trovato.", "common.notAvailableInRegion.error": "Non disponibile nella tua regione", "common.notCreated.label": "Non creato", + "common.notSupported": "Non supportato", "common.oneDay": "1 giorno", "common.oneHour": "1 ora", "common.oneMonth": "1 mese", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}e altre opzioni", "fiatOnRamp.quote.type.other": "Altre opzioni", "fiatOnRamp.quote.type.recent": "Usato di recente", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Puoi ricevere token e NFT su Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora e ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Da un conto", "fiatOnRamp.receiveCrypto.title": "Ricevi criptovaluta", "fiatOnRamp.receiveCrypto.transferFunds": "Finanzia il tuo portafoglio trasferendo criptovalute da un altro portafoglio o account", @@ -901,6 +903,8 @@ "home.activity.error.load": "Impossibile caricare l'attività", "home.activity.title": "Attività", "home.banner.offline": "Sei in modalità offline", + "home.banner.testnetMode": "Sei in modalità testnet", + "home.banner.testnetMode.nav": "Sei in modalità testnet. Disattiva questa opzione nelle impostazioni.", "home.explore.footer": "Tocca qui per esplorare migliaia di token, NFT e altro ancora", "home.explore.title": "Esplora i token", "home.extension.error": "Errore durante il caricamento degli account", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Crea posizione", "nav.tabs.createV2Position": "Crea posizione V2", "nav.tabs.createV3Position": "Crea posizione V3", + "nav.tabs.createV4Position": "Crea posizione V4", "nav.tabs.viewPosition": "Visualizza posizione", "network.lostConnection": "Potresti aver perso la connessione di rete.", "network.mightBeDown": "{{network}} potrebbe essere inattivo in questo momento o potresti aver perso la connessione di rete.", @@ -1331,6 +1336,7 @@ "pool.back": "Torniamo alla piscina", "pool.balances": "Saldi della piscina", "pool.claimFees": "Commissioni di reclamo", + "pool.claimFees.button.label": "Reclamo", "pool.collectAs": "Raccogli come {{nativeWrappedSymbol}}", "pool.collected": " Raccolto", "pool.collecting": "Collezionare", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Gestire la liquidità nel pool di premi", "pool.max.label": "Massimo:", "pool.maxPrice": "Prezzo massimo", + "pool.migrateToV4": "Migrare a V4", "pool.min.label": "Minimo:", "pool.minPrice": "Prezzo minimo", "pool.mustBeInitialized": "Questo pool deve essere inizializzato prima di poter aggiungere liquidità. Per inizializzare, seleziona un prezzo iniziale per il pool. Quindi, inserisci la fascia di prezzo della liquidità e l'importo del deposito. Le tariffe del gas saranno più alte del solito a causa della transazione di inizializzazione.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Suggerimento: utilizzare questo strumento per trovare pool v2 che non vengono visualizzati automaticamente nell'interfaccia.", "pools.approving.amount": "Approvazione {{amount}}", "position.addHook": "Aggiungi un gancio", + "position.addHook.tooltip": "Gli hook sono una funzionalità avanzata che consente ai pool di interagire con gli smart contract, sbloccando varie capacità. Fai attenzione quando aggiungi hook, poiché alcuni potrebbero essere dannosi o causare conseguenze indesiderate.", "position.appearHere": "La tua posizione apparirà qui.", + "position.create.modal.header": "Creazione della posizione", "position.currentValue": "Valore della posizione corrente", + "position.deposit.description": "Specifica gli importi dei token per il tuo contributo di liquidità.", "position.depositedCurrency": "Depositato {{currencySymbol}}", "position.migrate.liquidity": "Durante la migrazione delle posizioni, non è possibile modificare la coppia di token, ma è possibile aggiungere un hook per migliorarne la funzionalità.", "position.new": "Nuova posizione", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Connessione...", "qrScanner.status.loading": "Caricamento...", "qrScanner.title": "Scansiona un codice QR", - "qrScanner.wallet.title": "Puoi ricevere token e NFT su Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast e BNB Chain.", + "qrScanner.wallet.networks": "Reti supportate", + "qrScanner.wallet.title": "Puoi inviare e ricevere token e NFT su tutte le nostre reti supportate da {{numOfNetworks}} .", "removeLiquidity.collectFees": "Raccoglierai anche le commissioni guadagnate da questa posizione.", "removeLiquidity.outputEstimated": "La produzione è stimata. Se il prezzo cambia di oltre il {{allowed}}% la transazione verrà annullata.", "removeLiquidity.pendingText": "Rimozione di {{amtA}} {{symA}} e {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Soprannome", "settings.setting.wallet.notifications.title": "Notifiche", "settings.setting.wallet.preferences.title": "Preferenze del portafoglio", - "settings.showTestNets": "Mostra testnet", + "settings.setting.wallet.testnetMode.description": "Questo attiva le testnet per consentire agli sviluppatori di provare funzionalità e transazioni senza utilizzare asset reali. I token sulle testnet non hanno alcun valore reale.", + "settings.setting.wallet.testnetMode.title": "Modalità testnet", "settings.switchNetwork.warning": "Per utilizzare Uniswap su {{label}}, cambia la rete nelle impostazioni del tuo portafoglio.", "settings.title": "Impostazioni", "settings.version": "Versione {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Nuovo ingresso", "swap.details.newQuote.output": "Nuova uscita", "swap.details.orderRouting": "Instradamento degli ordini", - "swap.details.orderRoutingInfo": "Il prezzo include già i costi di rete sulla rete di destinazione e una commissione Across dello 0,05%.", + "swap.details.orderRoutingInfo": "Questo scambio viene instradato tramite Across, un protocollo decentralizzato che sposta le risorse tra le reti, dando priorità alla sicurezza, alla rapidità di esecuzione e ai prezzi bassi.", + "swap.details.poweredBy": "Offerto da", "swap.details.rate": "Valutare", "swap.details.slippage": "Slittamento massimo", "swap.details.uniswapFee": "Tassa", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Il cliente Uniswap seleziona il prezzo di factoring e i costi di rete dell'opzione commerciale più economici.", "swap.settings.routingPreference.option.v2.title": "piscine v2", "swap.settings.routingPreference.option.v3.title": "pool v3", + "swap.settings.routingPreference.option.v4.title": "piscine v4", "swap.settings.routingPreference.title": "Opzioni commerciali", "swap.settings.slippage.control.auto": "Auto", "swap.settings.slippage.description": "La transazione verrà annullata se il prezzo cambia più della percentuale di slittamento.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "La tua transazione verrà annullata se il prezzo cambia sfavorevolmente di più di questa percentuale.", "swap.signAndSwap": "Firma e scambia", "swap.slippage.amt": "{{amt}} slittamento", + "swap.slippage.bridging": "Nessuno slittamento durante lo scambio tra reti", "swap.slippage.settings.title": "Impostazioni di slittamento", "swap.slippage.tooltip": "Il movimento massimo del prezzo prima che la transazione venga annullata.", "swap.slippageBelow.warning": "Lo slittamento al di sotto di {{amt}} potrebbe comportare il fallimento della transazione", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Scadenza della transazione", "swap.transaction.revertAfter": "La tua transazione verrà annullata se rimane in sospeso per più di questo periodo di tempo.", "swap.unsupportedAssets.readMore": "Ulteriori informazioni sulle risorse non supportate", + "swap.warning.enterLargerAmount.title": "Inserisci un importo maggiore", "swap.warning.expectedFailure": "Si prevede che questa transazione fallirà", "swap.warning.feeOnTransfer.message": "Alcuni token richiedono una commissione quando vengono acquistati o venduti, che viene stabilita dall'emittente del token. Uniswap non riceve alcuna quota di queste commissioni.", "swap.warning.feeOnTransfer.title": "Perché è previsto un costo aggiuntivo?", "swap.warning.insufficientBalance.title": "Non ne hai abbastanza {{currencySymbol}}", "swap.warning.insufficientGas.button": "Non abbastanza {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Sostituisci {{ tokenSymbol }} con {{networkName}}", "swap.warning.insufficientGas.button.buy": "Acquista {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Acquista con carta", "swap.warning.insufficientGas.message.withNetwork": "Non abbastanza {{currencySymbol}} su {{networkName}} per lo scambio", "swap.warning.insufficientGas.message.withoutNetwork": "{{currencySymbol}} insufficienti per lo scambio", "swap.warning.insufficientGas.title": "Non hai abbastanza {{currencySymbol}} per coprire il costo della rete", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "dati del token per su {{chainName}}", "tdp.nameNotFound": "Nome non trovato", "tdp.noInfoAvailable": "Nessuna informazione sul token disponibile", + "tdp.noTestnetSupportDescription": "Alcune testnet non supportano lo scambio, l'invio o l'acquisto di token.", "tdp.stats.unsupportedChainDescription": "Le statistiche e i grafici dei token per {{chain}} sono disponibili su {{infoLink}}", "tdp.symbolNotFound": "Simbolo non trovato", + "testnet.unsupported": "Questa funzionalità non è supportata in modalità testnet.", "themeToggle.theme": "Tema", "title.betterPricesMoreListings": "Prezzi migliori. Più elenchi. Acquista, vendi e scambia NFT sui principali mercati come OpenSea. Esplora le collezioni di tendenza.", "title.buySellTradeEthereum": "Acquista, vendi e scambia Ethereum e altri token di punta su Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "Il saldo di {{ownerAddress}}", "token.bridge": "{{label}} ponte di gettoni", "token.chart.tooltip": "Commissioni: {{amount}}", + "token.details.testnet.unsupported": "I dettagli del token non sono disponibili per i token di testnet.", "token.error.unknown": "Gettone sconosciuto", "token.fee.buy.label": "acquistare la quota", "token.fee.label": "tassa", @@ -2212,7 +2233,6 @@ "walletConnect.pending.switchAccount": "Cambia account", "walletConnect.pending.switchNetwork": "Cambia rete", "walletConnect.pending.title": "Connettiti a {{dappName}}", - "walletConnect.permissions.networks": "Reti", "walletConnect.permissions.option.transferAssets": "Trasferisci i tuoi beni senza consenso", "walletConnect.permissions.option.viewTokenBalances": "Visualizza i saldi dei tuoi token", "walletConnect.permissions.option.viewWalletAddress": "Visualizza l'indirizzo del tuo portafoglio", diff --git a/packages/uniswap/src/i18n/locales/translations/ja-JP.json b/packages/uniswap/src/i18n/locales/translations/ja-JP.json index 10d5dc4fefa..d27e95ff5d0 100644 --- a/packages/uniswap/src/i18n/locales/translations/ja-JP.json +++ b/packages/uniswap/src/i18n/locales/translations/ja-JP.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "トークンを {{label}} ネットワークに預け入れます。", "common.deposited": "預け入れました", "common.depositing": "預け入れ中です", + "common.depositTokens": "トークンの入金", "common.detailed.label": "詳細", "common.detected": "検出されました", "common.developers": "開発者", @@ -430,6 +431,7 @@ "common.migrate.position": "ポジションを移行する", "common.migrated.liquidity": "流動性を移行しました", "common.migrating.liquidity": "流動性を移行中です", + "common.min": "分", "common.mint.cancelled": "発行をキャンセルしました", "common.mint.failed": "発行に失敗しました", "common.minted": "発行しました", @@ -450,6 +452,7 @@ "common.noResults": "結果が見つかりませんでした。", "common.notAvailableInRegion.error": "お住まいの地域ではご利用いただけません", "common.notCreated.label": "作成されていません", + "common.notSupported": "サポートされていません", "common.oneDay": "1 日", "common.oneHour": "1 時間", "common.oneMonth": "1 か月", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}、その他のオプション", "fiatOnRamp.quote.type.other": "その他のオプション", "fiatOnRamp.quote.type.recent": "最近使用したもの", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "イーサリアム、Polygon、Arbitrum、Optimism、Base、BNB、Blast、Avalanche、Zora、ZKsync でトークンと NFT を受け取ることができます。", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "アカウントから", "fiatOnRamp.receiveCrypto.title": "暗号通貨を受け取る", "fiatOnRamp.receiveCrypto.transferFunds": "別のウォレットまたはアカウントから暗号通貨を送金してウォレットに入金します", @@ -901,6 +903,8 @@ "home.activity.error.load": "アクティビティを読み込むことができませんでした", "home.activity.title": "アクティビティ", "home.banner.offline": "オフライン モードです", + "home.banner.testnetMode": "テストネットモードです", + "home.banner.testnetMode.nav": "テストネット モードになっています。設定でこれをオフに切り替えてください。", "home.explore.footer": "[検索する] をタップしてさらに詳しく調べる", "home.explore.title": "トークンを探索する", "home.extension.error": "アカウント読み込みエラーが発生しました", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "ポジションを作成する", "nav.tabs.createV2Position": "V2ポジションを作成", "nav.tabs.createV3Position": "V3ポジションを作成", + "nav.tabs.createV4Position": "V4ポジションを作成", "nav.tabs.viewPosition": "ポジションを表示する", "network.lostConnection": "ネットワーク接続が失われた可能性があります。", "network.mightBeDown": "{{network}} が現在ダウンしているか、ネットワーク接続が失われている可能性があります。", @@ -1331,6 +1336,7 @@ "pool.back": "プールに戻る", "pool.balances": "プール残高", "pool.claimFees": "手数料を請求する", + "pool.claimFees.button.label": "請求", "pool.collectAs": "{{nativeWrappedSymbol}} として徴収する", "pool.collected": " 徴収されました", "pool.collecting": "徴収中です", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "報酬プールの流動性を管理する", "pool.max.label": "最高:", "pool.maxPrice": "最高価格", + "pool.migrateToV4": "V4への移行", "pool.min.label": "最低:", "pool.minPrice": "最低価格", "pool.mustBeInitialized": "流動性を追加する前に、このプールを初期化する必要があります。初期化するには、プールの開始価格を選択します。次に、流動性価格範囲と預入金額を入力します。初期化トランザクションのため、ガス料金は通常より高くなります。", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "ヒント: このツールを使用して、インターフェイスに自動的に表示されない v2 プールを見つけます。", "pools.approving.amount": "{{amount}} を承認中です", "position.addHook": "フックを追加する", + "position.addHook.tooltip": "フックは、プールがスマート コントラクトと対話してさまざまな機能を利用できるようにする高度な機能です。フックを追加するときは注意してください。フックの中には悪意のあるものや予期しない結果を引き起こすものもあるためです。", "position.appearHere": "お客様のポジションがここに表示されます。", + "position.create.modal.header": "ポジションの作成", "position.currentValue": "現在の位置の値", + "position.deposit.description": "流動性貢献のためのトークン金額を指定します。", "position.depositedCurrency": "入金しました {{currencySymbol}}", "position.migrate.liquidity": "ポジションを移行する場合、トークンペアを変更することはできませんが、フックを追加して機能性を強化することができます。", "position.new": "新しいポジション", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "接続中です...", "qrScanner.status.loading": "読み込み中です...", "qrScanner.title": "QR コードをスキャン中です", - "qrScanner.wallet.title": "イーサリアム、Polygon、Arbitrum、Optimism、Base、ZKsync、Zora、Avalanche、Celo、Blast、BNB Chain でトークンと NFT を受け取ることができます。", + "qrScanner.wallet.networks": "サポートされているネットワーク", + "qrScanner.wallet.title": "弊社がサポートするすべての {{numOfNetworks}} ネットワークでトークンと NFT を送受信できます。", "removeLiquidity.collectFees": "このポジションから得た手数料も徴収します。", "removeLiquidity.outputEstimated": "出力は推定値です。価格が {{allowed}}% を超えて変化した場合、トランザクションは取り消されます。", "removeLiquidity.pendingText": "{{amtA}} {{symA}} と {{amtB}} {{symB}} を削除中です", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "ニックネーム", "settings.setting.wallet.notifications.title": "通知", "settings.setting.wallet.preferences.title": "ウォレットの設定", - "settings.showTestNets": "テストネットを表示する", + "settings.setting.wallet.testnetMode.description": "これにより、開発者が実際の資産を使用せずに機能やトランザクションを試すためのテストネットが有効になります。テストネット上のトークンには実際の価値はありません。", + "settings.setting.wallet.testnetMode.title": "テストネットモード", "settings.switchNetwork.warning": "Uniswapを {{label}} で使用するには、ウォレットの設定でネットワークを切り替えます。", "settings.title": "設定", "settings.version": "バージョン {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "新規の入金", "swap.details.newQuote.output": "新規の出金", "swap.details.orderRouting": "注文ルーティング", - "swap.details.orderRoutingInfo": "料金には、宛先ネットワークのネットワーク コストと 0.05% の Across 料金がすでに含まれています。", + "swap.details.orderRoutingInfo": "このスワップは、安全性、高速実行、低価格を優先しながらネットワーク間で資産を移動する分散型プロトコルである Across を介してルーティングされます。", + "swap.details.poweredBy": "搭載", "swap.details.rate": "レート", "swap.details.slippage": "最大スリッページ", "swap.details.uniswapFee": "手数料", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Uniswap クライアントは、価格とネットワーク代を考慮して最も安い取引を選択します。", "swap.settings.routingPreference.option.v2.title": "v2 プール", "swap.settings.routingPreference.option.v3.title": "v3 プール", + "swap.settings.routingPreference.option.v4.title": "v4プール", "swap.settings.routingPreference.title": "取引の選択肢", "swap.settings.slippage.control.auto": "自動", "swap.settings.slippage.description": "価格がスリッページ率を超えて変動した場合、トランザクションは取り消されます。", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "価格がこのパーセンテージを超えて不利に変化した場合、トランザクションは取り消されます。", "swap.signAndSwap": "署名してスワップする", "swap.slippage.amt": "{{amt}} スリッページ", + "swap.slippage.bridging": "ネットワーク間でのスワップ時にスリップが発生しない", "swap.slippage.settings.title": "最大スリッページ", "swap.slippage.tooltip": "トランザクションが取り消される前の最大価格変動です。", "swap.slippageBelow.warning": "スリッページが {{amt}} を下回るとトランザクションが失敗する可能性があります", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "トランザクション期限", "swap.transaction.revertAfter": "実施中になっている経過時間がこの時間を超えた場合、トランザクションは取り消されます。", "swap.unsupportedAssets.readMore": "サポートされていないアセットの詳細を読む", + "swap.warning.enterLargerAmount.title": "より大きな金額を入力してください", "swap.warning.expectedFailure": "このトランザクションは失敗することが予想されます", "swap.warning.feeOnTransfer.message": "一部のトークンは売買時に手数料がかかります。手数料はトークン発行者が設定します。Uniswap はこれらの手数料をまったく受け取りません。", "swap.warning.feeOnTransfer.title": "追加手数料がかかる理由", "swap.warning.insufficientBalance.title": "お客様の {{currencySymbol}} が十分にありません", "swap.warning.insufficientGas.button": "{{currencySymbol}} が十分にありません", + "swap.warning.insufficientGas.button.bridge": "{{networkName}}の {{ tokenSymbol }} と交換", "swap.warning.insufficientGas.button.buy": "{{ tokenSymbol }} を購入する", + "swap.warning.insufficientGas.button.buyWithCard": "カードで購入", "swap.warning.insufficientGas.message.withNetwork": "スワップする {{currencySymbol}} が {{networkName}} に十分にありません", "swap.warning.insufficientGas.message.withoutNetwork": "スワップする {{currencySymbol}} が十分にありません", "swap.warning.insufficientGas.title": "ネットワーク代をカバーするの十分な {{currencySymbol}} がありません", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "{{chainName}} の のトークン データ", "tdp.nameNotFound": "名前が見つかりません", "tdp.noInfoAvailable": "トークン情報がありません", + "tdp.noTestnetSupportDescription": "一部のテストネットでは、トークンの交換、送信、購入がサポートされていません。", "tdp.stats.unsupportedChainDescription": "{{chain}} のトークンの統計とグラフは、{{infoLink}} で入手できます", "tdp.symbolNotFound": "シンボルが見つかりません", + "testnet.unsupported": "この機能はテストネット モードではサポートされていません。", "themeToggle.theme": "テーマ", "title.betterPricesMoreListings": "より良い価格。さらに多くのリスティング。OpenSea のようなトップ マーケットプレイスで NFT を購入、販売、取引します。トレンドのコレクションをご覧ください。", "title.buySellTradeEthereum": "Uniswap でイーサリアムやその他の人気トークンを購入、販売、取引しましょう", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "{{ownerAddress}} の残高", "token.bridge": "{{label}} トークン ブリッジ", "token.chart.tooltip": "手数料: {{amount}}", + "token.details.testnet.unsupported": "テストネット トークンの場合、トークンの詳細は利用できません。", "token.error.unknown": "不明なトークンです", "token.fee.buy.label": "購入手数料", "token.fee.label": "手数料", @@ -2212,7 +2233,6 @@ "walletConnect.pending.switchAccount": "アカウントを切り替える", "walletConnect.pending.switchNetwork": "ネットワークを切り替える", "walletConnect.pending.title": "{{dappName}} に接続する", - "walletConnect.permissions.networks": "ネットワーク", "walletConnect.permissions.option.transferAssets": "同意なくお客様のアセットを送金しています", "walletConnect.permissions.option.viewTokenBalances": "トークン残高を表示する", "walletConnect.permissions.option.viewWalletAddress": "ウォレット アドレスを表示する", diff --git a/packages/uniswap/src/i18n/locales/translations/ko-KR.json b/packages/uniswap/src/i18n/locales/translations/ko-KR.json index b1d747a1d50..d712f4a8d87 100644 --- a/packages/uniswap/src/i18n/locales/translations/ko-KR.json +++ b/packages/uniswap/src/i18n/locales/translations/ko-KR.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "{{label}} 네트워크에 토큰 입금", "common.deposited": "입금됨", "common.depositing": "입금 중", + "common.depositTokens": "입금 토큰", "common.detailed.label": "상세", "common.detected": "감지됨", "common.developers": "개발자", @@ -430,6 +431,7 @@ "common.migrate.position": "포지션 마이그레이션", "common.migrated.liquidity": "이동된 유동성", "common.migrating.liquidity": "유동성 마이그레이션", + "common.min": "최소", "common.mint.cancelled": "민팅 취소됨", "common.mint.failed": "민팅 실패", "common.minted": "민팅됨", @@ -450,6 +452,7 @@ "common.noResults": "검색 결과가 없습니다.", "common.notAvailableInRegion.error": "해당 지역에서는 사용할 수 없습니다.", "common.notCreated.label": "생성되지 않음", + "common.notSupported": "지원되지 않음", "common.oneDay": "1 일", "common.oneHour": "1 시간", "common.oneMonth": "1 개월", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}및 기타 옵션", "fiatOnRamp.quote.type.other": "다른 옵션", "fiatOnRamp.quote.type.recent": "최근에 사용됨", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora 및 ZKsync에서 토큰 및 NFT를 받을 수 있습니다.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "계정에서", "fiatOnRamp.receiveCrypto.title": "암호화폐 받기", "fiatOnRamp.receiveCrypto.transferFunds": "다른 지갑이나 계정에서 암호화폐를 전송하여 지갑을 충전하세요", @@ -901,6 +903,8 @@ "home.activity.error.load": "액티비티를 로드할 수 없습니다.", "home.activity.title": "액티비티", "home.banner.offline": "현재 오프라인 모드입니다", + "home.banner.testnetMode": "테스트넷 모드에 있습니다", + "home.banner.testnetMode.nav": "테스트넷 모드에 있습니다. 설정에서 끄세요.", "home.explore.footer": "수천 개의 토큰, NFT 등을 탐색하려면 여기를 누르세요.", "home.explore.title": "토큰 살펴보기", "home.extension.error": "계정을 로드하는 중에 오류가 발생했습니다.", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "포지션 생성", "nav.tabs.createV2Position": "V2 포지션 생성", "nav.tabs.createV3Position": "V3 포지션 생성", + "nav.tabs.createV4Position": "V4 위치 생성", "nav.tabs.viewPosition": "포지션 보기", "network.lostConnection": "네트워크 연결이 끊어졌을 수 있습니다.", "network.mightBeDown": "{{network}} 이 지금 다운되었거나 네트워크 연결이 끊어졌을 수 있습니다.", @@ -1331,6 +1336,7 @@ "pool.back": "풀로 돌아가기", "pool.balances": "풀 잔액", "pool.claimFees": "클레임 수수료", + "pool.claimFees.button.label": "주장하다", "pool.collectAs": "{{nativeWrappedSymbol}}로 수집", "pool.collected": " 수집됨", "pool.collecting": "수집 중", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "보상 풀의 유동성 관리", "pool.max.label": "최대:", "pool.maxPrice": "최고가", + "pool.migrateToV4": "V4로 마이그레이션", "pool.min.label": "최소:", "pool.minPrice": "최저가", "pool.mustBeInitialized": "유동성을 추가하려면 먼저 이 풀을 초기화해야 합니다. 초기화하려면 풀의 시작 가격을 선택하세요. 그런 다음 유동성 가격 범위와 입금액을 입력하세요. 초기화 트랜잭션으로 인해 가스 요금이 평소보다 높아집니다.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "팁: 인터페이스에 자동으로 표시되지 않는 v2 풀을 찾으려면 이 도구를 사용하세요.", "pools.approving.amount": "{{amount}} 승인 중", "position.addHook": "훅 추가", + "position.addHook.tooltip": "후크는 풀이 스마트 계약과 상호 작용하여 다양한 기능을 잠금 해제할 수 있도록 하는 고급 기능입니다. 후크를 추가할 때는 일부는 악의적이거나 의도치 않은 결과를 초래할 수 있으므로 주의하세요.", "position.appearHere": "당신의 포지션이 여기에 표시됩니다.", + "position.create.modal.header": "위치 생성", "position.currentValue": "현재 위치 값", + "position.deposit.description": "유동성 기여에 필요한 토큰 금액을 지정하세요.", "position.depositedCurrency": "입금된 {{currencySymbol}}", "position.migrate.liquidity": "포지션을 마이그레이션할 때, 토큰 페어를 변경할 수는 없지만 기능을 향상시키기 위해 후크를 추가할 수 있습니다.", "position.new": "새 포지션", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "연결 중...", "qrScanner.status.loading": "로드 중...", "qrScanner.title": "QR 코드 스캔", - "qrScanner.wallet.title": "Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast 및 BNB Chain에서 토큰 및 NFT를 받을 수 있습니다.", + "qrScanner.wallet.networks": "지원 네트워크", + "qrScanner.wallet.title": "{{numOfNetworks}} 가 지원하는 모든 네트워크에서 토큰과 NFT를 보내고 받을 수 있습니다.", "removeLiquidity.collectFees": "이 포지션에서 얻은 수수료도 받게 됩니다.", "removeLiquidity.outputEstimated": "결과물은 추정치입니다. 가격이 {{allowed}}% 이상 변동하면 트랜잭션이 취소됩니다.", "removeLiquidity.pendingText": "{{amtA}} {{symA}} 및 {{amtB}} {{symB}}제거", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "별명", "settings.setting.wallet.notifications.title": "알림", "settings.setting.wallet.preferences.title": "지갑 환경설정", - "settings.showTestNets": "테스트넷 표시", + "settings.setting.wallet.testnetMode.description": "이는 개발자가 실제 자산을 사용하지 않고도 기능과 거래를 시도할 수 있는 테스트넷을 켭니다. 테스트넷의 토큰은 실제 가치를 보유하지 않습니다.", + "settings.setting.wallet.testnetMode.title": "테스트넷 모드", "settings.switchNetwork.warning": "{{label}}에서 Uniswap을 사용하려면 지갑 설정에서 네트워크를 전환하세요.", "settings.title": "설정", "settings.version": "버전 {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "새로운 입력", "swap.details.newQuote.output": "새로운 출력", "swap.details.orderRouting": "주문 라우팅", - "swap.details.orderRoutingInfo": "귀하의 가격에는 대상 네트워크의 네트워크 비용과 0.05%의 수수료가 이미 포함되어 있습니다.", + "swap.details.orderRoutingInfo": "이 스왑은 안전성, 빠른 실행, 낮은 가격을 우선시하면서 네트워크 전반에서 자산을 이동하는 분산형 프로토콜인 Across를 통해 라우팅됩니다.", + "swap.details.poweredBy": "제공자:", "swap.details.rate": "비율", "swap.details.slippage": "최대 슬리피지", "swap.details.uniswapFee": "수수료", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Uniswap 클라이언트는 가격과 네트워크 비용을 고려하여 가장 저렴한 거래 옵션을 선택합니다.", "swap.settings.routingPreference.option.v2.title": "v2 풀", "swap.settings.routingPreference.option.v3.title": "v3 풀", + "swap.settings.routingPreference.option.v4.title": "v4 풀", "swap.settings.routingPreference.title": "거래 옵션", "swap.settings.slippage.control.auto": "자동", "swap.settings.slippage.description": "가격이 슬리피지 비율 이상으로 변동하면 트랜잭션이 취소됩니다.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "가격이 이 비율 이상으로 불리하게 변경되면 트랜잭션이 취소됩니다.", "swap.signAndSwap": "서명 및 스왑", "swap.slippage.amt": "{{amt}} 슬리피지", + "swap.slippage.bridging": "네트워크 간 전환 시 미끄러짐 없음", "swap.slippage.settings.title": "최대 슬리피지", "swap.slippage.tooltip": "트랜잭션이 취소되기 전의 최대 가격 변동입니다.", "swap.slippageBelow.warning": "{{amt}} 아래의 슬리피지로 인해 트랜잭션이 실패할 수 있습니다.", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "트랜잭션 마감시간", "swap.transaction.revertAfter": "이 기간 이상 보류 중인 경우 트랜잭션이 취소됩니다.", "swap.unsupportedAssets.readMore": "지원되지 않는 자산에 대해 자세히 알아보세요.", + "swap.warning.enterLargerAmount.title": "더 큰 금액을 입력하세요", "swap.warning.expectedFailure": "이 트랜잭션은 실패할 것으로 예상됩니다.", "swap.warning.feeOnTransfer.message": "일부 토큰은 매수 또는 매도 시 토큰 발행자가 설정한 수수료를 부과합니다. Uniswap은 이러한 수수료의 일부를 받지 않습니다.", "swap.warning.feeOnTransfer.title": "왜 추가요금이 있나요?", "swap.warning.insufficientBalance.title": "{{currencySymbol}}이(가) 충분하지 않습니다", "swap.warning.insufficientGas.button": "{{currencySymbol}} 부족", + "swap.warning.insufficientGas.button.bridge": "{{ tokenSymbol }} 를 {{networkName}}로 바꾸세요", "swap.warning.insufficientGas.button.buy": "{{ tokenSymbol }}구매", + "swap.warning.insufficientGas.button.buyWithCard": "카드로 구매", "swap.warning.insufficientGas.message.withNetwork": "{{networkName}}에서 {{currencySymbol}}이(가) 부족함으로 스왑할 수 없습니다", "swap.warning.insufficientGas.message.withoutNetwork": "{{currencySymbol}} 스왑하기에 부족함", "swap.warning.insufficientGas.title": "네트워크 비용을 충당할 만큼 {{currencySymbol}} 이 부족합니다.", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "{{chainName}}의 에 대한 토큰 데이터", "tdp.nameNotFound": "이름을 찾을 수 없습니다", "tdp.noInfoAvailable": "사용 가능한 토큰 정보가 없습니다.", + "tdp.noTestnetSupportDescription": "일부 테스트넷은 토큰 교환, 전송, 구매를 지원하지 않습니다.", "tdp.stats.unsupportedChainDescription": "{{chain}} 에 대한 토큰 통계 및 차트는 {{infoLink}}에서 확인할 수 있습니다.", "tdp.symbolNotFound": "기호를 찾을 수 없습니다", + "testnet.unsupported": "이 기능은 테스트넷 모드에서는 지원되지 않습니다.", "themeToggle.theme": "테마", "title.betterPricesMoreListings": "더 좋은 가격. 더 많은 목록. OpenSea 같은 상위 마켓플레이스에서 NFT를 사고팔고 거래하세요. 트렌딩 컬렉션을 탐색하세요.", "title.buySellTradeEthereum": "Uniswap에서 이더리움 및 기타 주요 토큰을 구매, 판매 및 거래하세요", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "{{ownerAddress}}의 잔액", "token.bridge": "{{label}} 토큰 브릿지", "token.chart.tooltip": "수수료 : {{amount}}", + "token.details.testnet.unsupported": "테스트넷 토큰의 경우 토큰 세부정보를 사용할 수 없습니다.", "token.error.unknown": "알 수 없는 토큰", "token.fee.buy.label": "구매 수수료", "token.fee.label": "수수료", @@ -2109,6 +2130,8 @@ "transaction.warning.insufficientGas.modal.title.withoutNetwork": "{{tokenSymbol}}이 충분하지 않습니다.", "transaction.watcher.error.cancel": "트랜잭션을 취소할 수 없습니다.", "transaction.watcher.error.status": "트랜잭션 상태를 확인하는 중 오류가 발생했습니다.", + "unichain.launch.modal.description": "Uniswap Labs에서 구축한 DeFi를 위해 설계된 Ethereum L2. Testnet이 오늘 출시됩니다.", + "unichain.launch.modal.title": "유니체인 소개", "uniswapX.aggregatesLiquidity": "는 더 나은 가격과 가스 없는 스왑을 위해 유동성 원천을 집계합니다.", "uniswapx.description": "UniswapX는 더 나은 가격과 가스 프리 스왑을 위해 유동성 소스를 통합합니다.", "uniswapx.included": " UniswapX 포함", @@ -2212,7 +2235,6 @@ "walletConnect.pending.switchAccount": "계정 전환", "walletConnect.pending.switchNetwork": "네트워크 전환", "walletConnect.pending.title": "{{dappName}}에 연결", - "walletConnect.permissions.networks": "네트워크", "walletConnect.permissions.option.transferAssets": "동의 없이 자산을 양도하세요.", "walletConnect.permissions.option.viewTokenBalances": "토큰 잔액 보기", "walletConnect.permissions.option.viewWalletAddress": "지갑 주소 보기", diff --git a/packages/uniswap/src/i18n/locales/translations/ms-MY.json b/packages/uniswap/src/i18n/locales/translations/ms-MY.json index 6160170426e..1e984708872 100644 --- a/packages/uniswap/src/i18n/locales/translations/ms-MY.json +++ b/packages/uniswap/src/i18n/locales/translations/ms-MY.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Deposit token ke rangkaian {{label}} .", "common.deposited": "Didepositkan", "common.depositing": "Mendepositkan", + "common.depositTokens": "Token deposit", "common.detailed.label": "Terperinci", "common.detected": "Dikesan", "common.developers": "pemaju", @@ -430,6 +431,7 @@ "common.migrate.position": "Berhijrah kedudukan", "common.migrated.liquidity": "Kecairan berhijrah", "common.migrating.liquidity": "Menghijrahkan kecairan", + "common.min": "Min", "common.mint.cancelled": "Pudina dibatalkan", "common.mint.failed": "Pudina gagal", "common.minted": "Dicetak", @@ -450,6 +452,7 @@ "common.noResults": "Tiada keputusan dijumpai.", "common.notAvailableInRegion.error": "Tidak tersedia di wilayah anda", "common.notCreated.label": "Tidak dicipta", + "common.notSupported": "Tidak disokong", "common.oneDay": "1 hari", "common.oneHour": "1 jam", "common.oneMonth": "1 bulan", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}, dan pilihan lain", "fiatOnRamp.quote.type.other": "Pilihan lain", "fiatOnRamp.quote.type.recent": "Baru-baru ini digunakan", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Anda boleh menerima token & NFT pada Ethereum, Polygon, Arbitrum, Optimisme, Base, BNB, Blast, Avalanche, Zora dan ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Daripada akaun", "fiatOnRamp.receiveCrypto.title": "Terima crypto", "fiatOnRamp.receiveCrypto.transferFunds": "Membiayai dompet anda dengan memindahkan kripto daripada dompet atau akaun lain", @@ -901,6 +903,8 @@ "home.activity.error.load": "Tidak dapat memuatkan aktiviti", "home.activity.title": "Aktiviti", "home.banner.offline": "Anda berada dalam mod luar talian", + "home.banner.testnetMode": "Anda berada dalam mod testnet", + "home.banner.testnetMode.nav": "Anda berada dalam mod testnet. Togol ini mati dalam tetapan.", "home.explore.footer": "Ketik di sini untuk meneroka beribu-ribu token, NFT dan banyak lagi", "home.explore.title": "Terokai token", "home.extension.error": "Ralat memuatkan akaun", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Cipta kedudukan", "nav.tabs.createV2Position": "Buat kedudukan V2", "nav.tabs.createV3Position": "Buat kedudukan V3", + "nav.tabs.createV4Position": "Buat kedudukan V4", "nav.tabs.viewPosition": "Lihat kedudukan", "network.lostConnection": "Anda mungkin telah kehilangan sambungan rangkaian anda.", "network.mightBeDown": "{{network}} mungkin terputus sekarang, atau anda mungkin terputus sambungan rangkaian anda.", @@ -1331,6 +1336,7 @@ "pool.back": "Kembali ke Kolam", "pool.balances": "Baki kolam", "pool.claimFees": "Tuntutan yuran", + "pool.claimFees.button.label": "Tuntutan", "pool.collectAs": "Kumpul sebagai {{nativeWrappedSymbol}}", "pool.collected": " Dikumpul", "pool.collecting": "Mengumpul", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Urus kecairan dalam kumpulan ganjaran", "pool.max.label": "Maks:", "pool.maxPrice": "Harga maks", + "pool.migrateToV4": "Berhijrah ke V4", "pool.min.label": "Min:", "pool.minPrice": "Harga min", "pool.mustBeInitialized": "Kumpulan ini mesti dimulakan sebelum anda boleh menambah kecairan. Untuk memulakan, pilih harga permulaan untuk kumpulan. Kemudian, masukkan julat harga kecairan anda dan jumlah deposit. Yuran gas akan lebih tinggi daripada biasa disebabkan oleh transaksi permulaan.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Petua: Gunakan alat ini untuk mencari kumpulan v2 yang tidak muncul secara automatik dalam antara muka.", "pools.approving.amount": "Meluluskan {{amount}}", "position.addHook": "Tambah Cangkuk", + "position.addHook.tooltip": "Cangkuk ialah ciri lanjutan yang membolehkan kumpulan berinteraksi dengan kontrak pintar, membuka kunci pelbagai keupayaan. Berhati-hati apabila menambah cangkuk, kerana sesetengahnya mungkin berniat jahat atau menyebabkan akibat yang tidak diingini.", "position.appearHere": "Kedudukan anda akan dipaparkan di sini.", + "position.create.modal.header": "Mencipta kedudukan", "position.currentValue": "Nilai kedudukan semasa", + "position.deposit.description": "Nyatakan jumlah token untuk sumbangan kecairan anda.", "position.depositedCurrency": "Didepositkan {{currencySymbol}}", "position.migrate.liquidity": "Apabila memindahkan kedudukan, anda tidak boleh menukar pasangan token anda, tetapi anda boleh menambah cangkuk untuk meningkatkan fungsi.", "position.new": "Jawatan Baru", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Menyambung...", "qrScanner.status.loading": "Memuatkan...", "qrScanner.title": "Imbas kod QR", - "qrScanner.wallet.title": "Anda boleh menerima token & NFT pada Ethereum, Poligon, Arbitrum, Optimisme, Pangkalan, ZKsync, Zora, Avalanche, Celo, Blast dan Rantaian BNB.", + "qrScanner.wallet.networks": "Rangkaian yang Disokong", + "qrScanner.wallet.title": "Anda boleh menghantar dan menerima token dan NFT pada semua rangkaian {{numOfNetworks}} kami yang disokong.", "removeLiquidity.collectFees": "Anda juga akan mengutip yuran yang diperoleh daripada kedudukan ini.", "removeLiquidity.outputEstimated": "Output dianggarkan. Jika harga berubah lebih daripada {{allowed}}% transaksi anda akan kembali.", "removeLiquidity.pendingText": "Mengalih keluar {{amtA}} {{symA}} dan {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "nama samaran", "settings.setting.wallet.notifications.title": "Pemberitahuan", "settings.setting.wallet.preferences.title": "Keutamaan dompet", - "settings.showTestNets": "Tunjukkan testnets", + "settings.setting.wallet.testnetMode.description": "Ini menghidupkan testnets untuk pembangun mencuba ciri dan transaksi tanpa menggunakan aset sebenar. Token pada testnets tidak mempunyai sebarang nilai sebenar.", + "settings.setting.wallet.testnetMode.title": "Mod Testnet", "settings.switchNetwork.warning": "Untuk menggunakan Uniswap pada {{label}}, tukar rangkaian dalam tetapan dompet anda.", "settings.title": "tetapan", "settings.version": "Versi {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Input baharu", "swap.details.newQuote.output": "Keluaran baharu", "swap.details.orderRouting": "Penghalaan pesanan", - "swap.details.orderRoutingInfo": "Harga anda sudah termasuk kos rangkaian pada rangkaian destinasi dan yuran Sepanjang 0.05%.", + "swap.details.orderRoutingInfo": "Pertukaran ini dihalakan melalui Across, protokol terdesentralisasi yang memindahkan aset merentas rangkaian sambil mengutamakan keselamatan, pelaksanaan pantas dan harga yang rendah.", + "swap.details.poweredBy": "Dikuasakan oleh", "swap.details.rate": "Kadar", "swap.details.slippage": "Kegelinciran maksimum", "swap.details.uniswapFee": "Bayaran", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Pelanggan Uniswap memilih harga pemfaktoran pilihan perdagangan termurah dan kos rangkaian.", "swap.settings.routingPreference.option.v2.title": "kolam v2", "swap.settings.routingPreference.option.v3.title": "kolam v3", + "swap.settings.routingPreference.option.v4.title": "kolam v4", "swap.settings.routingPreference.title": "Pilihan perdagangan", "swap.settings.slippage.control.auto": "Auto", "swap.settings.slippage.description": "Urus niaga anda akan kembali jika harga berubah lebih daripada peratusan gelinciran.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Urus niaga anda akan kembali jika harga berubah secara tidak menguntungkan dengan lebih daripada peratusan ini.", "swap.signAndSwap": "Tandatangan dan tukar", "swap.slippage.amt": "{{amt}} gelincir", + "swap.slippage.bridging": "Tiada gelinciran apabila bertukar-tukar merentasi rangkaian", "swap.slippage.settings.title": "Tetapan gelinciran", "swap.slippage.tooltip": "Pergerakan harga maksimum sebelum transaksi anda akan kembali.", "swap.slippageBelow.warning": "Gelinciran di bawah {{amt}} boleh mengakibatkan transaksi gagal", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Tarikh akhir urus niaga", "swap.transaction.revertAfter": "Urus niaga anda akan kembali jika ia belum selesai lebih daripada tempoh masa ini.", "swap.unsupportedAssets.readMore": "Baca lebih lanjut tentang aset yang tidak disokong", + "swap.warning.enterLargerAmount.title": "Masukkan jumlah yang lebih besar", "swap.warning.expectedFailure": "Urus niaga ini dijangka gagal", "swap.warning.feeOnTransfer.message": "Sesetengah token mengambil bayaran apabila ia dibeli atau dijual, yang ditetapkan oleh pengeluar token. Uniswap tidak menerima sebarang bahagian daripada yuran ini.", "swap.warning.feeOnTransfer.title": "Kenapa ada bayaran tambahan?", "swap.warning.insufficientBalance.title": "Anda tidak mempunyai {{currencySymbol}}", "swap.warning.insufficientGas.button": "Tidak cukup {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Tukar kepada {{ tokenSymbol }} pada {{networkName}}", "swap.warning.insufficientGas.button.buy": "Beli {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Beli dengan kad", "swap.warning.insufficientGas.message.withNetwork": "Tidak cukup {{currencySymbol}} pada {{networkName}} untuk bertukar", "swap.warning.insufficientGas.message.withoutNetwork": "Tidak cukup {{currencySymbol}} untuk bertukar", "swap.warning.insufficientGas.title": "Anda tidak mempunyai cukup {{currencySymbol}} untuk menampung kos rangkaian", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "data token untuk pada {{chainName}}", "tdp.nameNotFound": "Nama tidak ditemui", "tdp.noInfoAvailable": "Tiada maklumat token tersedia", + "tdp.noTestnetSupportDescription": "Sesetengah testnet tidak menyokong pertukaran, penghantaran atau pembelian token.", "tdp.stats.unsupportedChainDescription": "Statistik dan carta token untuk {{chain}} tersedia di {{infoLink}}", "tdp.symbolNotFound": "Simbol tidak ditemui", + "testnet.unsupported": "Fungsi ini tidak disokong dalam mod testnet.", "themeToggle.theme": "Tema", "title.betterPricesMoreListings": "Harga yang lebih baik. Lebih banyak penyenaraian. Beli, jual dan dagangan NFT merentas pasaran teratas seperti OpenSea. Terokai koleksi sohor kini.", "title.buySellTradeEthereum": "Beli, jual & niagakan Ethereum dan token teratas lain di Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "baki {{ownerAddress}}", "token.bridge": "{{label}} jambatan token", "token.chart.tooltip": "Yuran: {{amount}}", + "token.details.testnet.unsupported": "Butiran token tidak tersedia untuk token testnet.", "token.error.unknown": "Token tidak diketahui", "token.fee.buy.label": "yuran beli", "token.fee.label": "bayaran", @@ -2212,7 +2233,6 @@ "walletConnect.pending.switchAccount": "Tukar akaun", "walletConnect.pending.switchNetwork": "Tukar Rangkaian", "walletConnect.pending.title": "Sambung ke {{dappName}}", - "walletConnect.permissions.networks": "Rangkaian", "walletConnect.permissions.option.transferAssets": "Pindahkan aset anda tanpa kebenaran", "walletConnect.permissions.option.viewTokenBalances": "Lihat baki token anda", "walletConnect.permissions.option.viewWalletAddress": "Lihat alamat dompet anda", diff --git a/packages/uniswap/src/i18n/locales/translations/nl-NL.json b/packages/uniswap/src/i18n/locales/translations/nl-NL.json index fc118595963..2f66013e130 100644 --- a/packages/uniswap/src/i18n/locales/translations/nl-NL.json +++ b/packages/uniswap/src/i18n/locales/translations/nl-NL.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Stort tokens op het {{label}} netwerk.", "common.deposited": "Gestort", "common.depositing": "Storten", + "common.depositTokens": "Stort tokens", "common.detailed.label": "Gedetailleerd", "common.detected": "Gedetecteerd", "common.developers": "Ontwikkelaars", @@ -430,6 +431,7 @@ "common.migrate.position": "Positie migreren", "common.migrated.liquidity": "Gemigreerde liquiditeit", "common.migrating.liquidity": "Het migreren van liquiditeit", + "common.min": "Mijn", "common.mint.cancelled": "Munt geannuleerd", "common.mint.failed": "Munt is mislukt", "common.minted": "Geslagen", @@ -450,6 +452,7 @@ "common.noResults": "geen resultaten gevonden.", "common.notAvailableInRegion.error": "Niet beschikbaar in uw regio", "common.notCreated.label": "Niet gemaakt", + "common.notSupported": "Niet ondersteund", "common.oneDay": "1 dag", "common.oneHour": "1 uur", "common.oneMonth": "1 maand", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}en andere opties", "fiatOnRamp.quote.type.other": "Andere opties", "fiatOnRamp.quote.type.recent": "Laatst gebruikt", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Je kunt tokens en NFT's ontvangen op Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora en ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Van een rekening", "fiatOnRamp.receiveCrypto.title": "Crypto ontvangen", "fiatOnRamp.receiveCrypto.transferFunds": "Stort op uw portemonnee door de overdracht van crypto van een andere portemonnee of account", @@ -901,6 +903,8 @@ "home.activity.error.load": "Kan activiteit niet laden", "home.activity.title": "Activiteit", "home.banner.offline": "U bevindt zich in de offlinemodus", + "home.banner.testnetMode": "U bevindt zich in de testnetmodus", + "home.banner.testnetMode.nav": "U bevindt zich in de testnetmodus. Schakel dit uit in de instellingen.", "home.explore.footer": "Tik hier om duizenden tokens, NFT's en meer te verkennen", "home.explore.title": "Ontdek tokens", "home.extension.error": "Fout bij het laden van accounts", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Positie creëren", "nav.tabs.createV2Position": "V2-positie maken", "nav.tabs.createV3Position": "V3-positie maken", + "nav.tabs.createV4Position": "V4-positie maken", "nav.tabs.viewPosition": "Positie bekijken", "network.lostConnection": "Mogelijk bent u uw netwerkverbinding kwijtgeraakt.", "network.mightBeDown": "Het kan zijn dat {{network}} momenteel niet beschikbaar is, of dat je netwerkverbinding verloren is gegaan.", @@ -1331,6 +1336,7 @@ "pool.back": "Terug naar Zwembad", "pool.balances": "Poolsaldi", "pool.claimFees": "Kosten claimen", + "pool.claimFees.button.label": "Claim", "pool.collectAs": "Verzamel als {{nativeWrappedSymbol}}", "pool.collected": " Verzameld", "pool.collecting": "Verzamelen", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Beheer de liquiditeit in de beloningspool", "pool.max.label": "Maximaal:", "pool.maxPrice": "Maximale prijs", + "pool.migrateToV4": "Migreren naar V4", "pool.min.label": "Min:", "pool.minPrice": "Minimale prijs", "pool.mustBeInitialized": "Deze pool moet worden geïnitialiseerd voordat u liquiditeit kunt toevoegen. Selecteer een startprijs voor de pool om te initialiseren. Voer vervolgens uw liquiditeitsprijsbereik en stortingsbedrag in. De gaskosten zullen hoger zijn dan normaal als gevolg van de initialisatietransactie.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Tip: Gebruik deze tool om v2-pools te vinden die niet automatisch in de interface verschijnen.", "pools.approving.amount": "{{amount}}goedkeuren", "position.addHook": "Voeg een haak toe", + "position.addHook.tooltip": "Hooks zijn een geavanceerde functie waarmee pools kunnen interacteren met smart contracts, waardoor verschillende mogelijkheden worden ontgrendeld. Wees voorzichtig bij het toevoegen van hooks, omdat sommige schadelijk kunnen zijn of onbedoelde gevolgen kunnen hebben.", "position.appearHere": "Uw positie zal hier verschijnen.", + "position.create.modal.header": "Positie creëren", "position.currentValue": "Huidige positiewaarde", + "position.deposit.description": "Geef de tokenbedragen voor uw liquiditeitsbijdrage op.", "position.depositedCurrency": "Gestort {{currencySymbol}}", "position.migrate.liquidity": "Bij het migreren van posities kunt u uw tokenpaar niet wijzigen, maar u kunt wel een hook toevoegen om de functionaliteit te verbeteren.", "position.new": "Nieuwe positie", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Verbinden...", "qrScanner.status.loading": "Bezig met laden...", "qrScanner.title": "Scan een QR-code", - "qrScanner.wallet.title": "Je kunt tokens en NFT's ontvangen op Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast en BNB Chain.", + "qrScanner.wallet.networks": "Ondersteunde netwerken", + "qrScanner.wallet.title": "U kunt tokens en NFT's verzenden en ontvangen op al onze {{numOfNetworks}} ondersteunde netwerken.", "removeLiquidity.collectFees": "U verzamelt ook de vergoedingen die u uit deze functie verdient.", "removeLiquidity.outputEstimated": "De output wordt geschat. Als de prijs met meer dan {{allowed}}% verandert, wordt uw transactie teruggedraaid.", "removeLiquidity.pendingText": "{{amtA}} {{symA}} en {{amtB}} {{symB}}verwijderen", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Bijnaam", "settings.setting.wallet.notifications.title": "Meldingen", "settings.setting.wallet.preferences.title": "Wallet-voorkeuren", - "settings.showTestNets": "Toon testnetten", + "settings.setting.wallet.testnetMode.description": "Dit schakelt testnets in voor ontwikkelaars om functies en transacties uit te proberen zonder echte assets te gebruiken. Tokens op testnets hebben geen echte waarde.", + "settings.setting.wallet.testnetMode.title": "Testnet-modus", "settings.switchNetwork.warning": "Om Uniswap op {{label}}te gebruiken, wijzigt u het netwerk in de instellingen van uw portemonnee.", "settings.title": "Instellingen", "settings.version": "Versie {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Nieuwe invoer", "swap.details.newQuote.output": "Nieuwe uitvoer", "swap.details.orderRouting": "Orderroutering", - "swap.details.orderRoutingInfo": "In uw prijs zijn de netwerkkosten voor het bestemmingsnetwerk en een toeslag van 0,05% inbegrepen.", + "swap.details.orderRoutingInfo": "Deze swap wordt gerouteerd via Across, een gedecentraliseerd protocol dat activa over netwerken verplaatst met prioriteit voor veiligheid, snelle uitvoering en lage prijzen.", + "swap.details.poweredBy": "Aangedreven door", "swap.details.rate": "Tarief", "swap.details.slippage": "Maximale slip", "swap.details.uniswapFee": "Tarief", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "De Uniswap-klant selecteert de goedkoopste handelsoptie, rekening houdend met de prijs en netwerkkosten.", "swap.settings.routingPreference.option.v2.title": "v2 zwembaden", "swap.settings.routingPreference.option.v3.title": "v3 zwembaden", + "swap.settings.routingPreference.option.v4.title": "v4-zwembaden", "swap.settings.routingPreference.title": "Handelsopties", "swap.settings.slippage.control.auto": "Auto", "swap.settings.slippage.description": "Uw transactie wordt teruggedraaid als de prijs meer verandert dan het slippercentage.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Uw transactie wordt teruggedraaid als de prijs met meer dan dit percentage ongunstig verandert.", "swap.signAndSwap": "Teken en ruil", "swap.slippage.amt": "{{amt}} slippen", + "swap.slippage.bridging": "Geen slip bij het wisselen tussen netwerken", "swap.slippage.settings.title": "Slip-instellingen", "swap.slippage.tooltip": "De maximale koersbeweging voordat uw transactie wordt teruggedraaid.", "swap.slippageBelow.warning": "Een slip onder {{amt}} kan resulteren in een mislukte transactie", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Transactiedeadline", "swap.transaction.revertAfter": "Uw transactie wordt teruggedraaid als deze langer dan deze periode in behandeling is.", "swap.unsupportedAssets.readMore": "Lees meer over niet-ondersteunde items", + "swap.warning.enterLargerAmount.title": "Voer een groter bedrag in", "swap.warning.expectedFailure": "Deze transactie zal naar verwachting mislukken", "swap.warning.feeOnTransfer.message": "Voor sommige tokens geldt een vergoeding wanneer ze worden gekocht of verkocht. Deze wordt bepaald door de tokenuitgever. Uniswap ontvangt geen enkel deel van deze vergoedingen.", "swap.warning.feeOnTransfer.title": "Waarom is er een extra vergoeding?", "swap.warning.insufficientBalance.title": "Je hebt niet genoeg {{currencySymbol}}", "swap.warning.insufficientGas.button": "Niet genoeg {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Ruil voor {{ tokenSymbol }} op {{networkName}}", "swap.warning.insufficientGas.button.buy": "{{ tokenSymbol }} kopen", + "swap.warning.insufficientGas.button.buyWithCard": "Kopen met kaart", "swap.warning.insufficientGas.message.withNetwork": "Niet genoeg {{currencySymbol}} op {{networkName}} om te wisselen", "swap.warning.insufficientGas.message.withoutNetwork": "Niet genoeg {{currencySymbol}} om te wisselen", "swap.warning.insufficientGas.title": "U heeft niet genoeg {{currencySymbol}} om de netwerkkosten te dekken", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "tokengegevens voor op {{chainName}}", "tdp.nameNotFound": "Naam niet gevonden", "tdp.noInfoAvailable": "Geen tokeninformatie beschikbaar", + "tdp.noTestnetSupportDescription": "Sommige testnetten ondersteunen het ruilen, verzenden of kopen van tokens niet.", "tdp.stats.unsupportedChainDescription": "Tokenstatistieken en grafieken voor {{chain}} zijn beschikbaar op {{infoLink}}", "tdp.symbolNotFound": "Symbool niet gevonden", + "testnet.unsupported": "Deze functionaliteit wordt niet ondersteund in de testnetmodus.", "themeToggle.theme": "Thema", "title.betterPricesMoreListings": "Betere prijzen. Meer vermeldingen. Koop, verkoop en verhandel NFT's op topmarktplaatsen zoals OpenSea. Ontdek trending collecties.", "title.buySellTradeEthereum": "Koop, verkoop en verhandel Ethereum en andere toptokens op Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "Het saldo van {{ownerAddress}}", "token.bridge": "{{label}} tokenbrug", "token.chart.tooltip": "Kosten: {{amount}}", + "token.details.testnet.unsupported": "Tokengegevens zijn niet beschikbaar voor testnettokens.", "token.error.unknown": "Onbekend teken", "token.fee.buy.label": "aankoopkosten", "token.fee.label": "tarief", @@ -2212,7 +2233,6 @@ "walletConnect.pending.switchAccount": "Verwissel van profiel", "walletConnect.pending.switchNetwork": "Schakel netwerk", "walletConnect.pending.title": "Maak verbinding met {{dappName}}", - "walletConnect.permissions.networks": "Netwerken", "walletConnect.permissions.option.transferAssets": "Breng uw bezittingen over zonder toestemming", "walletConnect.permissions.option.viewTokenBalances": "Bekijk uw tokensaldi", "walletConnect.permissions.option.viewWalletAddress": "Bekijk uw portemonnee-adres", diff --git a/packages/uniswap/src/i18n/locales/translations/no-NO.json b/packages/uniswap/src/i18n/locales/translations/no-NO.json index ec8ef39d7a6..c6e79788a1b 100644 --- a/packages/uniswap/src/i18n/locales/translations/no-NO.json +++ b/packages/uniswap/src/i18n/locales/translations/no-NO.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Sett inn tokens til {{label}} -nettverket.", "common.deposited": "Deponert", "common.depositing": "Innskudd", + "common.depositTokens": "Innskuddsbrikker", "common.detailed.label": "Detaljert", "common.detected": "Oppdaget", "common.developers": "Utviklere", @@ -430,6 +431,7 @@ "common.migrate.position": "Migrer posisjon", "common.migrated.liquidity": "Migrert likviditet", "common.migrating.liquidity": "Migrering av likviditet", + "common.min": "Min", "common.mint.cancelled": "Mint kansellert", "common.mint.failed": "Mint mislyktes", "common.minted": "Preget", @@ -450,6 +452,7 @@ "common.noResults": "Ingen resultater.", "common.notAvailableInRegion.error": "Ikke tilgjengelig i din region", "common.notCreated.label": "Ikke opprettet", + "common.notSupported": "Støttes ikke", "common.oneDay": "1 dag", "common.oneHour": "1 time", "common.oneMonth": "1 måned", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}og andre alternativer", "fiatOnRamp.quote.type.other": "Andre muligheter", "fiatOnRamp.quote.type.recent": "Nylig brukt", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Du kan motta tokens og NFT-er på Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora og ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Fra en konto", "fiatOnRamp.receiveCrypto.title": "Motta krypto", "fiatOnRamp.receiveCrypto.transferFunds": "Finansier lommeboken din ved å overføre krypto fra en annen lommebok eller konto", @@ -901,6 +903,8 @@ "home.activity.error.load": "Kunne ikke laste inn aktiviteten", "home.activity.title": "Aktivitet", "home.banner.offline": "Du er i frakoblet modus", + "home.banner.testnetMode": "Du er i testnettmodus", + "home.banner.testnetMode.nav": "Du er i testnettmodus. Slå dette av i innstillingene.", "home.explore.footer": "Trykk her for å utforske tusenvis av tokens, NFT-er og mer", "home.explore.title": "Utforsk tokens", "home.extension.error": "Feil ved innlasting av kontoer", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Opprett posisjon", "nav.tabs.createV2Position": "Opprett V2-posisjon", "nav.tabs.createV3Position": "Opprett V3-posisjon", + "nav.tabs.createV4Position": "Opprett V4-posisjon", "nav.tabs.viewPosition": "Se posisjon", "network.lostConnection": "Du kan ha mistet nettverkstilkoblingen.", "network.mightBeDown": "{{network}} kan være nede akkurat nå, eller du kan ha mistet nettverkstilkoblingen.", @@ -1331,6 +1336,7 @@ "pool.back": "Tilbake til bassenget", "pool.balances": "Pool saldo", "pool.claimFees": "Krav gebyrer", + "pool.claimFees.button.label": "Krav", "pool.collectAs": "Samle som {{nativeWrappedSymbol}}", "pool.collected": " Samlet", "pool.collecting": "Samler", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Administrer likviditet i belønningspoolen", "pool.max.label": "Maks:", "pool.maxPrice": "Maks pris", + "pool.migrateToV4": "Migrer til V4", "pool.min.label": "Min:", "pool.minPrice": "Min pris", "pool.mustBeInitialized": "Denne poolen må initialiseres før du kan legge til likviditet. For å initialisere, velg en startpris for bassenget. Angi deretter likviditetsprisområdet og innskuddsbeløpet. Gassavgiftene vil være høyere enn vanlig på grunn av initialiseringstransaksjonen.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Tips: Bruk dette verktøyet til å finne v2-pooler som ikke automatisk vises i grensesnittet.", "pools.approving.amount": "Godkjenner {{amount}}", "position.addHook": "Legg til en krok", + "position.addHook.tooltip": "Hooks er en avansert funksjon som lar bassenger samhandle med smarte kontrakter, og låser opp ulike funksjoner. Vær forsiktig når du legger til kroker, siden noen kan være skadelige eller forårsake utilsiktede konsekvenser.", "position.appearHere": "Posisjonen din vil vises her.", + "position.create.modal.header": "Skaper posisjon", "position.currentValue": "Nåværende posisjonsverdi", + "position.deposit.description": "Spesifiser symbolbeløpene for likviditetsbidraget ditt.", "position.depositedCurrency": "Deponert {{currencySymbol}}", "position.migrate.liquidity": "Når du migrerer posisjoner, kan du ikke endre token-paret ditt, men du kan legge til en krok for å forbedre funksjonaliteten.", "position.new": "Ny stilling", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Kobler til...", "qrScanner.status.loading": "Laster inn...", "qrScanner.title": "Skann en QR-kode", - "qrScanner.wallet.title": "Du kan motta tokens og NFT-er på Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast og BNB Chain.", + "qrScanner.wallet.networks": "Støttede nettverk", + "qrScanner.wallet.title": "Du kan sende og motta tokens og NFT-er på alle våre {{numOfNetworks}} -støttede nettverk.", "removeLiquidity.collectFees": "Du vil også kreve inn avgifter opptjent fra denne stillingen.", "removeLiquidity.outputEstimated": "Utgang er estimert. Hvis prisen endres med mer enn {{allowed}}% vil transaksjonen din gå tilbake.", "removeLiquidity.pendingText": "Fjerner {{amtA}} {{symA}} og {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Kallenavn", "settings.setting.wallet.notifications.title": "Varsler", "settings.setting.wallet.preferences.title": "Lommebok-preferanser", - "settings.showTestNets": "Vis testnett", + "settings.setting.wallet.testnetMode.description": "Dette slår på testnett for utviklere å prøve ut funksjoner og transaksjoner uten å bruke reelle eiendeler. Tokens på testnett har ingen reell verdi.", + "settings.setting.wallet.testnetMode.title": "Testnett-modus", "settings.switchNetwork.warning": "For å bruke Uniswap på {{label}}, bytt nettverk i lommebokens innstillinger.", "settings.title": "Innstillinger", "settings.version": "Versjon {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Nytt innspill", "swap.details.newQuote.output": "Ny utgang", "swap.details.orderRouting": "Bestillingsruting", - "swap.details.orderRoutingInfo": "Prisen din inkluderer allerede nettverkskostnader på destinasjonsnettverket og en 0,05 % Across-avgift.", + "swap.details.orderRoutingInfo": "Dette byttet rutes via Across, en desentralisert protokoll som flytter eiendeler på tvers av nettverk samtidig som sikkerhet, rask utførelse og lave priser prioriteres.", + "swap.details.poweredBy": "Drevet av", "swap.details.rate": "Vurdere", "swap.details.slippage": "Maks glidning", "swap.details.uniswapFee": "Avgift", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Uniswap-klienten velger det billigste alternativet med hensyn til pris og nettverkskostnader.", "swap.settings.routingPreference.option.v2.title": "v2 bassenger", "swap.settings.routingPreference.option.v3.title": "v3 bassenger", + "swap.settings.routingPreference.option.v4.title": "v4 bassenger", "swap.settings.routingPreference.title": "Handel med alternativer", "swap.settings.slippage.control.auto": "Auto", "swap.settings.slippage.description": "Transaksjonen din vil gå tilbake hvis prisen endres mer enn glidningsprosenten.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Transaksjonen din vil gå tilbake hvis prisen endres ugunstig med mer enn denne prosentandelen.", "swap.signAndSwap": "Signer og bytt", "swap.slippage.amt": "{{amt}} glidning", + "swap.slippage.bridging": "Ingen glidning når du bytter på tvers av nettverk", "swap.slippage.settings.title": "Slippinnstillinger", "swap.slippage.tooltip": "Den maksimale prisbevegelsen før transaksjonen din vil gå tilbake.", "swap.slippageBelow.warning": "Slipp under {{amt}} kan resultere i en mislykket transaksjon", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Transaksjonsfrist", "swap.transaction.revertAfter": "Transaksjonen din vil gå tilbake hvis den venter i mer enn denne perioden.", "swap.unsupportedAssets.readMore": "Les mer om ikke-støttede eiendeler", + "swap.warning.enterLargerAmount.title": "Angi et større beløp", "swap.warning.expectedFailure": "Denne transaksjonen forventes å mislykkes", "swap.warning.feeOnTransfer.message": "Noen tokens tar et gebyr når de kjøpes eller selges, som settes av tokenutstederen. Uniswap mottar ingen andel av disse avgiftene.", "swap.warning.feeOnTransfer.title": "Hvorfor er det en ekstra avgift?", "swap.warning.insufficientBalance.title": "Du har ikke nok {{currencySymbol}}", "swap.warning.insufficientGas.button": "Ikke nok {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Bytt til {{ tokenSymbol }} på {{networkName}}", "swap.warning.insufficientGas.button.buy": "Kjøp {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Kjøp med kort", "swap.warning.insufficientGas.message.withNetwork": "Ikke nok {{currencySymbol}} på {{networkName}} til å bytte", "swap.warning.insufficientGas.message.withoutNetwork": "Ikke nok {{currencySymbol}} til å bytte", "swap.warning.insufficientGas.title": "Du har ikke nok {{currencySymbol}} til å dekke nettverkskostnadene", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "tokendata for på {{chainName}}", "tdp.nameNotFound": "Finner ikke navn", "tdp.noInfoAvailable": "Ingen tokeninformasjon tilgjengelig", + "tdp.noTestnetSupportDescription": "Noen testnett støtter ikke bytte, sending eller kjøp av tokens.", "tdp.stats.unsupportedChainDescription": "Tokenstatistikk og diagrammer for {{chain}} er tilgjengelig på {{infoLink}}", "tdp.symbolNotFound": "Symbol ikke funnet", + "testnet.unsupported": "Denne funksjonaliteten støttes ikke i testnettmodus.", "themeToggle.theme": "Tema", "title.betterPricesMoreListings": "Bedre priser. Flere oppføringer. Kjøp, selg og bytt NFT-er på tvers av toppmarkedsplasser som OpenSea. Utforsk populære samlinger.", "title.buySellTradeEthereum": "Kjøp, selg og bytt Ethereum og andre topp-tokens på Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "{{ownerAddress}}sin saldo", "token.bridge": "{{label}} symbolbro", "token.chart.tooltip": "Avgifter: {{amount}}", + "token.details.testnet.unsupported": "Tokendetaljer er utilgjengelige for testnet-tokens.", "token.error.unknown": "Ukjent token", "token.fee.buy.label": "kjøpe gebyr", "token.fee.label": "avgift", @@ -2109,6 +2130,8 @@ "transaction.warning.insufficientGas.modal.title.withoutNetwork": "Ikke nok {{tokenSymbol}}", "transaction.watcher.error.cancel": "Kan ikke kansellere transaksjonen", "transaction.watcher.error.status": "Feil under kontroll av transaksjonsstatus", + "unichain.launch.modal.description": "", + "unichain.launch.modal.title": "", "uniswapX.aggregatesLiquidity": " samler likviditetskilder for bedre priser og gassfrie bytteavtaler.", "uniswapx.description": "UniswapX samler likviditetskilder for bedre priser og gassfrie bytteavtaler.", "uniswapx.included": "Inkluderer UniswapX ", @@ -2212,7 +2235,6 @@ "walletConnect.pending.switchAccount": "Bytt konto", "walletConnect.pending.switchNetwork": "Bytt nettverk", "walletConnect.pending.title": "Koble til {{dappName}}", - "walletConnect.permissions.networks": "Nettverk", "walletConnect.permissions.option.transferAssets": "Overfør eiendelene dine uten samtykke", "walletConnect.permissions.option.viewTokenBalances": "Se symbolsaldoene dine", "walletConnect.permissions.option.viewWalletAddress": "Se lommebokadressen din", diff --git a/packages/uniswap/src/i18n/locales/translations/pl-PL.json b/packages/uniswap/src/i18n/locales/translations/pl-PL.json index 4b6fb0df9aa..335a3eab1fc 100644 --- a/packages/uniswap/src/i18n/locales/translations/pl-PL.json +++ b/packages/uniswap/src/i18n/locales/translations/pl-PL.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Wpłać tokeny do sieci {{label}} .", "common.deposited": "Zdeponowane", "common.depositing": "Deponowanie", + "common.depositTokens": "Depozyt tokenów", "common.detailed.label": "Szczegółowe", "common.detected": "Wykryto", "common.developers": "Deweloperzy", @@ -430,6 +431,7 @@ "common.migrate.position": "Migracja pozycji", "common.migrated.liquidity": "Migrowana płynność", "common.migrating.liquidity": "Migracja płynności", + "common.min": "Min", "common.mint.cancelled": "Mennica odwołana", "common.mint.failed": "Mennica nie powiodła się", "common.minted": "Wybity", @@ -450,6 +452,7 @@ "common.noResults": "Nie znaleziono wyników.", "common.notAvailableInRegion.error": "Niedostępne w Twoim regionie", "common.notCreated.label": "Nie utworzono", + "common.notSupported": "Nieobsługiwane", "common.oneDay": "1 dzień", "common.oneHour": "1 godzina", "common.oneMonth": "1 miesiąc", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}i inne opcje", "fiatOnRamp.quote.type.other": "Inne opcje", "fiatOnRamp.quote.type.recent": "Ostatnio używany", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Możesz otrzymywać tokeny i NFT na Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora i ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Z konta", "fiatOnRamp.receiveCrypto.title": "Odbierz kryptowalutę", "fiatOnRamp.receiveCrypto.transferFunds": "Zasil swój portfel, przesyłając kryptowaluty z innego portfela lub konta", @@ -901,6 +903,8 @@ "home.activity.error.load": "Nie udało się wczytać aktywności", "home.activity.title": "Działalność", "home.banner.offline": "Jesteś w trybie offline", + "home.banner.testnetMode": "Jesteś w trybie testowym", + "home.banner.testnetMode.nav": "Jesteś w trybie testnet. Wyłącz to w ustawieniach.", "home.explore.footer": "Kliknij tutaj, aby poznać tysiące tokenów, NFT i nie tylko", "home.explore.title": "Przeglądaj tokeny", "home.extension.error": "Błąd ładowania kont", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Utwórz pozycję", "nav.tabs.createV2Position": "Utwórz pozycję V2", "nav.tabs.createV3Position": "Utwórz pozycję V3", + "nav.tabs.createV4Position": "Utwórz pozycję V4", "nav.tabs.viewPosition": "Zobacz pozycję", "network.lostConnection": "Być może utraciłeś połączenie sieciowe.", "network.mightBeDown": "{{network}} może być teraz wyłączony lub mogłeś utracić połączenie sieciowe.", @@ -1331,6 +1336,7 @@ "pool.back": "Powrót do Basenu", "pool.balances": "Bilanse basenowe", "pool.claimFees": "Opłaty za roszczenia", + "pool.claimFees.button.label": "Prawo", "pool.collectAs": "Zbierz jako {{nativeWrappedSymbol}}", "pool.collected": " Zebrane", "pool.collecting": "Zbieranie", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Zarządzaj płynnością w puli nagród", "pool.max.label": "Maks:", "pool.maxPrice": "Cena maksymalna", + "pool.migrateToV4": "Migracja do V4", "pool.min.label": "Min.:", "pool.minPrice": "Cena minimalna", "pool.mustBeInitialized": "Zanim będzie można dodać płynność, należy zainicjować tę pulę. Aby zainicjować, wybierz cenę początkową dla puli. Następnie wprowadź zakres cen płynności i kwotę depozytu. Opłaty za gaz będą wyższe niż zwykle ze względu na transakcję inicjującą.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Wskazówka: Użyj tego narzędzia, aby znaleźć pule w wersji 2, które nie pojawiają się automatycznie w interfejsie.", "pools.approving.amount": "Zatwierdzanie {{amount}}", "position.addHook": "Dodaj hak", + "position.addHook.tooltip": "Hooki to zaawansowana funkcja, która umożliwia pulom interakcję z inteligentnymi kontraktami, odblokowując różne możliwości. Zachowaj ostrożność podczas dodawania hooków, ponieważ niektóre mogą być złośliwe lub powodować niezamierzone konsekwencje.", "position.appearHere": "Twoje stanowisko pojawi się tutaj.", + "position.create.modal.header": "Tworzenie pozycji", "position.currentValue": "Wartość bieżącej pozycji", + "position.deposit.description": "Określ kwoty tokenów dla swojego wkładu płynnościowego.", "position.depositedCurrency": "Złożono {{currencySymbol}}", "position.migrate.liquidity": "Podczas migracji pozycji nie możesz zmienić pary tokenów, ale możesz dodać hak w celu zwiększenia funkcjonalności.", "position.new": "Nowe stanowisko", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Złączony...", "qrScanner.status.loading": "Ładowanie...", "qrScanner.title": "Zeskanuj kod QR", - "qrScanner.wallet.title": "Możesz otrzymywać tokeny i NFT na Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast i BNB Chain.", + "qrScanner.wallet.networks": "Obsługiwane sieci", + "qrScanner.wallet.title": "Możesz wysyłać i odbierać tokeny i NFT we wszystkich obsługiwanych przez nas sieciach {{numOfNetworks}} .", "removeLiquidity.collectFees": "Będziesz także pobierał opłaty zarobione z tego stanowiska.", "removeLiquidity.outputEstimated": "Wydajność jest szacowana. Jeśli cena zmieni się o więcej niż {{allowed}}%, Twoja transakcja zostanie cofnięta.", "removeLiquidity.pendingText": "Usuwanie {{amtA}} {{symA}} i {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Przezwisko", "settings.setting.wallet.notifications.title": "Powiadomienia", "settings.setting.wallet.preferences.title": "Preferencje portfela", - "settings.showTestNets": "Pokaż sieci testowe", + "settings.setting.wallet.testnetMode.description": "Włącza to sieci testowe, aby deweloperzy mogli wypróbować funkcje i transakcje bez korzystania z prawdziwych zasobów. Tokeny w sieciach testowych nie mają żadnej rzeczywistej wartości.", + "settings.setting.wallet.testnetMode.title": "Tryb testowy", "settings.switchNetwork.warning": "Aby korzystać z Uniswap na {{label}}, przełącz sieć w ustawieniach swojego portfela.", "settings.title": "Ustawienia", "settings.version": "Wersja {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Nowe wejście", "swap.details.newQuote.output": "Nowe wyjście", "swap.details.orderRouting": "Kierowanie zamówień", - "swap.details.orderRoutingInfo": "Cena obejmuje już koszty sieci docelowej i opłatę za połączenie w wysokości 0,05%.", + "swap.details.orderRoutingInfo": "Transakcje wymiany są przesyłane za pośrednictwem protokołu Across, zdecentralizowanego protokołu, który umożliwia przesyłanie aktywów pomiędzy sieciami, kładąc nacisk na bezpieczeństwo, szybką realizację transakcji i niskie ceny.", + "swap.details.poweredBy": "Zasilane przez", "swap.details.rate": "Wskaźnik", "swap.details.slippage": "Maksymalny poślizg", "swap.details.uniswapFee": "Opłata", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Klient Uniswap wybiera najtańszą opcję handlu, cenę faktoringu i koszty sieci.", "swap.settings.routingPreference.option.v2.title": "baseny v2", "swap.settings.routingPreference.option.v3.title": "baseny v3", + "swap.settings.routingPreference.option.v4.title": "pule v4", "swap.settings.routingPreference.title": "Opcje handlu", "swap.settings.slippage.control.auto": "Automatyczny", "swap.settings.slippage.description": "Twoja transakcja zostanie cofnięta, jeśli cena zmieni się bardziej niż procent poślizgu.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Twoja transakcja zostanie cofnięta, jeśli cena zmieni się niekorzystnie o więcej niż ten procent.", "swap.signAndSwap": "Podpisz i zamień", "swap.slippage.amt": "{{amt}} poślizg", + "swap.slippage.bridging": "Brak poślizgu podczas przełączania między sieciami", "swap.slippage.settings.title": "Ustawienia poślizgu", "swap.slippage.tooltip": "Maksymalny ruch ceny przed transakcją zostanie przywrócony.", "swap.slippageBelow.warning": "Spadek poniżej {{amt}} może skutkować nieudaną transakcją", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Termin transakcji", "swap.transaction.revertAfter": "Twoja transakcja zostanie cofnięta, jeśli będzie oczekująca dłużej niż ten okres.", "swap.unsupportedAssets.readMore": "Przeczytaj więcej o nieobsługiwanych zasobach", + "swap.warning.enterLargerAmount.title": "Wprowadź większą kwotę", "swap.warning.expectedFailure": "Oczekuje się, że ta transakcja zakończy się niepowodzeniem", "swap.warning.feeOnTransfer.message": "Niektóre tokeny pobierają opłatę przy zakupie lub sprzedaży, którą ustala wystawca tokena. Uniswap nie otrzymuje żadnej części tych opłat.", "swap.warning.feeOnTransfer.title": "Dlaczego jest dodatkowa opłata?", "swap.warning.insufficientBalance.title": "Nie masz dość {{currencySymbol}}", "swap.warning.insufficientGas.button": "Za mało {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Zamień na {{ tokenSymbol }} na {{networkName}}", "swap.warning.insufficientGas.button.buy": "Kup {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Kup kartą", "swap.warning.insufficientGas.message.withNetwork": "Za mało {{currencySymbol}} na {{networkName}} do zamiany", "swap.warning.insufficientGas.message.withoutNetwork": "Za mało {{currencySymbol}} do zamiany", "swap.warning.insufficientGas.title": "Nie masz wystarczającej ilości {{currencySymbol}} na pokrycie kosztów sieci", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "dane tokena dla na {{chainName}}", "tdp.nameNotFound": "Nie znaleziono nazwy", "tdp.noInfoAvailable": "Brak dostępnych informacji o tokenie", + "tdp.noTestnetSupportDescription": "Niektóre sieci testowe nie obsługują wymiany, wysyłania ani kupowania tokenów.", "tdp.stats.unsupportedChainDescription": "Statystyki i wykresy tokenów dla {{chain}} są dostępne na {{infoLink}}", "tdp.symbolNotFound": "Nie znaleziono symbolu", + "testnet.unsupported": "Ta funkcjonalność nie jest obsługiwana w trybie testnetowym.", "themeToggle.theme": "Temat", "title.betterPricesMoreListings": "Lepsze ceny. Więcej ofert. Kupuj, sprzedawaj i handluj NFT na najlepszych platformach handlowych, takich jak OpenSea. Przeglądaj popularne kolekcje.", "title.buySellTradeEthereum": "Kupuj, sprzedawaj i wymieniaj Ethereum i inne najlepsze tokeny na Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "Saldo {{ownerAddress}}", "token.bridge": "{{label}} most żetonowy", "token.chart.tooltip": "Opłaty: {{amount}}", + "token.details.testnet.unsupported": "Szczegóły tokenów sieci testowej są niedostępne.", "token.error.unknown": "Nieznany token", "token.fee.buy.label": "opłata za zakup", "token.fee.label": "opłata", @@ -2109,6 +2130,8 @@ "transaction.warning.insufficientGas.modal.title.withoutNetwork": "Za mało {{tokenSymbol}}", "transaction.watcher.error.cancel": "Nie można anulować transakcji", "transaction.watcher.error.status": "Błąd podczas sprawdzania statusu transakcji", + "unichain.launch.modal.description": "An Ethereum L2 designed for DeFi, built by Uniswap Labs. Testnet goes live today.", + "unichain.launch.modal.title": "Introducing Unichain", "uniswapX.aggregatesLiquidity": " agreguje źródła płynności w celu uzyskania lepszych cen i swapów bez gazu.", "uniswapx.description": "UniswapX agreguje źródła płynności w celu uzyskania lepszych cen i swapów bez gazu.", "uniswapx.included": "Obejmuje UniswapX ", @@ -2212,7 +2235,6 @@ "walletConnect.pending.switchAccount": "Przełącz konto", "walletConnect.pending.switchNetwork": "Przełącz sieć", "walletConnect.pending.title": "Połącz się z {{dappName}}", - "walletConnect.permissions.networks": "Sieci", "walletConnect.permissions.option.transferAssets": "Przenieś swój majątek bez zgody", "walletConnect.permissions.option.viewTokenBalances": "Zobacz saldo swoich tokenów", "walletConnect.permissions.option.viewWalletAddress": "Wyświetl adres swojego portfela", diff --git a/packages/uniswap/src/i18n/locales/translations/pt-BR.json b/packages/uniswap/src/i18n/locales/translations/pt-BR.json index 196b03bf81a..d7e8e265868 100644 --- a/packages/uniswap/src/i18n/locales/translations/pt-BR.json +++ b/packages/uniswap/src/i18n/locales/translations/pt-BR.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Deposite tokens na rede {{label}} .", "common.deposited": "Depositado", "common.depositing": "Depositando", + "common.depositTokens": "Tokens de depósito", "common.detailed.label": "Detalhado", "common.detected": "Detectou", "common.developers": "Desenvolvedores", @@ -430,6 +431,7 @@ "common.migrate.position": "Migrar posição", "common.migrated.liquidity": "Liquidez migrada", "common.migrating.liquidity": "Migrando liquidez", + "common.min": "Mínimo", "common.mint.cancelled": "Casa da moeda cancelada", "common.mint.failed": "A hortelã falhou", "common.minted": "Cunhado", @@ -450,6 +452,7 @@ "common.noResults": "Nenhum resultado encontrado.", "common.notAvailableInRegion.error": "Não disponível em sua região", "common.notCreated.label": "Não criado", + "common.notSupported": "Não suportado", "common.oneDay": "1 dia", "common.oneHour": "1 hora", "common.oneMonth": "1 mês", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}e outras opções", "fiatOnRamp.quote.type.other": "Outras opções", "fiatOnRamp.quote.type.recent": "Usado recentemente", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Você pode receber tokens e NFTs em Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora e ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "De uma conta", "fiatOnRamp.receiveCrypto.title": "Receba criptografia", "fiatOnRamp.receiveCrypto.transferFunds": "Financie sua carteira transferindo criptografia de outra carteira ou conta", @@ -901,6 +903,8 @@ "home.activity.error.load": "Não foi possível carregar a atividade", "home.activity.title": "Atividade", "home.banner.offline": "Você está no modo off-line", + "home.banner.testnetMode": "Você está no modo testnet", + "home.banner.testnetMode.nav": "Você está no modo testnet. Desative isso nas configurações.", "home.explore.footer": "Toque aqui para explorar milhares de tokens, NFTs e muito mais", "home.explore.title": "Explorar tokens", "home.extension.error": "Erro ao carregar contas", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Criar posição", "nav.tabs.createV2Position": "Criar posição V2", "nav.tabs.createV3Position": "Criar posição V3", + "nav.tabs.createV4Position": "Criar posição V4", "nav.tabs.viewPosition": "Ver posição", "network.lostConnection": "Você pode ter perdido sua conexão de rede.", "network.mightBeDown": "{{network}} pode estar fora do ar agora ou você pode ter perdido sua conexão de rede.", @@ -1331,6 +1336,7 @@ "pool.back": "De volta à piscina", "pool.balances": "Saldos do pool", "pool.claimFees": "Taxas de reclamação", + "pool.claimFees.button.label": "Alegar", "pool.collectAs": "Colete como {{nativeWrappedSymbol}}", "pool.collected": " Coletado", "pool.collecting": "Coletando", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Gerencie a liquidez no pool de recompensas", "pool.max.label": "Máx.:", "pool.maxPrice": "Preço máximo", + "pool.migrateToV4": "Migrar para V4", "pool.min.label": "Mínimo:", "pool.minPrice": "Preço mínimo", "pool.mustBeInitialized": "Este pool deve ser inicializado antes que você possa adicionar liquidez. Para inicializar, selecione um preço inicial para o pool. Em seguida, insira sua faixa de preço de liquidez e valor do depósito. As taxas do gás serão mais altas do que o normal devido à transação de inicialização.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Dica: Use esta ferramenta para encontrar pools v2 que não aparecem automaticamente na interface.", "pools.approving.amount": "Aprovando {{amount}}", "position.addHook": "Adicionar um gancho", + "position.addHook.tooltip": "Hooks são um recurso avançado que permite que pools interajam com contratos inteligentes, desbloqueando vários recursos. Tenha cuidado ao adicionar hooks, pois alguns podem ser maliciosos ou causar consequências não intencionais.", "position.appearHere": "Sua posição aparecerá aqui.", + "position.create.modal.header": "Criando posição", "position.currentValue": "Valor da posição atual", + "position.deposit.description": "Especifique os valores de token para sua contribuição de liquidez.", "position.depositedCurrency": "Depositado {{currencySymbol}}", "position.migrate.liquidity": "Ao migrar posições, você não pode alterar seu par de tokens, mas pode adicionar um gancho para melhorar a funcionalidade.", "position.new": "Nova posição", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Conectando...", "qrScanner.status.loading": "Carregando...", "qrScanner.title": "Digitalize um código QR", - "qrScanner.wallet.title": "Você pode receber tokens e NFTs em Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast e BNB Chain.", + "qrScanner.wallet.networks": "Redes Suportadas", + "qrScanner.wallet.title": "Você pode enviar e receber tokens e NFTs em todas as nossas {{numOfNetworks}} redes suportadas.", "removeLiquidity.collectFees": "Você também receberá as taxas obtidas nesta posição.", "removeLiquidity.outputEstimated": "A produção é estimada. Se o preço mudar em mais de {{allowed}}%, sua transação será revertida.", "removeLiquidity.pendingText": "Removendo {{amtA}} {{symA}} e {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Apelido", "settings.setting.wallet.notifications.title": "Notificações", "settings.setting.wallet.preferences.title": "Preferências da carteira", - "settings.showTestNets": "Mostrar redes de teste", + "settings.setting.wallet.testnetMode.description": "Isso ativa testnets para desenvolvedores testarem recursos e transações sem usar ativos reais. Tokens em testnets não têm valor real.", + "settings.setting.wallet.testnetMode.title": "Modo Testnet", "settings.switchNetwork.warning": "Para usar o Uniswap em {{label}}, troque a rede nas configurações da sua carteira.", "settings.title": "Configurações", "settings.version": "Versão {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Nova entrada", "swap.details.newQuote.output": "Nova saída", "swap.details.orderRouting": "Roteamento de pedidos", - "swap.details.orderRoutingInfo": "Seu preço já inclui custos de rede na rede de destino e uma taxa transversal de 0,05%.", + "swap.details.orderRoutingInfo": "Essa troca é roteada via Across, um protocolo descentralizado que movimenta ativos entre redes priorizando segurança, execução rápida e preços baixos.", + "swap.details.poweredBy": "Distribuído por", "swap.details.rate": "Avaliar", "swap.details.slippage": "Deslizamento máximo", "swap.details.uniswapFee": "Taxa", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "O cliente Uniswap seleciona a opção de negociação mais barata, preço de factoring e custos de rede.", "swap.settings.routingPreference.option.v2.title": "conjuntos v2", "swap.settings.routingPreference.option.v3.title": "conjuntos v3", + "swap.settings.routingPreference.option.v4.title": "piscinas v4", "swap.settings.routingPreference.title": "Opções de negociação", "swap.settings.slippage.control.auto": "Auto", "swap.settings.slippage.description": "Sua transação será revertida se o preço mudar mais do que a porcentagem de derrapagem.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "A sua transação será revertida se o preço mudar desfavoravelmente em mais do que esta percentagem.", "swap.signAndSwap": "Assine e troque", "swap.slippage.amt": "{{amt}} deslize", + "swap.slippage.bridging": "Sem deslizamento ao alternar entre redes", "swap.slippage.settings.title": "Configurações de deslizamento", "swap.slippage.tooltip": "O movimento máximo de preço antes da sua transação será revertido.", "swap.slippageBelow.warning": "Deslizamento abaixo de {{amt}} pode resultar em falha na transação", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Prazo de transação", "swap.transaction.revertAfter": "Sua transação será revertida se estiver pendente por mais que esse período de tempo.", "swap.unsupportedAssets.readMore": "Leia mais sobre ativos não suportados", + "swap.warning.enterLargerAmount.title": "Insira um valor maior", "swap.warning.expectedFailure": "Espera-se que esta transação falhe", "swap.warning.feeOnTransfer.message": "Alguns tokens cobram uma taxa quando são comprados ou vendidos, que é definida pelo emissor do token. O Uniswap não recebe nenhuma parcela dessas taxas.", "swap.warning.feeOnTransfer.title": "Por que há uma taxa adicional?", "swap.warning.insufficientBalance.title": "Você não tem o suficiente {{currencySymbol}}", "swap.warning.insufficientGas.button": "Não é suficiente {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Troque por {{ tokenSymbol }} por {{networkName}}", "swap.warning.insufficientGas.button.buy": "Compre {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Comprar com cartão", "swap.warning.insufficientGas.message.withNetwork": "Não é suficiente {{currencySymbol}} em {{networkName}} para trocar", "swap.warning.insufficientGas.message.withoutNetwork": "Não é suficiente {{currencySymbol}} para trocar", "swap.warning.insufficientGas.title": "Você não tem {{currencySymbol}} suficiente para cobrir o custo da rede", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "dados de token para em {{chainName}}", "tdp.nameNotFound": "Nome não encontrado", "tdp.noInfoAvailable": "Nenhuma informação de token disponível", + "tdp.noTestnetSupportDescription": "Algumas redes de teste não oferecem suporte à troca, envio ou compra de tokens.", "tdp.stats.unsupportedChainDescription": "Estatísticas e gráficos de token para {{chain}} estão disponíveis em {{infoLink}}", "tdp.symbolNotFound": "Símbolo não encontrado", + "testnet.unsupported": "Esta funcionalidade não é suportada no modo testnet.", "themeToggle.theme": "Tema", "title.betterPricesMoreListings": "Melhores preços. Mais listagens. Compre, venda e negocie NFTs nos principais mercados, como OpenSea. Explore coleções de tendências.", "title.buySellTradeEthereum": "Compre, venda e negocie Ethereum e outros tokens importantes no Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "Saldo de {{ownerAddress}}", "token.bridge": "{{label}} ponte de tokens", "token.chart.tooltip": "Taxas: {{amount}}", + "token.details.testnet.unsupported": "Os detalhes do token não estão disponíveis para tokens de testnet.", "token.error.unknown": "Token desconhecido", "token.fee.buy.label": "taxa de compra", "token.fee.label": "taxa", @@ -2109,6 +2130,8 @@ "transaction.warning.insufficientGas.modal.title.withoutNetwork": "Não é suficiente {{tokenSymbol}}", "transaction.watcher.error.cancel": "Não foi possível cancelar a transação", "transaction.watcher.error.status": "Erro ao verificar o status da transação", + "unichain.launch.modal.description": "An Ethereum L2 designed for DeFi, built by Uniswap Labs. Testnet goes live today.", + "unichain.launch.modal.title": "Introducing Unichain", "uniswapX.aggregatesLiquidity": " agrega fontes de liquidez para melhores preços e swaps sem gás.", "uniswapx.description": "O UniswapX agrega fontes de liquidez para melhores preços e swaps sem gás.", "uniswapx.included": "Inclui UniswapX ", @@ -2212,7 +2235,6 @@ "walletConnect.pending.switchAccount": "Mudar de conta", "walletConnect.pending.switchNetwork": "Trocar de rede", "walletConnect.pending.title": "Conecte-se a {{dappName}}", - "walletConnect.permissions.networks": "Redes", "walletConnect.permissions.option.transferAssets": "Transfira seus ativos sem consentimento", "walletConnect.permissions.option.viewTokenBalances": "Veja seus saldos de tokens", "walletConnect.permissions.option.viewWalletAddress": "Ver o endereço da sua carteira", diff --git a/packages/uniswap/src/i18n/locales/translations/pt-PT.json b/packages/uniswap/src/i18n/locales/translations/pt-PT.json index d85cdf04492..c1239f1ac6b 100644 --- a/packages/uniswap/src/i18n/locales/translations/pt-PT.json +++ b/packages/uniswap/src/i18n/locales/translations/pt-PT.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Deposita tokens na rede {{label}}.", "common.deposited": "Depositado", "common.depositing": "A depositar", + "common.depositTokens": "Tokens de depósito", "common.detailed.label": "Detalhado", "common.detected": "Detetado", "common.developers": "Programadores", @@ -430,6 +431,7 @@ "common.migrate.position": "Migrar posição", "common.migrated.liquidity": "Liquidez migrada", "common.migrating.liquidity": "A migrar liquidez", + "common.min": "Mínimo", "common.mint.cancelled": "Cunha cancelada", "common.mint.failed": "A cunha falhou", "common.minted": "Cunhado", @@ -450,6 +452,7 @@ "common.noResults": "Não foram encontrados resultados.", "common.notAvailableInRegion.error": "Não disponível na tua região", "common.notCreated.label": "Não criado", + "common.notSupported": "Não suportado", "common.oneDay": "1 dia", "common.oneHour": "1 hora", "common.oneMonth": "1 mês", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}} e outras opções", "fiatOnRamp.quote.type.other": "Outras opções", "fiatOnRamp.quote.type.recent": "Utilizado recentemente", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Podes receber tokens e NFT em Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora e ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "De uma conta", "fiatOnRamp.receiveCrypto.title": "Receber cripto", "fiatOnRamp.receiveCrypto.transferFunds": "Financia a tua carteira transferindo cripto de outra carteira ou conta", @@ -901,6 +903,8 @@ "home.activity.error.load": "Não foi possível carregar a atividade", "home.activity.title": "Atividade", "home.banner.offline": "Estás no modo offline", + "home.banner.testnetMode": "Você está no modo testnet", + "home.banner.testnetMode.nav": "Você está no modo testnet. Desative isso nas configurações.", "home.explore.footer": "Toca em \"pesquisar\" para explorares mais", "home.explore.title": "Explorar tokens", "home.extension.error": "Erro ao carregar contas", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Criar posição", "nav.tabs.createV2Position": "Criar posição V2", "nav.tabs.createV3Position": "Criar posição V3", + "nav.tabs.createV4Position": "Criar posição V4", "nav.tabs.viewPosition": "Ver posição", "network.lostConnection": "Poderás ter perdido a ligação à rede.", "network.mightBeDown": "{{network}} pode estar em baixo neste momento, ou podes ter perdido a tua ligação à rede.", @@ -1331,6 +1336,7 @@ "pool.back": "Voltar ao Pool", "pool.balances": "Saldos do Pool", "pool.claimFees": "Reivindicar tarifas", + "pool.claimFees.button.label": "Alegar", "pool.collectAs": "Cobrar como {{nativeWrappedSymbol}}", "pool.collected": " Cobrado", "pool.collecting": "A cobrar", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Gerir liquidez no pool de recompensas", "pool.max.label": "Máx.:", "pool.maxPrice": "Preço máx.", + "pool.migrateToV4": "Migrar para V4", "pool.min.label": "Mín:", "pool.minPrice": "Preço mín.", "pool.mustBeInitialized": "Este pool deve ser inicializado antes de poderes adicionar liquidez. Para inicializar, seleciona um preço inicial para o pool. Em seguida, introduz o teu intervalo de preços de liquidez e o montante do depósito. As tarifas de Gas serão mais elevadas do que o habitual devido à transação de inicialização.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Sugestão: utiliza esta ferramenta para encontrares pools v2 que não aparecem automaticamente na interface.", "pools.approving.amount": "A aprovar {{amount}}", "position.addHook": "Adicionar um gancho", + "position.addHook.tooltip": "Hooks são um recurso avançado que permite que pools interajam com contratos inteligentes, desbloqueando vários recursos. Tenha cuidado ao adicionar hooks, pois alguns podem ser maliciosos ou causar consequências não intencionais.", "position.appearHere": "A tua posição vai aparecer aqui.", + "position.create.modal.header": "Criando posição", "position.currentValue": "Valor da posição atual", + "position.deposit.description": "Especifique os valores de token para sua contribuição de liquidez.", "position.depositedCurrency": "Depositado {{currencySymbol}}", "position.migrate.liquidity": "Ao migrar posições, você não pode alterar seu par de tokens, mas pode adicionar um gancho para melhorar a funcionalidade.", "position.new": "Nova posição", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "A ligar…", "qrScanner.status.loading": "A carregar…", "qrScanner.title": "Ler um código QR", - "qrScanner.wallet.title": "Podes receber tokens e NFT em Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast e BNB Chain.", + "qrScanner.wallet.networks": "Redes Suportadas", + "qrScanner.wallet.title": "Você pode enviar e receber tokens e NFTs em todas as nossas {{numOfNetworks}} redes suportadas.", "removeLiquidity.collectFees": "Também cobrarás tarifas ganhas nesta posição.", "removeLiquidity.outputEstimated": "O rendimento é estimado. Se o preço variar mais de {{allowed}}%, a transação será revertida.", "removeLiquidity.pendingText": "A remover {{amtA}} {{symA}} e {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Pseudónimo", "settings.setting.wallet.notifications.title": "Notificações", "settings.setting.wallet.preferences.title": "Preferências da carteira", - "settings.showTestNets": "Mostrar redes de teste", + "settings.setting.wallet.testnetMode.description": "Isso ativa testnets para desenvolvedores testarem recursos e transações sem usar ativos reais. Tokens em testnets não têm valor real.", + "settings.setting.wallet.testnetMode.title": "Modo Testnet", "settings.switchNetwork.warning": "Para utilizares a Uniswap em {{label}}, muda a rede nas definições da tua carteira.", "settings.title": "Definições", "settings.version": "Versão {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Nova entrada", "swap.details.newQuote.output": "Nova saída", "swap.details.orderRouting": "Encaminhamento de ordens", - "swap.details.orderRoutingInfo": "Seu preço já inclui custos de rede na rede de destino e uma taxa transversal de 0,05%.", + "swap.details.orderRoutingInfo": "Essa troca é roteada via Across, um protocolo descentralizado que movimenta ativos entre redes priorizando segurança, execução rápida e preços baixos.", + "swap.details.poweredBy": "Distribuído por", "swap.details.rate": "Taxa", "swap.details.slippage": "Deslizamento máximo", "swap.details.uniswapFee": "Tarifa", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "O cliente Uniswap seleciona a opção de negociação mais barata, tendo em conta o preço e os custos de rede.", "swap.settings.routingPreference.option.v2.title": "Pools v2", "swap.settings.routingPreference.option.v3.title": "Pools v3", + "swap.settings.routingPreference.option.v4.title": "Pools v4", "swap.settings.routingPreference.title": "Opções de negociação", "swap.settings.slippage.control.auto": "Automático", "swap.settings.slippage.description": "A tua transação será revertida se o preço mudar mais do que a percentagem de deslizamento.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "A tua transação será revertida se o preço sofrer uma alteração desfavorável superior a esta percentagem.", "swap.signAndSwap": "Assinar e trocar", "swap.slippage.amt": "Deslizamento de {{amt}}", + "swap.slippage.bridging": "Sem deslizamento ao alternar entre redes", "swap.slippage.settings.title": "Deslizamento máximo", "swap.slippage.tooltip": "O movimento máximo do preço antes de a transação ser revertida.", "swap.slippageBelow.warning": "Deslizamentos abaixo de {{amt}} poderão resultar em transações falhadas", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Prazo da transação", "swap.transaction.revertAfter": "A tua transação será revertida se estiver pendente durante mais tempo do que este período.", "swap.unsupportedAssets.readMore": "Ler mais sobre ativos não suportados", + "swap.warning.enterLargerAmount.title": "Insira um valor maior", "swap.warning.expectedFailure": "Esta transação deverá falhar", "swap.warning.feeOnTransfer.message": "Alguns tokens cobram uma tarifa quando são comprados ou vendidos, que é definida pelo emissor do token. A Uniswap não recebe qualquer percentagem destas tarifas.", "swap.warning.feeOnTransfer.title": "Por que existe uma tarifa adicional?", "swap.warning.insufficientBalance.title": "Não tens {{currencySymbol}} suficientes", "swap.warning.insufficientGas.button": "{{currencySymbol}} insuficientes", + "swap.warning.insufficientGas.button.bridge": "Troque por {{ tokenSymbol }} por {{networkName}}", "swap.warning.insufficientGas.button.buy": "Comprar {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Comprar com cartão", "swap.warning.insufficientGas.message.withNetwork": "Não há {{currencySymbol}} suficientes em {{networkName}} para trocar", "swap.warning.insufficientGas.message.withoutNetwork": "Não há {{currencySymbol}} suficientes para trocar", "swap.warning.insufficientGas.title": "Não tens {{currencySymbol}} suficientes para cobrir o custo da rede", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "dados do token para em {{chainName}}", "tdp.nameNotFound": "Nome não encontrado", "tdp.noInfoAvailable": "Não estão disponíveis informações do token", + "tdp.noTestnetSupportDescription": "Algumas redes de teste não oferecem suporte à troca, envio ou compra de tokens.", "tdp.stats.unsupportedChainDescription": "As estatísticas e os gráficos do token de {{chain}} estão disponíveis em {{infoLink}}", "tdp.symbolNotFound": "Símbolo não encontrado", + "testnet.unsupported": "Esta funcionalidade não é suportada no modo testnet.", "themeToggle.theme": "Tema", "title.betterPricesMoreListings": "Melhores preços. Mais listagens. Compra, vende e negoceia NFT nos principais mercados, como o OpenSea. Explora coleções populares.", "title.buySellTradeEthereum": "Compra, vende e negoceia Ethereum e outros tokens de topo na Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "Saldo de {{ownerAddress}}", "token.bridge": "Ponte de tokens {{label}}", "token.chart.tooltip": "Tarifas: {{amount}}", + "token.details.testnet.unsupported": "Os detalhes do token não estão disponíveis para tokens de testnet.", "token.error.unknown": "Token desconhecido", "token.fee.buy.label": "tarifa de compra", "token.fee.label": "tarifa", @@ -2109,6 +2130,8 @@ "transaction.warning.insufficientGas.modal.title.withoutNetwork": "{{tokenSymbol}} insuficientes", "transaction.watcher.error.cancel": "Não foi possível cancelar a transação", "transaction.watcher.error.status": "Erro ao verificar o estado da transação", + "unichain.launch.modal.description": "An Ethereum L2 designed for DeFi, built by Uniswap Labs. Testnet goes live today.", + "unichain.launch.modal.title": "Introducing Unichain", "uniswapX.aggregatesLiquidity": " agrega fontes de liquidez para obter melhores preços e trocas sem Gas.", "uniswapx.description": "O UniswapX agrega fontes de liquidez para obter melhores preços e trocas sem Gas.", "uniswapx.included": "Inclui UniswapX ", @@ -2212,7 +2235,6 @@ "walletConnect.pending.switchAccount": "Mudar de conta", "walletConnect.pending.switchNetwork": "Mudar de rede", "walletConnect.pending.title": "Ligar-se a {{dappName}}", - "walletConnect.permissions.networks": "Redes", "walletConnect.permissions.option.transferAssets": "Transferir os teus ativos sem consentimento", "walletConnect.permissions.option.viewTokenBalances": "Ver os saldos dos teus tokens", "walletConnect.permissions.option.viewWalletAddress": "Ver o endereço da tua carteira", diff --git a/packages/uniswap/src/i18n/locales/translations/ro-RO.json b/packages/uniswap/src/i18n/locales/translations/ro-RO.json index a8e71eba3a2..7964ee8b839 100644 --- a/packages/uniswap/src/i18n/locales/translations/ro-RO.json +++ b/packages/uniswap/src/i18n/locales/translations/ro-RO.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Depuneți jetoane în rețeaua {{label}} .", "common.deposited": "Depus", "common.depositing": "Depozitare", + "common.depositTokens": "Jetoane de depozit", "common.detailed.label": "Detaliat", "common.detected": "Detectat", "common.developers": "Dezvoltatori", @@ -430,6 +431,7 @@ "common.migrate.position": "Migrați poziția", "common.migrated.liquidity": "Lichiditate migrată", "common.migrating.liquidity": "Migrarea lichidității", + "common.min": "Min", "common.mint.cancelled": "Monetăria anulată", "common.mint.failed": "Mint a eșuat", "common.minted": "Bateriat", @@ -450,6 +452,7 @@ "common.noResults": "Nici un rezultat gasit.", "common.notAvailableInRegion.error": "Nu este disponibil în regiunea dvs", "common.notCreated.label": "Nu este creat", + "common.notSupported": "Nu este acceptat", "common.oneDay": "1 zi", "common.oneHour": "1 oră", "common.oneMonth": "1 lună", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}și alte opțiuni", "fiatOnRamp.quote.type.other": "Alte optiuni", "fiatOnRamp.quote.type.recent": "Folosit recent", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Puteți primi jetoane și NFT-uri pe Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora și ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Dintr-un cont", "fiatOnRamp.receiveCrypto.title": "Primește cripto", "fiatOnRamp.receiveCrypto.transferFunds": "Finanțați-vă portofelul prin transferul criptomonelor dintr-un alt portofel sau cont", @@ -901,6 +903,8 @@ "home.activity.error.load": "Activitatea nu a putut fi încărcată", "home.activity.title": "Activitate", "home.banner.offline": "Sunteți în modul offline", + "home.banner.testnetMode": "Sunteți în modul testnet", + "home.banner.testnetMode.nav": "Sunteți în modul testnet. Dezactivați această opțiune în setări.", "home.explore.footer": "Atingeți aici pentru a explora mii de jetoane, NFT-uri și multe altele", "home.explore.title": "Explorați jetoanele", "home.extension.error": "Eroare la încărcarea conturilor", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Creați poziție", "nav.tabs.createV2Position": "Creați poziția V2", "nav.tabs.createV3Position": "Creați poziția V3", + "nav.tabs.createV4Position": "Creați poziția V4", "nav.tabs.viewPosition": "Vizualizare poziție", "network.lostConnection": "Este posibil să fi pierdut conexiunea la rețea.", "network.mightBeDown": "Este posibil ca {{network}} să fie întrerupt în acest moment sau este posibil să fi pierdut conexiunea la rețea.", @@ -1331,6 +1336,7 @@ "pool.back": "Înapoi la piscină", "pool.balances": "Soldurile piscinei", "pool.claimFees": "Taxe de revendicare", + "pool.claimFees.button.label": "Revendicare", "pool.collectAs": "Colectați ca {{nativeWrappedSymbol}}", "pool.collected": " Colectat", "pool.collecting": "Colectare", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Gestionați lichiditatea în fondul de recompense", "pool.max.label": "Max:", "pool.maxPrice": "Pret maxim", + "pool.migrateToV4": "Migrați la V4", "pool.min.label": "Min:", "pool.minPrice": "Preț minim", "pool.mustBeInitialized": "Acest pool trebuie inițializat înainte de a putea adăuga lichiditate. Pentru a inițializa, selectați un preț de pornire pentru piscină. Apoi, introduceți intervalul de preț al lichidității și suma depozitului. Taxele de gaz vor fi mai mari decât de obicei din cauza tranzacției de inițializare.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Sfat: Folosiți acest instrument pentru a găsi pool-uri v2 care nu apar automat în interfață.", "pools.approving.amount": "Se aprobă {{amount}}", "position.addHook": "Adăugați un cârlig", + "position.addHook.tooltip": "Cârligele sunt o caracteristică avansată care permite grupurilor să interacționeze cu contractele inteligente, deblocând diverse capabilități. Fiți atenți când adăugați cârlige, deoarece unele pot fi rău intenționate sau pot provoca consecințe nedorite.", "position.appearHere": "Poziția ta va apărea aici.", + "position.create.modal.header": "Crearea poziției", "position.currentValue": "Valoarea poziției curente", + "position.deposit.description": "Specificați sumele simbolice pentru contribuția dvs. de lichiditate.", "position.depositedCurrency": "Depus {{currencySymbol}}", "position.migrate.liquidity": "Când migrați pozițiile, nu vă puteți schimba perechea de jetoane, dar puteți adăuga un cârlig pentru a îmbunătăți funcționalitatea.", "position.new": "Poziție nouă", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Se conectează...", "qrScanner.status.loading": "Se încarcă...", "qrScanner.title": "Scanați un cod QR", - "qrScanner.wallet.title": "Puteți primi jetoane și NFT-uri pe Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast și BNB Chain.", + "qrScanner.wallet.networks": "Rețele acceptate", + "qrScanner.wallet.title": "Puteți trimite și primi jetoane și NFT-uri pe toate rețelele noastre acceptate {{numOfNetworks}} .", "removeLiquidity.collectFees": "De asemenea, veți încasa taxele câștigate din această poziție.", "removeLiquidity.outputEstimated": "Ieșirea este estimată. Dacă prețul se modifică cu mai mult de {{allowed}}%, tranzacția dvs. se va reveni.", "removeLiquidity.pendingText": "Se elimină {{amtA}} {{symA}} și {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Poreclă", "settings.setting.wallet.notifications.title": "Notificări", "settings.setting.wallet.preferences.title": "Preferințe pentru portofel", - "settings.showTestNets": "Afișați rețelele de testare", + "settings.setting.wallet.testnetMode.description": "Acest lucru activează rețelele de testare pentru ca dezvoltatorii să încerce funcții și tranzacții fără a utiliza active reale. Tokenurile de pe rețelele de testare nu au nicio valoare reală.", + "settings.setting.wallet.testnetMode.title": "Modul Testnet", "settings.switchNetwork.warning": "Pentru a utiliza Uniswap pe {{label}}, comutați rețeaua în setările portofelului.", "settings.title": "Setări", "settings.version": "Versiunea {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Intrare nouă", "swap.details.newQuote.output": "Ieșire nouă", "swap.details.orderRouting": "Dirijarea comenzilor", - "swap.details.orderRoutingInfo": "Prețul dvs. include deja costurile de rețea în rețeaua de destinație și o taxă de 0,05%.", + "swap.details.orderRoutingInfo": "Acest schimb este direcționat prin Across, un protocol descentralizat care mută activele peste rețele, acordând prioritate siguranței, execuției rapide și prețurilor scăzute.", + "swap.details.poweredBy": "Cu sprijinul", "swap.details.rate": "Rată", "swap.details.slippage": "Alunecare maximă", "swap.details.uniswapFee": "Taxa", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Clientul Uniswap selectează cea mai ieftină opțiune de tranzacționare factoring prețul și costurile de rețea.", "swap.settings.routingPreference.option.v2.title": "piscine v2", "swap.settings.routingPreference.option.v3.title": "piscine v3", + "swap.settings.routingPreference.option.v4.title": "piscine v4", "swap.settings.routingPreference.title": "Opțiuni comerciale", "swap.settings.slippage.control.auto": "Auto", "swap.settings.slippage.description": "Tranzacția dvs. va reveni dacă prețul se modifică mai mult decât procentul de alunecare.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Tranzacția dvs. va reveni dacă prețul se modifică nefavorabil cu mai mult de acest procent.", "swap.signAndSwap": "Semnează și schimbă", "swap.slippage.amt": "{{amt}} alunecare", + "swap.slippage.bridging": "Fără alunecare la schimbarea între rețele", "swap.slippage.settings.title": "Setări de alunecare", "swap.slippage.tooltip": "Mișcarea prețului maxim înainte de tranzacția dvs. se va reveni.", "swap.slippageBelow.warning": "Alunecarea sub {{amt}} poate duce la o tranzacție eșuată", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Termenul limită pentru tranzacție", "swap.transaction.revertAfter": "Tranzacția dvs. va reveni dacă este în așteptare mai mult de această perioadă de timp.", "swap.unsupportedAssets.readMore": "Citiți mai multe despre materialele neacceptate", + "swap.warning.enterLargerAmount.title": "Introduceți o sumă mai mare", "swap.warning.expectedFailure": "Se așteaptă ca această tranzacție să eșueze", "swap.warning.feeOnTransfer.message": "Unele jetoane percep o taxă atunci când sunt cumpărate sau vândute, care este stabilită de emitentul token-ului. Uniswap nu primește nicio parte din aceste taxe.", "swap.warning.feeOnTransfer.title": "De ce există o taxă suplimentară?", "swap.warning.insufficientBalance.title": "Nu ai suficient {{currencySymbol}}", "swap.warning.insufficientGas.button": "Nu este suficient {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Schimbați cu {{ tokenSymbol }} pe {{networkName}}", "swap.warning.insufficientGas.button.buy": "Cumpărați {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Cumpărați cu cardul", "swap.warning.insufficientGas.message.withNetwork": "Nu este suficient {{currencySymbol}} pe {{networkName}} pentru a schimba", "swap.warning.insufficientGas.message.withoutNetwork": "Nu este suficient {{currencySymbol}} pentru a schimba", "swap.warning.insufficientGas.title": "Nu aveți suficient {{currencySymbol}} pentru a acoperi costul rețelei", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "date indicative pentru pe {{chainName}}", "tdp.nameNotFound": "Numele nu a fost găsit", "tdp.noInfoAvailable": "Nu există informații despre simboluri disponibile", + "tdp.noTestnetSupportDescription": "Unele rețele de testare nu acceptă schimbarea, trimiterea sau cumpărarea de jetoane.", "tdp.stats.unsupportedChainDescription": "Statisticile și graficele pentru token pentru {{chain}} sunt disponibile pe {{infoLink}}", "tdp.symbolNotFound": "Simbolul nu a fost găsit", + "testnet.unsupported": "Această funcționalitate nu este acceptată în modul testnet.", "themeToggle.theme": "Temă", "title.betterPricesMoreListings": "Preturi mai bune. Mai multe listări. Cumpărați, vindeți și tranzacționați NFT-uri pe piețele de top precum OpenSea. Explorați colecțiile la modă.", "title.buySellTradeEthereum": "Cumpărați, vindeți și tranzacționați Ethereum și alte jetoane de top pe Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "soldul lui {{ownerAddress}}", "token.bridge": "{{label}} token bridge", "token.chart.tooltip": "Taxe: {{amount}}", + "token.details.testnet.unsupported": "Detaliile tokenului nu sunt disponibile pentru tokenurile testnet.", "token.error.unknown": "Jeton necunoscut", "token.fee.buy.label": "taxa de cumparare", "token.fee.label": "taxa", @@ -2109,6 +2130,8 @@ "transaction.warning.insufficientGas.modal.title.withoutNetwork": "Nu este suficient {{tokenSymbol}}", "transaction.watcher.error.cancel": "Nu se poate anula tranzacția", "transaction.watcher.error.status": "Eroare la verificarea stării tranzacției", + "unichain.launch.modal.description": "Un Ethereum L2 conceput pentru DeFi, construit de Uniswap Labs. Testnet este disponibil astăzi.", + "unichain.launch.modal.title": "Vă prezentăm Unichain", "uniswapX.aggregatesLiquidity": " reunește sursele de lichiditate pentru prețuri mai bune și swap fără gaz.", "uniswapx.description": "UniswapX reunește sursele de lichiditate pentru prețuri mai bune și swap fără gaz.", "uniswapx.included": "Include UniswapX ", @@ -2212,7 +2235,6 @@ "walletConnect.pending.switchAccount": "Schimba contul", "walletConnect.pending.switchNetwork": "Schimbați rețeaua", "walletConnect.pending.title": "Conectați-vă la {{dappName}}", - "walletConnect.permissions.networks": "Rețele", "walletConnect.permissions.option.transferAssets": "Transferați-vă bunurile fără consimțământ", "walletConnect.permissions.option.viewTokenBalances": "Vedeți soldurile dvs. de simboluri", "walletConnect.permissions.option.viewWalletAddress": "Vizualizați adresa portofelului dvs", diff --git a/packages/uniswap/src/i18n/locales/translations/ru-RU.json b/packages/uniswap/src/i18n/locales/translations/ru-RU.json index 6fbbd2e8156..124cad59783 100644 --- a/packages/uniswap/src/i18n/locales/translations/ru-RU.json +++ b/packages/uniswap/src/i18n/locales/translations/ru-RU.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Внесите токены в сеть {{label}} .", "common.deposited": "Депонированный", "common.depositing": "Внесение депозита", + "common.depositTokens": "Депозитные токены", "common.detailed.label": "Подробный", "common.detected": "Обнаружено", "common.developers": "Разработчики", @@ -430,6 +431,7 @@ "common.migrate.position": "Мигрировать позицию", "common.migrated.liquidity": "Мигрированная ликвидность", "common.migrating.liquidity": "Миграция ликвидности", + "common.min": "Мин.", "common.mint.cancelled": "Монетный двор отменен", "common.mint.failed": "Монетный двор не удался", "common.minted": "Чеканенный", @@ -450,6 +452,7 @@ "common.noResults": "результатов не найдено.", "common.notAvailableInRegion.error": "Недоступно в вашем регионе", "common.notCreated.label": "Не создано", + "common.notSupported": "Не поддерживается", "common.oneDay": "1 день", "common.oneHour": "1 час", "common.oneMonth": "1 месяц", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}и другие варианты.", "fiatOnRamp.quote.type.other": "Другие варианты", "fiatOnRamp.quote.type.recent": "Недавно использовано", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Вы можете получать токены и NFT на Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora и ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Из аккаунта", "fiatOnRamp.receiveCrypto.title": "Получить криптовалюту", "fiatOnRamp.receiveCrypto.transferFunds": "Пополните ваш кошелек, передав криптовалюту из другого кошелька или аккаунта", @@ -901,6 +903,8 @@ "home.activity.error.load": "Не удалось загрузить активность", "home.activity.title": "Активность", "home.banner.offline": "Вы находитесь в автономном режиме", + "home.banner.testnetMode": "Вы находитесь в режиме тестовой сети", + "home.banner.testnetMode.nav": "Вы находитесь в режиме testnet. Отключите его в настройках.", "home.explore.footer": "Нажмите здесь, чтобы изучить тысячи токенов, NFT и многое другое.", "home.explore.title": "Исследуйте токены", "home.extension.error": "Ошибка загрузки аккаунтов.", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Создать позицию", "nav.tabs.createV2Position": "Создать позицию V2", "nav.tabs.createV3Position": "Создать позицию V3", + "nav.tabs.createV4Position": "Создать позицию V4", "nav.tabs.viewPosition": "Посмотреть позицию", "network.lostConnection": "Возможно, вы потеряли сетевое соединение.", "network.mightBeDown": "{{network}} , возможно, сейчас не работает, или вы потеряли сетевое соединение.", @@ -1331,6 +1336,7 @@ "pool.back": "Вернуться в бассейн", "pool.balances": "Балансы пула", "pool.claimFees": "Взыскание комиссий", + "pool.claimFees.button.label": "Требовать", "pool.collectAs": "Собрать как {{nativeWrappedSymbol}}", "pool.collected": " Собрано", "pool.collecting": "Сбор", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Управляйте ликвидностью в пуле вознаграждений", "pool.max.label": "Макс:", "pool.maxPrice": "Максимальная цена", + "pool.migrateToV4": "Мигрировать на V4", "pool.min.label": "Мин:", "pool.minPrice": "Минимальная цена", "pool.mustBeInitialized": "Этот пул необходимо инициализировать, прежде чем вы сможете добавить ликвидность. Для инициализации выберите начальную цену пула. Затем введите диапазон цен ликвидности и сумму депозита. Плата за газ будет выше, чем обычно, из-за транзакции инициализации.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Совет. Используйте этот инструмент, чтобы найти пулы версии 2, которые не отображаются в интерфейсе автоматически.", "pools.approving.amount": "Одобрение {{amount}}", "position.addHook": "Добавить крючок", + "position.addHook.tooltip": "Хуки — это расширенная функция, которая позволяет пулам взаимодействовать со смарт-контрактами, открывая различные возможности. Будьте осторожны при добавлении хуков, так как некоторые из них могут быть вредоносными или вызывать непреднамеренные последствия.", "position.appearHere": "Здесь появится ваша позиция.", + "position.create.modal.header": "Создание позиции", "position.currentValue": "Текущее значение позиции", + "position.deposit.description": "Укажите суммы токенов для вашего взноса в ликвидность.", "position.depositedCurrency": "Внесено {{currencySymbol}}", "position.migrate.liquidity": "При миграции позиций вы не можете изменить пару токенов, но можете добавить хук для улучшения функциональности.", "position.new": "Новая позиция", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Подключение...", "qrScanner.status.loading": "Загрузка...", "qrScanner.title": "Сканируйте QR-код", - "qrScanner.wallet.title": "Вы можете получать токены и NFT на Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast и BNB Chain.", + "qrScanner.wallet.networks": "Поддерживаемые сети", + "qrScanner.wallet.title": "Вы можете отправлять и получать токены и NFT во всех наших поддерживаемых сетях {{numOfNetworks}} .", "removeLiquidity.collectFees": "Вы также будете получать гонорары, полученные от этой позиции.", "removeLiquidity.outputEstimated": "Выход оценивается. Если цена изменится более чем на {{allowed}}%, ваша транзакция будет отменена.", "removeLiquidity.pendingText": "Удаление {{amtA}} {{symA}} и {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Псевдоним", "settings.setting.wallet.notifications.title": "Уведомления", "settings.setting.wallet.preferences.title": "Настройки кошелька", - "settings.showTestNets": "Показать тестовые сети", + "settings.setting.wallet.testnetMode.description": "Это включает тестовые сети для разработчиков, чтобы опробовать функции и транзакции без использования реальных активов. Токены в тестовых сетях не имеют никакой реальной ценности.", + "settings.setting.wallet.testnetMode.title": "Тестовый режим", "settings.switchNetwork.warning": "Чтобы использовать Uniswap на {{label}}, переключите сеть в настройках вашего кошелька.", "settings.title": "Настройки", "settings.version": "Версия {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Новый ввод", "swap.details.newQuote.output": "Новый результат", "swap.details.orderRouting": "Маршрут заказа", - "swap.details.orderRoutingInfo": "В вашу цену уже включены сетевые расходы в сети назначения и комиссия Across в размере 0,05%.", + "swap.details.orderRoutingInfo": "Этот обмен осуществляется через Across — децентрализованный протокол, который перемещает активы по сетям, уделяя первостепенное внимание безопасности, быстрому исполнению и низким ценам.", + "swap.details.poweredBy": "Питаться от", "swap.details.rate": "Ставка", "swap.details.slippage": "Макс. проскальзывание", "swap.details.uniswapFee": "Платеж", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Клиент Uniswap выбирает самый дешевый вариант торговли с учетом цены и сетевых затрат.", "swap.settings.routingPreference.option.v2.title": "v2 пулы", "swap.settings.routingPreference.option.v3.title": "v3 пулы", + "swap.settings.routingPreference.option.v4.title": "v4 пулы", "swap.settings.routingPreference.title": "Варианты торговли", "swap.settings.slippage.control.auto": "Авто", "swap.settings.slippage.description": "Ваша транзакция будет отменена, если цена изменится больше, чем процент проскальзывания.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Ваша транзакция будет отменена, если цена изменится в неблагоприятную сторону более чем на этот процент.", "swap.signAndSwap": "Подпиши и поменяй", "swap.slippage.amt": "{{amt}} проскальзывание", + "swap.slippage.bridging": "Отсутствие проскальзывания при переключении между сетями", "swap.slippage.settings.title": "Настройки проскальзывания", "swap.slippage.tooltip": "Максимальное движение цены до того, как ваша сделка вернется в исходное состояние.", "swap.slippageBelow.warning": "Проскальзывание ниже {{amt}} может привести к неудачной транзакции.", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Срок транзакции", "swap.transaction.revertAfter": "Ваша транзакция будет отменена, если она ожидает рассмотрения более этого периода времени.", "swap.unsupportedAssets.readMore": "Подробнее о неподдерживаемых ресурсах…", + "swap.warning.enterLargerAmount.title": "Введите большую сумму", "swap.warning.expectedFailure": "Ожидается, что эта транзакция завершится неудачно.", "swap.warning.feeOnTransfer.message": "При покупке или продаже некоторых токенов взимается комиссия, устанавливаемая эмитентом токенов. Uniswap не получает никакой доли этих комиссий.", "swap.warning.feeOnTransfer.title": "Почему взимается дополнительная плата?", "swap.warning.insufficientBalance.title": "Тебе недостаточно {{currencySymbol}}", "swap.warning.insufficientGas.button": "Недостаточно {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Обмен на {{ tokenSymbol }} на {{networkName}}", "swap.warning.insufficientGas.button.buy": "Купить {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Купить с картой", "swap.warning.insufficientGas.message.withNetwork": "Недостаточно {{currencySymbol}} на {{networkName}} для обмена", "swap.warning.insufficientGas.message.withoutNetwork": "Недостаточно {{currencySymbol}} для обмена", "swap.warning.insufficientGas.title": "У вас недостаточно {{currencySymbol}} для покрытия расходов на сеть.", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "данные токена для на {{chainName}}", "tdp.nameNotFound": "Имя не найдено", "tdp.noInfoAvailable": "Информация о токене отсутствует.", + "tdp.noTestnetSupportDescription": "Некоторые тестовые сети не поддерживают обмен, отправку или покупку токенов.", "tdp.stats.unsupportedChainDescription": "Статистика и графики токенов для {{chain}} доступны на {{infoLink}}.", "tdp.symbolNotFound": "Символ не найден", + "testnet.unsupported": "Эта функция не поддерживается в режиме тестовой сети.", "themeToggle.theme": "Тема", "title.betterPricesMoreListings": "Лучшие цены. Больше объявлений. Покупайте, продавайте и обменивайте NFT на ведущих торговых площадках, таких как OpenSea. Изучите трендовые коллекции.", "title.buySellTradeEthereum": "Покупайте, продавайте и обменивайте Ethereum и другие топовые токены на Uniswap.", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "{{ownerAddress}}баланс", "token.bridge": "{{label}} токен-мост", "token.chart.tooltip": "Сборы: {{amount}}", + "token.details.testnet.unsupported": "Подробная информация о токенах для тестовых токенов недоступна.", "token.error.unknown": "Неизвестный токен", "token.fee.buy.label": "плата за покупку", "token.fee.label": "платеж", @@ -2109,6 +2130,8 @@ "transaction.warning.insufficientGas.modal.title.withoutNetwork": "Недостаточно {{tokenSymbol}}", "transaction.watcher.error.cancel": "Невозможно отменить транзакцию", "transaction.watcher.error.status": "Ошибка при проверке статуса транзакции", + "unichain.launch.modal.description": "An Ethereum L2 designed for DeFi, built by Uniswap Labs. Testnet goes live today.", + "unichain.launch.modal.title": "Introducing Unichain", "uniswapX.aggregatesLiquidity": " объединяет источники ликвидности для более выгодных цен и свопов без газа.", "uniswapx.description": "UniswapX объединяет источники ликвидности для более выгодных цен и свободных от газа свопов.", "uniswapx.included": "Включает UniswapX ", @@ -2212,7 +2235,6 @@ "walletConnect.pending.switchAccount": "Сменить аккаунт", "walletConnect.pending.switchNetwork": "Переключить сеть", "walletConnect.pending.title": "Подключитесь к {{dappName}}", - "walletConnect.permissions.networks": "Сети", "walletConnect.permissions.option.transferAssets": "Передавайте свои активы без согласия", "walletConnect.permissions.option.viewTokenBalances": "Просматривайте балансы своих токенов", "walletConnect.permissions.option.viewWalletAddress": "Посмотреть адрес своего кошелька", diff --git a/packages/uniswap/src/i18n/locales/translations/sl-SI.json b/packages/uniswap/src/i18n/locales/translations/sl-SI.json index 6ece5e3b027..5682fb7dca1 100644 --- a/packages/uniswap/src/i18n/locales/translations/sl-SI.json +++ b/packages/uniswap/src/i18n/locales/translations/sl-SI.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Položite žetone v omrežje {{label}} .", "common.deposited": "Deponiran", "common.depositing": "Deponiranje", + "common.depositTokens": "Depozitni žetoni", "common.detailed.label": "Podrobno", "common.detected": "Zaznano", "common.developers": "Razvijalci", @@ -430,6 +431,7 @@ "common.migrate.position": "Preseliti položaj", "common.migrated.liquidity": "Preseljena likvidnost", "common.migrating.liquidity": "Selitev likvidnosti", + "common.min": "Min", "common.mint.cancelled": "Kovnica preklicana", "common.mint.failed": "Kovnica ni uspela", "common.minted": "Kovano", @@ -450,6 +452,7 @@ "common.noResults": "Ni zadetkov.", "common.notAvailableInRegion.error": "Ni na voljo v vaši regiji", "common.notCreated.label": "Ni ustvarjeno", + "common.notSupported": "Ni podprto", "common.oneDay": "1 dan", "common.oneHour": "1 uro", "common.oneMonth": "1 mesec", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}in druge možnosti", "fiatOnRamp.quote.type.other": "Druge možnosti", "fiatOnRamp.quote.type.recent": "Nedavno uporabljeno", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Žetone in NFT-je lahko prejmete na Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora in ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Iz računa", "fiatOnRamp.receiveCrypto.title": "Prejemanje kripto", "fiatOnRamp.receiveCrypto.transferFunds": "Financirajte svojo denarnico s prenosom kripto iz druge denarnice ali računa", @@ -901,6 +903,8 @@ "home.activity.error.load": "Dejavnosti ni bilo mogoče naložiti", "home.activity.title": "dejavnost", "home.banner.offline": "Ste v načinu brez povezave", + "home.banner.testnetMode": "Ste v načinu testnega omrežja", + "home.banner.testnetMode.nav": "Ste v načinu testnega omrežja. To izklopite v nastavitvah.", "home.explore.footer": "Tapnite tukaj, če želite raziskati na tisoče žetonov, NFT-jev in drugega", "home.explore.title": "Raziščite žetone", "home.extension.error": "Napaka pri nalaganju računov", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Ustvari položaj", "nav.tabs.createV2Position": "Ustvari položaj V2", "nav.tabs.createV3Position": "Ustvari položaj V3", + "nav.tabs.createV4Position": "Ustvari položaj V4", "nav.tabs.viewPosition": "Položaj pogleda", "network.lostConnection": "Morda ste izgubili omrežno povezavo.", "network.mightBeDown": "{{network}} morda trenutno ne deluje ali pa ste izgubili omrežno povezavo.", @@ -1331,6 +1336,7 @@ "pool.back": "Nazaj v bazen", "pool.balances": "Bilanca bazena", "pool.claimFees": "Pristojbine za zahtevek", + "pool.claimFees.button.label": "Zahtevek", "pool.collectAs": "Zberite kot {{nativeWrappedSymbol}}", "pool.collected": " Zbrano", "pool.collecting": "Zbiranje", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Upravljajte likvidnost v skladu nagrad", "pool.max.label": "maks:", "pool.maxPrice": "Max cena", + "pool.migrateToV4": "Preselite na V4", "pool.min.label": "Min.:", "pool.minPrice": "Najmanjša cena", "pool.mustBeInitialized": "Ta bazen je treba inicializirati, preden lahko dodate likvidnost. Za inicializacijo izberite začetno ceno za bazen. Nato vnesite cenovni razpon likvidnosti in znesek depozita. Pristojbine za plin bodo zaradi inicializacijske transakcije višje kot običajno.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Namig: uporabite to orodje za iskanje skupin v2, ki se ne prikažejo samodejno v vmesniku.", "pools.approving.amount": "Odobritev {{amount}}", "position.addHook": "Dodajte kavelj", + "position.addHook.tooltip": "Kavlji so napredna funkcija, ki bazenom omogoča interakcijo s pametnimi pogodbami in odklepanje različnih zmogljivosti. Pri dodajanju kavljev bodite previdni, saj so nekateri lahko zlonamerni ali povzročijo neželene posledice.", "position.appearHere": "Vaš položaj bo prikazan tukaj.", + "position.create.modal.header": "Ustvarjanje položaja", "position.currentValue": "Vrednost trenutne pozicije", + "position.deposit.description": "Določite simbolične zneske za svoj likvidnostni prispevek.", "position.depositedCurrency": "Deponirano {{currencySymbol}}", "position.migrate.liquidity": "Pri selitvi pozicij ne morete spremeniti svojega para žetonov, lahko pa dodate kavelj za izboljšanje funkcionalnosti.", "position.new": "Nov položaj", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Povezovanje ...", "qrScanner.status.loading": "Nalaganje...", "qrScanner.title": "Skenirajte kodo QR", - "qrScanner.wallet.title": "Žetone in NFT-je lahko prejmete na Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast in BNB Chain.", + "qrScanner.wallet.networks": "Podprta omrežja", + "qrScanner.wallet.title": "Žetone in NFT-je lahko pošiljate in prejemate v vseh naših {{numOfNetworks}} podprtih omrežjih.", "removeLiquidity.collectFees": "Pobrali boste tudi honorarje, zaslužene s tega položaja.", "removeLiquidity.outputEstimated": "Izhod je ocenjen. Če se cena spremeni za več kot {{allowed}}%, bo vaša transakcija razveljavljena.", "removeLiquidity.pendingText": "Odstranjevanje {{amtA}} {{symA}} in {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Vzdevek", "settings.setting.wallet.notifications.title": "Obvestila", "settings.setting.wallet.preferences.title": "Nastavitve denarnice", - "settings.showTestNets": "Prikaži testna omrežja", + "settings.setting.wallet.testnetMode.description": "To vklopi testne mreže za razvijalce, da preizkusijo funkcije in transakcije brez uporabe resničnih sredstev. Žetoni v testnih omrežjih nimajo prave vrednosti.", + "settings.setting.wallet.testnetMode.title": "Način Testnet", "settings.switchNetwork.warning": "Če želite uporabljati Uniswap na {{label}}, preklopite omrežje v nastavitvah denarnice.", "settings.title": "nastavitve", "settings.version": "Različica {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Nov vnos", "swap.details.newQuote.output": "Nov rezultat", "swap.details.orderRouting": "Usmerjanje naročil", - "swap.details.orderRoutingInfo": "Vaša cena že vključuje omrežne stroške v ciljnem omrežju in 0,05-odstotno provizijo Across.", + "swap.details.orderRoutingInfo": "Ta zamenjava je usmerjena prek Across, decentraliziranega protokola, ki premika sredstva po omrežjih, pri čemer daje prednost varnosti, hitri izvedbi in nizkim cenam.", + "swap.details.poweredBy": "Poganja", "swap.details.rate": "Oceniti", "swap.details.slippage": "Največji zdrs", "swap.details.uniswapFee": "Pristojbina", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Odjemalec Uniswap izbere najcenejšo trgovinsko možnost, upoštevajoč ceno in stroške omrežja.", "swap.settings.routingPreference.option.v2.title": "bazeni v2", "swap.settings.routingPreference.option.v3.title": "v3 bazeni", + "swap.settings.routingPreference.option.v4.title": "v4 bazeni", "swap.settings.routingPreference.title": "Možnosti trgovanja", "swap.settings.slippage.control.auto": "Avto", "swap.settings.slippage.description": "Vaša transakcija se bo razveljavila, če se cena spremeni bolj kot odstotek zdrsa.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Vaša transakcija bo razveljavljena, če se cena neugodno spremeni za več kot ta odstotek.", "swap.signAndSwap": "Podpiši in zamenjaj", "swap.slippage.amt": "{{amt}} zdrs", + "swap.slippage.bridging": "Brez zdrsa pri zamenjavi med omrežji", "swap.slippage.settings.title": "Nastavitve zdrsa", "swap.slippage.tooltip": "Največje gibanje cene pred vašo transakcijo se bo razveljavilo.", "swap.slippageBelow.warning": "Zdrs pod {{amt}} lahko povzroči neuspešno transakcijo", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Rok transakcije", "swap.transaction.revertAfter": "Vaša transakcija bo razveljavljena, če je na čakanju dlje od tega časa.", "swap.unsupportedAssets.readMore": "Preberite več o nepodprtih sredstvih", + "swap.warning.enterLargerAmount.title": "Vnesite večji znesek", "swap.warning.expectedFailure": "Pričakuje se, da ta transakcija ne bo uspela", "swap.warning.feeOnTransfer.message": "Nekateri žetoni ob nakupu ali prodaji zaračunajo provizijo, ki jo določi izdajatelj žetona. Uniswap ne prejme nobenega deleža teh nadomestil.", "swap.warning.feeOnTransfer.title": "Zakaj obstaja dodatna pristojbina?", "swap.warning.insufficientBalance.title": "Nimate dovolj {{currencySymbol}}", "swap.warning.insufficientGas.button": "Ni dovolj {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Zamenjaj za {{ tokenSymbol }} na {{networkName}}", "swap.warning.insufficientGas.button.buy": "Kupi {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Nakup s kartico", "swap.warning.insufficientGas.message.withNetwork": "Ni dovolj {{currencySymbol}} na {{networkName}} za zamenjavo", "swap.warning.insufficientGas.message.withoutNetwork": "Ni dovolj {{currencySymbol}} za zamenjavo", "swap.warning.insufficientGas.title": "Nimate dovolj {{currencySymbol}} za kritje stroškov omrežja", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "podatki žetona za na {{chainName}}", "tdp.nameNotFound": "Ime ni bilo mogoče najti", "tdp.noInfoAvailable": "Informacije o žetonu niso na voljo", + "tdp.noTestnetSupportDescription": "Nekatera testna omrežja ne podpirajo zamenjave, pošiljanja ali nakupa žetonov.", "tdp.stats.unsupportedChainDescription": "Statistika žetonov in grafikoni za {{chain}} so na voljo na {{infoLink}}", "tdp.symbolNotFound": "Simbola ni mogoče najti", + "testnet.unsupported": "Ta funkcija ni podprta v načinu testnega omrežja.", "themeToggle.theme": "Tema", "title.betterPricesMoreListings": "Boljše cene. Več oglasov. Kupujte, prodajajte in trgujte z NFT-ji na najboljših tržnicah, kot je OpenSea. Raziščite priljubljene kolekcije.", "title.buySellTradeEthereum": "Kupujte, prodajajte in trgovajte z Ethereumom in drugimi vrhunskimi žetoni na Uniswapu", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "{{ownerAddress}}stanje", "token.bridge": "{{label}} žetonski most", "token.chart.tooltip": "Pristojbine: {{amount}}", + "token.details.testnet.unsupported": "Podrobnosti o žetonih niso na voljo za žetone testnega omrežja.", "token.error.unknown": "Neznan žeton", "token.fee.buy.label": "pristojbina za nakup", "token.fee.label": "pristojbina", @@ -2109,6 +2130,8 @@ "transaction.warning.insufficientGas.modal.title.withoutNetwork": "Ni dovolj {{tokenSymbol}}", "transaction.watcher.error.cancel": "Transakcije ni mogoče preklicati", "transaction.watcher.error.status": "Napaka pri preverjanju stanja transakcije", + "unichain.launch.modal.description": "An Ethereum L2 designed for DeFi, built by Uniswap Labs. Testnet goes live today.", + "unichain.launch.modal.title": "Introducing Unichain", "uniswapX.aggregatesLiquidity": " združuje vire likvidnosti za boljše cene in zamenjave brez plina.", "uniswapx.description": "UniswapX združuje vire likvidnosti za boljše cene in zamenjave brez plina.", "uniswapx.included": "Vključuje UniswapX ", @@ -2212,7 +2235,6 @@ "walletConnect.pending.switchAccount": "Preklopi račun", "walletConnect.pending.switchNetwork": "Preklopite omrežje", "walletConnect.pending.title": "Povežite se z {{dappName}}", - "walletConnect.permissions.networks": "Omrežja", "walletConnect.permissions.option.transferAssets": "Prenesite svoja sredstva brez soglasja", "walletConnect.permissions.option.viewTokenBalances": "Oglejte si svoja stanja žetonov", "walletConnect.permissions.option.viewWalletAddress": "Oglejte si naslov svoje denarnice", diff --git a/packages/uniswap/src/i18n/locales/translations/sr-SP.json b/packages/uniswap/src/i18n/locales/translations/sr-SP.json index 4dff2d95eb6..82bde900f1a 100644 --- a/packages/uniswap/src/i18n/locales/translations/sr-SP.json +++ b/packages/uniswap/src/i18n/locales/translations/sr-SP.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Депонујте токене на {{label}} мрежу.", "common.deposited": "Депоновано", "common.depositing": "Депоновање", + "common.depositTokens": "Deposit tokens", "common.detailed.label": "Детаљно", "common.detected": "Откривен", "common.developers": "Девелоперс", @@ -430,6 +431,7 @@ "common.migrate.position": "Migrate position", "common.migrated.liquidity": "Мигрирана ликвидност", "common.migrating.liquidity": "Миграција ликвидности", + "common.min": "Min", "common.mint.cancelled": "Минт отказана", "common.mint.failed": "Минт није успео", "common.minted": "Кован", @@ -450,6 +452,7 @@ "common.noResults": "Нема резултата.", "common.notAvailableInRegion.error": "Није доступно у вашем региону", "common.notCreated.label": "Није креирано", + "common.notSupported": "Not supported", "common.oneDay": "1 дан", "common.oneHour": "1 сат", "common.oneMonth": "1 месец", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}и друге опције", "fiatOnRamp.quote.type.other": "Друге опције", "fiatOnRamp.quote.type.recent": "Недавно коришћен", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Можете да примате токене и НФТ на Етхереум, Полигон, Арбитрум, Оптимисм, Басе, БНБ, Бласт, Аваланцхе, Зора и ЗКсинц.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Са налога", "fiatOnRamp.receiveCrypto.title": "Примите криптовалуте", "fiatOnRamp.receiveCrypto.transferFunds": "Финансирајте свој новчаник преносом криптовалута из другог новчаника или налога", @@ -901,6 +903,8 @@ "home.activity.error.load": "Учитавање активности није успело", "home.activity.title": "Активност", "home.banner.offline": "Налазите се у офлајн режиму", + "home.banner.testnetMode": "You are in testnet mode", + "home.banner.testnetMode.nav": "You are in testnet mode. Toggle this off in settings.", "home.explore.footer": "Додирните овде да истражите хиљаде токена, НФТ-ова и још много тога", "home.explore.title": "Истражите токене", "home.extension.error": "Грешка при учитавању налога", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Креирајте позицију", "nav.tabs.createV2Position": "Create V2 position", "nav.tabs.createV3Position": "Create V3 position", + "nav.tabs.createV4Position": "Create V4 position", "nav.tabs.viewPosition": "Положај погледа", "network.lostConnection": "Можда сте изгубили мрежну везу.", "network.mightBeDown": "{{network}} можда тренутно не ради или сте можда изгубили мрежну везу.", @@ -1331,6 +1336,7 @@ "pool.back": "Назад у базен", "pool.balances": "Поол биланси", "pool.claimFees": "Накнаде за потраживање", + "pool.claimFees.button.label": "Claim", "pool.collectAs": "Сакупите као {{nativeWrappedSymbol}}", "pool.collected": " Прикупљени", "pool.collecting": "Прикупљање", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Управљајте ликвидношћу у фонду награда", "pool.max.label": "Макс:", "pool.maxPrice": "Мак цена", + "pool.migrateToV4": "Migrate to V4", "pool.min.label": "Мин:", "pool.minPrice": "Мин цена", "pool.mustBeInitialized": "Овај базен мора бити иницијализован пре него што можете да додате ликвидност. Да бисте иницијализовали, изаберите почетну цену за базен. Затим унесите распон цена ликвидности и износ депозита. Накнаде за гас ће бити веће него иначе због трансакције иницијализације.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Савет: Користите овај алат да пронађете в2 скупове који се не појављују аутоматски у интерфејсу.", "pools.approving.amount": "Одобравање {{amount}}", "position.addHook": "Add a Hook", + "position.addHook.tooltip": "Hooks are an advanced feature that enable pools to interact with smart contracts, unlocking various capabilities. Exercise caution when adding hooks, as some may be malicious or cause unintended consequences.", "position.appearHere": "Овде ће се појавити ваша позиција.", + "position.create.modal.header": "Creating position", "position.currentValue": "Current position value", + "position.deposit.description": "Specify the token amounts for your liquidity contribution.", "position.depositedCurrency": "Deposited {{currencySymbol}}", "position.migrate.liquidity": "When migrating positions, you cannot change your token pair, but you can add a hook to enhance functionality.", "position.new": "New Position", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Повезивање...", "qrScanner.status.loading": "Учитавање...", "qrScanner.title": "Скенирајте КР код", - "qrScanner.wallet.title": "Можете да примате токене и НФТ на Етхереум, Полигон, Арбитрум, Оптимисм, Басе, ЗКсинц, Зора, Аваланцхе, Цело, Бласт и БНБ Цхаин.", + "qrScanner.wallet.networks": "Supported Networks", + "qrScanner.wallet.title": "You can send and receive tokens and NFTs on all of our {{numOfNetworks}} supported networks.", "removeLiquidity.collectFees": "Такође ћете прикупљати накнаде зарађене са ове позиције.", "removeLiquidity.outputEstimated": "Излаз је процењен. Ако се цена промени за више од {{allowed}}%, ваша трансакција ће се вратити.", "removeLiquidity.pendingText": "Уклањање {{amtA}} {{symA}} и {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Надимак", "settings.setting.wallet.notifications.title": "Обавештења", "settings.setting.wallet.preferences.title": "Подешавања новчаника", - "settings.showTestNets": "Прикажи тестне мреже", + "settings.setting.wallet.testnetMode.description": "This turns on testnets for developers to try out features and transactions without using real assets. Tokens on testnets do not hold any real value.", + "settings.setting.wallet.testnetMode.title": "Testnet mode", "settings.switchNetwork.warning": "Да бисте користили Унисвап на {{label}}, промените мрежу у подешавањима новчаника.", "settings.title": "Подешавања", "settings.version": "Верзија {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Нови унос", "swap.details.newQuote.output": "Нови излаз", "swap.details.orderRouting": "Рутирање налога", - "swap.details.orderRoutingInfo": "Your price already includes network costs on the destination network and a 0.05% Across fee.", + "swap.details.orderRoutingInfo": "This swap is routed via Across, a decentralized protocol that moves assets across networks while prioritizing safety, fast execution, and low prices.", + "swap.details.poweredBy": "Powered by", "swap.details.rate": "Рате", "swap.details.slippage": "Максимално клизање", "swap.details.uniswapFee": "Надокнада", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Унисвап клијент бира најјефтинију трговинску опцију факторинг цене и мрежних трошкова.", "swap.settings.routingPreference.option.v2.title": "в2 базени", "swap.settings.routingPreference.option.v3.title": "в3 базени", + "swap.settings.routingPreference.option.v4.title": "v4 pools", "swap.settings.routingPreference.title": "Опције трговине", "swap.settings.slippage.control.auto": "Ауто", "swap.settings.slippage.description": "Ваша трансакција ће се вратити ако се цена промени више од процента клизања.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Ваша трансакција ће се вратити ако се цена неповољно промени за више од овог процента.", "swap.signAndSwap": "Потпишите и замените", "swap.slippage.amt": "{{amt}} проклизавање", + "swap.slippage.bridging": "No slippage when swapping across networks", "swap.slippage.settings.title": "Подешавања клизања", "swap.slippage.tooltip": "Максимално кретање цене пре ваше трансакције ће се вратити.", "swap.slippageBelow.warning": "Проклизавање испод {{amt}} може довести до неуспешне трансакције", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Рок за трансакцију", "swap.transaction.revertAfter": "Ваша трансакција ће бити враћена ако је на чекању дуже од овог временског периода.", "swap.unsupportedAssets.readMore": "Прочитајте више о неподржаним средствима", + "swap.warning.enterLargerAmount.title": "Enter a larger amount", "swap.warning.expectedFailure": "Очекује се да ће ова трансакција пропасти", "swap.warning.feeOnTransfer.message": "Неки токени узимају накнаду када се купују или продају, коју поставља издавалац токена. Унисвап не прима никакав део ових накнада.", "swap.warning.feeOnTransfer.title": "Зашто постоји додатна накнада?", "swap.warning.insufficientBalance.title": "Немате довољно {{currencySymbol}}", "swap.warning.insufficientGas.button": "Није довољно {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Swap for {{ tokenSymbol }} on {{networkName}}", "swap.warning.insufficientGas.button.buy": "Купи {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Buy with card", "swap.warning.insufficientGas.message.withNetwork": "Нема довољно {{currencySymbol}} на {{networkName}} за замену", "swap.warning.insufficientGas.message.withoutNetwork": "Није довољно {{currencySymbol}} за замену", "swap.warning.insufficientGas.title": "Немате довољно {{currencySymbol}} да покријете трошкове мреже", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "подаци о токену за на {{chainName}}", "tdp.nameNotFound": "Име није пронађено", "tdp.noInfoAvailable": "Нема доступних информација о токену", + "tdp.noTestnetSupportDescription": "Some testnets do not support swapping, sending, or buying tokens.", "tdp.stats.unsupportedChainDescription": "Статистика токена и графикони за {{chain}} су доступни на {{infoLink}}", "tdp.symbolNotFound": "Симбол није пронађен", + "testnet.unsupported": "This functionality is not supported in testnet mode.", "themeToggle.theme": "Тема", "title.betterPricesMoreListings": "Боље цене. Више огласа. Купујте, продајте и тргујте НФТ-овима на врхунским тржиштима као што је ОпенСеа. Истражите трендовске колекције.", "title.buySellTradeEthereum": "Купујте, продајте и тргујте Етхереумом и другим врхунским токенима на Унисвап-у", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "{{ownerAddress}}'с баланс", "token.bridge": "{{label}} токен бридге", "token.chart.tooltip": "Накнаде: {{amount}}", + "token.details.testnet.unsupported": "Token details are unavailable for testnet tokens.", "token.error.unknown": "Непознати токен", "token.fee.buy.label": "buy fee", "token.fee.label": "fee", @@ -2109,6 +2130,8 @@ "transaction.warning.insufficientGas.modal.title.withoutNetwork": "Није довољно {{tokenSymbol}}", "transaction.watcher.error.cancel": "Није могуће отказати трансакцију", "transaction.watcher.error.status": "Грешка приликом провере статуса трансакције", + "unichain.launch.modal.description": "An Ethereum L2 designed for DeFi, built by Uniswap Labs. Testnet goes live today.", + "unichain.launch.modal.title": "Introducing Unichain", "uniswapX.aggregatesLiquidity": " обједињује изворе ликвидности за боље цене и размену без гаса.", "uniswapx.description": "УнисвапКс агрегира изворе ликвидности за боље цене и свопове без гаса.", "uniswapx.included": "Укључује УнисвапКс ", @@ -2212,7 +2235,6 @@ "walletConnect.pending.switchAccount": "Промени налог", "walletConnect.pending.switchNetwork": "Пребаците мрежу", "walletConnect.pending.title": "Повежите се са {{dappName}}", - "walletConnect.permissions.networks": "Мреже", "walletConnect.permissions.option.transferAssets": "Пренесите своју имовину без сагласности", "walletConnect.permissions.option.viewTokenBalances": "Погледајте стање токена", "walletConnect.permissions.option.viewWalletAddress": "Погледајте адресу свог новчаника", diff --git a/packages/uniswap/src/i18n/locales/translations/sv-SE.json b/packages/uniswap/src/i18n/locales/translations/sv-SE.json index d646ff935bd..b4e824582b6 100644 --- a/packages/uniswap/src/i18n/locales/translations/sv-SE.json +++ b/packages/uniswap/src/i18n/locales/translations/sv-SE.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Sätt in tokens till {{label}} -nätverket.", "common.deposited": "Deponerad", "common.depositing": "Insättning", + "common.depositTokens": "Insättningspoletter", "common.detailed.label": "Detaljerad", "common.detected": "Upptäckt", "common.developers": "Utvecklare", @@ -430,6 +431,7 @@ "common.migrate.position": "Migrera position", "common.migrated.liquidity": "Migrerad likviditet", "common.migrating.liquidity": "Migrerar likviditet", + "common.min": "Min", "common.mint.cancelled": "Mint inställd", "common.mint.failed": "Mint misslyckades", "common.minted": "Präglat", @@ -450,6 +452,7 @@ "common.noResults": "Inga resultat funna.", "common.notAvailableInRegion.error": "Ej tillgängligt i din region", "common.notCreated.label": "Inte skapad", + "common.notSupported": "Stöds inte", "common.oneDay": "1 dag", "common.oneHour": "1 timme", "common.oneMonth": "1 månad", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}och andra alternativ", "fiatOnRamp.quote.type.other": "Andra alternativ", "fiatOnRamp.quote.type.recent": "Nyligen använd", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Du kan ta emot tokens och NFT på Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora och ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Från ett konto", "fiatOnRamp.receiveCrypto.title": "Ta emot krypto", "fiatOnRamp.receiveCrypto.transferFunds": "Finansiera din plånbok genom att överföra krypto från en annan plånbok eller ett annat konto", @@ -901,6 +903,8 @@ "home.activity.error.load": "Det gick inte att ladda aktiviteten", "home.activity.title": "Aktivitet", "home.banner.offline": "Du är i offlineläge", + "home.banner.testnetMode": "Du är i testnet-läge", + "home.banner.testnetMode.nav": "Du är i testnet-läge. Stäng av detta i inställningarna.", "home.explore.footer": "Tryck här för att utforska tusentals tokens, NFT:er och mer", "home.explore.title": "Utforska tokens", "home.extension.error": "Det gick inte att läsa in konton", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Skapa position", "nav.tabs.createV2Position": "Skapa V2-position", "nav.tabs.createV3Position": "Skapa V3-position", + "nav.tabs.createV4Position": "Skapa V4-position", "nav.tabs.viewPosition": "Se position", "network.lostConnection": "Du kan ha tappat nätverksanslutningen.", "network.mightBeDown": "{{network}} kan vara nere just nu, eller så kan du ha tappat nätverksanslutningen.", @@ -1331,6 +1336,7 @@ "pool.back": "Tillbaka till poolen", "pool.balances": "Poolsaldon", "pool.claimFees": "Skaffa avgifter", + "pool.claimFees.button.label": "Krav", "pool.collectAs": "Samla som {{nativeWrappedSymbol}}", "pool.collected": " Samlade in", "pool.collecting": "Samlar", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Hantera likviditet i belöningspoolen", "pool.max.label": "Max:", "pool.maxPrice": "Maxpris", + "pool.migrateToV4": "Migrera till V4", "pool.min.label": "Min:", "pool.minPrice": "Minsta pris", "pool.mustBeInitialized": "Denna pool måste initieras innan du kan lägga till likviditet. För att initiera, välj ett startpris för poolen. Ange sedan ditt likviditetsprisintervall och insättningsbelopp. Gasavgifterna kommer att vara högre än vanligt på grund av initialiseringstransaktionen.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Tips: Använd det här verktyget för att hitta v2-pooler som inte automatiskt visas i gränssnittet.", "pools.approving.amount": "Godkänner {{amount}}", "position.addHook": "Lägg till en krok", + "position.addHook.tooltip": "Hooks är en avancerad funktion som gör det möjligt för pooler att interagera med smarta kontrakt, vilket låser upp olika funktioner. Var försiktig när du lägger till krokar, eftersom vissa kan vara skadliga eller orsaka oavsiktliga konsekvenser.", "position.appearHere": "Din position kommer att visas här.", + "position.create.modal.header": "Skapa position", "position.currentValue": "Aktuellt positionsvärde", + "position.deposit.description": "Ange symbolbeloppen för ditt likviditetsbidrag.", "position.depositedCurrency": "Deponerade {{currencySymbol}}", "position.migrate.liquidity": "När du migrerar positioner kan du inte ändra ditt tokenpar, men du kan lägga till en krok för att förbättra funktionaliteten.", "position.new": "Ny position", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Ansluter...", "qrScanner.status.loading": "Läser in...", "qrScanner.title": "Skanna en QR-kod", - "qrScanner.wallet.title": "Du kan ta emot tokens och NFT på Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast och BNB Chain.", + "qrScanner.wallet.networks": "Nätverk som stöds", + "qrScanner.wallet.title": "Du kan skicka och ta emot tokens och NFTs på alla våra {{numOfNetworks}} nätverk som stöds.", "removeLiquidity.collectFees": "Du kommer också att samla in avgifter från denna position.", "removeLiquidity.outputEstimated": "Utgången är uppskattad. Om priset ändras med mer än {{allowed}}% kommer din transaktion att återgå.", "removeLiquidity.pendingText": "Ta bort {{amtA}} {{symA}} och {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Smeknamn", "settings.setting.wallet.notifications.title": "Aviseringar", "settings.setting.wallet.preferences.title": "Plånboksinställningar", - "settings.showTestNets": "Visa testnät", + "settings.setting.wallet.testnetMode.description": "Detta aktiverar testnät för utvecklare att testa funktioner och transaktioner utan att använda riktiga tillgångar. Tokens på testnät har inget verkligt värde.", + "settings.setting.wallet.testnetMode.title": "Testnet-läge", "settings.switchNetwork.warning": "För att använda Uniswap på {{label}}byter du nätverk i plånbokens inställningar.", "settings.title": "inställningar", "settings.version": "Version {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Ny ingång", "swap.details.newQuote.output": "Ny utgång", "swap.details.orderRouting": "Beställningsdirigering", - "swap.details.orderRoutingInfo": "Ditt pris inkluderar redan nätverkskostnader på destinationsnätverket och en avgift på 0,05 %.", + "swap.details.orderRoutingInfo": "Denna swap dirigeras via Across, ett decentraliserat protokoll som flyttar tillgångar över nätverk samtidigt som säkerhet, snabb utförande och låga priser prioriteras.", + "swap.details.poweredBy": "Drivs av", "swap.details.rate": "Betygsätta", "swap.details.slippage": "Max glidning", "swap.details.uniswapFee": "Avgift", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Uniswap-klienten väljer det billigaste handelsalternativet med hänsyn till pris och nätverkskostnader.", "swap.settings.routingPreference.option.v2.title": "v2 pooler", "swap.settings.routingPreference.option.v3.title": "v3 pooler", + "swap.settings.routingPreference.option.v4.title": "v4 pooler", "swap.settings.routingPreference.title": "Handel med alternativ", "swap.settings.slippage.control.auto": "Bil", "swap.settings.slippage.description": "Din transaktion kommer att återgå om priset ändras mer än glidningsprocenten.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Din transaktion kommer att återgå om priset ändras ogynnsamt med mer än denna procentsats.", "swap.signAndSwap": "Signera och byt", "swap.slippage.amt": "{{amt}} glidning", + "swap.slippage.bridging": "Ingen glidning när du byter över nätverk", "swap.slippage.settings.title": "Slipage-inställningar", "swap.slippage.tooltip": "Den maximala prisrörelsen före din transaktion kommer att återgå.", "swap.slippageBelow.warning": "En glidning under {{amt}} kan resultera i en misslyckad transaktion", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Transaktionens deadline", "swap.transaction.revertAfter": "Din transaktion kommer att återställas om den väntar längre än denna tidsperiod.", "swap.unsupportedAssets.readMore": "Läs mer om tillgångar som inte stöds", + "swap.warning.enterLargerAmount.title": "Ange ett större belopp", "swap.warning.expectedFailure": "Denna transaktion förväntas misslyckas", "swap.warning.feeOnTransfer.message": "Vissa tokens tar en avgift när de köps eller säljs, vilket bestäms av tokenutgivaren. Uniswap erhåller ingen andel av dessa avgifter.", "swap.warning.feeOnTransfer.title": "Varför tillkommer en extra avgift?", "swap.warning.insufficientBalance.title": "Du har inte tillräckligt med {{currencySymbol}}", "swap.warning.insufficientGas.button": "Inte tillräckligt {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Byt mot {{ tokenSymbol }} på {{networkName}}", "swap.warning.insufficientGas.button.buy": "Köp {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Köp med kort", "swap.warning.insufficientGas.message.withNetwork": "Inte tillräckligt med {{currencySymbol}} på {{networkName}} för att byta", "swap.warning.insufficientGas.message.withoutNetwork": "Inte tillräckligt med {{currencySymbol}} för att byta", "swap.warning.insufficientGas.title": "Du har inte tillräckligt med {{currencySymbol}} för att täcka nätverkskostnaden", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "tokendata för på {{chainName}}", "tdp.nameNotFound": "Namnet hittades inte", "tdp.noInfoAvailable": "Ingen tokeninformation tillgänglig", + "tdp.noTestnetSupportDescription": "Vissa testnät stöder inte att byta, skicka eller köpa tokens.", "tdp.stats.unsupportedChainDescription": "Tokenstatistik och diagram för {{chain}} finns på {{infoLink}}", "tdp.symbolNotFound": "Symbolen hittades inte", + "testnet.unsupported": "Den här funktionen stöds inte i testnätläge.", "themeToggle.theme": "Tema", "title.betterPricesMoreListings": "Bättre priser. Fler listor. Köp, sälj och byt NFT på toppmarknadsplatser som OpenSea. Utforska trendiga samlingar.", "title.buySellTradeEthereum": "Köp, sälj och byt Ethereum och andra topptokens på Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "{{ownerAddress}}s saldo", "token.bridge": "{{label}} symbolbro", "token.chart.tooltip": "Avgifter: {{amount}}", + "token.details.testnet.unsupported": "Tokendetaljer är inte tillgängliga för testnet-tokens.", "token.error.unknown": "Okänd token", "token.fee.buy.label": "köpavgift", "token.fee.label": "avgift", @@ -2109,6 +2130,8 @@ "transaction.warning.insufficientGas.modal.title.withoutNetwork": "Inte tillräckligt {{tokenSymbol}}", "transaction.watcher.error.cancel": "Det gick inte att avbryta transaktionen", "transaction.watcher.error.status": "Fel vid kontroll av transaktionsstatus", + "unichain.launch.modal.description": "An Ethereum L2 designed for DeFi, built by Uniswap Labs. Testnet goes live today.", + "unichain.launch.modal.title": "Introducing Unichain", "uniswapX.aggregatesLiquidity": " samlar likviditetskällor för bättre priser och gasfria swappar.", "uniswapx.description": "UniswapX samlar likviditetskällor för bättre priser och gasfria swappar.", "uniswapx.included": "Inkluderar UniswapX ", @@ -2212,7 +2235,6 @@ "walletConnect.pending.switchAccount": "Byt konto", "walletConnect.pending.switchNetwork": "Byt nätverk", "walletConnect.pending.title": "Anslut till {{dappName}}", - "walletConnect.permissions.networks": "Nätverk", "walletConnect.permissions.option.transferAssets": "Överför dina tillgångar utan samtycke", "walletConnect.permissions.option.viewTokenBalances": "Se dina tokensaldon", "walletConnect.permissions.option.viewWalletAddress": "Visa din plånboksadress", diff --git a/packages/uniswap/src/i18n/locales/translations/sw-TZ.json b/packages/uniswap/src/i18n/locales/translations/sw-TZ.json index 82b128f6d80..483605421a4 100644 --- a/packages/uniswap/src/i18n/locales/translations/sw-TZ.json +++ b/packages/uniswap/src/i18n/locales/translations/sw-TZ.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Weka tokeni kwenye mtandao wa {{label}} .", "common.deposited": "Imewekwa", "common.depositing": "Kuweka amana", + "common.depositTokens": "Deposit tokens", "common.detailed.label": "Kina", "common.detected": "Imegunduliwa", "common.developers": "Watengenezaji", @@ -430,6 +431,7 @@ "common.migrate.position": "Migrate position", "common.migrated.liquidity": "Ukwasi uliohama", "common.migrating.liquidity": "Kuhama ukwasi", + "common.min": "Min", "common.mint.cancelled": "Mint imeghairiwa", "common.mint.failed": "Mint imeshindwa", "common.minted": "Imeandaliwa", @@ -450,6 +452,7 @@ "common.noResults": "Hakuna matokeo yaliyopatikana.", "common.notAvailableInRegion.error": "Haipatikani katika eneo lako", "common.notCreated.label": "Haijaundwa", + "common.notSupported": "Not supported", "common.oneDay": "siku 1", "common.oneHour": "Saa 1", "common.oneMonth": "mwezi 1", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}, na chaguzi zingine", "fiatOnRamp.quote.type.other": "Chaguzi zingine", "fiatOnRamp.quote.type.recent": "Iliyotumiwa hivi karibuni", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Unaweza kupokea tokeni na NFTs kwenye Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora, na ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Kutoka kwa akaunti", "fiatOnRamp.receiveCrypto.title": "Pokea crypto", "fiatOnRamp.receiveCrypto.transferFunds": "Fanya mkoba wako kwa kuhamisha crypto kutoka kwa pochi au akaunti nyingine", @@ -901,6 +903,8 @@ "home.activity.error.load": "Haikuweza kupakia shughuli", "home.activity.title": "Shughuli", "home.banner.offline": "Uko katika hali ya nje ya mtandao", + "home.banner.testnetMode": "You are in testnet mode", + "home.banner.testnetMode.nav": "You are in testnet mode. Toggle this off in settings.", "home.explore.footer": "Gusa hapa ili kuchunguza maelfu ya tokeni, NFTs, na zaidi", "home.explore.title": "Chunguza tokeni", "home.extension.error": "Hitilafu katika kupakia akaunti", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Unda msimamo", "nav.tabs.createV2Position": "Create V2 position", "nav.tabs.createV3Position": "Create V3 position", + "nav.tabs.createV4Position": "Create V4 position", "nav.tabs.viewPosition": "Tazama msimamo", "network.lostConnection": "Huenda umepoteza muunganisho wako wa mtandao.", "network.mightBeDown": "{{network}} huenda iko chini kwa sasa, au umepoteza muunganisho wako wa mtandao.", @@ -1331,6 +1336,7 @@ "pool.back": "Rudi kwenye Dimbwi", "pool.balances": "Mizani ya bwawa", "pool.claimFees": "Ada za madai", + "pool.claimFees.button.label": "Claim", "pool.collectAs": "Kusanya kama {{nativeWrappedSymbol}}", "pool.collected": " Imekusanywa", "pool.collecting": "Kusanya", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Dhibiti ukwasi katika hifadhi ya zawadi", "pool.max.label": "Upeo:", "pool.maxPrice": "Bei ya juu", + "pool.migrateToV4": "Migrate to V4", "pool.min.label": "Dak:", "pool.minPrice": "Bei ndogo", "pool.mustBeInitialized": "Dimbwi hili lazima lianzishwe kabla ya kuongeza ukwasi. Ili kuanzisha, chagua bei ya kuanzia ya bwawa. Kisha, weka safu yako ya bei ya ukwasi na kiasi cha amana. Ada za gesi zitakuwa juu kuliko kawaida kutokana na shughuli ya uanzishaji.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Kidokezo: Tumia zana hii kupata vidimbwi vya v2 ambavyo havionekani kiotomatiki kwenye kiolesura.", "pools.approving.amount": "Inaidhinisha {{amount}}", "position.addHook": "Add a Hook", + "position.addHook.tooltip": "Hooks are an advanced feature that enable pools to interact with smart contracts, unlocking various capabilities. Exercise caution when adding hooks, as some may be malicious or cause unintended consequences.", "position.appearHere": "Nafasi yako itaonekana hapa.", + "position.create.modal.header": "Creating position", "position.currentValue": "Current position value", + "position.deposit.description": "Specify the token amounts for your liquidity contribution.", "position.depositedCurrency": "Deposited {{currencySymbol}}", "position.migrate.liquidity": "When migrating positions, you cannot change your token pair, but you can add a hook to enhance functionality.", "position.new": "New Position", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Inaunganisha...", "qrScanner.status.loading": "Inapakia...", "qrScanner.title": "Changanua msimbo wa QR", - "qrScanner.wallet.title": "Unaweza kupokea tokeni na NFTs kwenye Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast, na BNB Chain.", + "qrScanner.wallet.networks": "Supported Networks", + "qrScanner.wallet.title": "You can send and receive tokens and NFTs on all of our {{numOfNetworks}} supported networks.", "removeLiquidity.collectFees": "Pia utakusanya ada utakazopata kutoka kwa nafasi hii.", "removeLiquidity.outputEstimated": "Pato linakadiriwa. Ikiwa bei itabadilika kwa zaidi ya {{allowed}}% muamala wako utarejeshwa.", "removeLiquidity.pendingText": "Inaondoa {{amtA}} {{symA}} na {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Jina la utani", "settings.setting.wallet.notifications.title": "Arifa", "settings.setting.wallet.preferences.title": "Mapendeleo ya Wallet", - "settings.showTestNets": "Onyesha majaribio", + "settings.setting.wallet.testnetMode.description": "This turns on testnets for developers to try out features and transactions without using real assets. Tokens on testnets do not hold any real value.", + "settings.setting.wallet.testnetMode.title": "Testnet mode", "settings.switchNetwork.warning": "Ili kutumia Uniswap kwenye {{label}}, badilisha mtandao katika mipangilio ya mkoba wako.", "settings.title": "Mipangilio", "settings.version": "Toleo {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Ingizo mpya", "swap.details.newQuote.output": "Toleo jipya", "swap.details.orderRouting": "Uelekezaji wa agizo", - "swap.details.orderRoutingInfo": "Your price already includes network costs on the destination network and a 0.05% Across fee.", + "swap.details.orderRoutingInfo": "This swap is routed via Across, a decentralized protocol that moves assets across networks while prioritizing safety, fast execution, and low prices.", + "swap.details.poweredBy": "Powered by", "swap.details.rate": "Kiwango", "swap.details.slippage": "Utelezi mkubwa zaidi", "swap.details.uniswapFee": "Ada", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Mteja wa Uniswap huchagua chaguo la biashara la bei rahisi zaidi la kuweka alama na gharama za mtandao.", "swap.settings.routingPreference.option.v2.title": "v2 mabwawa", "swap.settings.routingPreference.option.v3.title": "v3 mabwawa", + "swap.settings.routingPreference.option.v4.title": "v4 pools", "swap.settings.routingPreference.title": "Chaguzi za biashara", "swap.settings.slippage.control.auto": "Otomatiki", "swap.settings.slippage.description": "Muamala wako utarejeshwa ikiwa bei itabadilika zaidi ya asilimia ya utelezi.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Muamala wako utarejeshwa ikiwa bei itabadilika isivyofaa kwa zaidi ya asilimia hii.", "swap.signAndSwap": "Saini na ubadilishane", "swap.slippage.amt": "{{amt}} kuteleza", + "swap.slippage.bridging": "No slippage when swapping across networks", "swap.slippage.settings.title": "Mipangilio ya utelezi", "swap.slippage.tooltip": "Uhamisho wa bei ya juu zaidi kabla ya muamala utarejeshwa.", "swap.slippageBelow.warning": "Kuteleza hapa chini {{amt}} kunaweza kusababisha shughuli iliyofeli", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Tarehe ya mwisho ya muamala", "swap.transaction.revertAfter": "Muamala wako utarejeshwa ikiwa unasubiri kwa zaidi ya kipindi hiki.", "swap.unsupportedAssets.readMore": "Soma zaidi kuhusu vipengee visivyotumika", + "swap.warning.enterLargerAmount.title": "Enter a larger amount", "swap.warning.expectedFailure": "Muamala huu unatarajiwa kushindwa", "swap.warning.feeOnTransfer.message": "Ishara zingine huchukua ada wakati zinunuliwa au kuuzwa, ambayo imewekwa na mtoaji wa ishara. Uniswap haipokei sehemu yoyote ya ada hizi.", "swap.warning.feeOnTransfer.title": "Kwa nini kuna ada ya ziada?", "swap.warning.insufficientBalance.title": "Huna vya kutosha {{currencySymbol}}", "swap.warning.insufficientGas.button": "Haitoshi {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Swap for {{ tokenSymbol }} on {{networkName}}", "swap.warning.insufficientGas.button.buy": "Nunua {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Buy with card", "swap.warning.insufficientGas.message.withNetwork": "Haitoshi {{currencySymbol}} kwenye {{networkName}} kubadilishana", "swap.warning.insufficientGas.message.withoutNetwork": "Haitoshi {{currencySymbol}} kubadilishana", "swap.warning.insufficientGas.title": "Huna {{currencySymbol}} ya kutosha kulipia gharama ya mtandao", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "data ya ishara ya on {{chainName}}", "tdp.nameNotFound": "Jina halijapatikana", "tdp.noInfoAvailable": "Hakuna maelezo ya tokeni yanayopatikana", + "tdp.noTestnetSupportDescription": "Some testnets do not support swapping, sending, or buying tokens.", "tdp.stats.unsupportedChainDescription": "Takwimu na chati za tokeni za {{chain}} zinapatikana kwenye {{infoLink}}", "tdp.symbolNotFound": "Alama haijapatikana", + "testnet.unsupported": "This functionality is not supported in testnet mode.", "themeToggle.theme": "Mandhari", "title.betterPricesMoreListings": "Bei bora. Orodha zaidi. Nunua, uza na ufanye biashara ya NFTs kwenye soko kuu kama OpenSea. Gundua mikusanyiko inayovuma.", "title.buySellTradeEthereum": "Nunua, uza na ufanye biashara ya Ethereum na tokeni zingine kuu kwenye Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "{{ownerAddress}}salio", "token.bridge": "{{label}} daraja la ishara", "token.chart.tooltip": "Ada: {{amount}}", + "token.details.testnet.unsupported": "Token details are unavailable for testnet tokens.", "token.error.unknown": "Tokeni isiyojulikana", "token.fee.buy.label": "buy fee", "token.fee.label": "fee", @@ -2109,6 +2130,8 @@ "transaction.warning.insufficientGas.modal.title.withoutNetwork": "Haitoshi {{tokenSymbol}}", "transaction.watcher.error.cancel": "Imeshindwa kughairi muamala", "transaction.watcher.error.status": "Hitilafu wakati wa kuangalia hali ya muamala", + "unichain.launch.modal.description": "An Ethereum L2 designed for DeFi, built by Uniswap Labs. Testnet goes live today.", + "unichain.launch.modal.title": "Introducing Unichain", "uniswapX.aggregatesLiquidity": " hujumlisha vyanzo vya ukwasi kwa bei bora na ubadilishaji usio na gesi.", "uniswapx.description": "UniswapX hujumlisha vyanzo vya ukwasi kwa bei bora na ubadilishaji usio na gesi.", "uniswapx.included": "Inajumuisha UniswapX ", @@ -2212,7 +2235,6 @@ "walletConnect.pending.switchAccount": "Badili Akaunti", "walletConnect.pending.switchNetwork": "Badilisha Mtandao", "walletConnect.pending.title": "Unganisha kwa {{dappName}}", - "walletConnect.permissions.networks": "Mitandao", "walletConnect.permissions.option.transferAssets": "Hamisha mali yako bila idhini", "walletConnect.permissions.option.viewTokenBalances": "Tazama mizani yako ya tokeni", "walletConnect.permissions.option.viewWalletAddress": "Tazama anwani ya mkoba wako", diff --git a/packages/uniswap/src/i18n/locales/translations/th-TH.json b/packages/uniswap/src/i18n/locales/translations/th-TH.json index 5ea5596057c..289361f3090 100644 --- a/packages/uniswap/src/i18n/locales/translations/th-TH.json +++ b/packages/uniswap/src/i18n/locales/translations/th-TH.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "ฝากโทเค็นไปยังเครือข่าย {{label}}", "common.deposited": "ฝากแล้ว", "common.depositing": "การฝากเงิน", + "common.depositTokens": "ฝากโทเค็น", "common.detailed.label": "รายละเอียด", "common.detected": "ตรวจพบ", "common.developers": "นักพัฒนา", @@ -430,6 +431,7 @@ "common.migrate.position": "ย้ายตำแหน่ง", "common.migrated.liquidity": "สภาพคล่องที่ถูกย้าย", "common.migrating.liquidity": "การย้ายสภาพคล่อง", + "common.min": "นาที", "common.mint.cancelled": "มิ้นท์ยกเลิกแล้ว", "common.mint.failed": "มิ้นท์ล้มเหลว", "common.minted": "มิ้นต์", @@ -450,6 +452,7 @@ "common.noResults": "ไม่พบผลลัพธ์.", "common.notAvailableInRegion.error": "ไม่มีให้บริการในภูมิภาคของคุณ", "common.notCreated.label": "ไม่ได้สร้าง", + "common.notSupported": "ไม่รองรับ", "common.oneDay": "1 วัน", "common.oneHour": "1 ชั่วโมง", "common.oneMonth": "1 เดือน", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}และตัวเลือกอื่น ๆ", "fiatOnRamp.quote.type.other": "ตัวเลือกอื่น", "fiatOnRamp.quote.type.recent": "ใช้ล่าสุด", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "คุณสามารถรับโทเค็นและ NFT บน Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora และ ZKsync", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "จากบัญชี", "fiatOnRamp.receiveCrypto.title": "รับการเข้ารหัสลับ", "fiatOnRamp.receiveCrypto.transferFunds": "เติมเงินในกระเป๋าของคุณโดยการโอน crypto จากกระเป๋าเงินหรือบัญชีอื่น", @@ -901,6 +903,8 @@ "home.activity.error.load": "ไม่สามารถโหลดกิจกรรมได้", "home.activity.title": "กิจกรรม", "home.banner.offline": "คุณอยู่ในโหมดออฟไลน์", + "home.banner.testnetMode": "คุณอยู่ในโหมดทดสอบเน็ต", + "home.banner.testnetMode.nav": "คุณอยู่ในโหมดทดสอบเครือข่าย ปิดใช้งานโหมดนี้ได้ในการตั้งค่า", "home.explore.footer": "แตะ \"ค้นหา\" เพื่อสำรวจเพิ่มเติม", "home.explore.title": "สำรวจโทเค็น", "home.extension.error": "เกิดข้อผิดพลาดในการโหลดบัญชี", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "สร้างตำแหน่ง", "nav.tabs.createV2Position": "สร้างตำแหน่ง V2", "nav.tabs.createV3Position": "สร้างตำแหน่ง V3", + "nav.tabs.createV4Position": "สร้างตำแหน่ง V4", "nav.tabs.viewPosition": "ดูตำแหน่ง", "network.lostConnection": "คุณอาจสูญเสียการเชื่อมต่อเครือข่ายของคุณ", "network.mightBeDown": "{{network}} อาจหยุดทำงานในขณะนี้ หรือคุณอาจสูญเสียการเชื่อมต่อเครือข่าย", @@ -1331,6 +1336,7 @@ "pool.back": "กลับไปที่สระน้ำ", "pool.balances": "ยอดคงเหลือของสระน้ำ", "pool.claimFees": "เรียกร้องค่าธรรมเนียม", + "pool.claimFees.button.label": "เรียกร้อง", "pool.collectAs": "รวบรวมเป็น {{nativeWrappedSymbol}}", "pool.collected": " รวบรวม", "pool.collecting": "การรวบรวม", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "จัดการสภาพคล่องในกลุ่มรางวัล", "pool.max.label": "สูงสุด:", "pool.maxPrice": "ราคาสูงสุด", + "pool.migrateToV4": "ย้ายไปยัง V4", "pool.min.label": "นาที:", "pool.minPrice": "ราคาขั้นต่ำ", "pool.mustBeInitialized": "ต้องเริ่มต้นพูลนี้ก่อนจึงจะสามารถเพิ่มสภาพคล่องได้ ในการเริ่มต้น ให้เลือกราคาเริ่มต้นสำหรับพูล จากนั้น ป้อนช่วงราคาสภาพคล่องและจำนวนเงินฝากของคุณ ค่าธรรมเนียมน้ำมันจะสูงกว่าปกติเนื่องจากการทำธุรกรรมเริ่มต้น", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "เคล็ดลับ: ใช้เครื่องมือนี้เพื่อค้นหาพูล v2 ที่ไม่ปรากฏในอินเทอร์เฟซโดยอัตโนมัติ", "pools.approving.amount": "กำลังอนุมัติ {{amount}}", "position.addHook": "เพิ่มตะขอ", + "position.addHook.tooltip": "Hooks เป็นฟีเจอร์ขั้นสูงที่ช่วยให้พูลสามารถโต้ตอบกับสัญญาอัจฉริยะได้ ซึ่งจะปลดล็อกความสามารถต่างๆ ควรใช้ความระมัดระวังเมื่อเพิ่ม Hooks เนื่องจากบางฟีเจอร์อาจเป็นอันตรายหรือก่อให้เกิดผลที่ไม่พึงประสงค์", "position.appearHere": "ตำแหน่งของคุณจะปรากฏที่นี่", + "position.create.modal.header": "การสร้างตำแหน่ง", "position.currentValue": "ค่าตำแหน่งปัจจุบัน", + "position.deposit.description": "ระบุจำนวนโทเค็นสำหรับการสนับสนุนสภาพคล่องของคุณ", "position.depositedCurrency": "ฝากไว้ {{currencySymbol}}", "position.migrate.liquidity": "เมื่อย้ายตำแหน่ง คุณไม่สามารถเปลี่ยนคู่โทเค็นของคุณได้ แต่คุณสามารถเพิ่มตัวเชื่อมเพื่อเพิ่มประสิทธิภาพการทำงานได้", "position.new": "ตำแหน่งใหม่", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "กำลังเชื่อมต่อ...", "qrScanner.status.loading": "กำลังโหลด...", "qrScanner.title": "สแกนรหัส QR", - "qrScanner.wallet.title": "คุณสามารถรับโทเค็นและ NFT บน Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast และ BNB Chain", + "qrScanner.wallet.networks": "เครือข่ายที่รองรับ", + "qrScanner.wallet.title": "คุณสามารถส่งและรับโทเค็นและ NFT บนเครือข่ายที่รองรับ {{numOfNetworks}} ทั้งหมดของเราได้", "removeLiquidity.collectFees": "คุณจะเก็บค่าธรรมเนียมที่ได้รับจากตำแหน่งนี้ด้วย", "removeLiquidity.outputEstimated": "มีการประเมินผลผลิต หากราคาเปลี่ยนแปลงมากกว่า {{allowed}}% ธุรกรรมของคุณจะเปลี่ยนกลับ", "removeLiquidity.pendingText": "กำลังลบ {{amtA}} {{symA}} และ {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "ชื่อเล่น", "settings.setting.wallet.notifications.title": "การแจ้งเตือน", "settings.setting.wallet.preferences.title": "การตั้งค่ากระเป๋าเงิน", - "settings.showTestNets": "แสดงเทสเน็ต", + "settings.setting.wallet.testnetMode.description": "สิ่งนี้จะเปิดเครือข่ายทดสอบเพื่อให้นักพัฒนาสามารถทดลองใช้ฟีเจอร์และธุรกรรมต่างๆ โดยไม่ต้องใช้สินทรัพย์จริง โทเค็นบนเครือข่ายทดสอบไม่มีมูลค่าที่แท้จริง", + "settings.setting.wallet.testnetMode.title": "โหมดทดสอบเน็ต", "settings.switchNetwork.warning": "หากต้องการใช้ Uniswap บน {{label}}ให้สลับเครือข่ายในการตั้งค่ากระเป๋าเงินของคุณ", "settings.title": "การตั้งค่า", "settings.version": "เวอร์ชัน {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "อินพุตใหม่", "swap.details.newQuote.output": "เอาท์พุทใหม่", "swap.details.orderRouting": "เส้นทางการสั่งซื้อ", - "swap.details.orderRoutingInfo": "ราคาของคุณรวมค่าใช้จ่ายเครือข่ายในเครือข่ายปลายทางและค่าธรรมเนียม 0.05% ข้ามเครือข่ายแล้ว", + "swap.details.orderRoutingInfo": "สวอปนี้จะถูกส่งผ่าน Across ซึ่งเป็นโปรโตคอลแบบกระจายอำนาจที่ย้ายสินทรัพย์ข้ามเครือข่ายพร้อมทั้งให้ความสำคัญกับความปลอดภัย การดำเนินการที่รวดเร็ว และราคาต่ำ", + "swap.details.poweredBy": "ขับเคลื่อนโดย", "swap.details.rate": "ประเมิน", "swap.details.slippage": "การเลื่อนหลุดสูงสุด", "swap.details.uniswapFee": "ค่าธรรมเนียม", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "ลูกค้า Uniswap จะเลือกตัวเลือกการซื้อขายที่ถูกที่สุดจากราคาแฟคตอริ่งและต้นทุนเครือข่าย", "swap.settings.routingPreference.option.v2.title": "พูล v2", "swap.settings.routingPreference.option.v3.title": "พูล v3", + "swap.settings.routingPreference.option.v4.title": "สระน้ำ v4", "swap.settings.routingPreference.title": "ตัวเลือกการค้า", "swap.settings.slippage.control.auto": "อัตโนมัติ", "swap.settings.slippage.description": "ธุรกรรมของคุณจะกลับมาหากราคาเปลี่ยนแปลงมากกว่าเปอร์เซ็นต์การคลาดเคลื่อนของราคา", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "ธุรกรรมของคุณจะเปลี่ยนกลับหากราคาเปลี่ยนแปลงไปอย่างไม่น่าพอใจมากกว่าเปอร์เซ็นต์นี้", "swap.signAndSwap": "ลงนามและแลกเปลี่ยน", "swap.slippage.amt": "{{amt}} เลื่อนหลุด", + "swap.slippage.bridging": "ไม่มีการลื่นไถลเมื่อสลับข้ามเครือข่าย", "swap.slippage.settings.title": "การเลื่อนหลุดสูงสุด", "swap.slippage.tooltip": "การเคลื่อนไหวของราคาสูงสุดก่อนที่ธุรกรรมของคุณจะกลับมา", "swap.slippageBelow.warning": "Slippage ต่ำกว่า {{amt}} อาจส่งผลให้ธุรกรรมล้มเหลว", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "กำหนดเวลาการทำธุรกรรม", "swap.transaction.revertAfter": "ธุรกรรมของคุณจะถูกเปลี่ยนกลับหากมีการรอดำเนินการนานกว่าระยะเวลานี้", "swap.unsupportedAssets.readMore": "อ่านเพิ่มเติมเกี่ยวกับเนื้อหาที่ไม่รองรับ", + "swap.warning.enterLargerAmount.title": "ใส่จำนวนเงินที่มากขึ้น", "swap.warning.expectedFailure": "ธุรกรรมนี้คาดว่าจะล้มเหลว", "swap.warning.feeOnTransfer.message": "โทเค็นบางตัวต้องเสียค่าธรรมเนียมเมื่อมีการซื้อหรือขาย ซึ่งกำหนดโดยผู้ออกโทเค็น Uniswap ไม่ได้รับส่วนแบ่งค่าธรรมเนียมเหล่านี้", "swap.warning.feeOnTransfer.title": "ทำไมถึงมีค่าธรรมเนียมเพิ่มเติม?", "swap.warning.insufficientBalance.title": "คุณมีไม่พอ {{currencySymbol}}", "swap.warning.insufficientGas.button": "ยังไม่พอ {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "สลับระหว่าง {{ tokenSymbol }} กับ {{networkName}}", "swap.warning.insufficientGas.button.buy": "ซื้อ {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "ซื้อด้วยบัตร", "swap.warning.insufficientGas.message.withNetwork": "ไม่เพียงพอ {{currencySymbol}} บน {{networkName}} เพื่อสลับ", "swap.warning.insufficientGas.message.withoutNetwork": "ไม่พอ {{currencySymbol}} ที่จะสลับ", "swap.warning.insufficientGas.title": "คุณมี {{currencySymbol}} ไม่เพียงพอสำหรับครอบคลุมค่าใช้จ่ายเครือข่าย", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "ข้อมูลโทเค็นสำหรับ บน {{chainName}}", "tdp.nameNotFound": "ไม่พบชื่อ", "tdp.noInfoAvailable": "ไม่มีข้อมูลโทเค็น", + "tdp.noTestnetSupportDescription": "เครือข่ายทดสอบบางแห่งไม่รองรับการแลกเปลี่ยน การส่ง หรือการซื้อโทเค็น", "tdp.stats.unsupportedChainDescription": "สถิติและแผนภูมิโทเค็นสำหรับ {{chain}} มีอยู่ใน {{infoLink}}", "tdp.symbolNotFound": "ไม่พบสัญลักษณ์", + "testnet.unsupported": "ฟังก์ชันนี้ไม่ได้รับการสนับสนุนในโหมดทดสอบเครือข่าย", "themeToggle.theme": "ธีม", "title.betterPricesMoreListings": "ราคาที่ดีขึ้น รายการเพิ่มเติม ซื้อ ขาย และแลกเปลี่ยน NFT ในตลาดชั้นนำเช่น OpenSea สำรวจคอลเลกชันที่ได้รับความนิยม", "title.buySellTradeEthereum": "ซื้อ ขาย และแลกเปลี่ยน Ethereum และโทเค็นชั้นนำอื่น ๆ บน Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "ความสมดุลของ {{ownerAddress}}", "token.bridge": "{{label}} สะพานโทเค็น", "token.chart.tooltip": "ค่าธรรมเนียม: {{amount}}", + "token.details.testnet.unsupported": "รายละเอียดโทเค็นไม่พร้อมใช้งานสำหรับโทเค็นทดสอบเครือข่าย", "token.error.unknown": "โทเค็นที่ไม่รู้จัก", "token.fee.buy.label": "ค่าธรรมเนียมการซื้อ", "token.fee.label": "ค่าธรรมเนียม", @@ -2109,6 +2130,8 @@ "transaction.warning.insufficientGas.modal.title.withoutNetwork": "ยังไม่พอ {{tokenSymbol}}", "transaction.watcher.error.cancel": "ไม่สามารถยกเลิกธุรกรรมได้", "transaction.watcher.error.status": "เกิดข้อผิดพลาดขณะตรวจสอบสถานะธุรกรรม", + "unichain.launch.modal.description": "An Ethereum L2 designed for DeFi, built by Uniswap Labs. Testnet goes live today.", + "unichain.launch.modal.title": "Introducing Unichain", "uniswapX.aggregatesLiquidity": " รวบรวมแหล่งสภาพคล่องเพื่อให้ได้ราคาที่ดีขึ้นและสวอปแบบไม่มีก๊าซ", "uniswapx.description": "UniswapX รวบรวมแหล่งสภาพคล่องเพื่อให้ได้ราคาที่ดีกว่าและสวอปที่ปราศจากก๊าซ", "uniswapx.included": "รวม UniswapX ", @@ -2212,7 +2235,6 @@ "walletConnect.pending.switchAccount": "สลับบัญชี", "walletConnect.pending.switchNetwork": "สลับเครือข่าย", "walletConnect.pending.title": "เชื่อมต่อกับ {{dappName}}", - "walletConnect.permissions.networks": "เครือข่าย", "walletConnect.permissions.option.transferAssets": "โอนทรัพย์สินของคุณโดยไม่ได้รับความยินยอม", "walletConnect.permissions.option.viewTokenBalances": "ดูยอดคงเหลือโทเค็นของคุณ", "walletConnect.permissions.option.viewWalletAddress": "ดูที่อยู่กระเป๋าสตางค์ของคุณ", diff --git a/packages/uniswap/src/i18n/locales/translations/tr-TR.json b/packages/uniswap/src/i18n/locales/translations/tr-TR.json index 1b7cd4e94be..0c0bcf4a318 100644 --- a/packages/uniswap/src/i18n/locales/translations/tr-TR.json +++ b/packages/uniswap/src/i18n/locales/translations/tr-TR.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "{{label}} ağına token yatırın.", "common.deposited": "Yatırıldı", "common.depositing": "Para yatırma", + "common.depositTokens": "Para yatırma jetonları", "common.detailed.label": "Detaylı", "common.detected": "Saptanmış", "common.developers": "Geliştiriciler", @@ -430,6 +431,7 @@ "common.migrate.position": "Pozisyonu taşı", "common.migrated.liquidity": "Taşınan likidite", "common.migrating.liquidity": "Likiditenin taşınması", + "common.min": "Dakika", "common.mint.cancelled": "Darphane iptal edildi", "common.mint.failed": "Nane başarısız oldu", "common.minted": "Darphane", @@ -450,6 +452,7 @@ "common.noResults": "Sonuç bulunamadı.", "common.notAvailableInRegion.error": "Bölgenizde mevcut değil", "common.notCreated.label": "Oluşturulmadı", + "common.notSupported": "Desteklenmiyor", "common.oneDay": "1 gün", "common.oneHour": "1 saat", "common.oneMonth": "1 ay", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}ve diğer seçenekler", "fiatOnRamp.quote.type.other": "Diğer seçenekler", "fiatOnRamp.quote.type.recent": "Son zamanlarda kullanılmış", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora ve ZKsync üzerinden token ve NFT alabilirsiniz.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Bir hesaptan", "fiatOnRamp.receiveCrypto.title": "Kripto al", "fiatOnRamp.receiveCrypto.transferFunds": "Başka bir cüzdan veya hesaptan kripto aktararak cüzdanınıza para yatırın", @@ -901,6 +903,8 @@ "home.activity.error.load": "Etkinlik yüklenemedi", "home.activity.title": "Aktivite", "home.banner.offline": "Çevrimdışı moddasınız", + "home.banner.testnetMode": "Testnet modundasınız", + "home.banner.testnetMode.nav": "Testnet modundasınız. Bunu ayarlardan kapatın.", "home.explore.footer": "Binlerce jetonu, NFT'yi ve daha fazlasını keşfetmek için buraya dokunun", "home.explore.title": "Jetonları keşfedin", "home.extension.error": "Hesaplar yüklenirken hata oluştu", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Pozisyon oluştur", "nav.tabs.createV2Position": "V2 pozisyonunu oluştur", "nav.tabs.createV3Position": "V3 pozisyonunu oluştur", + "nav.tabs.createV4Position": "V4 pozisyonunu oluştur", "nav.tabs.viewPosition": "Konumu görüntüle", "network.lostConnection": "Ağ bağlantınızı kaybetmiş olabilirsiniz.", "network.mightBeDown": "{{network}} şu anda kapalı olabilir veya ağ bağlantınızı kaybetmiş olabilirsiniz.", @@ -1331,6 +1336,7 @@ "pool.back": "Havuza Geri Dön", "pool.balances": "Havuz bakiyeleri", "pool.claimFees": "Talep ücretleri", + "pool.claimFees.button.label": "İddia", "pool.collectAs": "{{nativeWrappedSymbol}}olarak topla", "pool.collected": " Toplanmış", "pool.collecting": "Toplama", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Ödül havuzundaki likiditeyi yönetin", "pool.max.label": "Maksimum:", "pool.maxPrice": "Maksimum fiyat", + "pool.migrateToV4": "V4'e geçiş yapın", "pool.min.label": "Min:", "pool.minPrice": "Minimum fiyat", "pool.mustBeInitialized": "Likidite ekleyebilmeniz için bu havuzun başlatılması gerekir. Başlatmak için havuz için bir başlangıç fiyatı seçin. Daha sonra likidite fiyat aralığınızı ve mevduat tutarınızı girin. Başlatma işlemi nedeniyle gaz ücretleri normalden yüksek olacaktır.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "İpucu: Arayüzde otomatik olarak görünmeyen v2 havuzlarını bulmak için bu aracı kullanın.", "pools.approving.amount": "Onaylanıyor {{amount}}", "position.addHook": "Bir Kanca Ekle", + "position.addHook.tooltip": "Kancalar, havuzların akıllı sözleşmelerle etkileşime girmesini ve çeşitli yeteneklerin kilidini açmasını sağlayan gelişmiş bir özelliktir. Kancalar eklerken dikkatli olun, çünkü bazıları kötü amaçlı olabilir veya beklenmeyen sonuçlara yol açabilir.", "position.appearHere": "Konumunuz burada görünecek.", + "position.create.modal.header": "Pozisyon oluşturma", "position.currentValue": "Mevcut pozisyon değeri", + "position.deposit.description": "Likidite katkınız için token miktarlarını belirtin.", "position.depositedCurrency": "Yatırılan {{currencySymbol}}", "position.migrate.liquidity": "Pozisyonları taşırken token çiftinizi değiştiremezsiniz ancak işlevselliği artırmak için bir kanca ekleyebilirsiniz.", "position.new": "Yeni Pozisyon", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Bağlanıyor...", "qrScanner.status.loading": "Yükleniyor...", "qrScanner.title": "QR kodunu tarayın", - "qrScanner.wallet.title": "Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast ve BNB Chain üzerinden token ve NFT alabilirsiniz.", + "qrScanner.wallet.networks": "Desteklenen Ağlar", + "qrScanner.wallet.title": "{{numOfNetworks}} desteklediğimiz tüm ağlarda token ve NFT gönderebilir ve alabilirsiniz.", "removeLiquidity.collectFees": "Ayrıca bu pozisyondan kazanılan ücretleri de tahsil edeceksiniz.", "removeLiquidity.outputEstimated": "Çıktı tahmin ediliyor. Fiyat {{allowed}}%'den fazla değişirse işleminiz geri alınacaktır.", "removeLiquidity.pendingText": "{{amtA}} {{symA}} ve {{amtB}} {{symB}}kaldırılıyor", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Takma ad", "settings.setting.wallet.notifications.title": "Bildirimler", "settings.setting.wallet.preferences.title": "Cüzdan tercihleri", - "settings.showTestNets": "Test ağlarını göster", + "settings.setting.wallet.testnetMode.description": "Bu, geliştiricilerin gerçek varlıklar kullanmadan özellikleri ve işlemleri denemeleri için test ağlarını açar. Test ağlarındaki tokenlar gerçek bir değere sahip değildir.", + "settings.setting.wallet.testnetMode.title": "Testnet modu", "settings.switchNetwork.warning": "Uniswap'i {{label}}üzerinde kullanmak için cüzdanınızın ayarlarından ağı değiştirin.", "settings.title": "Ayarlar", "settings.version": "Sürüm {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Yeni giriş", "swap.details.newQuote.output": "Yeni çıktı", "swap.details.orderRouting": "Sipariş yönlendirme", - "swap.details.orderRoutingInfo": "Fiyatınıza hedef ağdaki ağ maliyetleri ve %0,05 Across ücreti dahildir.", + "swap.details.orderRoutingInfo": "Bu takas, varlıkları ağlar arasında taşırken güvenliği, hızlı yürütmeyi ve düşük fiyatları önceliklendiren merkezi olmayan bir protokol olan Across üzerinden yönlendirilir.", + "swap.details.poweredBy": "Tarafından desteklenmektedir", "swap.details.rate": "Oran", "swap.details.slippage": "Maksimum kayma", "swap.details.uniswapFee": "Ücret", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Uniswap müşterisi, fiyatı ve ağ maliyetlerini faktoring ederek en ucuz ticaret seçeneğini seçer.", "swap.settings.routingPreference.option.v2.title": "v2 havuzları", "swap.settings.routingPreference.option.v3.title": "v3 havuzları", + "swap.settings.routingPreference.option.v4.title": "v4 havuzları", "swap.settings.routingPreference.title": "Ticaret seçenekleri", "swap.settings.slippage.control.auto": "Oto", "swap.settings.slippage.description": "Fiyatın kayma yüzdesinden daha fazla değişmesi durumunda işleminiz eski durumuna dönecektir.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Fiyatın bu yüzdeden daha fazla olumsuz yönde değişmesi durumunda işleminiz geri alınacaktır.", "swap.signAndSwap": "İmzala ve değiştir", "swap.slippage.amt": "{{amt}} kayma", + "swap.slippage.bridging": "Ağlar arasında geçiş yaparken kayma olmaz", "swap.slippage.settings.title": "Kayma ayarları", "swap.slippage.tooltip": "İşleminizden önceki maksimum fiyat hareketi geri dönecektir.", "swap.slippageBelow.warning": "{{amt}} altındaki kayma işlemin başarısız olmasına neden olabilir", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "İşlem son tarihi", "swap.transaction.revertAfter": "İşleminiz bu süreden daha uzun süre beklemede kalırsa geri alınacaktır.", "swap.unsupportedAssets.readMore": "Desteklenmeyen öğeler hakkında daha fazlasını okuyun", + "swap.warning.enterLargerAmount.title": "Daha büyük bir miktar girin", "swap.warning.expectedFailure": "Bu işlemin başarısız olması bekleniyor", "swap.warning.feeOnTransfer.message": "Bazı tokenlar satın alınırken veya satılırken token veren kuruluş tarafından belirlenen bir ücret alır. Uniswap bu ücretlerden herhangi bir pay almamaktadır.", "swap.warning.feeOnTransfer.title": "Neden ek ücret var?", "swap.warning.insufficientBalance.title": "Yeterli {{currencySymbol}}yok", "swap.warning.insufficientGas.button": "Yeterli değil {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "{{networkName}}için {{ tokenSymbol }} ile takas yapın", "swap.warning.insufficientGas.button.buy": "{{ tokenSymbol }}satın al", + "swap.warning.insufficientGas.button.buyWithCard": "Kartla satın al", "swap.warning.insufficientGas.message.withNetwork": "Değiştirmek için {{networkName}} üzerinde {{currencySymbol}} yeterli değil", "swap.warning.insufficientGas.message.withoutNetwork": "Değiştirmek için yeterli değil {{currencySymbol}}", "swap.warning.insufficientGas.title": "Ağ maliyetini karşılamaya yetecek kadar {{currencySymbol}} paranız yok", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "{{chainName}}üzerinde için token verileri", "tdp.nameNotFound": "İsim bulunamadı", "tdp.noInfoAvailable": "Belirteç bilgisi mevcut değil", + "tdp.noTestnetSupportDescription": "Bazı test ağları token takasını, göndermeyi veya satın almayı desteklemez.", "tdp.stats.unsupportedChainDescription": "{{chain}} için token istatistikleri ve grafikleri {{infoLink}}adresinde mevcuttur", "tdp.symbolNotFound": "Sembol bulunamadı", + "testnet.unsupported": "Bu işlevsellik testnet modunda desteklenmiyor.", "themeToggle.theme": "Tema", "title.betterPricesMoreListings": "Daha iyi fiyatlar. Daha fazla liste. OpenSea gibi en iyi pazar yerlerinde NFT'leri satın alın, satın ve ticaretini yapın. Trend olan koleksiyonları keşfedin.", "title.buySellTradeEthereum": "Uniswap'te Ethereum ve diğer en iyi tokenleri satın alın, satın ve ticaret yapın", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "{{ownerAddress}}bakiyesi", "token.bridge": "{{label}} jeton köprüsü", "token.chart.tooltip": "Ücretler: {{amount}}", + "token.details.testnet.unsupported": "Testnet tokenları için token detayları mevcut değil.", "token.error.unknown": "Bilinmeyen jeton", "token.fee.buy.label": "satın alma ücreti", "token.fee.label": "ücret", @@ -2109,6 +2130,8 @@ "transaction.warning.insufficientGas.modal.title.withoutNetwork": "Yeterli değil {{tokenSymbol}}", "transaction.watcher.error.cancel": "İşlem iptal edilemiyor", "transaction.watcher.error.status": "İşlem durumu kontrol edilirken hata oluştu", + "unichain.launch.modal.description": "An Ethereum L2 designed for DeFi, built by Uniswap Labs. Testnet goes live today.", + "unichain.launch.modal.title": "Introducing Unichain", "uniswapX.aggregatesLiquidity": " daha iyi fiyatlar ve gazsız takaslar için likidite kaynaklarını bir araya getirir.", "uniswapx.description": "UniswapX, daha iyi fiyatlar ve gazsız takaslar için likidite kaynaklarını bir araya getirir.", "uniswapx.included": " UniswapX içerir", @@ -2212,7 +2235,6 @@ "walletConnect.pending.switchAccount": "Hesap değiştir", "walletConnect.pending.switchNetwork": "Ağı Değiştir", "walletConnect.pending.title": "{{dappName}}'e bağlanın", - "walletConnect.permissions.networks": "Ağlar", "walletConnect.permissions.option.transferAssets": "Varlıklarınızı izinsiz devredin", "walletConnect.permissions.option.viewTokenBalances": "Token bakiyelerinizi görüntüleyin", "walletConnect.permissions.option.viewWalletAddress": "Cüzdan adresinizi görüntüleyin", diff --git a/packages/uniswap/src/i18n/locales/translations/uk-UA.json b/packages/uniswap/src/i18n/locales/translations/uk-UA.json index dd7229fb2e5..2111edd430c 100644 --- a/packages/uniswap/src/i18n/locales/translations/uk-UA.json +++ b/packages/uniswap/src/i18n/locales/translations/uk-UA.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Внесіть токени в мережу {{label}} .", "common.deposited": "Депонований", "common.depositing": "Депонування", + "common.depositTokens": "Депозитні жетони", "common.detailed.label": "Детальний", "common.detected": "Виявлено", "common.developers": "Розробники", @@ -430,6 +431,7 @@ "common.migrate.position": "Мігрувати позицію", "common.migrated.liquidity": "Перенесена ліквідність", "common.migrating.liquidity": "Мігруюча ліквідність", + "common.min": "Хв", "common.mint.cancelled": "Монетний двір скасовано", "common.mint.failed": "Монетний двір не вдався", "common.minted": "Карбувати", @@ -450,6 +452,7 @@ "common.noResults": "Нічого не знайдено.", "common.notAvailableInRegion.error": "Недоступно у вашому регіоні", "common.notCreated.label": "Не створений", + "common.notSupported": "Не підтримується", "common.oneDay": "1 день", "common.oneHour": "1 година", "common.oneMonth": "1 місяць", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}та інші параметри", "fiatOnRamp.quote.type.other": "Інші варіанти", "fiatOnRamp.quote.type.recent": "Нещодавно використаний", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Ви можете отримувати токени та NFT на Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora та ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "З облікового запису", "fiatOnRamp.receiveCrypto.title": "Отримайте криптовалюту", "fiatOnRamp.receiveCrypto.transferFunds": "Поповніть свій гаманець за переказом крипто з іншого гаманця або рахунку", @@ -901,6 +903,8 @@ "home.activity.error.load": "Не вдалося завантажити активність", "home.activity.title": "діяльність", "home.banner.offline": "Ви в автономному режимі", + "home.banner.testnetMode": "Ви в режимі тестової мережі", + "home.banner.testnetMode.nav": "Ви в режимі тестової мережі. Вимкніть це в налаштуваннях.", "home.explore.footer": "Торкніться тут, щоб ознайомитися з тисячами токенів, NFT тощо", "home.explore.title": "Дослідіть жетони", "home.extension.error": "Помилка завантаження облікових записів", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Створити позицію", "nav.tabs.createV2Position": "Створіть позицію V2", "nav.tabs.createV3Position": "Створіть позицію V3", + "nav.tabs.createV4Position": "Створіть позицію V4", "nav.tabs.viewPosition": "Позиція перегляду", "network.lostConnection": "Можливо, ви втратили підключення до мережі.", "network.mightBeDown": "Можливо, {{network}} зараз не працює, або ви втратили з’єднання з мережею.", @@ -1331,6 +1336,7 @@ "pool.back": "Назад до басейну", "pool.balances": "Баланс пулу", "pool.claimFees": "Плата за позов", + "pool.claimFees.button.label": "Претензія", "pool.collectAs": "Збирайте як {{nativeWrappedSymbol}}", "pool.collected": " Зібрано", "pool.collecting": "Колекціонування", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Керуйте ліквідністю в пулі винагород", "pool.max.label": "Макс:", "pool.maxPrice": "Максимальна ціна", + "pool.migrateToV4": "Перейти на V4", "pool.min.label": "Хв:", "pool.minPrice": "Мінімальна ціна", "pool.mustBeInitialized": "Цей пул потрібно ініціалізувати, перш ніж ви зможете додати ліквідність. Для ініціалізації виберіть початкову ціну для пулу. Потім введіть ціновий діапазон ліквідності та суму депозиту. Плата за газ буде вищою, ніж зазвичай, через транзакцію ініціалізації.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Порада. Використовуйте цей інструмент, щоб знайти пули v2, які не з’являються автоматично в інтерфейсі.", "pools.approving.amount": "Схвалення {{amount}}", "position.addHook": "Додайте гачок", + "position.addHook.tooltip": "Хуки — це розширена функція, яка дозволяє пулам взаємодіяти зі смарт-контрактами, розблоковуючи різні можливості. Будьте обережні, додаючи хуки, оскільки деякі з них можуть бути зловмисними або викликати небажані наслідки.", "position.appearHere": "Тут з'явиться ваша посада.", + "position.create.modal.header": "Створення позиції", "position.currentValue": "Поточне значення позиції", + "position.deposit.description": "Вкажіть символічні суми вашого внеску ліквідності.", "position.depositedCurrency": "Депоновано {{currencySymbol}}", "position.migrate.liquidity": "Під час міграції позицій ви не можете змінити свою пару токенів, але ви можете додати гачок для покращення функціональності.", "position.new": "Нова позиція", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Підключення...", "qrScanner.status.loading": "Завантаження...", "qrScanner.title": "Відскануйте QR-код", - "qrScanner.wallet.title": "Ви можете отримувати токени та NFT на Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast і BNB Chain.", + "qrScanner.wallet.networks": "Підтримувані мережі", + "qrScanner.wallet.title": "Ви можете надсилати й отримувати токени та NFT у всіх {{numOfNetworks}} підтримуваних мережах.", "removeLiquidity.collectFees": "Ви також отримуватимете гонорари, зароблені на цій посаді.", "removeLiquidity.outputEstimated": "Вихід оцінюється. Якщо ціна зміниться більш ніж на {{allowed}}%, ваша транзакція буде скасована.", "removeLiquidity.pendingText": "Видалення {{amtA}} {{symA}} та {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "псевдонім", "settings.setting.wallet.notifications.title": "Сповіщення", "settings.setting.wallet.preferences.title": "Параметри гаманця", - "settings.showTestNets": "Показати тестові мережі", + "settings.setting.wallet.testnetMode.description": "Це вмикає тестові мережі для розробників, щоб випробувати функції та транзакції без використання реальних ресурсів. Токени в тестових мережах не мають реальної цінності.", + "settings.setting.wallet.testnetMode.title": "Режим Testnet", "settings.switchNetwork.warning": "Щоб використовувати Uniswap на {{label}}, перемкніть мережу в налаштуваннях свого гаманця.", "settings.title": "Налаштування", "settings.version": "Версія {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Новий вхід", "swap.details.newQuote.output": "Новий вихід", "swap.details.orderRouting": "Маршрутизація замовлень", - "swap.details.orderRoutingInfo": "Ваша ціна вже включає мережеві витрати в мережі призначення та 0,05% комісії за передачу.", + "swap.details.orderRoutingInfo": "Цей своп маршрутизується через Across, децентралізований протокол, який переміщує активи між мережами, надаючи пріоритет безпеці, швидкому виконанню та низьким цінам.", + "swap.details.poweredBy": "Працює на", "swap.details.rate": "Оцінка", "swap.details.slippage": "Максимальне ковзання", "swap.details.uniswapFee": "Комісія", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Клієнт Uniswap вибирає найдешевший варіант торгівлі з урахуванням ціни та вартості мережі.", "swap.settings.routingPreference.option.v2.title": "v2 пули", "swap.settings.routingPreference.option.v3.title": "v3 пули", + "swap.settings.routingPreference.option.v4.title": "v4 пули", "swap.settings.routingPreference.title": "Варіанти торгівлі", "swap.settings.slippage.control.auto": "Авто", "swap.settings.slippage.description": "Ваша трансакція буде скасована, якщо ціна зміниться більше, ніж відсоток ковзання.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Ваша трансакція буде скасована, якщо ціна зміниться несприятливо більш ніж на цей відсоток.", "swap.signAndSwap": "Підписати і поміняти місцями", "swap.slippage.amt": "{{amt}} прослизання", + "swap.slippage.bridging": "Відсутність прослизання під час перемикання між мережами", "swap.slippage.settings.title": "Налаштування ковзання", "swap.slippage.tooltip": "Максимальний рух ціни до повернення вашої транзакції.", "swap.slippageBelow.warning": "Зниження нижче {{amt}} може призвести до невдалої транзакції", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Кінцевий термін транзакції", "swap.transaction.revertAfter": "Ваша трансакція буде скасована, якщо вона очікує на розгляд більше цього періоду часу.", "swap.unsupportedAssets.readMore": "Докладніше про непідтримувані активи", + "swap.warning.enterLargerAmount.title": "Введіть більшу суму", "swap.warning.expectedFailure": "Очікується, що ця транзакція буде невдалою", "swap.warning.feeOnTransfer.message": "Деякі токени беруть комісію під час купівлі або продажу, яка встановлюється емітентом токенів. Uniswap не отримує жодної частки цих зборів.", "swap.warning.feeOnTransfer.title": "Чому стягується додаткова плата?", "swap.warning.insufficientBalance.title": "Вам не вистачає {{currencySymbol}}", "swap.warning.insufficientGas.button": "Недостатньо {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Поміняти {{ tokenSymbol }} на {{networkName}}", "swap.warning.insufficientGas.button.buy": "Придбати {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Купуйте на картку", "swap.warning.insufficientGas.message.withNetwork": "Недостатньо {{currencySymbol}} на {{networkName}} для заміни", "swap.warning.insufficientGas.message.withoutNetwork": "Недостатньо {{currencySymbol}} для обміну", "swap.warning.insufficientGas.title": "У вас недостатньо {{currencySymbol}} , щоб покрити витрати на мережу", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "дані маркерів для на {{chainName}}", "tdp.nameNotFound": "Ім'я не знайдено", "tdp.noInfoAvailable": "Інформація про маркери недоступна", + "tdp.noTestnetSupportDescription": "Деякі тестові мережі не підтримують обмін, надсилання або купівлю токенів.", "tdp.stats.unsupportedChainDescription": "Статистика токенів і діаграми для {{chain}} доступні на {{infoLink}}", "tdp.symbolNotFound": "Символ не знайдено", + "testnet.unsupported": "Ця функція не підтримується в режимі тестової мережі.", "themeToggle.theme": "Тема", "title.betterPricesMoreListings": "Кращі ціни. Більше оголошень. Купуйте, продавайте та торгуйте NFT на провідних ринках, таких як OpenSea. Досліджуйте трендові колекції.", "title.buySellTradeEthereum": "Купуйте, продавайте та торгуйте Ethereum та іншими найкращими токенами на Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "{{ownerAddress}}балансу", "token.bridge": "{{label}} маркерний міст", "token.chart.tooltip": "Комісії: {{amount}}", + "token.details.testnet.unsupported": "Деталі маркерів недоступні для маркерів testnet.", "token.error.unknown": "Невідомий маркер", "token.fee.buy.label": "плата за покупку", "token.fee.label": "гонорар", @@ -2109,6 +2130,8 @@ "transaction.warning.insufficientGas.modal.title.withoutNetwork": "Недостатньо {{tokenSymbol}}", "transaction.watcher.error.cancel": "Не вдалося скасувати трансакцію", "transaction.watcher.error.status": "Помилка під час перевірки статусу трансакції", + "unichain.launch.modal.description": "Ethereum L2, розроблений для DeFi, створений Uniswap Labs. Testnet працює сьогодні.", + "unichain.launch.modal.title": "Представляємо Unichain", "uniswapX.aggregatesLiquidity": " об’єднує джерела ліквідності для кращих цін і свопів без газу.", "uniswapx.description": "UniswapX об’єднує джерела ліквідності для кращих цін і свопів без газу.", "uniswapx.included": "Включає UniswapX ", @@ -2212,7 +2235,6 @@ "walletConnect.pending.switchAccount": "Змінити обліковий запис", "walletConnect.pending.switchNetwork": "Перемикач мережі", "walletConnect.pending.title": "Підключіться до {{dappName}}", - "walletConnect.permissions.networks": "мережі", "walletConnect.permissions.option.transferAssets": "Передавати свої активи без згоди", "walletConnect.permissions.option.viewTokenBalances": "Перегляньте баланси своїх токенів", "walletConnect.permissions.option.viewWalletAddress": "Перегляньте адресу свого гаманця", diff --git a/packages/uniswap/src/i18n/locales/translations/ur-PK.json b/packages/uniswap/src/i18n/locales/translations/ur-PK.json index c09bab0867d..9f4f20fa333 100644 --- a/packages/uniswap/src/i18n/locales/translations/ur-PK.json +++ b/packages/uniswap/src/i18n/locales/translations/ur-PK.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "ٹوکنز {{label}} نیٹ ورک پر جمع کریں۔", "common.deposited": "جمع کرایا", "common.depositing": "جمع کرنا", + "common.depositTokens": "ٹوکن جمع کروائیں۔", "common.detailed.label": "تفصیلی", "common.detected": "پتہ چلا", "common.developers": "ڈویلپرز", @@ -430,6 +431,7 @@ "common.migrate.position": "ہجرت کی پوزیشن", "common.migrated.liquidity": "منتقلی لیکویڈیٹی", "common.migrating.liquidity": "منتقلی لیکویڈیٹی", + "common.min": "کم از کم", "common.mint.cancelled": "ٹکسال منسوخ", "common.mint.failed": "ٹکسال ناکام ہو گیا۔", "common.minted": "ٹکسال", @@ -450,6 +452,7 @@ "common.noResults": "کوئی نتیجہ نہیں.", "common.notAvailableInRegion.error": "آپ کے علاقے میں دستیاب نہیں ہے۔", "common.notCreated.label": "پیدا نہیں کیا گیا۔", + "common.notSupported": "تعاون یافتہ نہیں۔", "common.oneDay": "1 دن", "common.oneHour": "1 گھنٹہ", "common.oneMonth": "1 مہینہ", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}، اور دیگر اختیارات", "fiatOnRamp.quote.type.other": "دوسرے اختیارات", "fiatOnRamp.quote.type.recent": "حال ہی میں استعمال کیا گیا۔", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "آپ Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora, اور ZKsync پر ٹوکنز اور NFTs وصول کر سکتے ہیں۔", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "اکاؤنٹ سے", "fiatOnRamp.receiveCrypto.title": "کرپٹو وصول کریں۔", "fiatOnRamp.receiveCrypto.transferFunds": "دوسرے بٹوے یا اکاؤنٹ سے کریپٹو منتقل کر کے اپنے بٹوے کو فنڈ دیں۔", @@ -901,6 +903,8 @@ "home.activity.error.load": "سرگرمی لوڈ نہیں ہو سکی", "home.activity.title": "سرگرمی", "home.banner.offline": "آپ آف لائن موڈ میں ہیں۔", + "home.banner.testnetMode": "آپ ٹیسٹ نیٹ موڈ میں ہیں۔", + "home.banner.testnetMode.nav": "آپ ٹیسٹ نیٹ موڈ میں ہیں۔ اسے ترتیبات میں ٹوگل کریں۔", "home.explore.footer": "ہزاروں ٹوکنز، NFTs، اور مزید دریافت کرنے کے لیے یہاں تھپتھپائیں۔", "home.explore.title": "ٹوکن دریافت کریں۔", "home.extension.error": "اکاؤنٹس لوڈ کرنے میں خرابی۔", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "پوزیشن بنائیں", "nav.tabs.createV2Position": "V2 پوزیشن بنائیں", "nav.tabs.createV3Position": "V3 پوزیشن بنائیں", + "nav.tabs.createV4Position": "V4 پوزیشن بنائیں", "nav.tabs.viewPosition": "پوزیشن دیکھیں", "network.lostConnection": "ہو سکتا ہے آپ کا نیٹ ورک کنکشن ختم ہو گیا ہو۔", "network.mightBeDown": "{{network}} ہو سکتا ہے ابھی بند ہو، یا آپ کا نیٹ ورک کنکشن ختم ہو گیا ہو۔", @@ -1331,6 +1336,7 @@ "pool.back": "واپس پول پر", "pool.balances": "پول بیلنس", "pool.claimFees": "فیس کا دعوی کریں۔", + "pool.claimFees.button.label": "دعویٰ", "pool.collectAs": "{{nativeWrappedSymbol}}کے بطور جمع کریں۔", "pool.collected": " جمع", "pool.collecting": "جمع کرنا", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "انعامات کے تالاب میں لیکویڈیٹی کا نظم کریں۔", "pool.max.label": "زیادہ سے زیادہ:", "pool.maxPrice": "زیادہ سے زیادہ قیمت", + "pool.migrateToV4": "V4 پر منتقل کریں۔", "pool.min.label": "کم از کم:", "pool.minPrice": "کم از کم قیمت", "pool.mustBeInitialized": "اس پول کو شروع کرنا ضروری ہے اس سے پہلے کہ آپ لیکویڈیٹی شامل کر سکیں۔ شروع کرنے کے لیے، پول کے لیے ایک ابتدائی قیمت منتخب کریں۔ پھر، اپنی لیکویڈیٹی قیمت کی حد اور جمع کی رقم درج کریں۔ ابتدائی لین دین کی وجہ سے گیس کی فیسیں معمول سے زیادہ ہوں گی۔", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "ٹپ: اس ٹول کو v2 پولز تلاش کرنے کے لیے استعمال کریں جو خود بخود انٹرفیس میں ظاہر نہیں ہوتے ہیں۔", "pools.approving.amount": "{{amount}}کی منظوری دے رہا ہے۔", "position.addHook": "ایک ہک شامل کریں۔", + "position.addHook.tooltip": "ہکس ایک جدید خصوصیت ہے جو پولز کو سمارٹ کنٹریکٹس کے ساتھ تعامل کرنے کے قابل بناتی ہے، مختلف صلاحیتوں کو کھول کر۔ ہکس شامل کرتے وقت احتیاط برتیں، کیونکہ کچھ نقصان دہ ہو سکتے ہیں یا غیر ارادی نتائج کا سبب بن سکتے ہیں۔", "position.appearHere": "آپ کی پوزیشن یہاں ظاہر ہوگی۔", + "position.create.modal.header": "پوزیشن بنانا", "position.currentValue": "موجودہ پوزیشن کی قیمت", + "position.deposit.description": "اپنی لیکویڈیٹی شراکت کے لیے ٹوکن کی رقم کی وضاحت کریں۔", "position.depositedCurrency": "جمع شدہ {{currencySymbol}}", "position.migrate.liquidity": "پوزیشنز کو منتقل کرتے وقت، آپ اپنے ٹوکن جوڑے کو تبدیل نہیں کر سکتے، لیکن آپ فعالیت کو بڑھانے کے لیے ایک ہک شامل کر سکتے ہیں۔", "position.new": "نئی پوزیشن", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "منسلک ہو رہا ہے...", "qrScanner.status.loading": "لوڈ ہو رہا ہے...", "qrScanner.title": "QR کوڈ اسکین کریں۔", - "qrScanner.wallet.title": "آپ Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast, اور BNB چین پر ٹوکنز اور NFTs وصول کر سکتے ہیں۔", + "qrScanner.wallet.networks": "تعاون یافتہ نیٹ ورکس", + "qrScanner.wallet.title": "آپ ہمارے تمام {{numOfNetworks}} تعاون یافتہ نیٹ ورکس پر ٹوکن اور NFTs بھیج اور وصول کر سکتے ہیں۔", "removeLiquidity.collectFees": "آپ اس پوزیشن سے حاصل کردہ فیس بھی جمع کریں گے۔", "removeLiquidity.outputEstimated": "آؤٹ پٹ کا تخمینہ لگایا گیا ہے۔ اگر قیمت میں {{allowed}}% سے زیادہ تبدیلی آتی ہے تو آپ کا لین دین واپس ہو جائے گا۔", "removeLiquidity.pendingText": "{{amtA}} {{symA}} اور {{amtB}} {{symB}}کو ہٹایا جا رہا ہے", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "عرفی نام", "settings.setting.wallet.notifications.title": "اطلاعات", "settings.setting.wallet.preferences.title": "والیٹ کی ترجیحات", - "settings.showTestNets": "ٹیسٹ نیٹ دکھائیں۔", + "settings.setting.wallet.testnetMode.description": "یہ ڈیولپرز کے لیے ٹیسٹ نیٹ کو آن کر دیتا ہے تاکہ وہ اصلی اثاثے استعمال کیے بغیر خصوصیات اور لین دین کو آزما سکیں۔ testnets پر ٹوکن کی کوئی حقیقی قدر نہیں ہوتی۔", + "settings.setting.wallet.testnetMode.title": "ٹیسٹ نیٹ موڈ", "settings.switchNetwork.warning": "{{label}}پر Uniswap استعمال کرنے کے لیے، اپنے بٹوے کی ترتیبات میں نیٹ ورک کو تبدیل کریں۔", "settings.title": "ترتیبات", "settings.version": "ورژن {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "نیا ان پٹ", "swap.details.newQuote.output": "نیا آؤٹ پٹ", "swap.details.orderRouting": "آرڈر روٹنگ", - "swap.details.orderRoutingInfo": "آپ کی قیمت میں پہلے سے ہی منزل کے نیٹ ورک پر نیٹ ورک کی لاگت اور 0.05% فیس شامل ہے۔", + "swap.details.orderRoutingInfo": "اس سویپ کو اکروس کے ذریعے روٹ کیا جاتا ہے، ایک وکندریقرت پروٹوکول جو حفاظت، تیز رفتار عمل درآمد، اور کم قیمتوں کو ترجیح دیتے ہوئے نیٹ ورکس میں اثاثوں کو منتقل کرتا ہے۔", + "swap.details.poweredBy": "کی طرف سے طاقت", "swap.details.rate": "شرح", "swap.details.slippage": "زیادہ سے زیادہ پھسلنا", "swap.details.uniswapFee": "فیس", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Uniswap کلائنٹ سب سے سستا تجارتی آپشن فیکٹرنگ قیمت اور نیٹ ورک کے اخراجات کا انتخاب کرتا ہے۔", "swap.settings.routingPreference.option.v2.title": "v2 تالاب", "swap.settings.routingPreference.option.v3.title": "v3 تالاب", + "swap.settings.routingPreference.option.v4.title": "v4 تالاب", "swap.settings.routingPreference.title": "تجارت کے اختیارات", "swap.settings.slippage.control.auto": "آٹو", "swap.settings.slippage.description": "اگر قیمت slippage فیصد سے زیادہ تبدیل ہوتی ہے تو آپ کا لین دین واپس آجائے گا۔", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "اگر قیمت اس فیصد سے زیادہ ناموافق طور پر تبدیل ہوتی ہے تو آپ کا لین دین واپس آجائے گا۔", "swap.signAndSwap": "دستخط کریں اور تبادلہ کریں۔", "swap.slippage.amt": "{{amt}} پھسلنا", + "swap.slippage.bridging": "نیٹ ورکس میں تبادلہ کرتے وقت کوئی پھسلن نہیں ہوتی", "swap.slippage.settings.title": "پھسلن کی ترتیبات", "swap.slippage.tooltip": "آپ کے لین دین سے پہلے قیمت کی زیادہ سے زیادہ حرکت واپس آجائے گی۔", "swap.slippageBelow.warning": "{{amt}} کے نیچے پھسلنے کے نتیجے میں ٹرانزیکشن ناکام ہو سکتی ہے۔", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "لین دین کی آخری تاریخ", "swap.transaction.revertAfter": "اگر آپ کا لین دین اس مدت سے زیادہ عرصے سے زیر التوا ہے تو وہ واپس آجائے گا۔", "swap.unsupportedAssets.readMore": "غیر تعاون یافتہ اثاثوں کے بارے میں مزید پڑھیں", + "swap.warning.enterLargerAmount.title": "ایک بڑی رقم درج کریں۔", "swap.warning.expectedFailure": "اس لین دین کے ناکام ہونے کی توقع ہے۔", "swap.warning.feeOnTransfer.message": "کچھ ٹوکن جب خریدے یا بیچے جاتے ہیں تو فیس لیتے ہیں، جو ٹوکن جاری کرنے والے کی طرف سے مقرر کیا جاتا ہے۔ Uniswap ان فیسوں کا کوئی حصہ وصول نہیں کرتا ہے۔", "swap.warning.feeOnTransfer.title": "اضافی فیس کیوں ہے؟", "swap.warning.insufficientBalance.title": "آپ کے پاس کافی {{currencySymbol}}نہیں ہے۔", "swap.warning.insufficientGas.button": "کافی نہیں {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "{{networkName}}پر {{ tokenSymbol }} کے لیے تبادلہ کریں۔", "swap.warning.insufficientGas.button.buy": "{{ tokenSymbol }}خریدیں۔", + "swap.warning.insufficientGas.button.buyWithCard": "کارڈ کے ساتھ خریدیں۔", "swap.warning.insufficientGas.message.withNetwork": "تبدیل کرنے کے لیے کافی {{currencySymbol}} پر {{networkName}}", "swap.warning.insufficientGas.message.withoutNetwork": "تبدیل کرنے کے لیے کافی نہیں {{currencySymbol}}", "swap.warning.insufficientGas.title": "آپ کے پاس نیٹ ورک کی لاگت کو پورا کرنے کے لیے کافی {{currencySymbol}} نہیں ہے۔", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "{{chainName}}پر کے لیے ٹوکن ڈیٹا", "tdp.nameNotFound": "نام نہیں ملا", "tdp.noInfoAvailable": "کوئی ٹوکن معلومات دستیاب نہیں ہے۔", + "tdp.noTestnetSupportDescription": "کچھ ٹیسٹ نیٹ ٹوکن تبدیل کرنے، بھیجنے یا خریدنے کی حمایت نہیں کرتے ہیں۔", "tdp.stats.unsupportedChainDescription": "{{chain}} کے لیے ٹوکن کے اعدادوشمار اور چارٹ {{infoLink}}پر دستیاب ہیں", "tdp.symbolNotFound": "علامت نہیں ملی", + "testnet.unsupported": "یہ فعالیت ٹیسٹ نیٹ موڈ میں تعاون یافتہ نہیں ہے۔", "themeToggle.theme": "خیالیہ", "title.betterPricesMoreListings": "بہتر قیمتیں۔ مزید فہرستیں OpenSea جیسے بڑے بازاروں میں NFTs خریدیں، بیچیں اور تجارت کریں۔ رجحان ساز مجموعے دریافت کریں۔", "title.buySellTradeEthereum": "Uniswap پر Ethereum اور دیگر ٹاپ ٹوکن خریدیں، بیچیں اور تجارت کریں۔", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "{{ownerAddress}}کا بیلنس", "token.bridge": "{{label}} ٹوکن پل", "token.chart.tooltip": "فیس: {{amount}}", + "token.details.testnet.unsupported": "ٹیسٹ نیٹ ٹوکنز کے لیے ٹوکن کی تفصیلات دستیاب نہیں ہیں۔", "token.error.unknown": "نامعلوم ٹوکن", "token.fee.buy.label": "خریدنے کی فیس", "token.fee.label": "فیس", @@ -2109,6 +2130,8 @@ "transaction.warning.insufficientGas.modal.title.withoutNetwork": "کافی نہیں {{tokenSymbol}}", "transaction.watcher.error.cancel": "لین دین منسوخ کرنے سے قاصر", "transaction.watcher.error.status": "ٹرانزیکشن اسٹیٹس چیک کرتے وقت خرابی", + "unichain.launch.modal.description": "DeFi کے لیے ڈیزائن کیا گیا ایک Ethereum L2، جسے Uniswap Labs نے بنایا ہے۔ Testnet آج لائیو ہوتا ہے۔", + "unichain.launch.modal.title": "Introducing Unichain", "uniswapX.aggregatesLiquidity": " بہتر قیمتوں اور گیس سے پاک تبادلہ کے لیے لیکویڈیٹی ذرائع کو جمع کرتا ہے۔", "uniswapx.description": "UniswapX بہتر قیمتوں اور گیس سے پاک تبادلہ کے لیے لیکویڈیٹی ذرائع کو جمع کرتا ہے۔", "uniswapx.included": "شامل ہے UniswapX ", @@ -2212,7 +2235,6 @@ "walletConnect.pending.switchAccount": "کھاتہ بدلیں", "walletConnect.pending.switchNetwork": "نیٹ ورک سوئچ کریں۔", "walletConnect.pending.title": "{{dappName}}سے جڑیں۔", - "walletConnect.permissions.networks": "نیٹ ورکس", "walletConnect.permissions.option.transferAssets": "رضامندی کے بغیر اپنے اثاثے منتقل کریں۔", "walletConnect.permissions.option.viewTokenBalances": "اپنے ٹوکن بیلنس دیکھیں", "walletConnect.permissions.option.viewWalletAddress": "اپنے بٹوے کا پتہ دیکھیں", diff --git a/packages/uniswap/src/i18n/locales/translations/vi-VN.json b/packages/uniswap/src/i18n/locales/translations/vi-VN.json index f8ff6d6d3cf..5cb704b7110 100644 --- a/packages/uniswap/src/i18n/locales/translations/vi-VN.json +++ b/packages/uniswap/src/i18n/locales/translations/vi-VN.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "Gửi mã thông báo vào mạng {{label}} .", "common.deposited": "Đã gửi tiền", "common.depositing": "Gửi tiền", + "common.depositTokens": "Tiền gửi token", "common.detailed.label": "Chi tiết", "common.detected": "Đã phát hiện", "common.developers": "Nhà phát triển", @@ -430,6 +431,7 @@ "common.migrate.position": "Di chuyển vị trí", "common.migrated.liquidity": "Thanh khoản di chuyển", "common.migrating.liquidity": "Thanh khoản di chuyển", + "common.min": "Tối thiểu", "common.mint.cancelled": "Mint đã bị hủy", "common.mint.failed": "Mint thất bại", "common.minted": "đúc", @@ -450,6 +452,7 @@ "common.noResults": "không có kết quả nào được tìm thấy.", "common.notAvailableInRegion.error": "Không có sẵn ở khu vực của bạn", "common.notCreated.label": "Chưa tạo", + "common.notSupported": "Không được hỗ trợ", "common.oneDay": "1 ngày", "common.oneHour": "1 giờ", "common.oneMonth": "1 tháng", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}và các tùy chọn khác", "fiatOnRamp.quote.type.other": "Sự lựa chọn khác", "fiatOnRamp.quote.type.recent": "Được sử dụng gần đây", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "Bạn có thể nhận mã thông báo & NFT trên Ethereum, Polygon, Arbitrum, Optimism, Base, BNB, Blast, Avalanche, Zora và ZKsync.", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "Từ một tài khoản", "fiatOnRamp.receiveCrypto.title": "Nhận tiền điện tử", "fiatOnRamp.receiveCrypto.transferFunds": "Nạp tiền vào ví của bạn bằng cách chuyển tiền điện tử từ ví hoặc tài khoản khác", @@ -901,6 +903,8 @@ "home.activity.error.load": "Không thể tải hoạt động", "home.activity.title": "Hoạt động", "home.banner.offline": "Bạn đang ở chế độ ngoại tuyến", + "home.banner.testnetMode": "Bạn đang ở chế độ testnet", + "home.banner.testnetMode.nav": "Bạn đang ở chế độ testnet. Tắt chế độ này trong cài đặt.", "home.explore.footer": "Nhấn vào đây để khám phá hàng nghìn mã thông báo, NFT, v.v.", "home.explore.title": "Khám phá mã thông báo", "home.extension.error": "Lỗi tải tài khoản", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "Tạo vị trí", "nav.tabs.createV2Position": "Tạo vị trí V2", "nav.tabs.createV3Position": "Tạo vị trí V3", + "nav.tabs.createV4Position": "Tạo vị trí V4", "nav.tabs.viewPosition": "Xem vị trí", "network.lostConnection": "Có thể bạn đã mất kết nối mạng.", "network.mightBeDown": "{{network}} có thể hiện đang ngừng hoạt động hoặc có thể bạn đã mất kết nối mạng.", @@ -1331,6 +1336,7 @@ "pool.back": "Quay lại hồ bơi", "pool.balances": "Số dư trong hồ bơi", "pool.claimFees": "Phí yêu cầu bồi thường", + "pool.claimFees.button.label": "Khẳng định", "pool.collectAs": "Thu thập dưới dạng {{nativeWrappedSymbol}}", "pool.collected": " Đã sưu tầm", "pool.collecting": "Sưu tập", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "Quản lý tính thanh khoản trong nhóm phần thưởng", "pool.max.label": "Tối đa:", "pool.maxPrice": "Giá tối đa", + "pool.migrateToV4": "Di chuyển đến V4", "pool.min.label": "tối thiểu:", "pool.minPrice": "Giá tối thiểu", "pool.mustBeInitialized": "Nhóm này phải được khởi tạo trước khi bạn có thể thêm thanh khoản. Để khởi tạo, hãy chọn giá khởi điểm cho nhóm. Sau đó, nhập phạm vi giá thanh khoản và số tiền gửi của bạn. Phí gas sẽ cao hơn bình thường do giao dịch khởi tạo.", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "Mẹo: Sử dụng công cụ này để tìm nhóm v2 không tự động xuất hiện trong giao diện.", "pools.approving.amount": "Đang phê duyệt {{amount}}", "position.addHook": "Thêm một cái móc", + "position.addHook.tooltip": "Hook là một tính năng nâng cao cho phép các nhóm tương tác với hợp đồng thông minh, mở khóa nhiều khả năng khác nhau. Hãy thận trọng khi thêm hook, vì một số hook có thể độc hại hoặc gây ra hậu quả không mong muốn.", "position.appearHere": "Vị trí của bạn sẽ xuất hiện ở đây.", + "position.create.modal.header": "Tạo vị trí", "position.currentValue": "Giá trị vị trí hiện tại", + "position.deposit.description": "Chỉ định số tiền mã thông báo cho đóng góp thanh khoản của bạn.", "position.depositedCurrency": "Đã gửi {{currencySymbol}}", "position.migrate.liquidity": "Khi di chuyển vị trí, bạn không thể thay đổi cặp mã thông báo, nhưng bạn có thể thêm một móc để tăng cường chức năng.", "position.new": "Vị trí mới", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "Đang kết nối...", "qrScanner.status.loading": "Đang tải...", "qrScanner.title": "Quét mã QR", - "qrScanner.wallet.title": "Bạn có thể nhận mã thông báo & NFT trên Ethereum, Polygon, Arbitrum, Optimism, Base, ZKsync, Zora, Avalanche, Celo, Blast và BNB Chain.", + "qrScanner.wallet.networks": "Mạng được hỗ trợ", + "qrScanner.wallet.title": "Bạn có thể gửi và nhận token và NFT trên tất cả {{numOfNetworks}} mạng được chúng tôi hỗ trợ.", "removeLiquidity.collectFees": "Bạn cũng sẽ thu phí kiếm được từ vị trí này.", "removeLiquidity.outputEstimated": "Sản lượng được ước tính. Nếu giá thay đổi nhiều hơn {{allowed}}% giao dịch của bạn sẽ hoàn nguyên.", "removeLiquidity.pendingText": "Xóa {{amtA}} {{symA}} và {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "Tên nick", "settings.setting.wallet.notifications.title": "Thông báo", "settings.setting.wallet.preferences.title": "Tùy chọn ví", - "settings.showTestNets": "Hiển thị mạng thử nghiệm", + "settings.setting.wallet.testnetMode.description": "Điều này bật các mạng thử nghiệm để các nhà phát triển thử nghiệm các tính năng và giao dịch mà không cần sử dụng tài sản thực. Mã thông báo trên mạng thử nghiệm không giữ bất kỳ giá trị thực nào.", + "settings.setting.wallet.testnetMode.title": "Chế độ Testnet", "settings.switchNetwork.warning": "Để sử dụng Uniswap trên {{label}}, hãy chuyển đổi mạng trong cài đặt ví của bạn.", "settings.title": "Cài đặt", "settings.version": "Phiên bản {{appVersion}}", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "Đầu vào mới", "swap.details.newQuote.output": "Đầu ra mới", "swap.details.orderRouting": "Định tuyến đơn hàng", - "swap.details.orderRoutingInfo": "Giá của bạn đã bao gồm chi phí mạng lưới trên mạng đích và phí Across 0,05%.", + "swap.details.orderRoutingInfo": "Việc hoán đổi này được định tuyến thông qua Across, một giao thức phi tập trung di chuyển tài sản qua các mạng lưới trong khi ưu tiên tính an toàn, thực hiện nhanh chóng và giá thấp.", + "swap.details.poweredBy": "Được cung cấp bởi", "swap.details.rate": "Tỷ lệ", "swap.details.slippage": "Độ trượt tối đa", "swap.details.uniswapFee": "Phí", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Ứng dụng khách Uniswap chọn tùy chọn giao dịch rẻ nhất bao thanh toán giá và chi phí mạng.", "swap.settings.routingPreference.option.v2.title": "hồ bơi v2", "swap.settings.routingPreference.option.v3.title": "hồ bơi v3", + "swap.settings.routingPreference.option.v4.title": "v4 hồ bơi", "swap.settings.routingPreference.title": "Tùy chọn giao dịch", "swap.settings.slippage.control.auto": "Tự động", "swap.settings.slippage.description": "Giao dịch của bạn sẽ hoàn nguyên nếu giá thay đổi nhiều hơn tỷ lệ trượt giá.", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "Giao dịch của bạn sẽ hoàn nguyên nếu giá thay đổi bất lợi nhiều hơn tỷ lệ phần trăm này.", "swap.signAndSwap": "Ký và trao đổi", "swap.slippage.amt": "{{amt}} trượt", + "swap.slippage.bridging": "Không bị trượt khi chuyển đổi qua các mạng", "swap.slippage.settings.title": "Cài đặt trượt giá", "swap.slippage.tooltip": "Biến động giá tối đa trước khi giao dịch của bạn sẽ hoàn nguyên.", "swap.slippageBelow.warning": "Trượt xuống dưới {{amt}} có thể dẫn đến giao dịch không thành công", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "Thời hạn giao dịch", "swap.transaction.revertAfter": "Giao dịch của bạn sẽ hoàn nguyên nếu nó đang chờ xử lý lâu hơn khoảng thời gian này.", "swap.unsupportedAssets.readMore": "Đọc thêm về nội dung không được hỗ trợ", + "swap.warning.enterLargerAmount.title": "Nhập số tiền lớn hơn", "swap.warning.expectedFailure": "Giao dịch này được cho là sẽ thất bại", "swap.warning.feeOnTransfer.message": "Một số mã thông báo sẽ tính phí khi chúng được mua hoặc bán, do nhà phát hành mã thông báo quy định. Uniswap không nhận được bất kỳ phần chia sẻ nào trong số phí này.", "swap.warning.feeOnTransfer.title": "Tại sao có một khoản phí bổ sung?", "swap.warning.insufficientBalance.title": "Bạn không có đủ {{currencySymbol}}", "swap.warning.insufficientGas.button": "Không đủ {{currencySymbol}}", + "swap.warning.insufficientGas.button.bridge": "Đổi lấy {{ tokenSymbol }} trên {{networkName}}", "swap.warning.insufficientGas.button.buy": "Mua {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "Mua bằng thẻ", "swap.warning.insufficientGas.message.withNetwork": "Không đủ {{currencySymbol}} trên {{networkName}} để hoán đổi", "swap.warning.insufficientGas.message.withoutNetwork": "Không đủ {{currencySymbol}} để hoán đổi", "swap.warning.insufficientGas.title": "Bạn không có đủ {{currencySymbol}} để trang trải chi phí mạng", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "dữ liệu mã thông báo cho trên {{chainName}}", "tdp.nameNotFound": "Không tìm thấy tên", "tdp.noInfoAvailable": "Không có thông tin về mã thông báo", + "tdp.noTestnetSupportDescription": "Một số mạng thử nghiệm không hỗ trợ việc trao đổi, gửi hoặc mua token.", "tdp.stats.unsupportedChainDescription": "Số liệu thống kê và biểu đồ về mã thông báo cho {{chain}} có sẵn trên {{infoLink}}", "tdp.symbolNotFound": "Không tìm thấy biểu tượng", + "testnet.unsupported": "Chức năng này không được hỗ trợ ở chế độ testnet.", "themeToggle.theme": "chủ đề", "title.betterPricesMoreListings": "Giá tốt hơn. Nhiều danh sách hơn. Mua, bán và giao dịch NFT trên các thị trường hàng đầu như OpenSea. Khám phá các bộ sưu tập theo xu hướng.", "title.buySellTradeEthereum": "Mua, bán và giao dịch Ethereum và các token hàng đầu khác trên Uniswap", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "số dư {{ownerAddress}}", "token.bridge": "{{label}} cầu mã thông báo", "token.chart.tooltip": "Lệ phí: {{amount}}", + "token.details.testnet.unsupported": "Không có thông tin chi tiết về mã thông báo cho mã thông báo mạng thử nghiệm.", "token.error.unknown": "Mã thông báo không xác định", "token.fee.buy.label": "mua phí", "token.fee.label": "phí", @@ -2109,6 +2130,8 @@ "transaction.warning.insufficientGas.modal.title.withoutNetwork": "Không đủ {{tokenSymbol}}", "transaction.watcher.error.cancel": "Không thể hủy giao dịch", "transaction.watcher.error.status": "Lỗi khi kiểm tra trạng thái giao dịch", + "unichain.launch.modal.description": "Ethereum L2 được thiết kế cho DeFi, do Uniswap Labs xây dựng. Testnet đã chính thức hoạt động vào hôm nay.", + "unichain.launch.modal.title": "Giới thiệu Unichain", "uniswapX.aggregatesLiquidity": " tổng hợp các nguồn thanh khoản để có giá tốt hơn và giao dịch hoán đổi không tốn gas.", "uniswapx.description": "UniswapX tổng hợp các nguồn thanh khoản để có giá tốt hơn và hoán đổi miễn phí.", "uniswapx.included": "Bao gồm UniswapX ", @@ -2212,7 +2235,6 @@ "walletConnect.pending.switchAccount": "Chuyển tài khoản", "walletConnect.pending.switchNetwork": "Chuyển mạng", "walletConnect.pending.title": "Kết nối với {{dappName}}", - "walletConnect.permissions.networks": "Mạng", "walletConnect.permissions.option.transferAssets": "Chuyển tài sản của bạn mà không có sự đồng ý", "walletConnect.permissions.option.viewTokenBalances": "Xem số dư token của bạn", "walletConnect.permissions.option.viewWalletAddress": "Xem địa chỉ ví của bạn", diff --git a/packages/uniswap/src/i18n/locales/translations/zh-CN.json b/packages/uniswap/src/i18n/locales/translations/zh-CN.json index 22f8c849599..ad0996ff666 100644 --- a/packages/uniswap/src/i18n/locales/translations/zh-CN.json +++ b/packages/uniswap/src/i18n/locales/translations/zh-CN.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "将代币存入 {{label}} 网络。", "common.deposited": "已存入", "common.depositing": "正在存入", + "common.depositTokens": "存入代币", "common.detailed.label": "已详细说明", "common.detected": "已检测", "common.developers": "开发人员", @@ -430,6 +431,7 @@ "common.migrate.position": "迁移头寸", "common.migrated.liquidity": "已迁移流动性", "common.migrating.liquidity": "正在迁移流动性", + "common.min": "分钟", "common.mint.cancelled": "铸造已取消", "common.mint.failed": "铸造失败", "common.minted": "已铸造", @@ -450,6 +452,7 @@ "common.noResults": "未找到结果。", "common.notAvailableInRegion.error": "在您所在地区不可用", "common.notCreated.label": "未创建", + "common.notSupported": "不支持", "common.oneDay": "1 天", "common.oneHour": "1 小时", "common.oneMonth": "1 个月", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}}以及其他选项", "fiatOnRamp.quote.type.other": "其他选项", "fiatOnRamp.quote.type.recent": "最近使用", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "您可以在以太坊、Polygon、Arbitrum、Optimism、Base、BNB、Blast、Avalanche、Zora 和 ZKsync 上接收代币和 NFT。", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "从账户", "fiatOnRamp.receiveCrypto.title": "接收加密货币", "fiatOnRamp.receiveCrypto.transferFunds": "从另一个钱包或账户转移加密货币,为您的钱包充值", @@ -901,6 +903,8 @@ "home.activity.error.load": "未能加载活动", "home.activity.title": "活动", "home.banner.offline": "您处于离线模式", + "home.banner.testnetMode": "您处于测试网模式", + "home.banner.testnetMode.nav": "您处于测试网模式。请在设置中关闭该模式。", "home.explore.footer": "点击“搜索”探索更多", "home.explore.title": "浏览代币", "home.extension.error": "加载账户时出错", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "创建头寸", "nav.tabs.createV2Position": "创建 V2 头寸", "nav.tabs.createV3Position": "创建 V3 头寸", + "nav.tabs.createV4Position": "创建 V4 位置", "nav.tabs.viewPosition": "查看头寸", "network.lostConnection": "您的网络连接可能已断开。", "network.mightBeDown": "{{network}} 现在可能已关闭,或者您的网络连接可能已断开。", @@ -1331,6 +1336,7 @@ "pool.back": "返回资金池", "pool.balances": "资金池余额", "pool.claimFees": "申领费用", + "pool.claimFees.button.label": "宣称", "pool.collectAs": "作为 {{nativeWrappedSymbol}} 收取", "pool.collected": " 已收取", "pool.collecting": "正在收取", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "管理奖励资金池的流动性", "pool.max.label": "最高:", "pool.maxPrice": "最高价格", + "pool.migrateToV4": "迁移至 V4", "pool.min.label": "最低:", "pool.minPrice": "最低价格", "pool.mustBeInitialized": "在添加流动性之前,必须先初始化该资金池。要初始化,请选择资金池的起始价格。然后,输入您的流动性价格范围和存款金额。由于初始化交易,gas 费用将比平时更高。", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "提示:使用此工具查找不会自动出现在界面中的 v2 资金池。", "pools.approving.amount": "批准 {{amount}}", "position.addHook": "添加挂钩", + "position.addHook.tooltip": "钩子是一项高级功能,可让矿池与智能合约进行交互,从而解锁各种功能。添加钩子时请务必小心,因为有些钩子可能是恶意的或会导致意外后果。", "position.appearHere": "您的头寸将出现在这里。", + "position.create.modal.header": "创建职位", "position.currentValue": "当前位置值", + "position.deposit.description": "指定您的流动性贡献的代币数量。", "position.depositedCurrency": "已存入 {{currencySymbol}}", "position.migrate.liquidity": "迁移位置时,您无法更改代币对,但可以添加挂钩来增强功能。", "position.new": "新职位", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "正在连接...", "qrScanner.status.loading": "正在加载...", "qrScanner.title": "扫描二维码", - "qrScanner.wallet.title": "您可以在以太坊、Polygon、Arbitrum、Optimism、Base、ZKsync、Zora、Avalanche、Celo、Blast 和 BNB Chain 上接收代币和 NFT。", + "qrScanner.wallet.networks": "支持的网络", + "qrScanner.wallet.title": "您可以在我们所有 {{numOfNetworks}} 支持的网络上发送和接收代币和 NFT。", "removeLiquidity.collectFees": "您还将收取从该头寸赚取的费用。", "removeLiquidity.outputEstimated": "输出为估计值。如果价格变动超过 {{allowed}}%,您的交易将撤回。", "removeLiquidity.pendingText": "正在移除 {{amtA}} {{symA}} 和 {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "昵称", "settings.setting.wallet.notifications.title": "通知", "settings.setting.wallet.preferences.title": "钱包首选项", - "settings.showTestNets": "显示测试网", + "settings.setting.wallet.testnetMode.description": "这将启用测试网,让开发人员无需使用真实资产即可试用功能和交易。测试网上的代币不具备任何实际价值。", + "settings.setting.wallet.testnetMode.title": "测试网模式", "settings.switchNetwork.warning": "要在 {{label}} 上使用 Uniswap,请在钱包的设置中切换网络。", "settings.title": "设置", "settings.version": "{{appVersion}} 版", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "新输入", "swap.details.newQuote.output": "新输出", "swap.details.orderRouting": "订单路由", - "swap.details.orderRoutingInfo": "您的价格已经包含目标网络的网络费用和 0.05% 的跨网费。", + "swap.details.orderRoutingInfo": "此交换通过 Across 进行,Across 是一种去中心化协议,可在网络之间移动资产,同时优先考虑安全性、快速执行和低价。", + "swap.details.poweredBy": "供电", "swap.details.rate": "费率", "swap.details.slippage": "滑点上限", "swap.details.uniswapFee": "费用", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Uniswap 客户端根据价格和网络费用选择最便宜的交易选项。", "swap.settings.routingPreference.option.v2.title": "v2 资金池", "swap.settings.routingPreference.option.v3.title": "v3 资金池", + "swap.settings.routingPreference.option.v4.title": "v4 资金池", "swap.settings.routingPreference.title": "交易选项", "swap.settings.slippage.control.auto": "自动", "swap.settings.slippage.description": "如果价格变动超过滑点百分比,则您的交易将撤回。", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "如果价格的不利变化超过此百分比,您的交易将撤销。", "swap.signAndSwap": "签名和兑换", "swap.slippage.amt": "{{amt}} 滑点", + "swap.slippage.bridging": "跨网络交换时无滑点", "swap.slippage.settings.title": "滑点上限", "swap.slippage.tooltip": "在您的交易撤回之前的最大价格变动。", "swap.slippageBelow.warning": "滑点低于 {{amt}} 可能导致交易失败", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "交易截止日期", "swap.transaction.revertAfter": "如果您的交易等待时间超过此时间段,则交易将撤销。", "swap.unsupportedAssets.readMore": "阅读更多关于不受支持资产的信息", + "swap.warning.enterLargerAmount.title": "输入更大的金额", "swap.warning.expectedFailure": "这笔交易预计将失败", "swap.warning.feeOnTransfer.message": "在购买或出售某些代币时收取由代币发行者设置的费用。Uniswap 不从这些费用分成。", "swap.warning.feeOnTransfer.title": "为什么有额外费用?", "swap.warning.insufficientBalance.title": "您的 {{currencySymbol}} 不足", "swap.warning.insufficientGas.button": "{{currencySymbol}} 不足", + "swap.warning.insufficientGas.button.bridge": "将 {{ tokenSymbol }} 交换为 {{networkName}}", "swap.warning.insufficientGas.button.buy": "购买 {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "使用卡购买", "swap.warning.insufficientGas.message.withNetwork": "{{networkName}} 上的 {{currencySymbol}} 不足,无法兑换", "swap.warning.insufficientGas.message.withoutNetwork": "{{currencySymbol}} 不足,无法兑换", "swap.warning.insufficientGas.title": "您的 {{currencySymbol}} 不足以支付网络费用", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "{{chainName}} 上的 代币数据", "tdp.nameNotFound": "未找到名称", "tdp.noInfoAvailable": "没有可用的代币信息", + "tdp.noTestnetSupportDescription": "一些测试网不支持交换、发送或购买代币。", "tdp.stats.unsupportedChainDescription": "{{chain}} 的代币统计数据和图表可在 {{infoLink}} 上查阅", "tdp.symbolNotFound": "未找到符号", + "testnet.unsupported": "测试网模式不支持此功能。", "themeToggle.theme": "主题", "title.betterPricesMoreListings": "更优惠的价格。更多挂牌。在 OpenSea 等顶级市场上购买、出售和交易 NFT。探索热门收藏品。", "title.buySellTradeEthereum": "在 Uniswap 上购买、出售和交易以太坊及其他顶级代币", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "{{ownerAddress}} 的余额", "token.bridge": "{{label}} 代币桥", "token.chart.tooltip": "费用:{{amount}}", + "token.details.testnet.unsupported": "测试网代币无法提供代币详细信息。", "token.error.unknown": "未知代币", "token.fee.buy.label": "购买费用", "token.fee.label": "费用", @@ -2212,7 +2233,6 @@ "walletConnect.pending.switchAccount": "切换账户", "walletConnect.pending.switchNetwork": "切换网络", "walletConnect.pending.title": "连接到 {{dappName}}", - "walletConnect.permissions.networks": "网络", "walletConnect.permissions.option.transferAssets": "未经同意转移您的资产", "walletConnect.permissions.option.viewTokenBalances": "查看您的代币余额", "walletConnect.permissions.option.viewWalletAddress": "查看您的钱包地址", diff --git a/packages/uniswap/src/i18n/locales/translations/zh-TW.json b/packages/uniswap/src/i18n/locales/translations/zh-TW.json index 93f2a0008c7..ba5c5539426 100644 --- a/packages/uniswap/src/i18n/locales/translations/zh-TW.json +++ b/packages/uniswap/src/i18n/locales/translations/zh-TW.json @@ -317,6 +317,7 @@ "common.deposit.toNetwork": "將代幣存入 {{label}} 網路。", "common.deposited": "已存入", "common.depositing": "正在存入", + "common.depositTokens": "存入代幣", "common.detailed.label": "詳細的", "common.detected": "已偵測到", "common.developers": "開發人員", @@ -430,6 +431,7 @@ "common.migrate.position": "移轉部位", "common.migrated.liquidity": "流動性移轉", "common.migrating.liquidity": "正在移轉流動性", + "common.min": "最小", "common.mint.cancelled": "已取消鑄造", "common.mint.failed": "鑄造失敗", "common.minted": "已鑄造", @@ -450,6 +452,7 @@ "common.noResults": "找不到結果。", "common.notAvailableInRegion.error": "在您所在地區不適用", "common.notCreated.label": "未建立", + "common.notSupported": "不支援", "common.oneDay": "1 天", "common.oneHour": "1 小時", "common.oneMonth": "1 個月", @@ -869,7 +872,6 @@ "fiatOnRamp.quote.type.list": "{{optionsList}},以及其他選項", "fiatOnRamp.quote.type.other": "其他選項", "fiatOnRamp.quote.type.recent": "最近使用過", - "fiatOnRamp.receiveCrypto.modal.addressQr.supportedNetworks": "您可以在乙太坊、Polygon、Arbitrum、Optimism、Base、BNB、Blast、Avalanche、Zora 和 ZKsync 上接收代幣和 NFT。", "fiatOnRamp.receiveCrypto.modal.sectionTitle.fromAccount": "來自帳戶", "fiatOnRamp.receiveCrypto.title": "接收加密貨幣", "fiatOnRamp.receiveCrypto.transferFunds": "從另一個錢包或帳戶轉移加密貨幣來儲值您的錢包", @@ -901,6 +903,8 @@ "home.activity.error.load": "無法載入活動", "home.activity.title": "活動", "home.banner.offline": "您處於離線模式", + "home.banner.testnetMode": "您處於測試網模式", + "home.banner.testnetMode.nav": "您處於測試網模式。在設定中將其關閉。", "home.explore.footer": "點擊「搜尋」探索更多", "home.explore.title": "探索代幣", "home.extension.error": "載入帳戶時發生錯誤", @@ -1048,6 +1052,7 @@ "nav.tabs.createPosition": "建立部位", "nav.tabs.createV2Position": "建立 V2 部位", "nav.tabs.createV3Position": "建立 V3 部位", + "nav.tabs.createV4Position": "創建V4位置", "nav.tabs.viewPosition": "檢視部位", "network.lostConnection": "您可能失去了網路連線。", "network.mightBeDown": "{{network}} 可能目前無法使用,或您可能失去了網路連線。", @@ -1331,6 +1336,7 @@ "pool.back": "返回資金集區", "pool.balances": "資金集區餘額", "pool.claimFees": "領取費用", + "pool.claimFees.button.label": "宣稱", "pool.collectAs": "收取為 {{nativeWrappedSymbol}}", "pool.collected": " 已收取", "pool.collecting": "正在收取", @@ -1367,6 +1373,7 @@ "pool.manageRewardsLiquidity": "管理獎勵集區的流動性", "pool.max.label": "最高:", "pool.maxPrice": "最高價格", + "pool.migrateToV4": "遷移到V4", "pool.min.label": "最低:", "pool.minPrice": "最低價格", "pool.mustBeInitialized": "在增加流動性之前,必須先初始化該集區。若要初始化,請選擇集區的起始價格。然後,輸入您的流動性價格範圍和存款金額。由於初始化交易,Gas 費用將比平時更高。", @@ -1427,8 +1434,11 @@ "poolFinder.tip": "提示:使用此工具並尋找未自動出現在介面中的 v2 資金集區。", "pools.approving.amount": "核准 {{amount}}", "position.addHook": "添加一個鉤子", + "position.addHook.tooltip": "掛鉤是一項高級功能,使礦池能夠與智能合約交互,從而解鎖各種功能。在添加掛鉤時請務必小心,因為有些掛鉤可能是惡意的或會導致意外後果。", "position.appearHere": "您的位置將會出現在這裡。", + "position.create.modal.header": "建立位置", "position.currentValue": "目前位置值", + "position.deposit.description": "指定您的流動性貢獻的代幣金額。", "position.depositedCurrency": "已存入 {{currencySymbol}}", "position.migrate.liquidity": "遷移部位時,您無法變更代幣對,但可以新增掛鉤來增強功能。", "position.new": "新職位", @@ -1469,7 +1479,8 @@ "qrScanner.status.connecting": "正在連線...", "qrScanner.status.loading": "載入中...", "qrScanner.title": "掃描行動條碼", - "qrScanner.wallet.title": "您可以在乙太坊、Polygon、Arbitrum、Optimism、Base、ZKsync、Zora、Avalanche、Celo、Blast 和 BNB Chain 上接收代幣和 NFT。", + "qrScanner.wallet.networks": "支援的網路", + "qrScanner.wallet.title": "您可以在我們所有 {{numOfNetworks}} 支援的網路上發送和接收代幣和 NFT。", "removeLiquidity.collectFees": "您還將收取從此職位賺取的費用。", "removeLiquidity.outputEstimated": "輸出為估計值。如果價格變化超過 {{allowed}}%,您的交易將還原。", "removeLiquidity.pendingText": "正在移除 {{amtA}} {{symA}} 和 {{amtB}} {{symB}}", @@ -1668,7 +1679,8 @@ "settings.setting.wallet.label": "暱稱", "settings.setting.wallet.notifications.title": "通知", "settings.setting.wallet.preferences.title": "錢包偏好設定", - "settings.showTestNets": "顯示測試網", + "settings.setting.wallet.testnetMode.description": "這將為開發人員打開測試網,以便在不使用真實資產的情況下嘗試功能和交易。測試網上的代幣不具有任何實際價值。", + "settings.setting.wallet.testnetMode.title": "測試網模式", "settings.switchNetwork.warning": "若要在 {{label}}上使用 Uniswap,請在錢包設定中切換網路。", "settings.title": "設定", "settings.version": "{{appVersion}} 版", @@ -1721,7 +1733,8 @@ "swap.details.newQuote.input": "新輸入", "swap.details.newQuote.output": "新輸出", "swap.details.orderRouting": "訂單路由", - "swap.details.orderRoutingInfo": "您的價格已包含目標網路的網路費用和 0.05% 的跨站費用。", + "swap.details.orderRoutingInfo": "這種交換是透過 Across 進行的,Across 是一種去中心化協議,可以跨網路轉移資產,同時優先考慮安全性、快速執行和低廉的價格。", + "swap.details.poweredBy": "供電", "swap.details.rate": "費率", "swap.details.slippage": "滑點上限", "swap.details.uniswapFee": "費用", @@ -1786,6 +1799,7 @@ "swap.settings.routingPreference.option.default.description": "Uniswap 用戶端考慮價格和網路費用來選擇最便宜的交易選項。", "swap.settings.routingPreference.option.v2.title": "v2 資金集區", "swap.settings.routingPreference.option.v3.title": "v3 資金集區", + "swap.settings.routingPreference.option.v4.title": "v4 集區", "swap.settings.routingPreference.title": "交易選項", "swap.settings.slippage.control.auto": "自動", "swap.settings.slippage.description": "如果價格變化超過滑點百分比,您的交易將還原。", @@ -1800,6 +1814,7 @@ "swap.settings.transactionRevertPrice": "如果價格不利變化超過此百分比,您的交易將還原。", "swap.signAndSwap": "簽名和兌換", "swap.slippage.amt": "{{amt}} 滑點", + "swap.slippage.bridging": "跨網路交換時不會出現滑點", "swap.slippage.settings.title": "滑點上限", "swap.slippage.tooltip": "在您交易前的最高價格變動將還原。", "swap.slippageBelow.warning": "滑點低於 {{amt}} 可能會導致交易失敗", @@ -1813,12 +1828,15 @@ "swap.transaction.deadline": "交易截止日期", "swap.transaction.revertAfter": "如果您的交易等待處理時間超過此時段,您的交易將會還原。", "swap.unsupportedAssets.readMore": "了解有關不支援的資產的更多資訊", + "swap.warning.enterLargerAmount.title": "輸入更大的金額", "swap.warning.expectedFailure": "這筆交易預計會失敗", "swap.warning.feeOnTransfer.message": "有些代幣在購買或出售時會收取費用,該費用由代幣發行者設定。Uniswap 不會從中收取任何費用。", "swap.warning.feeOnTransfer.title": "為什麼要收取額外費用?", "swap.warning.insufficientBalance.title": "您的 {{currencySymbol}} 不足", "swap.warning.insufficientGas.button": "{{currencySymbol}} 不足", + "swap.warning.insufficientGas.button.bridge": "在 {{networkName}}上交換 {{ tokenSymbol }}", "swap.warning.insufficientGas.button.buy": "買 {{ tokenSymbol }}", + "swap.warning.insufficientGas.button.buyWithCard": "用卡片購買", "swap.warning.insufficientGas.message.withNetwork": "{{networkName}} 上的 {{currencySymbol}} 不足,無法兌換", "swap.warning.insufficientGas.message.withoutNetwork": "{{currencySymbol}}不足,無法兌換", "swap.warning.insufficientGas.title": "您的 {{currencySymbol}} 不足,無法支付網路費用", @@ -1862,8 +1880,10 @@ "tdp.loading.title.withChain": "{{chainName}} 上 的代幣資料", "tdp.nameNotFound": "找不到名稱", "tdp.noInfoAvailable": "沒有可用的代幣資訊", + "tdp.noTestnetSupportDescription": "某些測試網不支援交換、發送或購買代幣。", "tdp.stats.unsupportedChainDescription": "{{chain}} 的代幣統計資料和圖表可在 {{infoLink}} 上找到", "tdp.symbolNotFound": "找不到 Symbol", + "testnet.unsupported": "測試網模式不支援此功能。", "themeToggle.theme": "主題", "title.betterPricesMoreListings": "更好的價格。更多列出。在 OpenSea 等頂級市場上購買、出售和交易 NFT。探索熱門選集。", "title.buySellTradeEthereum": "在 Uniswap 上購買、出售和交易乙太坊和其他頂級代幣", @@ -1896,6 +1916,7 @@ "token.balances.viewOnly": "{{ownerAddress}} 的餘額", "token.bridge": "{{label}} 代幣橋", "token.chart.tooltip": "費用:{{amount}}", + "token.details.testnet.unsupported": "測試網代幣的代幣詳細資料不可用。", "token.error.unknown": "未知代幣", "token.fee.buy.label": "購買費用", "token.fee.label": "費用", @@ -2212,7 +2233,6 @@ "walletConnect.pending.switchAccount": "切換帳戶", "walletConnect.pending.switchNetwork": "切換網路", "walletConnect.pending.title": "連線到 {{dappName}}", - "walletConnect.permissions.networks": "網路", "walletConnect.permissions.option.transferAssets": "未經同意轉移資產", "walletConnect.permissions.option.viewTokenBalances": "檢視您的代幣餘額", "walletConnect.permissions.option.viewWalletAddress": "檢視您的錢包地址", diff --git a/packages/uniswap/src/react-native-dotenv.d.ts b/packages/uniswap/src/react-native-dotenv.d.ts index 811436e8eb4..3cf09439c61 100644 --- a/packages/uniswap/src/react-native-dotenv.d.ts +++ b/packages/uniswap/src/react-native-dotenv.d.ts @@ -24,7 +24,10 @@ declare module 'react-native-dotenv' { export const QUICKNODE_POLYGON_RPC_URL: string export const QUICKNODE_ZORA_RPC_URL: string export const QUICKNODE_ZKSYNC_RPC_URL: string + export const QUICKNODE_WORLDCHAIN_RPC_URL: string + export const QUICKNODE_ASTROCHAIN_SEPOLIA_RPC_URL: string export const QUICKNODE_MAINNET_RPC_URL: string + export const QUICKNODE_SEPOLIA_RPC_URL: string export const TRADING_API_KEY: string export const FIREBASE_APP_CHECK_DEBUG_TOKEN: string } diff --git a/packages/uniswap/src/test/fixtures/gql/misc.ts b/packages/uniswap/src/test/fixtures/gql/misc.ts index e6cf1af9781..1ca6ff8bd14 100644 --- a/packages/uniswap/src/test/fixtures/gql/misc.ts +++ b/packages/uniswap/src/test/fixtures/gql/misc.ts @@ -4,8 +4,8 @@ import { createFixture } from 'uniswap/src/test/utils' export const GQL_CHAINS = [ Chain.Ethereum, + Chain.EthereumSepolia, Chain.Arbitrum, - Chain.EthereumGoerli, Chain.Optimism, Chain.Polygon, Chain.Base, diff --git a/packages/uniswap/src/test/fixtures/lib/sdk.ts b/packages/uniswap/src/test/fixtures/lib/sdk.ts index aa68c7eeadf..a9958e2f270 100644 --- a/packages/uniswap/src/test/fixtures/lib/sdk.ts +++ b/packages/uniswap/src/test/fixtures/lib/sdk.ts @@ -1,14 +1,9 @@ import { Token } from '@uniswap/sdk-core' import { getWrappedNativeAddress } from 'uniswap/src/constants/addresses' +import { DEFAULT_NATIVE_ADDRESS } from 'uniswap/src/constants/chains' import { UniverseChainId } from 'uniswap/src/types/chains' -export const ETH = new Token( - UniverseChainId.Mainnet, - '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', - 18, - 'ETH', - 'Ethereum', -) +export const ETH = new Token(UniverseChainId.Mainnet, DEFAULT_NATIVE_ADDRESS, 18, 'ETH', 'Ethereum') export const WETH = new Token( UniverseChainId.Mainnet, @@ -74,14 +69,6 @@ export const USDC_POLYGON = new Token( 'USD//C', ) -export const USDC_GOERLI = new Token( - UniverseChainId.Polygon, - '0x07865c6e87b9f70255377e024ace6630c1eaa37f', - 6, - 'USDC', - 'USD//C', -) - export const USDT = new Token( UniverseChainId.Mainnet, '0xdac17f958d2ee523a2206206994597c13d831ec7', @@ -115,7 +102,6 @@ export const SDK_TOKENS = [ USDBC_BASE, USDC_OPTIMISM, USDC_POLYGON, - USDC_GOERLI, USDT, USDT_BNB, WBTC, diff --git a/packages/uniswap/src/test/fixtures/transactions/swap.ts b/packages/uniswap/src/test/fixtures/transactions/swap.ts index 4208673130c..9b73e0df1c4 100644 --- a/packages/uniswap/src/test/fixtures/transactions/swap.ts +++ b/packages/uniswap/src/test/fixtures/transactions/swap.ts @@ -55,6 +55,7 @@ export const createMockTradeWithStatus = ( outputAmount, }, ], + v4Routes: [], mixedRoutes: [], }), indicativeTrade: undefined, diff --git a/packages/uniswap/src/test/fixtures/wallet/currencies.ts b/packages/uniswap/src/test/fixtures/wallet/currencies.ts index 7da42cfbb15..eb566e5012b 100644 --- a/packages/uniswap/src/test/fixtures/wallet/currencies.ts +++ b/packages/uniswap/src/test/fixtures/wallet/currencies.ts @@ -13,6 +13,7 @@ export const OPTIMISM_CURRENCY = NativeCurrency.onChain(UniverseChainId.Optimism export const POLYGON_CURRENCY = NativeCurrency.onChain(UniverseChainId.Polygon) export const CELO_CURRENCY = NativeCurrency.onChain(UniverseChainId.Celo) export const AVALANCHE_CURRENCY = NativeCurrency.onChain(UniverseChainId.Avalanche) +export const WORLD_CHAIN_CURRENCY = NativeCurrency.onChain(UniverseChainId.WorldChain) export const ZORA_CURRENCY = NativeCurrency.onChain(UniverseChainId.Zora) export const ZKSYNC_CURRENCY = NativeCurrency.onChain(UniverseChainId.Zksync) diff --git a/packages/uniswap/src/test/fixtures/wallet/transactions/fixtures.ts b/packages/uniswap/src/test/fixtures/wallet/transactions/fixtures.ts index db40d8c5434..c36a0a06ccc 100644 --- a/packages/uniswap/src/test/fixtures/wallet/transactions/fixtures.ts +++ b/packages/uniswap/src/test/fixtures/wallet/transactions/fixtures.ts @@ -32,11 +32,11 @@ import { import { dappInfoWC } from 'uniswap/src/test/fixtures/wallet/walletConnect' import { faker } from 'uniswap/src/test/shared' import { createFixture, randomChoice, randomEnumValue } from 'uniswap/src/test/utils' -import { WALLET_SUPPORTED_CHAIN_IDS } from 'uniswap/src/types/chains' +import { SUPPORTED_CHAIN_IDS } from 'uniswap/src/types/chains' export const transactionId = createFixture()(() => ({ id: faker.datatype.uuid(), - chainId: randomChoice(WALLET_SUPPORTED_CHAIN_IDS), + chainId: randomChoice(SUPPORTED_CHAIN_IDS), })) export const nftSummaryInfo = createFixture()(() => ({ diff --git a/packages/uniswap/src/types/chains.ts b/packages/uniswap/src/types/chains.ts index 4bc62d34a36..41a1e0d6030 100644 --- a/packages/uniswap/src/types/chains.ts +++ b/packages/uniswap/src/types/chains.ts @@ -8,42 +8,23 @@ import { Chain as WagmiChain } from 'wagmi/chains' export enum UniverseChainId { Mainnet = UniswapSDKChainId.MAINNET, - Goerli = UniswapSDKChainId.GOERLI, Sepolia = UniswapSDKChainId.SEPOLIA, Optimism = UniswapSDKChainId.OPTIMISM, - OptimismGoerli = UniswapSDKChainId.OPTIMISM_GOERLI, ArbitrumOne = UniswapSDKChainId.ARBITRUM_ONE, - ArbitrumGoerli = UniswapSDKChainId.ARBITRUM_GOERLI, Polygon = UniswapSDKChainId.POLYGON, - PolygonMumbai = UniswapSDKChainId.POLYGON_MUMBAI, Avalanche = UniswapSDKChainId.AVALANCHE, Celo = UniswapSDKChainId.CELO, - CeloAlfajores = UniswapSDKChainId.CELO_ALFAJORES, Bnb = UniswapSDKChainId.BNB, Base = UniswapSDKChainId.BASE, Blast = UniswapSDKChainId.BLAST, + WorldChain = UniswapSDKChainId.WORLDCHAIN, Zora = UniswapSDKChainId.ZORA, Zksync = UniswapSDKChainId.ZKSYNC, + AstrochainSepolia = UniswapSDKChainId.ASTROCHAIN_SEPOLIA, } -export type WalletChainId = - | UniverseChainId.Mainnet - | UniverseChainId.Goerli - | UniverseChainId.ArbitrumOne - | UniverseChainId.Avalanche - | UniverseChainId.Base - | UniverseChainId.Celo - | UniverseChainId.Optimism - | UniverseChainId.Polygon - | UniverseChainId.PolygonMumbai - | UniverseChainId.Blast - | UniverseChainId.Bnb - | UniverseChainId.Zora - | UniverseChainId.Zksync - // DON'T CHANGE - order here determines ordering of networks in app -// TODO: [MOB-250] Add back in testnets once our endpoints support them -export const WALLET_SUPPORTED_CHAIN_IDS: WalletChainId[] = [ +export const SUPPORTED_CHAIN_IDS: UniverseChainId[] = [ UniverseChainId.Mainnet, UniverseChainId.Polygon, UniverseChainId.ArbitrumOne, @@ -53,31 +34,14 @@ export const WALLET_SUPPORTED_CHAIN_IDS: WalletChainId[] = [ UniverseChainId.Blast, UniverseChainId.Avalanche, UniverseChainId.Celo, + UniverseChainId.WorldChain, UniverseChainId.Zora, UniverseChainId.Zksync, ] -export type InterfaceChainId = UniverseChainId +export const SUPPORTED_TESTNET_CHAIN_IDS: UniverseChainId[] = [UniverseChainId.Sepolia, UniverseChainId.AstrochainSepolia] -export const WEB_SUPPORTED_CHAIN_IDS: InterfaceChainId[] = [ - UniverseChainId.Mainnet, - UniverseChainId.Goerli, - UniverseChainId.Sepolia, - UniverseChainId.Optimism, - UniverseChainId.OptimismGoerli, - UniverseChainId.ArbitrumOne, - UniverseChainId.ArbitrumGoerli, - UniverseChainId.Polygon, - UniverseChainId.PolygonMumbai, - UniverseChainId.Avalanche, - UniverseChainId.Celo, - UniverseChainId.CeloAlfajores, - UniverseChainId.Bnb, - UniverseChainId.Base, - UniverseChainId.Blast, - UniverseChainId.Zora, - UniverseChainId.Zksync, -] +export const COMBINED_CHAIN_IDS: UniverseChainId[] = [...SUPPORTED_CHAIN_IDS, ...SUPPORTED_TESTNET_CHAIN_IDS] export enum RPCType { Public = 'public', @@ -96,7 +60,7 @@ export interface RetryOptions { maxWait: number } -export type InterfaceGqlChain = Exclude +export type InterfaceGqlChain = Exclude export interface BackendChain { chain: InterfaceGqlChain diff --git a/packages/uniswap/src/utils/colors.tsx b/packages/uniswap/src/utils/colors.tsx index 28a34131d07..ea519e9a9be 100644 --- a/packages/uniswap/src/utils/colors.tsx +++ b/packages/uniswap/src/utils/colors.tsx @@ -1,18 +1,18 @@ import { useMemo } from 'react' import { useExtractedColors, useSporeColors } from 'ui/src' import { GlobalColorNames, colors as GlobalColors, GlobalPalette, colorsLight, opacify } from 'ui/src/theme' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { assert } from 'utilities/src/errors' import { hex } from 'wcag-contrast' export const MIN_COLOR_CONTRAST_THRESHOLD = 3 -export function getNetworkColorKey(chainId: WalletChainId): `chain_${WalletChainId}` { +export function getNetworkColorKey(chainId: UniverseChainId): `chain_${UniverseChainId}` { return `chain_${chainId}` } /** Helper to retrieve foreground and background colors for a given chain */ -export function useNetworkColors(chainId: WalletChainId): { +export function useNetworkColors(chainId: UniverseChainId): { foreground: string background: string } { diff --git a/packages/uniswap/src/utils/currencyId.ts b/packages/uniswap/src/utils/currencyId.ts index c50c350eaf5..d140d4878c1 100644 --- a/packages/uniswap/src/utils/currencyId.ts +++ b/packages/uniswap/src/utils/currencyId.ts @@ -76,8 +76,8 @@ export function currencyIdToAddress(_currencyId: string): Address { return currencyIdParts[1] } -function isPolygonChain(chainId: number): chainId is UniverseChainId.Polygon | UniverseChainId.PolygonMumbai { - return chainId === UniverseChainId.PolygonMumbai || chainId === UniverseChainId.Polygon +function isPolygonChain(chainId: number): chainId is UniverseChainId.Polygon { + return chainId === UniverseChainId.Polygon } function isCeloChain(chainId: number): chainId is UniverseChainId.Celo { diff --git a/packages/uniswap/src/utils/getRoutingDiagramEntries.ts b/packages/uniswap/src/utils/getRoutingDiagramEntries.ts index 1634ad06436..ecf76334bd4 100644 --- a/packages/uniswap/src/utils/getRoutingDiagramEntries.ts +++ b/packages/uniswap/src/utils/getRoutingDiagramEntries.ts @@ -1,12 +1,12 @@ import { Protocol, ZERO } from '@uniswap/router-sdk' import { Currency, Percent, TradeType } from '@uniswap/sdk-core' import { Pair } from '@uniswap/v2-sdk' -import { FeeAmount } from '@uniswap/v3-sdk' +import { FeeAmount, Pool as V3Pool } from '@uniswap/v3-sdk' import { ClassicTrade } from 'uniswap/src/features/transactions/swap/types/trade' export interface RoutingDiagramEntry { percent: Percent - path: [Currency, Currency, FeeAmount][] + path: [Currency, Currency, FeeAmount, Protocol][] protocol: Protocol } @@ -34,10 +34,14 @@ export default function getRoutingDiagramEntries(trade: ClassicTrade): RoutingDi break } + const poolProtocol = + nextPool instanceof Pair ? Protocol.V2 : nextPool instanceof V3Pool ? Protocol.V3 : Protocol.V4 + const entry: RoutingDiagramEntry['path'][0] = [ tokenIn, tokenOut, nextPool instanceof Pair ? V2_DEFAULT_FEE_TIER : nextPool.fee, + poolProtocol, ] path.push(entry) } diff --git a/packages/uniswap/src/utils/linking.test.ts b/packages/uniswap/src/utils/linking.test.ts index d14257538ef..18000fb0d04 100644 --- a/packages/uniswap/src/utils/linking.test.ts +++ b/packages/uniswap/src/utils/linking.test.ts @@ -12,14 +12,11 @@ describe(getExplorerLink, () => { expect(getExplorerLink(UniverseChainId.Polygon, 'hash', ExplorerDataType.TOKEN)).toEqual( 'https://polygonscan.com/token/hash', ) - expect(getExplorerLink(UniverseChainId.PolygonMumbai, 'hash', ExplorerDataType.BLOCK)).toEqual( - 'https://mumbai.polygonscan.com/block/hash', - ) }) it('handles chain with explorer URL', () => { - expect(getExplorerLink(UniverseChainId.Goerli, 'hash', ExplorerDataType.TRANSACTION)).toEqual( - 'https://goerli.etherscan.io/tx/hash', + expect(getExplorerLink(UniverseChainId.Sepolia, 'hash', ExplorerDataType.TRANSACTION)).toEqual( + 'https://sepolia.etherscan.io/tx/hash', ) }) diff --git a/packages/utilities/package.json b/packages/utilities/package.json index 8b68309a1ba..4794865a0ad 100644 --- a/packages/utilities/package.json +++ b/packages/utilities/package.json @@ -21,8 +21,8 @@ "@sentry/react": "7.80.0", "@sentry/react-native": "5.5.0", "@uniswap/analytics": "1.7.0", - "@uniswap/analytics-events": "2.37.0", - "@uniswap/sdk-core": "5.3.0", + "@uniswap/analytics-events": "2.38.0", + "@uniswap/sdk-core": "5.8.0", "aws-appsync-auth-link": "3.0.7", "aws-appsync-subscription-link": "3.1.3", "dayjs": "1.11.7", diff --git a/packages/utilities/src/logger/Datadog.native.ts b/packages/utilities/src/logger/Datadog.native.ts index 0a83729747e..359f8da2950 100644 --- a/packages/utilities/src/logger/Datadog.native.ts +++ b/packages/utilities/src/logger/Datadog.native.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { DdLogs, DdRum, ErrorSource, RumActionType } from '@datadog/mobile-react-native' +import { DdLogs, DdRum, DdSdkReactNative, ErrorSource, RumActionType } from '@datadog/mobile-react-native' import dayjs from 'dayjs' import { AnyAction, PreloadedState, Reducer, StoreEnhancerStoreCreator } from 'redux' import { addErrorExtras } from 'utilities/src/logger/logger' @@ -125,3 +125,7 @@ export function attachUnhandledRejectionHandler(): void { }, }) } + +export async function setAttributesToDatadog(attributes: { [key: string]: unknown }): Promise { + await DdSdkReactNative.setAttributes(attributes) +} diff --git a/packages/utilities/src/logger/Datadog.ts b/packages/utilities/src/logger/Datadog.ts index c0ef28155aa..300b6e9ee55 100644 --- a/packages/utilities/src/logger/Datadog.ts +++ b/packages/utilities/src/logger/Datadog.ts @@ -48,3 +48,7 @@ export function logErrorToDatadog(_error: Error, _context?: LoggerErrorContext): export function attachUnhandledRejectionHandler(): void { throw new PlatformSplitStubError('attachUnhandledRejectionHandler') } + +export async function setAttributesToDatadog(_attributes: { [key: string]: unknown }): Promise { + throw new PlatformSplitStubError('setAttributes') +} diff --git a/packages/utilities/src/logger/Datadog.web.ts b/packages/utilities/src/logger/Datadog.web.ts index ee8c0e7136d..239c3226cbc 100644 --- a/packages/utilities/src/logger/Datadog.web.ts +++ b/packages/utilities/src/logger/Datadog.web.ts @@ -90,3 +90,7 @@ export function logErrorToDatadog(error: Error, context?: LoggerErrorContext): v export function attachUnhandledRejectionHandler(): void { throw new NotImplementedError('attachUnhandledRejectionHandler') } + +export async function setAttributesToDatadog(_attributes: { [key: string]: unknown }): Promise { + throw new NotImplementedError('setAttributes') +} diff --git a/packages/utilities/src/logger/Sentry.native.ts b/packages/utilities/src/logger/Sentry.native.ts index f6a7f32dfd3..13df2038f25 100644 --- a/packages/utilities/src/logger/Sentry.native.ts +++ b/packages/utilities/src/logger/Sentry.native.ts @@ -33,8 +33,13 @@ function addBreadCrumb(breadCrumb: BreadCrumb): void { SentryRN.addBreadcrumb(breadCrumb) } +function setTag(key: string, value: Primitive): void { + SentryRN.setTag(key, value) +} + export const Sentry: ISentry = { captureException, captureMessage, addBreadCrumb, + setTag, } as ISentry diff --git a/packages/utilities/src/logger/Sentry.ts b/packages/utilities/src/logger/Sentry.ts index 84af2c97d53..399764e78aa 100644 --- a/packages/utilities/src/logger/Sentry.ts +++ b/packages/utilities/src/logger/Sentry.ts @@ -1,4 +1,4 @@ -import { SeverityLevel } from '@sentry/types' +import { Primitive, SeverityLevel } from '@sentry/types' import { PlatformSplitStubError } from 'utilities/src/errors' import { LoggerErrorContext } from 'utilities/src/logger/types' @@ -16,6 +16,7 @@ export interface ISentry { captureException(error: unknown, captureContext: LoggerErrorContext): void captureMessage(level: SeverityLevel, context: string, message: string, ...extraArgs: unknown[]): void addBreadCrumb(breadCrumb: BreadCrumb): void + setTag(key: string, value: Primitive): void } /** This will be overridden by the compiler with platform-specific Sentry file. */ @@ -29,4 +30,7 @@ export const Sentry: ISentry = { addBreadCrumb: (_breadCrumb: BreadCrumb) => { throw new PlatformSplitStubError('Sentry not implemented') }, + setTag: (_key: string, _value: Primitive) => { + throw new PlatformSplitStubError('Sentry not implemented') + }, } diff --git a/packages/utilities/src/logger/Sentry.web.ts b/packages/utilities/src/logger/Sentry.web.ts index f7087c079f0..283f6b79ac1 100644 --- a/packages/utilities/src/logger/Sentry.web.ts +++ b/packages/utilities/src/logger/Sentry.web.ts @@ -33,8 +33,13 @@ function addBreadCrumb(breadCrumb: BreadCrumb): void { SentryReact.addBreadcrumb(breadCrumb) } +function setTag(key: string, value: Primitive): void { + SentryReact.setTag(key, value) +} + export const Sentry: ISentry = { captureException, captureMessage, addBreadCrumb, + setTag, } as ISentry diff --git a/packages/utilities/src/telemetry/analytics/analytics.native.ts b/packages/utilities/src/telemetry/analytics/analytics.native.ts index c2a7a910f9c..c7af4589942 100644 --- a/packages/utilities/src/telemetry/analytics/analytics.native.ts +++ b/packages/utilities/src/telemetry/analytics/analytics.native.ts @@ -14,6 +14,7 @@ import { generateAnalyticsLoggers } from 'utilities/src/telemetry/analytics/logg const loggers = generateAnalyticsLoggers('telemetry/analytics.native') let allowAnalytics: Maybe +let testnetMode: Maybe let userId: Maybe export async function getAnalyticsAtomDirect(_forceRead?: boolean): Promise { @@ -67,10 +68,16 @@ export const analytics: Analytics = { setDeviceId(ANONYMOUS_DEVICE_ID) } }, + setTestnetMode(enabled: boolean): void { + testnetMode = enabled + }, sendEvent(eventName: string, eventProperties?: Record): void { if (!allowAnalytics && !ANONYMOUS_EVENT_NAMES.includes(eventName)) { return } + if (testnetMode) { + return + } loggers.sendEvent(eventName, eventProperties) track(eventName, eventProperties) }, diff --git a/packages/utilities/src/telemetry/analytics/analytics.ts b/packages/utilities/src/telemetry/analytics/analytics.ts index f83e01c391d..48568c84592 100644 --- a/packages/utilities/src/telemetry/analytics/analytics.ts +++ b/packages/utilities/src/telemetry/analytics/analytics.ts @@ -16,6 +16,7 @@ export interface Analytics { userIdGetter?: () => Promise, ): Promise setAllowAnalytics(allowed: boolean): Promise + setTestnetMode(enabled: boolean): void sendEvent(eventName: string, eventProperties: Record): void flushEvents(): void setUserProperty(property: string, value: UserPropertyValue, insert?: boolean): void @@ -33,6 +34,9 @@ export const analytics: Analytics = { setAllowAnalytics(_allowed: boolean): Promise { throw new PlatformSplitStubError('flushAnalyticsEvents') }, + setTestnetMode(_enabled: boolean): void { + throw new PlatformSplitStubError('setTestnetMode') + }, sendEvent(_eventName: string, ..._eventProperties: unknown[]): void { throw new PlatformSplitStubError('sendAnalyticsEvent') }, diff --git a/packages/utilities/src/telemetry/analytics/analytics.web.ts b/packages/utilities/src/telemetry/analytics/analytics.web.ts index 82a1b283e26..54f7680cf44 100644 --- a/packages/utilities/src/telemetry/analytics/analytics.web.ts +++ b/packages/utilities/src/telemetry/analytics/analytics.web.ts @@ -14,6 +14,7 @@ import { generateAnalyticsLoggers } from 'utilities/src/telemetry/analytics/logg const loggers = generateAnalyticsLoggers('telemetry/analytics.web') let allowAnalytics: boolean = true +let testnetMode: boolean = false let commitHash: Maybe let userId: Maybe @@ -100,10 +101,16 @@ export const analytics: Analytics = { setDeviceId(ANONYMOUS_DEVICE_ID) } }, + setTestnetMode(enabled: boolean): void { + testnetMode = enabled + }, async sendEvent(eventName: string, eventProperties?: Record): Promise { if (!(await getAnalyticsAtomDirect()) && !ANONYMOUS_EVENT_NAMES.includes(eventName)) { return } + if (testnetMode) { + return + } const finalProperties = { ...eventProperties, ...(commitHash ? { git_commit_hash: commitHash } : {}), diff --git a/packages/wallet/.eslintignore b/packages/wallet/.eslintignore index ea1bf72da19..644c128adc6 100644 --- a/packages/wallet/.eslintignore +++ b/packages/wallet/.eslintignore @@ -1,3 +1 @@ **/__generated__/** - -/src/i18n/locales/@types/resources.d.ts diff --git a/packages/wallet/package.json b/packages/wallet/package.json index fc2536e0c07..3b345775351 100644 --- a/packages/wallet/package.json +++ b/packages/wallet/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@apollo/client": "3.10.4", + "@ethersproject/abstract-signer": "5.7.0", "@ethersproject/constants": "5.7.0", "@ethersproject/contracts": "5.7.0", "@ethersproject/providers": "5.7.2", @@ -23,13 +24,13 @@ "@scure/bip32": "1.3.2", "@sentry/types": "7.80.0", "@shopify/flash-list": "1.6.3", - "@uniswap/analytics-events": "2.37.0", + "@uniswap/analytics-events": "2.38.0", "@uniswap/permit2-sdk": "1.3.0", - "@uniswap/router-sdk": "1.9.2", - "@uniswap/sdk-core": "5.3.0", + "@uniswap/router-sdk": "1.14.2", + "@uniswap/sdk-core": "5.8.0", "@uniswap/uniswapx-sdk": "^2.1.0-beta.14", - "@uniswap/universal-router-sdk": "2.2.0", - "@uniswap/v3-sdk": "3.14.0", + "@uniswap/universal-router-sdk": "4.2.0", + "@uniswap/v3-sdk": "3.17.0", "apollo3-cache-persist": "0.14.1", "axios": "1.6.5", "dayjs": "1.11.7", diff --git a/packages/wallet/src/components/CurrencyLogo/LogoWithTxStatus.test.tsx b/packages/wallet/src/components/CurrencyLogo/LogoWithTxStatus.test.tsx index fe8feb7c189..bad42eafbc9 100644 --- a/packages/wallet/src/components/CurrencyLogo/LogoWithTxStatus.test.tsx +++ b/packages/wallet/src/components/CurrencyLogo/LogoWithTxStatus.test.tsx @@ -2,7 +2,7 @@ import { AssetType } from 'uniswap/src/entities/assets' import { TransactionStatus, TransactionType } from 'uniswap/src/features/transactions/types/transactionDetails' import { ETH_CURRENCY_INFO, ethCurrencyInfo } from 'uniswap/src/test/fixtures/wallet/currencies' import { createFixture, randomChoice, randomEnumValue } from 'uniswap/src/test/utils' -import { UniverseChainId, WALLET_SUPPORTED_CHAIN_IDS, WalletChainId } from 'uniswap/src/types/chains' +import { SUPPORTED_CHAIN_IDS, UniverseChainId } from 'uniswap/src/types/chains' import { WalletConnectEvent } from 'uniswap/src/types/walletConnect' import { DappLogoWithTxStatus, @@ -22,7 +22,7 @@ const currencyLogoProps = createFixture()(() => ({ currencyInfo: ethCurrencyInfo(), txStatus: randomEnumValue(TransactionStatus), size: 40, - chainId: randomChoice(WALLET_SUPPORTED_CHAIN_IDS), + chainId: randomChoice(SUPPORTED_CHAIN_IDS), })) const nftLogoProps = createFixture()(() => ({ @@ -30,7 +30,7 @@ const nftLogoProps = createFixture()(() => ({ txType: TransactionType.NFTMint, txStatus: randomEnumValue(TransactionStatus), size: 40, - chainId: randomChoice(WALLET_SUPPORTED_CHAIN_IDS), + chainId: randomChoice(SUPPORTED_CHAIN_IDS), })) describe(LogoWithTxStatus, () => { @@ -191,7 +191,7 @@ describe(DappLogoWithTxStatus, () => { const props = { event: WalletConnectEvent.Connected, size: 40, - chainId: UniverseChainId.ArbitrumOne as WalletChainId, + chainId: UniverseChainId.ArbitrumOne as UniverseChainId, dappImageUrl: 'https://example.com/dapp.png', dappName: 'Dapp', } @@ -259,7 +259,7 @@ describe(DappLogoWithTxStatus, () => { describe(DappLogoWithWCBadge, () => { const props = { size: 40, - chainId: UniverseChainId.ArbitrumOne as WalletChainId, + chainId: UniverseChainId.ArbitrumOne as UniverseChainId, dappImageUrl: 'https://example.com/dapp.png', dappName: 'Dapp', } diff --git a/packages/wallet/src/components/CurrencyLogo/LogoWithTxStatus.tsx b/packages/wallet/src/components/CurrencyLogo/LogoWithTxStatus.tsx index 16e5b922808..14fef9cdf68 100644 --- a/packages/wallet/src/components/CurrencyLogo/LogoWithTxStatus.tsx +++ b/packages/wallet/src/components/CurrencyLogo/LogoWithTxStatus.tsx @@ -21,7 +21,7 @@ import { TransactionStatus, TransactionType, } from 'uniswap/src/features/transactions/types/transactionDetails' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { WalletConnectEvent } from 'uniswap/src/types/walletConnect' import { logger } from 'utilities/src/logger/logger' import { DappIconPlaceholder } from 'wallet/src/components/WalletConnect/DappIconPlaceholder' @@ -34,13 +34,13 @@ interface LogoWithTxStatusBaseProps { txType: TransactionType txStatus: TransactionStatus size: number - chainId: WalletChainId | null + chainId: UniverseChainId | null } interface DappLogoWithTxStatusProps { event: WalletConnectEvent size: number - chainId: WalletChainId | null + chainId: UniverseChainId | null dappImageUrl: Maybe dappName: string } @@ -239,7 +239,7 @@ export function DappLogoWithWCBadge({ dappImageUrl: Maybe dappName: string size: number - chainId: WalletChainId | null + chainId: UniverseChainId | null hideWCBadge?: boolean circular?: boolean }): JSX.Element { diff --git a/packages/wallet/src/components/CurrencyLogo/__snapshots__/LogoWithTxStatus.test.tsx.snap b/packages/wallet/src/components/CurrencyLogo/__snapshots__/LogoWithTxStatus.test.tsx.snap index 0f8dc1a6638..49b08b466f6 100644 --- a/packages/wallet/src/components/CurrencyLogo/__snapshots__/LogoWithTxStatus.test.tsx.snap +++ b/packages/wallet/src/components/CurrencyLogo/__snapshots__/LogoWithTxStatus.test.tsx.snap @@ -130,19 +130,19 @@ exports[`DappLogoWithWCBadge renders without error 1`] = ` style={ { "borderBottomColor": "rgba(255,255,255,1.00)", - "borderBottomLeftRadius": "6.6px", - "borderBottomRightRadius": "6.6px", - "borderBottomWidth": "1px", + "borderBottomLeftRadius": "6.8999999999999995px", + "borderBottomRightRadius": "6.8999999999999995px", + "borderBottomWidth": "1.5px", "borderLeftColor": "rgba(255,255,255,1.00)", - "borderLeftWidth": "1px", + "borderLeftWidth": "1.5px", "borderRightColor": "rgba(255,255,255,1.00)", - "borderRightWidth": "1px", + "borderRightWidth": "1.5px", "borderTopColor": "rgba(255,255,255,1.00)", - "borderTopLeftRadius": "6.6px", - "borderTopRightRadius": "6.6px", - "borderTopWidth": "1px", - "height": "22px", - "width": "22px", + "borderTopLeftRadius": "6.8999999999999995px", + "borderTopRightRadius": "6.8999999999999995px", + "borderTopWidth": "1.5px", + "height": "23px", + "width": "23px", } } > diff --git a/packages/wallet/src/components/QRCodeScanner/WalletQRCode.tsx b/packages/wallet/src/components/QRCodeScanner/WalletQRCode.tsx index 773d1f9c93f..7406cc2d072 100644 --- a/packages/wallet/src/components/QRCodeScanner/WalletQRCode.tsx +++ b/packages/wallet/src/components/QRCodeScanner/WalletQRCode.tsx @@ -1,12 +1,10 @@ import { useTranslation } from 'react-i18next' import { Flex, QRCodeDisplay, Text, isWeb, useMedia, useSporeColors } from 'ui/src' -import { iconSizes, spacing } from 'ui/src/theme' +import { spacing } from 'ui/src/theme' import { NetworkLogos } from 'uniswap/src/components/network/NetworkLogos' -import { LearnMoreLink } from 'uniswap/src/components/text/LearnMoreLink' -import { uniswapUrls } from 'uniswap/src/constants/urls' import { useAvatar } from 'uniswap/src/features/address/avatar' import { useAddressColorProps } from 'uniswap/src/features/address/color' -import { WALLET_SUPPORTED_CHAIN_IDS } from 'uniswap/src/types/chains' +import { SUPPORTED_CHAIN_IDS } from 'uniswap/src/types/chains' import { isExtension } from 'utilities/src/platform' import { AccountIcon } from 'wallet/src/components/accounts/AccountIcon' import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' @@ -62,17 +60,10 @@ export function WalletQRCode({ address }: { address: Address }): JSX.Element | n size={UNICON_SIZE} /> - - {t('qrScanner.wallet.title')} + {t('qrScanner.wallet.title', { numOfNetworks: SUPPORTED_CHAIN_IDS.length })} - + ) } diff --git a/packages/wallet/src/components/RecipientSearch/RecipientList.tsx b/packages/wallet/src/components/RecipientSearch/RecipientList.tsx index 85c745d88d4..8762fbe31d0 100644 --- a/packages/wallet/src/components/RecipientSearch/RecipientList.tsx +++ b/packages/wallet/src/components/RecipientSearch/RecipientList.tsx @@ -2,7 +2,7 @@ import { BottomSheetSectionList } from '@gorhom/bottom-sheet' import { memo, useCallback } from 'react' import { ListRenderItemInfo, SectionList, SectionListData } from 'react-native' import { FadeIn, FadeOut } from 'react-native-reanimated' -import { Text, TouchableArea, isWeb, useDeviceInsets } from 'ui/src' +import { Text, TouchableArea, isWeb } from 'ui/src' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { spacing } from 'ui/src/theme' import { AccountType } from 'uniswap/src/features/accounts/types' @@ -10,6 +10,7 @@ import { SearchableRecipient } from 'uniswap/src/features/address/types' import { SearchResultType, extractDomain } from 'uniswap/src/features/search/SearchResult' import { WalletEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay' interface RecipientListProps { @@ -19,7 +20,7 @@ interface RecipientListProps { } export function RecipientList({ onPress, sections, renderedInModal = false }: RecipientListProps): JSX.Element { - const insets = useDeviceInsets() + const insets = useAppInsets() const onRecipientPress = useCallback( (recipient: SearchableRecipient) => { diff --git a/packages/wallet/src/components/RecipientSearch/RecipientSelectSpeedBumps.tsx b/packages/wallet/src/components/RecipientSearch/RecipientSelectSpeedBumps.tsx index 3d8139c5f8c..74b6ce9140a 100644 --- a/packages/wallet/src/components/RecipientSearch/RecipientSelectSpeedBumps.tsx +++ b/packages/wallet/src/components/RecipientSearch/RecipientSelectSpeedBumps.tsx @@ -6,8 +6,9 @@ import { iconSizes } from 'ui/src/theme' import { PaginatedModalRenderer } from 'uniswap/src/components/modals/PaginatedModals' import { WarningModal } from 'uniswap/src/components/modals/WarningModal/WarningModal' import { WarningSeverity } from 'uniswap/src/components/modals/WarningModal/types' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { ModalName } from 'uniswap/src/features/telemetry/constants' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { isSameAddress } from 'utilities/src/addresses' import { NewAddressWarningModal } from 'wallet/src/components/RecipientSearch/modals/NewAddressWarningModal' import { ConditionalModalRenderer, SpeedBumps } from 'wallet/src/components/modals/SpeedBumps' @@ -22,7 +23,7 @@ import { interface RecipientSelectSpeedBumpsProps { recipientAddress?: string - chainId?: WalletChainId + chainId?: UniverseChainId checkSpeedBumps: boolean setCheckSpeedBumps: (value: boolean) => void onConfirm: () => void @@ -36,6 +37,7 @@ export function RecipientSelectSpeedBumps({ }: RecipientSelectSpeedBumpsProps): JSX.Element | null { const { t } = useTranslation() const colors = useSporeColors() + const { defaultChainId } = useEnabledChains() const activeAddress = useActiveAccountAddressWithThrow() const viewOnlyAccounts = useViewOnlyAccounts() @@ -43,11 +45,11 @@ export function RecipientSelectSpeedBumps({ const previousTransactions = useAllTransactionsBetweenAddresses(activeAddress, recipientAddress) const { isSmartContractAddress, loading: smartContractLoading } = useIsSmartContractAddress( recipientAddress, - chainId ?? UniverseChainId.Mainnet, + chainId ?? defaultChainId, ) const { isERC20ContractAddress, loading: erc20ContractLoading } = useIsErc20Contract( recipientAddress, - chainId ?? UniverseChainId.Mainnet, + chainId ?? defaultChainId, ) const renderViewOnlyWarning = useCallback( diff --git a/packages/wallet/src/components/landing/elements/PolygonElement.tsx b/packages/wallet/src/components/landing/elements/PolygonElement.tsx index 96a073d95c7..a06471eb0f8 100644 --- a/packages/wallet/src/components/landing/elements/PolygonElement.tsx +++ b/packages/wallet/src/components/landing/elements/PolygonElement.tsx @@ -1,5 +1,5 @@ import { Flex, useIsDarkMode } from 'ui/src' -import { PolygonPurple } from 'ui/src/components/logos' +import { PolygonPurple } from 'ui/src/components/logos/PolygonPurple' import { colors, imageSizes, opacify } from 'ui/src/theme' export const PolygonElement = (): JSX.Element => { diff --git a/packages/wallet/src/components/nfts/NftViewWithContextMenu.web.tsx b/packages/wallet/src/components/nfts/NftViewWithContextMenu.web.tsx index 636af89676f..f420526100f 100644 --- a/packages/wallet/src/components/nfts/NftViewWithContextMenu.web.tsx +++ b/packages/wallet/src/components/nfts/NftViewWithContextMenu.web.tsx @@ -2,7 +2,7 @@ import { useSelector } from 'react-redux' import { ContextMenu, Flex } from 'ui/src' import { fromGraphQLChain } from 'uniswap/src/features/chains/utils' import { selectNftsVisibility } from 'uniswap/src/features/favorites/selectors' -import { UniverseChainId } from 'uniswap/src/types/chains' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { NftView } from 'wallet/src/components/nfts/NftView' import { NftViewWithContextMenuProps } from 'wallet/src/components/nfts/NftViewProps' import { useNFTContextMenu } from 'wallet/src/features/nfts/useNftContextMenu' @@ -10,6 +10,7 @@ import { getIsNftHidden } from 'wallet/src/features/nfts/utils' // WALL-4875 TODO try to combine web and mobile versions export function NftViewWithContextMenu(props: NftViewWithContextMenuProps): JSX.Element { + const { defaultChainId } = useEnabledChains() const { owner, item } = props const { menuActions } = useNFTContextMenu({ @@ -17,7 +18,7 @@ export function NftViewWithContextMenu(props: NftViewWithContextMenuProps): JSX. tokenId: item.tokenId, owner, isSpam: item.isSpam, - chainId: fromGraphQLChain(item.chain) ?? UniverseChainId.Mainnet, + chainId: fromGraphQLChain(item.chain) ?? defaultChainId, }) const menuOptions = menuActions.map((action) => ({ diff --git a/packages/wallet/src/components/nfts/NftsList.tsx b/packages/wallet/src/components/nfts/NftsList.tsx index b085c5e34d7..f1f3fbf1d0c 100644 --- a/packages/wallet/src/components/nfts/NftsList.tsx +++ b/packages/wallet/src/components/nfts/NftsList.tsx @@ -11,6 +11,7 @@ import { useDeviceDimensions } from 'ui/src/hooks/useDeviceDimensions' import { BaseCard } from 'uniswap/src/components/BaseCard/BaseCard' import { useNftsTabQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { GQLQueries } from 'uniswap/src/data/graphql/uniswap-data-api/queries' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { WalletEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { HiddenNftsRow } from 'wallet/src/components/nfts/NFTHiddenRow' @@ -71,11 +72,17 @@ export const NftsList = forwardRef, NftsListProps>(function _ ) { const { t } = useTranslation() const { fullHeight } = useDeviceDimensions() + const { gqlChains } = useEnabledChains() const [hiddenNftsExpanded, setHiddenNftsExpanded] = useState(false) const { data, fetchMore, refetch, networkStatus } = useNftsTabQuery({ - variables: { ownerAddress: owner, first: NUM_FIRST_NFTS, filter: { filterSpam: false } }, + variables: { + ownerAddress: owner, + first: NUM_FIRST_NFTS, + filter: { filterSpam: false }, + chains: gqlChains, + }, notifyOnNetworkStatusChange: true, // Used to trigger network state / loading on refetch or fetchMore errorPolicy: 'all', // Suppress non-null image.url fields from backend }) diff --git a/packages/wallet/src/components/nfts/ShowNFTModal.tsx b/packages/wallet/src/components/nfts/ShowNFTModal.tsx index 04d91b4301b..f4835aa1b37 100644 --- a/packages/wallet/src/components/nfts/ShowNFTModal.tsx +++ b/packages/wallet/src/components/nfts/ShowNFTModal.tsx @@ -2,12 +2,12 @@ import { t } from 'i18next' import { useState } from 'react' import { Flex } from 'ui/src' import { ShieldCheck } from 'ui/src/components/icons' +import { InfoLinkModal } from 'uniswap/src/components/modals/InfoLinkModal' import { uniswapUrls } from 'uniswap/src/constants/urls' import { ModalName, WalletEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { isExtension } from 'utilities/src/platform' import { InformationBanner } from 'wallet/src/components/banners/InformationBanner' -import { InfoLinkModal } from 'wallet/src/components/modals/InfoLinkModal' export function ShowNFTModal(): JSX.Element { const [isModalVisible, setModalVisible] = useState(false) diff --git a/packages/wallet/src/contexts/WalletNavigationContext.tsx b/packages/wallet/src/contexts/WalletNavigationContext.tsx index badb24d95c2..d85b5fc9fda 100644 --- a/packages/wallet/src/contexts/WalletNavigationContext.tsx +++ b/packages/wallet/src/contexts/WalletNavigationContext.tsx @@ -4,7 +4,7 @@ import { AssetType } from 'uniswap/src/entities/assets' import { FiatOnRampCurrency } from 'uniswap/src/features/fiatOnRamp/types' import { getSwapPrefilledState } from 'uniswap/src/features/transactions/swap/hooks/useSwapPrefilledState' import { TransactionState } from 'uniswap/src/features/transactions/types/transactionState' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { NFTItem } from 'wallet/src/features/nfts/types' import { getSendPrefilledState } from 'wallet/src/features/transactions/send/getSendPrefilledState' @@ -16,7 +16,7 @@ type NavigateToTransactionFlowTransactionState = { type NavigateToSwapFlowPartialState = { currencyField: CurrencyField currencyAddress: Address - currencyChainId: WalletChainId + currencyChainId: UniverseChainId } type NavigateToSwapFlowWithActions = { @@ -24,7 +24,7 @@ type NavigateToSwapFlowWithActions = { } type NavigateToSendFlowPartialState = { - chainId: WalletChainId + chainId: UniverseChainId currencyAddress?: Address } @@ -56,7 +56,10 @@ function isNavigateToSendFlowArgsPartialState(args: NavigateToSendFlowArgs): arg return Boolean(args && (args as NavigateToSendFlowPartialState).chainId !== undefined) } -export function getNavigateToSwapFlowArgsInitialState(args: NavigateToSwapFlowArgs): TransactionState | undefined { +export function getNavigateToSwapFlowArgsInitialState( + args: NavigateToSwapFlowArgs, + defaultChainId: UniverseChainId, +): TransactionState | undefined { if (isNavigateToTransactionFlowArgsInitialState(args)) { return args.initialState } else if (isNavigateToSwapFlowArgsPartialState(args)) { @@ -65,7 +68,7 @@ export function getNavigateToSwapFlowArgsInitialState(args: NavigateToSwapFlowAr return { [CurrencyField.INPUT]: { address: DEFAULT_NATIVE_ADDRESS, - chainId: UniverseChainId.Mainnet, + chainId: defaultChainId, type: AssetType.Currency, }, [CurrencyField.OUTPUT]: null, @@ -90,7 +93,7 @@ export type NavigateToNftItemArgs = { owner?: Address address: Address tokenId: string - chainId?: WalletChainId + chainId?: UniverseChainId isSpam?: boolean fallbackData?: NFTItem } diff --git a/packages/wallet/src/features/accounts/hooks.ts b/packages/wallet/src/features/accounts/hooks.ts index 72bdb8eef54..f674de431c7 100644 --- a/packages/wallet/src/features/accounts/hooks.ts +++ b/packages/wallet/src/features/accounts/hooks.ts @@ -7,6 +7,7 @@ import { import { GqlResult } from 'uniswap/src/data/types' // eslint-disable-next-line no-restricted-imports import { usePortfolioValueModifiers } from 'uniswap/src/features/dataApi/balances' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' export function useAccountList({ addresses, @@ -22,9 +23,11 @@ export function useAccountList({ networkStatus: NetworkStatus refetch: () => void } { + const { gqlChains } = useEnabledChains() + const valueModifiers = usePortfolioValueModifiers(addresses) const { data, loading, networkStatus, refetch, startPolling, stopPolling } = useAccountListQuery({ - variables: { addresses, valueModifiers }, + variables: { addresses, valueModifiers, chains: gqlChains }, notifyOnNetworkStatusChange, fetchPolicy, }) diff --git a/packages/wallet/src/features/activity/hooks.ts b/packages/wallet/src/features/activity/hooks.ts index 28de10f7218..287b50831d5 100644 --- a/packages/wallet/src/features/activity/hooks.ts +++ b/packages/wallet/src/features/activity/hooks.ts @@ -9,6 +9,7 @@ import { import { usePersistedError } from 'uniswap/src/features/dataApi/utils' import { selectNftsVisibility } from 'uniswap/src/features/favorites/selectors' import { useLocalizedDayjs } from 'uniswap/src/features/language/localizedDayjs' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { useCurrencyIdToVisibility } from 'uniswap/src/features/transactions/selectors' import { TransactionDetails } from 'uniswap/src/features/transactions/types/transactionDetails' import { isNonPollingRequestInFlight } from 'wallet/src/data/utils' @@ -18,6 +19,7 @@ import { parseDataResponseToFeedTransactionDetails, parseDataResponseToTransactionDetails, } from 'wallet/src/features/transactions/history/utils' +import { useMergeLocalAndRemoteTransactions } from 'wallet/src/features/transactions/hooks' import { useAccounts } from 'wallet/src/features/wallet/hooks' const LOADING_ITEM = (index: number): LoadingItem => ({ itemType: 'LOADING', id: index }) @@ -34,6 +36,8 @@ export function useFormattedTransactionDataForFeed( keyExtractor: (item: TransactionDetails | SectionHeader | LoadingItem) => string onRetry: () => void } { + const { gqlChains } = useEnabledChains() + const { refetch, networkStatus, @@ -41,7 +45,7 @@ export function useFormattedTransactionDataForFeed( data, error: requestError, } = useFeedTransactionListQuery({ - variables: { addresses }, + variables: { addresses, chains: gqlChains }, notifyOnNetworkStatusChange: true, // TODO: determine how often to poll for feed - currently slow pollInterval: PollingInterval.Slow, @@ -122,10 +126,6 @@ export function useFormattedTransactionDataForFeed( export function useFormattedTransactionDataForActivity( address: Address, hideSpamTokens: boolean, - useMergeLocalFunction: ( - address: Address, - remoteTransactions: TransactionDetails[] | undefined, - ) => TransactionDetails[] | undefined, ): { hasData: boolean isLoading: boolean @@ -134,6 +134,8 @@ export function useFormattedTransactionDataForActivity( keyExtractor: (item: TransactionDetails | SectionHeader | LoadingItem) => string onRetry: () => void } { + const { gqlChains } = useEnabledChains() + const { refetch, networkStatus, @@ -141,7 +143,7 @@ export function useFormattedTransactionDataForActivity( data, error: requestError, } = useTransactionListQuery({ - variables: { address }, + variables: { address, chains: gqlChains }, notifyOnNetworkStatusChange: true, // rely on TransactionHistoryUpdater for polling pollInterval: undefined, @@ -175,7 +177,7 @@ export function useFormattedTransactionDataForActivity( return parseDataResponseToTransactionDetails(data, hideSpamTokens, nftVisibility, tokenVisibilityOverrides) }, [data, hideSpamTokens, tokenVisibilityOverrides, nftVisibility]) - const transactions = useMergeLocalFunction(address, formattedTransactions) + const transactions = useMergeLocalAndRemoteTransactions(address, formattedTransactions) // Format transactions for section list const localizedDayjs = useLocalizedDayjs() diff --git a/packages/wallet/src/features/activity/useActivityData.tsx b/packages/wallet/src/features/activity/useActivityData.tsx index 427a6351247..390bf0c7d35 100644 --- a/packages/wallet/src/features/activity/useActivityData.tsx +++ b/packages/wallet/src/features/activity/useActivityData.tsx @@ -13,7 +13,7 @@ import { useFormattedTransactionDataForActivity } from 'wallet/src/features/acti import { LoadingItem, SectionHeader } from 'wallet/src/features/activity/utils' import { SwapSummaryCallbacks } from 'wallet/src/features/transactions/SummaryCards/types' import { ActivityItemRenderer, generateActivityItemRenderer } from 'wallet/src/features/transactions/SummaryCards/utils' -import { useCreateSwapFormState, useMergeLocalAndRemoteTransactions } from 'wallet/src/features/transactions/hooks' +import { useCreateSwapFormState } from 'wallet/src/features/transactions/hooks' import { useMostRecentSwapTx } from 'wallet/src/features/transactions/swap/hooks/useMostRecentSwapTx' const SectionTitle = ({ title, index }: { title: string; index?: number }): JSX.Element => ( @@ -71,11 +71,7 @@ export function useActivityData({ return generateActivityItemRenderer(, SectionTitle, swapCallbacks, authTrigger) }, [swapCallbacks, authTrigger]) - const { onRetry, isError, sectionData, keyExtractor } = useFormattedTransactionDataForActivity( - owner, - hideSpamTokens, - useMergeLocalAndRemoteTransactions, - ) + const { onRetry, isError, sectionData, keyExtractor } = useFormattedTransactionDataForActivity(owner, hideSpamTokens) const errorCard = ( diff --git a/packages/wallet/src/features/contracts/ContractManager.ts b/packages/wallet/src/features/contracts/ContractManager.ts index a958957478f..bb2ac3df51e 100644 --- a/packages/wallet/src/features/contracts/ContractManager.ts +++ b/packages/wallet/src/features/contracts/ContractManager.ts @@ -1,15 +1,15 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { Contract, ContractInterface, providers } from 'ethers' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { getValidAddress } from 'uniswap/src/utils/addresses' import { isNativeCurrencyAddress } from 'uniswap/src/utils/currencyId' import { logger } from 'utilities/src/logger/logger' export class ContractManager { - private _contracts: Partial>> = {} + private _contracts: Partial>> = {} createContract( - chainId: WalletChainId, + chainId: UniverseChainId, address: Address, provider: providers.Provider, ABI: ContractInterface, @@ -28,7 +28,7 @@ export class ContractManager { } } - removeContract(chainId: WalletChainId, address: Address): void { + removeContract(chainId: UniverseChainId, address: Address): void { if (!this._contracts[chainId]?.[address]) { logger.warn( 'ContractManager', @@ -45,12 +45,12 @@ export class ContractManager { } // Returns contract or null - getContract(chainId: WalletChainId, address: Address): Nullable { + getContract(chainId: UniverseChainId, address: Address): Nullable { return (this._contracts[chainId]?.[address] as T | undefined) ?? null } getOrCreateContract( - chainId: WalletChainId, + chainId: UniverseChainId, address: Address, provider: providers.Provider, ABI: ContractInterface, diff --git a/packages/wallet/src/features/contracts/hooks.ts b/packages/wallet/src/features/contracts/hooks.ts index fc7abfe8d5c..08bdee48586 100644 --- a/packages/wallet/src/features/contracts/hooks.ts +++ b/packages/wallet/src/features/contracts/hooks.ts @@ -1,14 +1,14 @@ import { Contract } from '@ethersproject/contracts' import { useCallback } from 'react' import ERC20_ABI from 'uniswap/src/abis/erc20.json' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { useAsyncData } from 'utilities/src/react/hooks' import { useIsSmartContractAddress } from 'wallet/src/features/transactions/send/hooks/useIsSmartContractAddress' import { useProvider } from 'wallet/src/features/wallet/context' export function useIsErc20Contract( address: string | undefined, - chainId: WalletChainId, + chainId: UniverseChainId, ): { loading: boolean isERC20ContractAddress: boolean diff --git a/packages/wallet/src/features/nfts/useNftContextMenu.tsx b/packages/wallet/src/features/nfts/useNftContextMenu.tsx index 561d2ba26f0..d49cd64b5c5 100644 --- a/packages/wallet/src/features/nfts/useNftContextMenu.tsx +++ b/packages/wallet/src/features/nfts/useNftContextMenu.tsx @@ -15,7 +15,7 @@ import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' import { WalletEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { logger } from 'utilities/src/logger/logger' import { ONE_SECOND_MS } from 'utilities/src/time/time' import { useWalletNavigation } from 'wallet/src/contexts/WalletNavigationContext' @@ -31,7 +31,7 @@ interface NFTMenuParams { owner?: Address showNotification?: boolean isSpam?: boolean - chainId?: WalletChainId + chainId?: UniverseChainId } type MenuAction = ContextMenuAction & { onPress: () => void; Icon?: GeneratedIcon } diff --git a/packages/wallet/src/features/notifications/components/NotificationToast.tsx b/packages/wallet/src/features/notifications/components/NotificationToast.tsx index e62df3d6233..8cb7d175b52 100644 --- a/packages/wallet/src/features/notifications/components/NotificationToast.tsx +++ b/packages/wallet/src/features/notifications/components/NotificationToast.tsx @@ -6,18 +6,10 @@ import { useCallback, useEffect } from 'react' import { Directions, FlingGestureHandler, FlingGestureHandlerGestureEvent, State } from 'react-native-gesture-handler' import { useAnimatedStyle, useSharedValue, withDelay, withSpring } from 'react-native-reanimated' import { useDispatch, useSelector } from 'react-redux' -import { - ElementAfterText, - Flex, - Text, - TouchableArea, - isWeb, - styled, - useDeviceInsets, - useShadowPropsShort, -} from 'ui/src' +import { ElementAfterText, Flex, Text, TouchableArea, isWeb, styled, useShadowPropsShort } from 'ui/src' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { borderRadii, spacing } from 'ui/src/theme' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { TestID } from 'uniswap/src/test/fixtures/testIDs' import { useTimeout } from 'utilities/src/time/timing' import { selectActiveAccountNotifications } from 'wallet/src/features/notifications/selectors' @@ -92,7 +84,7 @@ export function NotificationToast({ const currentNotification = notifications?.[0] const hasQueuedNotification = !!notifications?.[1] - const showOffset = useDeviceInsets().top + spacing.spacing4 + (isWeb ? spacing.spacing12 : 0) + const showOffset = useAppInsets().top + spacing.spacing4 + (isWeb ? spacing.spacing12 : 0) const bannerOffset = useSharedValue(HIDE_OFFSET_Y) // Run this only once to ensure that if a new notification is created it doesn't show on the next screen diff --git a/packages/wallet/src/features/notifications/types.ts b/packages/wallet/src/features/notifications/types.ts index 980e3119ecf..1c48bd61f25 100644 --- a/packages/wallet/src/features/notifications/types.ts +++ b/packages/wallet/src/features/notifications/types.ts @@ -3,7 +3,7 @@ import { AssetType } from 'uniswap/src/entities/assets' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { FinalizedTransactionStatus, TransactionType } from 'uniswap/src/features/transactions/types/transactionDetails' import { WrapType } from 'uniswap/src/features/transactions/types/wrap' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { WalletConnectEvent } from 'uniswap/src/types/walletConnect' export enum AppNotificationType { @@ -58,7 +58,7 @@ export interface TransactionNotificationBase extends AppNotificationBase { txType: TransactionType txStatus: FinalizedTransactionStatus txId: string - chainId: WalletChainId + chainId: UniverseChainId tokenAddress?: string } @@ -220,7 +220,7 @@ export interface NotSupportedNetworkNotification extends AppNotificationBase { export interface TransactionPendingNotification extends AppNotificationBase { type: AppNotificationType.TransactionPending - chainId: WalletChainId + chainId: UniverseChainId } export interface PasswordChangedNotification extends AppNotificationBase { diff --git a/packages/wallet/src/features/onboarding/hooks/useImportableAccounts.tsx b/packages/wallet/src/features/onboarding/hooks/useImportableAccounts.tsx index 701d34f072c..27ad8acc170 100644 --- a/packages/wallet/src/features/onboarding/hooks/useImportableAccounts.tsx +++ b/packages/wallet/src/features/onboarding/hooks/useImportableAccounts.tsx @@ -5,6 +5,7 @@ import { SelectWalletScreenQuery, } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { useENSName } from 'uniswap/src/features/ens/api' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { useAsyncData } from 'utilities/src/react/hooks' import { NUMBER_OF_WALLETS_TO_GENERATE } from 'wallet/src/features/onboarding/OnboardingContext' import { fetchUnitagByAddresses } from 'wallet/src/features/unitags/api' @@ -77,6 +78,8 @@ export function useAddressesBalanceAndNames(addresses?: Address[]): { const { ensMap, loading: ensLoading } = useAddressesEnsNames(addressesArray) + const { gqlChains } = useEnabledChains() + const fetchBalanceAndUnitags = useCallback(async (): Promise | undefined> => { if (addressesArray.length === 0) { return undefined @@ -90,7 +93,7 @@ export function useAddressesBalanceAndNames(addresses?: Address[]): { const fetchBalances = apolloClient.query({ query: SelectWalletScreenDocument, - variables: { ownerAddresses: addressesArray, valueModifiers }, + variables: { ownerAddresses: addressesArray, chains: gqlChains, valueModifiers }, }) const fetchUnitags = fetchUnitagByAddresses(addressesArray) @@ -123,7 +126,7 @@ export function useAddressesBalanceAndNames(addresses?: Address[]): { // We use `refetchCount` as a dependency to manually trigger a refetch when calling the `refetch` function. // eslint-disable-next-line react-hooks/exhaustive-deps - }, [addressesArray, apolloClient, refetchCount]) + }, [addressesArray, apolloClient, refetchCount, gqlChains]) const { data: balanceAndUnitags, diff --git a/packages/wallet/src/features/portfolio/PortfolioTokenBalances.graphql b/packages/wallet/src/features/portfolio/PortfolioTokenBalances.graphql deleted file mode 100644 index 6ab207b4f12..00000000000 --- a/packages/wallet/src/features/portfolio/PortfolioTokenBalances.graphql +++ /dev/null @@ -1,34 +0,0 @@ -# note: this query is called (confusingly) PortfolioBalances in mobile codebase -# renaming to PortfolioTokenBalances here to make it more distinct from PortfolioBalance -query PortfolioTokenBalances($ownerAddress: String!) { - portfolios(ownerAddresses: [$ownerAddress], chains: [ETHEREUM, POLYGON, ARBITRUM, OPTIMISM, BASE, BNB]) { - id - tokenBalances { - id - quantity - denominatedValue { - currency - value - } - token { - id - chain - address - symbol - decimals - project { - id - name - logoUrl - safetyLevel - isSpam - } - } - tokenProjectMarket { - relativeChange24: pricePercentChange(duration: DAY) { - value - } - } - } - } -} diff --git a/packages/wallet/src/features/portfolio/TokenBalanceItem.tsx b/packages/wallet/src/features/portfolio/TokenBalanceItem.tsx index f09d91ccba3..96469e0b644 100644 --- a/packages/wallet/src/features/portfolio/TokenBalanceItem.tsx +++ b/packages/wallet/src/features/portfolio/TokenBalanceItem.tsx @@ -4,6 +4,7 @@ import { Flex, ImpactFeedbackStyle, Shine, Text, TouchableArea, isWeb } from 'ui import { TokenLogo } from 'uniswap/src/components/CurrencyLogo/TokenLogo' import { PortfolioBalance } from 'uniswap/src/features/dataApi/types' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { CurrencyId } from 'uniswap/src/types/currency' import { getSymbolDisplayText } from 'uniswap/src/utils/currency' import { NumberType } from 'utilities/src/format/types' @@ -34,6 +35,8 @@ export const TokenBalanceItem = memo(function _TokenBalanceItem({ const { t } = useTranslation() const { convertFiatAmountFormatted, formatNumberOrString } = useLocalizationContext() + const { isTestnetModeEnabled } = useEnabledChains() + const onPress = (): void => { onPressToken?.(currencyInfo.currencyId) } @@ -75,28 +78,30 @@ export const TokenBalanceItem = memo(function _TokenBalanceItem({ - - - {!portfolioBalance.balanceUSD ? ( - - {t('common.text.notAvailable')} - - ) : ( - - - {balance} - - - - )} - - + {!isTestnetModeEnabled && ( + + + {!portfolioBalance.balanceUSD ? ( + + {t('common.text.notAvailable')} + + ) : ( + + + {balance} + + + + )} + + + )} ) }) diff --git a/packages/wallet/src/features/portfolio/useTokenContextMenu.tsx b/packages/wallet/src/features/portfolio/useTokenContextMenu.tsx index 25811015500..b5b857850aa 100644 --- a/packages/wallet/src/features/portfolio/useTokenContextMenu.tsx +++ b/packages/wallet/src/features/portfolio/useTokenContextMenu.tsx @@ -8,9 +8,10 @@ import { CoinConvert, Eye, EyeOff, ReceiveAlt, SendAction } from 'ui/src/compone import { usePortfolioCacheUpdater } from 'uniswap/src/features/dataApi/balances' import { PortfolioBalance } from 'uniswap/src/features/dataApi/types' import { toggleTokenVisibility } from 'uniswap/src/features/favorites/slice' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { WalletEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField, CurrencyId } from 'uniswap/src/types/currency' import { areCurrencyIdsEqual, currencyIdToAddress, currencyIdToChain } from 'uniswap/src/utils/currencyId' import { ONE_SECOND_MS } from 'utilities/src/time/time' @@ -40,6 +41,7 @@ export function useTokenContextMenu({ const { t } = useTranslation() const dispatch = useDispatch() const activeAccountAddress = useActiveAccountAddressWithThrow() + const { defaultChainId } = useEnabledChains() const { navigateToSwapFlow, navigateToReceive, navigateToSend, handleShareToken } = useWalletNavigation() @@ -48,7 +50,7 @@ export function useTokenContextMenu({ const isHidden = !!portfolioBalance?.isHidden const currencyAddress = currencyIdToAddress(currencyId) - const currencyChainId = (currencyIdToChain(currencyId) as WalletChainId) ?? UniverseChainId.Mainnet + const currencyChainId = (currencyIdToChain(currencyId) as UniverseChainId) ?? defaultChainId const onPressSend = useCallback(() => { // Do not show warning modal speed-bump if user is trying to send tokens they own diff --git a/packages/wallet/src/features/providers/ProviderManager.ts b/packages/wallet/src/features/providers/ProviderManager.ts index ab4ac08c10e..b7acd2033db 100644 --- a/packages/wallet/src/features/providers/ProviderManager.ts +++ b/packages/wallet/src/features/providers/ProviderManager.ts @@ -1,7 +1,7 @@ import { Signer, providers as ethersProviders } from 'ethers' import { Task } from 'redux-saga' import { createEthersProvider } from 'uniswap/src/features/providers/createEthersProvider' -import { RPCType, WalletChainId } from 'uniswap/src/types/chains' +import { RPCType, UniverseChainId } from 'uniswap/src/types/chains' import { logger } from 'utilities/src/logger/logger' enum ProviderStatus { @@ -26,7 +26,7 @@ type ProviderInfo = Partial<{ private: PrivateProviderDetails }> -type ChainIdToProvider = Partial> +type ChainIdToProvider = Partial> export class ProviderManager { private readonly _providers: ChainIdToProvider = {} @@ -37,7 +37,7 @@ export class ProviderManager { this.onUpdate = onUpdate } - tryGetProvider(chainId: WalletChainId): ethersProviders.JsonRpcProvider | null { + tryGetProvider(chainId: UniverseChainId): ethersProviders.JsonRpcProvider | null { try { return this.getProvider(chainId) } catch (error) { @@ -45,7 +45,7 @@ export class ProviderManager { } } - getProvider(chainId: WalletChainId): ethersProviders.JsonRpcProvider { + getProvider(chainId: UniverseChainId): ethersProviders.JsonRpcProvider { const cachedProviderDetails = this._providers[chainId]?.public if (!cachedProviderDetails || cachedProviderDetails.status !== ProviderStatus.Connected) { this.createProvider(chainId) @@ -60,7 +60,7 @@ export class ProviderManager { return providerDetails.provider } - async getPrivateProvider(chainId: WalletChainId, signer?: Signer): Promise { + async getPrivateProvider(chainId: UniverseChainId, signer?: Signer): Promise { const signerAddress = await signer?.getAddress() const cachedProviderDetails = this._providers[chainId]?.private if ( @@ -79,7 +79,7 @@ export class ProviderManager { return providerDetails.provider } - createProvider(chainId: WalletChainId): undefined { + createProvider(chainId: UniverseChainId): undefined { const provider = createEthersProvider(chainId) if (!provider) { return @@ -92,7 +92,7 @@ export class ProviderManager { this.onUpdate?.() } - createPrivateProvider(chainId: WalletChainId, signer?: Signer, address?: Address): undefined { + createPrivateProvider(chainId: UniverseChainId, signer?: Signer, address?: Address): undefined { const provider = createEthersProvider(chainId, RPCType.Private, signer) if (!provider) { return @@ -105,7 +105,7 @@ export class ProviderManager { this.onUpdate?.() } - removeProviders(chainId: WalletChainId): void { + removeProviders(chainId: UniverseChainId): void { const providersInfo = this._providers[chainId] if (!providersInfo) { logger.warn('ProviderManager', 'removeProviders', `Attempting to remove non-existent provider: ${chainId}`) diff --git a/packages/wallet/src/features/providers/saga.ts b/packages/wallet/src/features/providers/saga.ts index 3e4b15178f1..ae92e1e573d 100644 --- a/packages/wallet/src/features/providers/saga.ts +++ b/packages/wallet/src/features/providers/saga.ts @@ -1,5 +1,5 @@ import { call, fork, join } from 'typed-redux-saga' -import { WALLET_SUPPORTED_CHAIN_IDS, WalletChainId } from 'uniswap/src/types/chains' +import { COMBINED_CHAIN_IDS, UniverseChainId } from 'uniswap/src/types/chains' import { logger } from 'utilities/src/logger/logger' import { ProviderManager } from 'wallet/src/features/providers/ProviderManager' import { getProviderManager } from 'wallet/src/features/wallet/context' @@ -9,7 +9,7 @@ export function* initProviders() { logger.debug('providerSaga', 'initProviders', 'Initializing providers') const manager = yield* call(getProviderManager) const initTasks = [] - for (const chainId of WALLET_SUPPORTED_CHAIN_IDS) { + for (const chainId of COMBINED_CHAIN_IDS) { const task = yield* fork(initProvider, chainId, manager) initTasks.push(task) } @@ -18,7 +18,7 @@ export function* initProviders() { logger.debug('providerSaga', 'initProviders', 'Providers ready') } -function* initProvider(chainId: WalletChainId, manager: ProviderManager) { +function* initProvider(chainId: UniverseChainId, manager: ProviderManager) { try { logger.debug('providerSaga', 'initProvider', 'Creating a provider for:', chainId) yield* call([manager, manager.createProvider], chainId) diff --git a/packages/wallet/src/features/providers/utils.ts b/packages/wallet/src/features/providers/utils.ts index 6980f467c51..d8b67188ad9 100644 --- a/packages/wallet/src/features/providers/utils.ts +++ b/packages/wallet/src/features/providers/utils.ts @@ -1,6 +1,6 @@ import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' -import { RPCType, WalletChainId } from 'uniswap/src/types/chains' +import { RPCType, UniverseChainId } from 'uniswap/src/types/chains' -export function isPrivateRpcSupportedOnChain(chainId: WalletChainId): boolean { +export function isPrivateRpcSupportedOnChain(chainId: UniverseChainId): boolean { return Boolean(UNIVERSE_CHAIN_INFO[chainId]?.rpcUrls?.[RPCType.Private]) } diff --git a/packages/wallet/src/features/telemetry/hooks.ts b/packages/wallet/src/features/telemetry/hooks.ts index f10266b1e8e..3b32f07e085 100644 --- a/packages/wallet/src/features/telemetry/hooks.ts +++ b/packages/wallet/src/features/telemetry/hooks.ts @@ -1,10 +1,10 @@ import { useEffect, useMemo } from 'react' import { useDispatch, useSelector } from 'react-redux' -import { GQL_MAINNET_CHAINS_MUTABLE } from 'uniswap/src/constants/chains' import { useAccountMeta } from 'uniswap/src/contexts/UniswapContext' import { useTotalBalancesUsdPerChain } from 'uniswap/src/data/balances/utils' import { usePortfolioBalancesQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { AccountType } from 'uniswap/src/features/accounts/types' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { MobileAppsFlyerEvents, UniswapEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent, sendAppsFlyerEvent } from 'uniswap/src/features/telemetry/send' import { logger } from 'utilities/src/logger/logger' @@ -48,9 +48,11 @@ export function useLastBalancesReporter(): void { fetchPolicy: 'cache-first', }) + const { gqlChains } = useEnabledChains() + const portfolioBalancesQuery = usePortfolioBalancesQuery({ fetchPolicy: 'cache-only', - variables: account?.address ? { ownerAddress: account.address, chains: GQL_MAINNET_CHAINS_MUTABLE } : undefined, + variables: account?.address ? { ownerAddress: account.address, chains: gqlChains } : undefined, }) const totalBalancesUsdPerChain = useTotalBalancesUsdPerChain(portfolioBalancesQuery) diff --git a/packages/wallet/src/features/testnetMode/hooks.ts b/packages/wallet/src/features/testnetMode/hooks.ts new file mode 100644 index 00000000000..6256bbfa622 --- /dev/null +++ b/packages/wallet/src/features/testnetMode/hooks.ts @@ -0,0 +1,21 @@ +import { useEffect } from 'react' +import { FeatureFlags } from 'uniswap/src/features/gating/flags' +import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' +import { setAttributesToDatadog } from 'utilities/src/logger/Datadog' +import { Sentry } from 'utilities/src/logger/Sentry' +// eslint-disable-next-line no-restricted-imports +import { analytics } from 'utilities/src/telemetry/analytics/analytics' + +export function useTestnetModeForLoggingAndAnalytics(): void { + const { isTestnetModeEnabled } = useEnabledChains() + const datadogEnabled = useFeatureFlag(FeatureFlags.Datadog) + useEffect(() => { + analytics.setTestnetMode(isTestnetModeEnabled) + if (datadogEnabled) { + setAttributesToDatadog({ TestnetMode: isTestnetModeEnabled }).catch(() => undefined) + } else { + Sentry.setTag('TestnetMode', isTestnetModeEnabled) + } + }, [datadogEnabled, isTestnetModeEnabled]) +} diff --git a/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/BridgeTransactionDetails.tsx b/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/BridgeTransactionDetails.tsx index c80ad387664..b2f67b4a4c5 100644 --- a/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/BridgeTransactionDetails.tsx +++ b/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/BridgeTransactionDetails.tsx @@ -7,9 +7,9 @@ import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' import { toSupportedChainId } from 'uniswap/src/features/chains/utils' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { useCurrencyInfo } from 'uniswap/src/features/tokens/useCurrencyInfo' import { BridgeTransactionInfo } from 'uniswap/src/features/transactions/types/transactionDetails' -import { UniverseChainId } from 'uniswap/src/types/chains' import { useNetworkColors } from 'uniswap/src/utils/colors' import { getSymbolDisplayText } from 'uniswap/src/utils/currency' import { ValueText } from 'wallet/src/features/transactions/SummaryCards/DetailsModal/SwapTransactionDetails' @@ -84,7 +84,8 @@ export function CurrencyValueWithIcon({ formattedFiatAmount: string formattedTokenAmount: string }): JSX.Element { - const chainId = toSupportedChainId(currencyInfo.currency.chainId) ?? UniverseChainId.Mainnet + const { defaultChainId } = useEnabledChains() + const chainId = toSupportedChainId(currencyInfo.currency.chainId) ?? defaultChainId const networkColors = useNetworkColors(chainId) const networkLabel = UNIVERSE_CHAIN_INFO[chainId].label const networkColor = validColor(networkColors.foreground) diff --git a/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/TransactionDetailsInfoRows.tsx b/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/TransactionDetailsInfoRows.tsx index dc1de74d3c3..7d49cb00211 100644 --- a/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/TransactionDetailsInfoRows.tsx +++ b/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/TransactionDetailsInfoRows.tsx @@ -138,19 +138,18 @@ export function useTransactionDetailsInfoRows( ) break case TransactionType.Bridge: - if (typeInfo.routingDappInfo && typeInfo.routingDappInfo.name) { - const dappInfo = ( - - ) - defaultRows.splice(1, 0, dappInfo) - } - if (isShowingMore) { + if (typeInfo.routingDappInfo && typeInfo.routingDappInfo.name) { + const dappInfo = ( + + ) + specificRows.splice(1, 0, dappInfo) + } specificRows.push() } break diff --git a/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/__snapshots__/TransactionDetailsModal.test.tsx.snap b/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/__snapshots__/TransactionDetailsModal.test.tsx.snap index ab3159e5b9a..aca1e1b92b0 100644 --- a/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/__snapshots__/TransactionDetailsModal.test.tsx.snap +++ b/packages/wallet/src/features/transactions/SummaryCards/DetailsModal/__snapshots__/TransactionDetailsModal.test.tsx.snap @@ -153,19 +153,19 @@ exports[`TransactionDetails Components renders TransactionDetailsHeader without style={ { "borderBottomColor": "rgba(255,255,255,1.00)", - "borderBottomLeftRadius": "6.6px", - "borderBottomRightRadius": "6.6px", - "borderBottomWidth": "1px", + "borderBottomLeftRadius": "6.8999999999999995px", + "borderBottomRightRadius": "6.8999999999999995px", + "borderBottomWidth": "1.5px", "borderLeftColor": "rgba(255,255,255,1.00)", - "borderLeftWidth": "1px", + "borderLeftWidth": "1.5px", "borderRightColor": "rgba(255,255,255,1.00)", - "borderRightWidth": "1px", + "borderRightWidth": "1.5px", "borderTopColor": "rgba(255,255,255,1.00)", - "borderTopLeftRadius": "6.6px", - "borderTopRightRadius": "6.6px", - "borderTopWidth": "1px", - "height": "22px", - "width": "22px", + "borderTopLeftRadius": "6.8999999999999995px", + "borderTopRightRadius": "6.8999999999999995px", + "borderTopWidth": "1.5px", + "height": "23px", + "width": "23px", } } > diff --git a/packages/wallet/src/features/transactions/SummaryCards/SummaryItems/CancelConfirmationView.tsx b/packages/wallet/src/features/transactions/SummaryCards/SummaryItems/CancelConfirmationView.tsx index 7988a6e8fee..0f207120256 100644 --- a/packages/wallet/src/features/transactions/SummaryCards/SummaryItems/CancelConfirmationView.tsx +++ b/packages/wallet/src/features/transactions/SummaryCards/SummaryItems/CancelConfirmationView.tsx @@ -5,7 +5,7 @@ import { Button, Flex, FlexLoader, Separator, Skeleton, Text, isWeb, useHapticFe import { SlashCircle } from 'ui/src/components/icons' import { fonts } from 'ui/src/theme' import { AuthTrigger } from 'uniswap/src/features/auth/types' -import { useUSDValue } from 'uniswap/src/features/gas/hooks' +import { useUSDValueOfGasFee } from 'uniswap/src/features/gas/hooks' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { isUniswapX } from 'uniswap/src/features/transactions/swap/utils/routing' import { TransactionDetails, TransactionStatus } from 'uniswap/src/features/transactions/types/transactionDetails' @@ -30,7 +30,7 @@ export function CancelConfirmationView({ const { hapticFeedback } = useHapticFeedback() const cancelationGasFeeInfo = useCancelationGasFeeInfo(transactionDetails) - const gasFeeUSD = useUSDValue(transactionDetails.chainId, cancelationGasFeeInfo?.cancelationGasFee) + const { value: gasFeeUSD } = useUSDValueOfGasFee(transactionDetails.chainId, cancelationGasFeeInfo?.cancelationGasFee) const gasFee = convertFiatAmountFormatted(gasFeeUSD, NumberType.FiatGasPrice) const onCancelConfirm = useCallback(() => { diff --git a/packages/wallet/src/features/transactions/SummaryCards/SummaryItems/TransactionSummaryLayout.tsx b/packages/wallet/src/features/transactions/SummaryCards/SummaryItems/TransactionSummaryLayout.tsx index e60de26cb7b..4c4215091d7 100644 --- a/packages/wallet/src/features/transactions/SummaryCards/SummaryItems/TransactionSummaryLayout.tsx +++ b/packages/wallet/src/features/transactions/SummaryCards/SummaryItems/TransactionSummaryLayout.tsx @@ -134,7 +134,7 @@ function TransactionSummaryLayout({ {!inProgress && rightBlock} - {typeof caption === 'string' ? {caption} : caption} + {typeof caption === 'string' ? {caption} : caption} {status === TransactionStatus.Failed && onRetry && ( diff --git a/packages/wallet/src/features/transactions/SummaryCards/types.ts b/packages/wallet/src/features/transactions/SummaryCards/types.ts index 220a3d5828c..15ec417c2d2 100644 --- a/packages/wallet/src/features/transactions/SummaryCards/types.ts +++ b/packages/wallet/src/features/transactions/SummaryCards/types.ts @@ -1,7 +1,7 @@ import { AuthTrigger } from 'uniswap/src/features/auth/types' import { TransactionDetails } from 'uniswap/src/features/transactions/types/transactionDetails' import { TransactionState } from 'uniswap/src/features/transactions/types/transactionState' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' export interface TransactionSummaryLayoutProps { authTrigger?: AuthTrigger @@ -24,7 +24,7 @@ export interface SwapSummaryCallbacks { useLatestSwapTransaction: (address: string) => TransactionDetails | undefined useSwapFormTransactionState: ( address: Address | undefined, - chainId: WalletChainId | undefined, + chainId: UniverseChainId | undefined, txId: string | undefined, ) => TransactionState | undefined onRetryGenerator?: (swapFormState: TransactionState | undefined) => () => void diff --git a/packages/wallet/src/features/transactions/TransactionHistoryUpdater.tsx b/packages/wallet/src/features/transactions/TransactionHistoryUpdater.tsx index cdb85ff2484..1f4d68af440 100644 --- a/packages/wallet/src/features/transactions/TransactionHistoryUpdater.tsx +++ b/packages/wallet/src/features/transactions/TransactionHistoryUpdater.tsx @@ -11,7 +11,7 @@ import { useTransactionListLazyQuery, } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { GQLQueries } from 'uniswap/src/data/graphql/uniswap-data-api/queries' -import { useHideSpamTokensSetting } from 'uniswap/src/features/settings/hooks' +import { useEnabledChains, useHideSpamTokensSetting } from 'uniswap/src/features/settings/hooks' import { useSelectAddressTransactions } from 'uniswap/src/features/transactions/selectors' import { TransactionStatus, TransactionType } from 'uniswap/src/features/transactions/types/transactionDetails' import { ONE_SECOND_MS } from 'utilities/src/time/time' @@ -46,17 +46,19 @@ export function TransactionHistoryUpdater(): JSX.Element | null { return Object.keys(allAccounts).filter((address) => address !== activeAccountAddress) }, [activeAccountAddress, allAccounts]) + const { gqlChains } = useEnabledChains() + // Poll at different intervals to reduce requests made for non active accounts. const { data: activeAccountData } = useTransactionHistoryUpdaterQuery({ - variables: { addresses: activeAccountAddress ?? [] }, + variables: { addresses: activeAccountAddress ?? [], chains: gqlChains }, pollInterval: PollingInterval.KindaFast, fetchPolicy: 'network-only', // Ensure latest data. skip: !activeAccountAddress, }) const { data: nonActiveAccountData } = useTransactionHistoryUpdaterQuery({ - variables: { addresses: nonActiveAccountAddresses }, + variables: { addresses: nonActiveAccountAddresses, chains: gqlChains }, pollInterval: PollingInterval.Normal, fetchPolicy: 'network-only', // Ensure latest data. skip: nonActiveAccountAddresses.length === 0, @@ -182,6 +184,7 @@ export function useFetchAndDispatchReceiveNotification(): ( ) => Promise { const [fetchFullTransactionData] = useTransactionListLazyQuery() const dispatch = useDispatch() + const { gqlChains } = useEnabledChains() return async ( address: string, @@ -190,7 +193,7 @@ export function useFetchAndDispatchReceiveNotification(): ( ): Promise => { // Fetch full transaction history for user address. const { data: fullTransactionData } = await fetchFullTransactionData({ - variables: { address }, + variables: { address, chains: gqlChains }, fetchPolicy: 'network-only', // Ensure latest data. }) diff --git a/packages/wallet/src/features/transactions/TransactionRequest/NetworkFeeFooter.tsx b/packages/wallet/src/features/transactions/TransactionRequest/NetworkFeeFooter.tsx index 767c79f19e9..1e51c98d5d1 100644 --- a/packages/wallet/src/features/transactions/TransactionRequest/NetworkFeeFooter.tsx +++ b/packages/wallet/src/features/transactions/TransactionRequest/NetworkFeeFooter.tsx @@ -3,40 +3,44 @@ import { Flex, Text } from 'ui/src' import { iconSizes } from 'ui/src/theme' import { NetworkLogo } from 'uniswap/src/components/CurrencyLogo/NetworkLogo' import { UniswapXFee } from 'uniswap/src/components/gas/NetworkFee' -import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' -import { WalletChainId } from 'uniswap/src/types/chains' -import { NumberType } from 'utilities/src/format/types' +import { useGasFeeFormattedAmounts } from 'uniswap/src/features/gas/hooks' +import { GasFeeResult } from 'uniswap/src/features/gas/types' +import { UniverseChainId } from 'uniswap/src/types/chains' import { isMobileApp } from 'utilities/src/platform' import { ContentRow } from 'wallet/src/features/transactions/TransactionRequest/ContentRow' interface NetworkFeeFooterProps { - chainId: WalletChainId + chainId: UniverseChainId showNetworkLogo: boolean - gasFeeUSD: string | undefined + gasFee: GasFeeResult | undefined isUniswapX?: boolean } export function NetworkFeeFooter({ chainId, showNetworkLogo, - gasFeeUSD, + gasFee, isUniswapX, }: NetworkFeeFooterProps): JSX.Element | null { const { t } = useTranslation() - const { convertFiatAmountFormatted } = useLocalizationContext() const variant = isMobileApp ? 'body3' : 'body4' - const formattedFiat = convertFiatAmountFormatted(gasFeeUSD, NumberType.FiatGasPrice) + const { gasFeeFormatted } = useGasFeeFormattedAmounts({ + gasFee, + chainId, + placeholder: '-', + }) + return ( {showNetworkLogo && } {isUniswapX ? ( - + ) : ( - {formattedFiat} + {gasFeeFormatted} )} diff --git a/packages/wallet/src/features/transactions/TransactionRequest/SpendingDetails.tsx b/packages/wallet/src/features/transactions/TransactionRequest/SpendingDetails.tsx index 69b71fc2c39..3e991a3fff8 100644 --- a/packages/wallet/src/features/transactions/TransactionRequest/SpendingDetails.tsx +++ b/packages/wallet/src/features/transactions/TransactionRequest/SpendingDetails.tsx @@ -3,17 +3,17 @@ import { Flex, Text } from 'ui/src' import { iconSizes } from 'ui/src/theme' import { CurrencyLogo } from 'uniswap/src/components/CurrencyLogo/CurrencyLogo' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' -import { useUSDValue } from 'uniswap/src/features/gas/hooks' +import { useUSDValueOfGasFee } from 'uniswap/src/features/gas/hooks' import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { ValueType, getCurrencyAmount } from 'uniswap/src/features/tokens/getCurrencyAmount' import { useNativeCurrencyInfo } from 'uniswap/src/features/tokens/useCurrencyInfo' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { getSymbolDisplayText } from 'uniswap/src/utils/currency' import { NumberType } from 'utilities/src/format/types' import { isMobileApp } from 'utilities/src/platform' import { ContentRow } from 'wallet/src/features/transactions/TransactionRequest/ContentRow' -export function SpendingEthDetails({ value, chainId }: { value: string; chainId: WalletChainId }): JSX.Element { +export function SpendingEthDetails({ value, chainId }: { value: string; chainId: UniverseChainId }): JSX.Element { const variant = isMobileApp ? 'body3' : 'body4' const { t } = useTranslation() @@ -27,7 +27,8 @@ export function SpendingEthDetails({ value, chainId }: { value: string; chainId: currency: nativeCurrencyInfo.currency, }) : null - const usdValue = useUSDValue(chainId, value) + + const { value: usdValue } = useUSDValueOfGasFee(chainId, value) const tokenAmountWithSymbol = formatCurrencyAmount({ value: nativeCurrencyAmount, type: NumberType.TokenTx }) + diff --git a/packages/wallet/src/features/transactions/contexts/SendContext.tsx b/packages/wallet/src/features/transactions/contexts/SendContext.tsx index 138b0d543d8..8ec8b237d52 100644 --- a/packages/wallet/src/features/transactions/contexts/SendContext.tsx +++ b/packages/wallet/src/features/transactions/contexts/SendContext.tsx @@ -5,9 +5,10 @@ import { ReactNode, createContext, useCallback, useContext, useMemo, useState } import { useTranslation } from 'react-i18next' import { WarningAction } from 'uniswap/src/components/modals/WarningModal/types' import { getNativeAddress } from 'uniswap/src/constants/addresses' -import { AssetType, TradeableAsset } from 'uniswap/src/entities/assets' +import { AssetType } from 'uniswap/src/entities/assets' import { useTransactionGasFee, useTransactionGasWarning } from 'uniswap/src/features/gas/hooks' import { GasFeeResult } from 'uniswap/src/features/gas/types' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { useFormattedWarnings } from 'uniswap/src/features/transactions/hooks/useParsedTransactionWarnings' import { ParsedWarnings } from 'uniswap/src/features/transactions/types/transactionDetails' import { TransactionState } from 'uniswap/src/features/transactions/types/transactionState' @@ -19,14 +20,12 @@ import { useSendTransactionRequest } from 'wallet/src/features/transactions/send import { useSendWarnings } from 'wallet/src/features/transactions/send/hooks/useSendWarnings' import { useActiveAccountWithThrow } from 'wallet/src/features/wallet/hooks' -const ETH_TRADEABLE_ASSET: TradeableAsset = { - address: getNativeAddress(UniverseChainId.Mainnet), - chainId: UniverseChainId.Mainnet, - type: AssetType.Currency, -} - -export const DEFAULT_SEND_STATE: Readonly = { - [CurrencyField.INPUT]: ETH_TRADEABLE_ASSET, +export const getDefaultSendState = (defaultChainId: UniverseChainId): Readonly => ({ + [CurrencyField.INPUT]: { + address: getNativeAddress(defaultChainId), + chainId: defaultChainId, + type: AssetType.Currency, + }, [CurrencyField.OUTPUT]: null, exactCurrencyField: CurrencyField.INPUT, focusOnCurrencyField: CurrencyField.INPUT, @@ -36,7 +35,7 @@ export const DEFAULT_SEND_STATE: Readonly = { selectingCurrencyField: undefined, showRecipientSelector: true, customSlippageTolerance: undefined, -} +}) type SendContextState = { derivedSendInfo: ReturnType @@ -58,9 +57,11 @@ export function SendContextProvider({ }): JSX.Element { const { t } = useTranslation() const account = useActiveAccountWithThrow() + const { defaultChainId } = useEnabledChains() + const defaultSendState = getDefaultSendState(defaultChainId) // state - const [sendForm, setSendForm] = useState(prefilledTransactionState || DEFAULT_SEND_STATE) + const [sendForm, setSendForm] = useState(prefilledTransactionState || defaultSendState) const updateSendForm = useCallback( (newState: Parameters[0]): void => { setSendForm((prevState) => ({ ...prevState, ...newState })) diff --git a/packages/wallet/src/features/transactions/history/conversion/extractFiatOnRampTransactionDetails.ts b/packages/wallet/src/features/transactions/history/conversion/extractFiatOnRampTransactionDetails.ts index 41bfd8c3479..072caeaa451 100644 --- a/packages/wallet/src/features/transactions/history/conversion/extractFiatOnRampTransactionDetails.ts +++ b/packages/wallet/src/features/transactions/history/conversion/extractFiatOnRampTransactionDetails.ts @@ -114,6 +114,7 @@ export function extractOnRampTransactionDetails(transaction: TransactionListQuer return { routing: Routing.CLASSIC, id: transaction.details.onRampTransfer.externalSessionId, + // TODO: WALL-4919: Remove hardcoded Mainnet chainId: fromGraphQLChain(transaction.chain) ?? UniverseChainId.Mainnet, addedTime: transaction.timestamp * 1000, // convert to ms, status: remoteTxStatusToLocalTxStatus(RemoteTransactionType.OnRamp, transaction.details.status), diff --git a/packages/wallet/src/features/transactions/history/conversion/extractTransactionDetails.ts b/packages/wallet/src/features/transactions/history/conversion/extractTransactionDetails.ts index 7d6d78fa471..37a2328bcff 100644 --- a/packages/wallet/src/features/transactions/history/conversion/extractTransactionDetails.ts +++ b/packages/wallet/src/features/transactions/history/conversion/extractTransactionDetails.ts @@ -107,6 +107,7 @@ export default function extractTransactionDetails( return { routing: transaction.details.type === RemoteTransactionType.SwapOrder ? Routing.DUTCH_V2 : Routing.CLASSIC, id: transaction.details.hash, + // TODO: WALL-4919: Remove hardcoded Mainnet // fallback to mainnet, although this should never happen chainId: chainId ?? UniverseChainId.Mainnet, hash: transaction.details.hash, diff --git a/packages/wallet/src/features/transactions/history/conversion/extractUniswapXOrderDetails.ts b/packages/wallet/src/features/transactions/history/conversion/extractUniswapXOrderDetails.ts index a6c336be458..6bfc3758926 100644 --- a/packages/wallet/src/features/transactions/history/conversion/extractUniswapXOrderDetails.ts +++ b/packages/wallet/src/features/transactions/history/conversion/extractUniswapXOrderDetails.ts @@ -35,6 +35,7 @@ export function extractUniswapXOrderDetails(transaction: TransactionListQueryRes return { routing, id: transaction.details.id, + // TODO: WALL-4919: Remove hardcoded Mainnet chainId: fromGraphQLChain(transaction.chain) ?? UniverseChainId.Mainnet, addedTime: transaction.timestamp * 1000, // convert to ms, status: remoteOrderStatusToLocalTxStatus(transaction.details.orderStatus), diff --git a/packages/wallet/src/features/transactions/hooks.ts b/packages/wallet/src/features/transactions/hooks.ts index e6ed9253a0a..daad039d3eb 100644 --- a/packages/wallet/src/features/transactions/hooks.ts +++ b/packages/wallet/src/features/transactions/hooks.ts @@ -2,6 +2,7 @@ import { Currency } from '@uniswap/sdk-core' import { BigNumberish } from 'ethers' import { useMemo } from 'react' import { useDispatch, useSelector } from 'react-redux' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { useCurrencyInfo } from 'uniswap/src/features/tokens/useCurrencyInfo' import { makeSelectTransaction, useSelectAddressTransactions } from 'uniswap/src/features/transactions/selectors' import { finalizeTransaction } from 'uniswap/src/features/transactions/slice' @@ -15,7 +16,7 @@ import { isFinalizedTx, } from 'uniswap/src/features/transactions/types/transactionDetails' import { TransactionState } from 'uniswap/src/features/transactions/types/transactionState' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { ensureLeading0x } from 'uniswap/src/utils/addresses' import { areCurrencyIdsEqual, buildCurrencyId } from 'uniswap/src/utils/currencyId' import { @@ -94,7 +95,7 @@ export function useSortedPendingTransactions(address: Address | null): Transacti export function useSelectTransaction( address: Address | undefined, - chainId: WalletChainId | undefined, + chainId: UniverseChainId | undefined, txId: string | undefined, ): TransactionDetails | undefined { const selectTransaction = useMemo(makeSelectTransaction, []) @@ -103,7 +104,7 @@ export function useSelectTransaction( export function useCreateSwapFormState( address: Address | undefined, - chainId: WalletChainId | undefined, + chainId: UniverseChainId | undefined, txId: string | undefined, ): TransactionState | undefined { const transaction = useSelectTransaction(address, chainId, txId) @@ -136,7 +137,7 @@ export function useCreateSwapFormState( export function useCreateWrapFormState( address: Address | undefined, - chainId: WalletChainId | undefined, + chainId: UniverseChainId | undefined, txId: string | undefined, inputCurrency: Maybe, outputCurrency: Maybe, @@ -166,6 +167,8 @@ export function useMergeLocalAndRemoteTransactions( const dispatch = useDispatch() const localTransactions = useSelectAddressTransactions(address) + const { chains } = useEnabledChains() + // Merge local and remote txs into one array and reconcile data discrepancies return useMemo((): TransactionDetails[] | undefined => { if (!remoteTransactions?.length) { @@ -202,6 +205,11 @@ export function useMergeLocalAndRemoteTransactions( const hashes = new Set() const offChainFiatOnRampTxs = new Map() function addToMap(map: HashToTxMap, tx: TransactionDetails): HashToTxMap { + // If the FOR tx was done on a disabled chain, then omit it + if (!chains.includes(tx.chainId)) { + return map + } + const hash = getTrackingHash(tx) if (hash) { map.set(hash, tx) @@ -232,6 +240,11 @@ export function useMergeLocalAndRemoteTransactions( continue } + // If the tx was done on a disabled chain, then omit it + if (!chains.includes(localTx.chainId)) { + continue + } + // If the BE hasn't detected the tx, then use local data if (!remoteTx) { deDupedTxs.push(localTx) @@ -253,6 +266,13 @@ export function useMergeLocalAndRemoteTransactions( continue } + // If the local tx is canceled and the remote tx is successful, the transaction is a cancellation, + // and we have better data about the user's intent locally + if (localTx.status === TransactionStatus.Canceled && remoteTx.status === TransactionStatus.Success) { + deDupedTxs.push(localTx) + continue + } + // If the tx was done via WC, then add the dapp info from WC to the remote data if (localTx.typeInfo.type === TransactionType.WCConfirm) { const externalDappInfo = { ...localTx.typeInfo.dapp } @@ -287,7 +307,7 @@ export function useMergeLocalAndRemoteTransactions( return a.addedTime > b.addedTime ? -1 : 1 }) - }, [dispatch, localTransactions, remoteTransactions]) + }, [dispatch, localTransactions, remoteTransactions, chains]) } function useLowestPendingNonce(): BigNumberish | undefined { diff --git a/packages/wallet/src/features/transactions/refetchGQLQueriesSaga.ts b/packages/wallet/src/features/transactions/refetchGQLQueriesSaga.ts index 1748e096e55..0af6606e9bf 100644 --- a/packages/wallet/src/features/transactions/refetchGQLQueriesSaga.ts +++ b/packages/wallet/src/features/transactions/refetchGQLQueriesSaga.ts @@ -1,12 +1,17 @@ import { ApolloClient, NormalizedCacheObject } from '@apollo/client' import { call, delay, select } from 'typed-redux-saga' import { getNativeAddress } from 'uniswap/src/constants/addresses' +import { GQL_MAINNET_CHAINS_MUTABLE, GQL_TESTNET_CHAINS_MUTABLE } from 'uniswap/src/constants/chains' import { PortfolioBalancesDocument, PortfolioBalancesQuery, } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' import { GQLQueries } from 'uniswap/src/data/graphql/uniswap-data-api/queries' import { fromGraphQLChain } from 'uniswap/src/features/chains/utils' +import { FeatureFlags } from 'uniswap/src/features/gating/flags' +import { getFeatureFlag } from 'uniswap/src/features/gating/hooks' +// eslint-disable-next-line no-restricted-imports +import { selectIsTestnetModeEnabled } from 'uniswap/src/features/settings/selectors' import { WalletEventName } from 'uniswap/src/features/telemetry/constants' import { sendAnalyticsEvent } from 'uniswap/src/features/telemetry/send' import { TransactionDetails, TransactionType } from 'uniswap/src/features/transactions/types/transactionDetails' @@ -29,11 +34,16 @@ export function* refetchGQLQueries({ apolloClient: ApolloClient }) { const owner = transaction.from + const isTestnetFlagEnabled = getFeatureFlag(FeatureFlags.TestnetMode) + const isTestnetModeEnabled = yield* select(selectIsTestnetModeEnabled) + const isTestnetMode = isTestnetFlagEnabled && isTestnetModeEnabled + const currenciesWithBalToUpdate = getCurrenciesWithExpectedUpdates(transaction) const currencyIdToStartingBalance = readBalancesFromCache({ owner, currencyIds: currenciesWithBalToUpdate, apolloClient, + isTestnetMode, }) const activeAddress = yield* select(selectActiveAccountAddress) @@ -60,6 +70,7 @@ export function* refetchGQLQueries({ owner, currencyIds: currenciesWithBalToUpdate, apolloClient, + isTestnetMode, }) if (checkIfBalancesUpdated(currencyIdToStartingBalance, currencyIdToUpdatedBalance)) { @@ -115,10 +126,12 @@ function readBalancesFromCache({ owner, currencyIds, apolloClient, + isTestnetMode, }: { owner: string currencyIds: Set | undefined apolloClient: ApolloClient + isTestnetMode: boolean }): CurrencyIdToBalance | undefined { if (!currencyIds?.size) { return undefined @@ -130,9 +143,11 @@ function readBalancesFromCache({ {}, ) + const chains = isTestnetMode ? GQL_MAINNET_CHAINS_MUTABLE : GQL_TESTNET_CHAINS_MUTABLE + const cachedBalancesData = apolloClient.readQuery({ query: PortfolioBalancesDocument, - variables: { ownerAddress: owner }, + variables: { ownerAddress: owner, chains }, }) for (const tokenData of cachedBalancesData?.portfolios?.[0]?.tokenBalances ?? []) { diff --git a/packages/wallet/src/features/transactions/send/GasFeeRow.tsx b/packages/wallet/src/features/transactions/send/GasFeeRow.tsx index 719eaad0445..271c7dc452c 100644 --- a/packages/wallet/src/features/transactions/send/GasFeeRow.tsx +++ b/packages/wallet/src/features/transactions/send/GasFeeRow.tsx @@ -4,25 +4,24 @@ import { Flex, isWeb, SpinningLoader, Text } from 'ui/src' import { Gas } from 'ui/src/components/icons' import { AnimatedFlex } from 'ui/src/components/layout/AnimatedFlex' import { iconSizes } from 'ui/src/theme' -import { useUSDValue } from 'uniswap/src/features/gas/hooks' +import { useGasFeeFormattedAmounts } from 'uniswap/src/features/gas/hooks' import { GasFeeResult } from 'uniswap/src/features/gas/types' -import { useLocalizationContext } from 'uniswap/src/features/language/LocalizationContext' import { NetworkFeeWarning } from 'uniswap/src/features/transactions/swap/modals/NetworkFeeWarning' -import { WalletChainId } from 'uniswap/src/types/chains' -import { NumberType } from 'utilities/src/format/types' +import { UniverseChainId } from 'uniswap/src/types/chains' type GasFeeRowProps = { gasFee: GasFeeResult - chainId: WalletChainId + chainId: UniverseChainId } export function GasFeeRow({ gasFee, chainId }: GasFeeRowProps): JSX.Element | null { - const { convertFiatAmountFormatted } = useLocalizationContext() + const { gasFeeFormatted } = useGasFeeFormattedAmounts({ + gasFee, + chainId, + placeholder: undefined, + }) - const gasFeeUSD = useUSDValue(chainId, gasFee.value ?? undefined) - const gasFeeFormatted = convertFiatAmountFormatted(gasFeeUSD, NumberType.FiatTokenPrice) - - if (!gasFeeUSD) { + if (!gasFeeFormatted) { return null } diff --git a/packages/wallet/src/features/transactions/send/SendAmountInput.tsx b/packages/wallet/src/features/transactions/send/SendAmountInput.tsx index 68191f902ef..15e498940d5 100644 --- a/packages/wallet/src/features/transactions/send/SendAmountInput.tsx +++ b/packages/wallet/src/features/transactions/send/SendAmountInput.tsx @@ -9,6 +9,7 @@ import { AmountInput } from 'uniswap/src/components/CurrencyInputPanel/AmountInp import { WarningLabel } from 'uniswap/src/components/modals/WarningModal/types' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { useAppFiatCurrencyInfo } from 'uniswap/src/features/fiatCurrency/hooks' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { useTokenAndFiatDisplayAmounts } from 'uniswap/src/features/transactions/hooks/useTokenAndFiatDisplayAmounts' import { ParsedWarnings } from 'uniswap/src/features/transactions/types/transactionDetails' @@ -43,6 +44,7 @@ export function SendAmountInput({ ...rest }: SendAmountInputProps): JSX.Element { const { symbol } = useAppFiatCurrencyInfo() + const { isTestnetModeEnabled } = useEnabledChains() const onSelectionChange = useCallback( ({ @@ -143,14 +145,17 @@ export function SendAmountInput({ )} - - - - {subTextValue} - - {!warning && } - - + {/* hide on testnet mode if there's no warning, otherwise show */} + {(!isTestnetModeEnabled || warning) && ( + + + + {subTextValue} + + {!warning && } + + + )} ) } diff --git a/packages/wallet/src/features/transactions/send/SendReviewDetails.tsx b/packages/wallet/src/features/transactions/send/SendReviewDetails.tsx index cafda2c9dc5..0411b131caa 100644 --- a/packages/wallet/src/features/transactions/send/SendReviewDetails.tsx +++ b/packages/wallet/src/features/transactions/send/SendReviewDetails.tsx @@ -23,7 +23,7 @@ import { useTransactionModalContext, } from 'uniswap/src/features/transactions/TransactionModal/TransactionModalContext' import { useUSDCValue } from 'uniswap/src/features/transactions/swap/hooks/useUSDCPrice' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { currencyAddress } from 'uniswap/src/utils/currencyId' import { NumberType } from 'utilities/src/format/types' @@ -91,7 +91,7 @@ export function SendReviewDetails({ const transferERC20Callback = useSendERC20Callback( txId, - chainId as WalletChainId, + chainId as UniverseChainId, recipient, currencyInInfo ? currencyAddress(currencyInInfo.currency) : undefined, currencyAmounts[CurrencyField.INPUT]?.quotient.toString(), @@ -103,7 +103,7 @@ export function SendReviewDetails({ const transferNFTCallback = useSendNFTCallback( txId, - chainId as WalletChainId, + chainId as UniverseChainId, recipient, nftIn?.nftContract?.address, nftIn?.tokenId, @@ -272,7 +272,7 @@ export function SendReviewDetails({ /> } - chainId={chainId as WalletChainId} + chainId={chainId as UniverseChainId} gasFee={gasFee} showWarning={Boolean(transferWarning)} warning={transferWarning} diff --git a/packages/wallet/src/features/transactions/send/getSendPrefilledState.ts b/packages/wallet/src/features/transactions/send/getSendPrefilledState.ts index d03c84d411b..369f3521f46 100644 --- a/packages/wallet/src/features/transactions/send/getSendPrefilledState.ts +++ b/packages/wallet/src/features/transactions/send/getSendPrefilledState.ts @@ -1,14 +1,14 @@ import { getNativeAddress } from 'uniswap/src/constants/addresses' import { AssetType, CurrencyAsset } from 'uniswap/src/entities/assets' import { TransactionState } from 'uniswap/src/features/transactions/types/transactionState' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' export function getSendPrefilledState({ chainId, currencyAddress, }: { - chainId: WalletChainId + chainId: UniverseChainId currencyAddress?: Address }): TransactionState { const nativeTokenAddress = getNativeAddress(chainId) diff --git a/packages/wallet/src/features/transactions/send/hooks/useDerivedSendInfo.ts b/packages/wallet/src/features/transactions/send/hooks/useDerivedSendInfo.ts index 1788f997b45..c863127fa3a 100644 --- a/packages/wallet/src/features/transactions/send/hooks/useDerivedSendInfo.ts +++ b/packages/wallet/src/features/transactions/send/hooks/useDerivedSendInfo.ts @@ -1,11 +1,11 @@ import { useMemo } from 'react' import { AssetType } from 'uniswap/src/entities/assets' import { useOnChainCurrencyBalance, useOnChainNativeCurrencyBalance } from 'uniswap/src/features/portfolio/api' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { ValueType, getCurrencyAmount } from 'uniswap/src/features/tokens/getCurrencyAmount' import { useCurrencyInfo } from 'uniswap/src/features/tokens/useCurrencyInfo' import { DerivedSendInfo } from 'uniswap/src/features/transactions/send/types' import { TransactionState } from 'uniswap/src/features/transactions/types/transactionState' -import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { buildCurrencyId } from 'uniswap/src/utils/currencyId' import { useNFT } from 'wallet/src/features/nfts/hooks' @@ -22,7 +22,8 @@ export function useDerivedSendInfo(state: TransactionState): DerivedSendInfo { } = state const activeAccount = useActiveAccount() - const chainId = tradeableAsset?.chainId ?? UniverseChainId.Mainnet + const { defaultChainId } = useEnabledChains() + const chainId = tradeableAsset?.chainId ?? defaultChainId const currencyInInfo = useCurrencyInfo( tradeableAsset?.type === AssetType.Currency @@ -53,7 +54,7 @@ export function useDerivedSendInfo(state: TransactionState): DerivedSendInfo { ) const { balance: nativeInBalance } = useOnChainNativeCurrencyBalance( - chainId ?? UniverseChainId.Mainnet, + chainId ?? defaultChainId, activeAccount?.address, ) diff --git a/packages/wallet/src/features/transactions/send/hooks/useIsSmartContractAddress.ts b/packages/wallet/src/features/transactions/send/hooks/useIsSmartContractAddress.ts index 06c688757ee..1f657624cc3 100644 --- a/packages/wallet/src/features/transactions/send/hooks/useIsSmartContractAddress.ts +++ b/packages/wallet/src/features/transactions/send/hooks/useIsSmartContractAddress.ts @@ -1,11 +1,11 @@ import { useCallback } from 'react' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { useAsyncData } from 'utilities/src/react/hooks' import { useProvider } from 'wallet/src/features/wallet/context' export function useIsSmartContractAddress( address: string | undefined, - chainId: WalletChainId, + chainId: UniverseChainId, ): { loading: boolean isSmartContractAddress: boolean diff --git a/packages/wallet/src/features/transactions/send/hooks/useSendCallback.ts b/packages/wallet/src/features/transactions/send/hooks/useSendCallback.ts index b8ab043ac96..8a471bf208a 100644 --- a/packages/wallet/src/features/transactions/send/hooks/useSendCallback.ts +++ b/packages/wallet/src/features/transactions/send/hooks/useSendCallback.ts @@ -4,7 +4,7 @@ import { useMemo } from 'react' import { useDispatch } from 'react-redux' import { AssetType } from 'uniswap/src/entities/assets' import { GasFeeEstimates } from 'uniswap/src/features/transactions/types/transactionDetails' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { sendTokenActions } from 'wallet/src/features/transactions/send/sendTokenSaga' import { SendTokenParams } from 'wallet/src/features/transactions/send/types' import { useActiveAccount } from 'wallet/src/features/wallet/hooks' @@ -12,7 +12,7 @@ import { useActiveAccount } from 'wallet/src/features/wallet/hooks' /** Helper send callback for ERC20s */ export function useSendERC20Callback( txId?: string, - chainId?: WalletChainId, + chainId?: UniverseChainId, toAddress?: Address, tokenAddress?: Address, amountInWei?: string, @@ -45,7 +45,7 @@ export function useSendERC20Callback( /** Helper send callback for NFTs */ export function useSendNFTCallback( txId?: string, - chainId?: WalletChainId, + chainId?: UniverseChainId, toAddress?: Address, tokenAddress?: Address, tokenId?: string, diff --git a/packages/wallet/src/features/transactions/send/hooks/useSendTransactionRequest.ts b/packages/wallet/src/features/transactions/send/hooks/useSendTransactionRequest.ts index 4bd0ff961a0..d489740da08 100644 --- a/packages/wallet/src/features/transactions/send/hooks/useSendTransactionRequest.ts +++ b/packages/wallet/src/features/transactions/send/hooks/useSendTransactionRequest.ts @@ -6,8 +6,9 @@ import ERC721_ABI from 'uniswap/src/abis/erc721.json' import { Erc1155, Erc20, Erc721 } from 'uniswap/src/abis/types' import { AssetType } from 'uniswap/src/entities/assets' import { toSupportedChainId } from 'uniswap/src/features/chains/utils' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { DerivedSendInfo } from 'uniswap/src/features/transactions/send/types' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { currencyAddress, isNativeCurrencyAddress } from 'uniswap/src/utils/currencyId' import { useAsyncData } from 'utilities/src/react/hooks' @@ -18,9 +19,10 @@ import { useContractManager, useProvider } from 'wallet/src/features/wallet/cont import { useActiveAccountWithThrow } from 'wallet/src/features/wallet/hooks' export function useSendTransactionRequest(derivedSendInfo: DerivedSendInfo): providers.TransactionRequest | undefined { + const { defaultChainId } = useEnabledChains() const account = useActiveAccountWithThrow() const chainId = toSupportedChainId(derivedSendInfo.chainId) - const provider = useProvider(chainId ?? UniverseChainId.Mainnet) + const provider = useProvider(chainId ?? defaultChainId) const contractManager = useContractManager() const transactionFetcher = useCallback(() => { @@ -79,7 +81,7 @@ function getSendParams(account: Account, derivedSendInfo: DerivedSendInfo): Send return { account, - chainId: chainId as WalletChainId, + chainId: chainId as UniverseChainId, toAddress: recipient, tokenAddress, type: assetType, @@ -94,7 +96,7 @@ function getSendParams(account: Account, derivedSendInfo: DerivedSendInfo): Send return { account, - chainId: chainId as WalletChainId, + chainId: chainId as UniverseChainId, toAddress: recipient, tokenAddress, type: AssetType.Currency, diff --git a/packages/wallet/src/features/transactions/send/hooks/useSendWarnings.ts b/packages/wallet/src/features/transactions/send/hooks/useSendWarnings.ts index 6b0ceb9860d..e78186a8338 100644 --- a/packages/wallet/src/features/transactions/send/hooks/useSendWarnings.ts +++ b/packages/wallet/src/features/transactions/send/hooks/useSendWarnings.ts @@ -5,7 +5,7 @@ import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' import { GQLNftAsset } from 'uniswap/src/features/nfts/types' import { getNetworkWarning } from 'uniswap/src/features/transactions/hooks/useParsedTransactionWarnings' import { DerivedSendInfo } from 'uniswap/src/features/transactions/send/types' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyField } from 'uniswap/src/types/currency' import { currencyAddress } from 'uniswap/src/utils/currencyId' import { useIsOffline } from 'utilities/src/connection/useIsOffline' @@ -25,7 +25,7 @@ export function getSendWarnings(t: TFunction, derivedSendInfo: DerivedSendInfo, const isMissingRequiredParams = checkIsMissingRequiredParams( currencyInInfo, nftIn, - chainId as WalletChainId, + chainId as UniverseChainId, recipient, !!currencyAmountIn, !!currencyBalanceIn, @@ -67,7 +67,7 @@ export function useSendWarnings(t: TFunction, derivedSendInfo: DerivedSendInfo): const checkIsMissingRequiredParams = ( currencyInInfo: Maybe, nftIn: GQLNftAsset | undefined, - chainId: WalletChainId | undefined, + chainId: UniverseChainId | undefined, recipient: Address | undefined, hasCurrencyAmount: boolean, hasCurrencyBalance: boolean, diff --git a/packages/wallet/src/features/transactions/send/hooks/useShowSendNetworkNotification.ts b/packages/wallet/src/features/transactions/send/hooks/useShowSendNetworkNotification.ts index 316e098e389..33ff6d740fa 100644 --- a/packages/wallet/src/features/transactions/send/hooks/useShowSendNetworkNotification.ts +++ b/packages/wallet/src/features/transactions/send/hooks/useShowSendNetworkNotification.ts @@ -1,12 +1,12 @@ import { useEffect } from 'react' import { useDispatch } from 'react-redux' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { usePrevious } from 'utilities/src/react/hooks' import { ONE_SECOND_MS } from 'utilities/src/time/time' import { pushNotification } from 'wallet/src/features/notifications/slice' import { AppNotificationType } from 'wallet/src/features/notifications/types' -export function useShowSendNetworkNotification({ chainId }: { chainId?: WalletChainId }): void { +export function useShowSendNetworkNotification({ chainId }: { chainId?: UniverseChainId }): void { const dispatch = useDispatch() const prevChainId = usePrevious(chainId) diff --git a/packages/wallet/src/features/transactions/send/sendTokenSaga.test.ts b/packages/wallet/src/features/transactions/send/sendTokenSaga.test.ts index 6751783e7bd..f8a9a27a474 100644 --- a/packages/wallet/src/features/transactions/send/sendTokenSaga.test.ts +++ b/packages/wallet/src/features/transactions/send/sendTokenSaga.test.ts @@ -27,24 +27,24 @@ const account = signerMnemonicAccount() const { txRequest, ethersTxReceipt } = getTxFixtures() const { mockProvider } = getTxProvidersMocks(ethersTxReceipt) -const erc20TranferParams: SendCurrencyParams = { +const erc20TransferParams: SendCurrencyParams = { txId: '1', type: AssetType.Currency, account, tokenAddress: DAI.address, - chainId: UniverseChainId.Goerli, + chainId: UniverseChainId.Mainnet, toAddress: '0xdefaced', amountInWei: '100000000000000000', currencyAmountUSD: undefined, } -const nativeTranferParams: SendCurrencyParams = { - ...erc20TranferParams, - tokenAddress: getNativeAddress(UniverseChainId.Goerli), +const nativeTransferParams: SendCurrencyParams = { + ...erc20TransferParams, + tokenAddress: getNativeAddress(UniverseChainId.Mainnet), } const erc721TransferParams: SendNFTParams = { txId: '1', type: AssetType.ERC721, - chainId: UniverseChainId.Goerli, + chainId: UniverseChainId.Mainnet, account, toAddress: '0xdefaced', tokenAddress: '0xdeadbeef', @@ -58,9 +58,9 @@ const erc1155TransferParams: SendNFTParams = { const typeInfo: SendTokenTransactionInfo = { assetType: AssetType.Currency, - currencyAmountRaw: erc20TranferParams.amountInWei, - recipient: erc20TranferParams.toAddress, - tokenAddress: erc20TranferParams.tokenAddress, + currencyAmountRaw: erc20TransferParams.amountInWei, + recipient: erc20TransferParams.toAddress, + tokenAddress: erc20TransferParams.tokenAddress, type: TransactionType.Send, currencyAmountUSD: undefined, gasEstimates: undefined, @@ -70,27 +70,27 @@ describe('sendTokenSaga', () => { it('Transfers native currency', async () => { const tx = { from: account.address, - to: nativeTranferParams.toAddress, - value: nativeTranferParams.amountInWei, + to: nativeTransferParams.toAddress, + value: nativeTransferParams.amountInWei, } await expectSaga(sendToken, { - sendTokenParams: nativeTranferParams, + sendTokenParams: nativeTransferParams, txRequest: tx, }) .provide([ - [call(getProvider, nativeTranferParams.chainId), mockProvider], + [call(getProvider, nativeTransferParams.chainId), mockProvider], [call(getContractManager), mockContractManager], [matchers.call.fn(sendTransaction), true], ]) .call(sendTransaction, { transactionOriginType: TransactionOriginType.Internal, - chainId: nativeTranferParams.chainId, - account: nativeTranferParams.account, + chainId: nativeTransferParams.chainId, + account: nativeTransferParams.account, options: { request: tx }, typeInfo: { ...typeInfo, - tokenAddress: nativeTranferParams.tokenAddress, + tokenAddress: nativeTransferParams.tokenAddress, }, txId: '1', }) @@ -98,7 +98,7 @@ describe('sendTokenSaga', () => { }) it('Transfers token currency', async () => { const params = { - ...erc20TranferParams, + ...erc20TransferParams, tokenAddress: DAI.address, } @@ -107,14 +107,14 @@ describe('sendTokenSaga', () => { txRequest, }) .provide([ - [call(getProvider, erc20TranferParams.chainId), mockProvider], + [call(getProvider, erc20TransferParams.chainId), mockProvider], [call(getContractManager), mockContractManager], [matchers.call.fn(sendTransaction), true], ]) .call(sendTransaction, { transactionOriginType: TransactionOriginType.Internal, - chainId: erc20TranferParams.chainId, - account: erc20TranferParams.account, + chainId: erc20TransferParams.chainId, + account: erc20TransferParams.account, options: { request: txRequest }, typeInfo, txId: '1', @@ -180,11 +180,11 @@ describe('sendTokenSaga', () => { getBalance: jest.fn(() => BigNumber.from('0')), } await expectSaga(sendToken, { - sendTokenParams: nativeTranferParams, + sendTokenParams: nativeTransferParams, txRequest, }) .provide([ - [call(getProvider, nativeTranferParams.chainId), provider], + [call(getProvider, nativeTransferParams.chainId), provider], [call(getContractManager), mockContractManager], ]) .silentRun() diff --git a/packages/wallet/src/features/transactions/send/types.ts b/packages/wallet/src/features/transactions/send/types.ts index 84556cd67f4..6db179762bd 100644 --- a/packages/wallet/src/features/transactions/send/types.ts +++ b/packages/wallet/src/features/transactions/send/types.ts @@ -1,14 +1,14 @@ import { Currency, CurrencyAmount } from '@uniswap/sdk-core' import { AssetType, NFTAssetType } from 'uniswap/src/entities/assets' import { GasFeeEstimates } from 'uniswap/src/features/transactions/types/transactionDetails' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { Account } from 'wallet/src/features/wallet/accounts/types' interface BaseSendParams { type: AssetType txId?: string account: Account - chainId: WalletChainId + chainId: UniverseChainId toAddress: Address tokenAddress: Address currencyAmountUSD?: Maybe> // for analytics diff --git a/packages/wallet/src/features/transactions/sendTransactionSaga.test.ts b/packages/wallet/src/features/transactions/sendTransactionSaga.test.ts index effffeeb073..368625f0578 100644 --- a/packages/wallet/src/features/transactions/sendTransactionSaga.test.ts +++ b/packages/wallet/src/features/transactions/sendTransactionSaga.test.ts @@ -8,7 +8,7 @@ import { AccountType } from 'uniswap/src/features/accounts/types' import { addTransaction } from 'uniswap/src/features/transactions/slice' import { TransactionOriginType, TransactionStatus } from 'uniswap/src/features/transactions/types/transactionDetails' import { getTxFixtures } from 'uniswap/src/test/fixtures' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { noOpFunction } from 'utilities/src/test/utils' import { isPrivateRpcSupportedOnChain } from 'wallet/src/features/providers/utils' import { @@ -42,7 +42,7 @@ const { txRequest, txResponse, txTypeInfo } = getTxFixtures() const sendParams = { txId: '0', - chainId: UniverseChainId.Mainnet as WalletChainId, + chainId: UniverseChainId.Mainnet as UniverseChainId, account, options: { request: txRequest }, typeInfo: txTypeInfo, diff --git a/packages/wallet/src/features/transactions/sendTransactionSaga.ts b/packages/wallet/src/features/transactions/sendTransactionSaga.ts index 90c0efa597e..5c57e7e4212 100644 --- a/packages/wallet/src/features/transactions/sendTransactionSaga.ts +++ b/packages/wallet/src/features/transactions/sendTransactionSaga.ts @@ -21,7 +21,7 @@ import { TransactionTypeInfo, isBridgeTypeInfo, } from 'uniswap/src/features/transactions/types/transactionDetails' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { createTransactionId } from 'uniswap/src/utils/createTransactionId' import { logger } from 'utilities/src/logger/logger' import { ONE_MINUTE_MS } from 'utilities/src/time/time' @@ -32,7 +32,7 @@ import { SignerManager } from 'wallet/src/features/wallet/signing/SignerManager' import { hexlifyTransaction } from 'wallet/src/utils/transaction' // This timeout is used to trigger a log event if the transaction is pending for too long -const getTransactionTimeoutMs = (chainId: WalletChainId) => { +const getTransactionTimeoutMs = (chainId: UniverseChainId) => { if (chainId === UniverseChainId.Mainnet) { return 10 * ONE_MINUTE_MS } @@ -43,7 +43,7 @@ export interface SendTransactionParams { // internal id used for tracking transactions before they're submitted // this is optional as an override in txDetail.id calculation txId?: string - chainId: WalletChainId + chainId: UniverseChainId account: AccountMeta options: TransactionOptions typeInfo: TransactionTypeInfo @@ -171,7 +171,7 @@ function* addTransaction( * @param chainId - The chain ID to fetch the nonce for. * @returns The nonce if it was successfully fetched, otherwise undefined. */ -export function* tryGetNonce(account: SignerMnemonicAccountMeta, chainId: WalletChainId) { +export function* tryGetNonce(account: SignerMnemonicAccountMeta, chainId: UniverseChainId) { try { const isPrivateRpcEnabled = Statsig.checkGate(getFeatureFlagName(FeatureFlags.PrivateRpc)) const shouldUseFlashbots = diff --git a/packages/wallet/src/features/transactions/swap/BridgingCurrencyRow.tsx b/packages/wallet/src/features/transactions/swap/BridgingCurrencyRow.tsx index 614562361bd..5b49187ec1b 100644 --- a/packages/wallet/src/features/transactions/swap/BridgingCurrencyRow.tsx +++ b/packages/wallet/src/features/transactions/swap/BridgingCurrencyRow.tsx @@ -2,7 +2,7 @@ import { Flex, Text } from 'ui/src' import { iconSizes } from 'ui/src/theme' import { NetworkLogo } from 'uniswap/src/components/CurrencyLogo/NetworkLogo' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { getSymbolDisplayText } from 'uniswap/src/utils/currency' /** @@ -42,7 +42,7 @@ function CurrencyAmount({ amount, symbol, }: { - chainId: WalletChainId | null + chainId: UniverseChainId | null amount: string symbol: string }): JSX.Element { diff --git a/packages/wallet/src/features/transactions/swap/createSwapFormFromTxDetails.ts b/packages/wallet/src/features/transactions/swap/createSwapFormFromTxDetails.ts index ca2d46cc604..e03b5a3b099 100644 --- a/packages/wallet/src/features/transactions/swap/createSwapFormFromTxDetails.ts +++ b/packages/wallet/src/features/transactions/swap/createSwapFormFromTxDetails.ts @@ -8,7 +8,7 @@ import { } from 'uniswap/src/features/transactions/types/transactionDetails' import { TransactionState } from 'uniswap/src/features/transactions/types/transactionState' import { CurrencyField } from 'uniswap/src/types/currency' -import { currencyAddress, currencyIdToAddress } from 'uniswap/src/utils/currencyId' +import { currencyAddress, currencyIdToAddress, currencyIdToChain } from 'uniswap/src/utils/currencyId' import { logger } from 'utilities/src/logger/logger' import { getAmountsFromTrade } from 'wallet/src/features/transactions/getAmountsFromTrade' @@ -44,6 +44,7 @@ export function createSwapFormFromTxDetails({ const { inputCurrencyAmountRaw, outputCurrencyAmountRaw } = getAmountsFromTrade(typeInfo) const inputAddress = currencyIdToAddress(typeInfo.inputCurrencyId) const outputAddress = currencyIdToAddress(typeInfo.outputCurrencyId) + const outputChainId = currencyIdToChain(typeInfo.outputCurrencyId) const inputAsset: CurrencyAsset = { address: inputAddress, @@ -51,11 +52,13 @@ export function createSwapFormFromTxDetails({ type: AssetType.Currency, } - const outputAsset: CurrencyAsset = { - address: outputAddress, - chainId, - type: AssetType.Currency, - } + const outputAsset: CurrencyAsset | null = outputChainId + ? { + address: outputAddress, + chainId: outputChainId, + type: AssetType.Currency, + } + : null const exactCurrencyField = isBridging ? CurrencyField.INPUT diff --git a/packages/wallet/src/features/transactions/swap/customRpc.ts b/packages/wallet/src/features/transactions/swap/customRpc.ts index 511b9aa3380..6fd4c49ebd7 100644 --- a/packages/wallet/src/features/transactions/swap/customRpc.ts +++ b/packages/wallet/src/features/transactions/swap/customRpc.ts @@ -1,6 +1,6 @@ import { FeatureFlags } from 'uniswap/src/features/gating/flags' import { useFeatureFlag } from 'uniswap/src/features/gating/hooks' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { isPrivateRpcSupportedOnChain } from 'wallet/src/features/providers/utils' import { useSwapProtectionSetting } from 'wallet/src/features/wallet/hooks' import { SwapProtectionSetting } from 'wallet/src/features/wallet/slice' @@ -13,7 +13,7 @@ import { SwapProtectionSetting } from 'wallet/src/features/wallet/slice' * 3. Private RPC is supported on chain * */ -export function useShouldUsePrivateRpc(chainId: Maybe): boolean { +export function useShouldUsePrivateRpc(chainId: Maybe): boolean { const privateRpcFeatureEnabled = useFeatureFlag(FeatureFlags.PrivateRpc) const swapProtectionSettingEnabled = useSwapProtectionSetting() === SwapProtectionSetting.On const privateRpcSupportedOnChain = chainId ? isPrivateRpcSupportedOnChain(chainId) : false diff --git a/packages/wallet/src/features/transactions/swap/submitOrderSaga.ts b/packages/wallet/src/features/transactions/swap/submitOrderSaga.ts index d02fc7560f6..f4ab8d43b48 100644 --- a/packages/wallet/src/features/transactions/swap/submitOrderSaga.ts +++ b/packages/wallet/src/features/transactions/swap/submitOrderSaga.ts @@ -16,7 +16,7 @@ import { UniswapXOrderDetails, } from 'uniswap/src/features/transactions/types/transactionDetails' import { WrapType } from 'uniswap/src/features/transactions/types/wrap' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { createTransactionId } from 'uniswap/src/utils/createTransactionId' import { logger } from 'utilities/src/logger/logger' import { ONE_SECOND_MS } from 'utilities/src/time/time' @@ -34,7 +34,7 @@ export interface SubmitUniswapXOrderParams { quote: DutchQuoteV2 | PriorityQuote routing: Routing.DUTCH_V2 | Routing.PRIORITY permit: ValidatedPermit - chainId: WalletChainId + chainId: UniverseChainId account: AccountMeta typeInfo: TransactionTypeInfo analytics: ReturnType diff --git a/packages/wallet/src/features/transactions/swap/swapSaga.test.ts b/packages/wallet/src/features/transactions/swap/swapSaga.test.ts index 130a580c984..8f552497529 100644 --- a/packages/wallet/src/features/transactions/swap/swapSaga.test.ts +++ b/packages/wallet/src/features/transactions/swap/swapSaga.test.ts @@ -3,7 +3,7 @@ import { call, select } from '@redux-saga/core/effects' import { permit2Address } from '@uniswap/permit2-sdk' import { Protocol } from '@uniswap/router-sdk' import { TradeType } from '@uniswap/sdk-core' -import { UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk' +import { UNIVERSAL_ROUTER_ADDRESS, UniversalRouterVersion } from '@uniswap/universal-router-sdk' import JSBI from 'jsbi' import { expectSaga, testSaga } from 'redux-saga-test-plan' import { EffectProviders, StaticProvider } from 'redux-saga-test-plan/providers' @@ -47,7 +47,7 @@ jest.mock('uniswap/src/features/gating/sdk/statsig', () => ({ const account = signerMnemonicAccount() const CHAIN_ID = UniverseChainId.Mainnet -const universalRouterAddress = UNIVERSAL_ROUTER_ADDRESS(CHAIN_ID) +const universalRouterAddress = UNIVERSAL_ROUTER_ADDRESS(UniversalRouterVersion.V1_2, CHAIN_ID) const { mockProvider } = getTxProvidersMocks() diff --git a/packages/wallet/src/features/transactions/swap/swapSaga.ts b/packages/wallet/src/features/transactions/swap/swapSaga.ts index b0fe35e044d..61ea0016226 100644 --- a/packages/wallet/src/features/transactions/swap/swapSaga.ts +++ b/packages/wallet/src/features/transactions/swap/swapSaga.ts @@ -38,6 +38,7 @@ export function* approveAndSwap(params: SwapParams) { const { swapTxContext, account, txId, analytics, onSuccess, onFailure } = params const { routing, approveTxRequest } = swapTxContext const isUniswapX = routing === Routing.DUTCH_V2 || routing === Routing.PRIORITY + const isBridge = routing === Routing.BRIDGE const chainId = swapTxContext.trade.inputAmount.currency.chainId // For classic swaps, trigger UI changes immediately after click @@ -47,7 +48,8 @@ export function* approveAndSwap(params: SwapParams) { } // MEV protection is not needed for UniswapX approval and/or wrap transactions. - const submitViaPrivateRpc = !isUniswapX && (yield* call(shouldSubmitViaPrivateRpc, chainId)) + // We disable for bridge to avoid any potential issues with BE checking status. + const submitViaPrivateRpc = !isUniswapX && !isBridge && (yield* call(shouldSubmitViaPrivateRpc, chainId)) // We must manually set the nonce when submitting multiple transactions in a row, // otherwise for some L2s the Provider might fetch the same nonce for both transactions. let nonce = yield* call(tryGetNonce, account, chainId) diff --git a/packages/wallet/src/features/transactions/transactionWatcherSaga.test.ts b/packages/wallet/src/features/transactions/transactionWatcherSaga.test.ts index ea3e9cdda56..f81a08d5453 100644 --- a/packages/wallet/src/features/transactions/transactionWatcherSaga.test.ts +++ b/packages/wallet/src/features/transactions/transactionWatcherSaga.test.ts @@ -27,7 +27,8 @@ import { attemptCancelTransaction } from 'wallet/src/features/transactions/cance import { deleteTransaction, transactionWatcher, - waitForTxnInvalidated, + waitForBridgeSendCompleted, + waitForSameNonceFinalized, watchFiatOnRampTransaction, watchTransaction, } from 'wallet/src/features/transactions/transactionWatcherSaga' @@ -64,6 +65,7 @@ describe(transactionWatcher, () => { }, }, wallet: { activeAccountAddress: ACTIVE_ACCOUNT_ADDRESS }, + userSettings: { isTestnetModeEnabled: false }, }) .provide([ [call(getProvider, UniverseChainId.Mainnet), mockProvider], @@ -108,6 +110,7 @@ describe(watchTransaction, () => { }) .withState({ wallet: { activeAccountAddress: ACTIVE_ACCOUNT_ADDRESS }, + userSettings: { isTestnetModeEnabled: false }, }) .provide([[call(getProvider, chainId), receiptProvider]]) .put(finalizeTransaction(finalizedTxAction.payload)) @@ -128,6 +131,7 @@ describe(watchTransaction, () => { }) .withState({ wallet: { activeAccountAddress: ACTIVE_ACCOUNT_ADDRESS }, + userSettings: { isTestnetModeEnabled: false }, }) .provide([ [call(getProvider, chainId), receiptProvider], @@ -138,7 +142,7 @@ describe(watchTransaction, () => { .silentRun() }) - it('Invalidates stale transaction', () => { + it('Invalidates stale transaction when another transaction with same nonce is finalized', () => { const receiptProvider = { waitForTransaction: jest.fn(async () => { await sleep(1000) @@ -152,10 +156,36 @@ describe(watchTransaction, () => { }) .withState({ wallet: { activeAccountAddress: ACTIVE_ACCOUNT_ADDRESS }, + userSettings: { isTestnetModeEnabled: false }, }) .provide([ [call(getProvider, chainId), receiptProvider], - [call(waitForTxnInvalidated, chainId, id, options.request.nonce), true], + [call(waitForSameNonceFinalized, chainId, id, options.request.nonce), true], + ]) + .call(deleteTransaction, txDetailsPending) + .dispatch(transactionActions.deleteTransaction({ address: from, id, chainId })) + .silentRun() + }) + + it('Invalidates stale transaction when bridge send is confirmed with same nonce', () => { + const receiptProvider = { + waitForTransaction: jest.fn(async () => { + await sleep(1000) + return null + }), + } + + return expectSaga(watchTransaction, { + transaction: txDetailsPending, + apolloClient: mockApolloClient, + }) + .withState({ + wallet: { activeAccountAddress: ACTIVE_ACCOUNT_ADDRESS }, + userSettings: { isTestnetModeEnabled: false }, + }) + .provide([ + [call(getProvider, chainId), receiptProvider], + [call(waitForBridgeSendCompleted, chainId, id, options.request.nonce), true], ]) .call(deleteTransaction, txDetailsPending) .dispatch(transactionActions.deleteTransaction({ address: from, id, chainId })) diff --git a/packages/wallet/src/features/transactions/transactionWatcherSaga.ts b/packages/wallet/src/features/transactions/transactionWatcherSaga.ts index 5f897c0058f..577014d45af 100644 --- a/packages/wallet/src/features/transactions/transactionWatcherSaga.ts +++ b/packages/wallet/src/features/transactions/transactionWatcherSaga.ts @@ -32,6 +32,7 @@ import { isBridge, isClassic, isUniswapX } from 'uniswap/src/features/transactio import { toTradingApiSupportedChainId } from 'uniswap/src/features/transactions/swap/utils/tradingApi' import { BaseSwapTransactionInfo, + BridgeTransactionDetails, FinalizedTransactionDetails, QueuedOrderStatus, SendTokenTransactionInfo, @@ -41,7 +42,7 @@ import { isFinalizedTx, } from 'uniswap/src/features/transactions/types/transactionDetails' import i18n from 'uniswap/src/i18n/i18n' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { logger } from 'utilities/src/logger/logger' import { ONE_SECOND_MS } from 'utilities/src/time/time' import { fetchFiatOnRampTransaction } from 'wallet/src/features/fiatOnRamp/api' @@ -329,19 +330,25 @@ function* waitForRemoteUpdate(transaction: TransactionDetails, provider: provide const receipt = receiptFromEthersReceipt(ethersReceipt) if (isBridge(transaction)) { - // Bridge swaps become non-cancellable after the transaction is submitted to chain. - // Update should happen without watching to avoid an infinite re-watch loop. - const updatedTransaction = { ...transaction, cancellable: false } - yield* put(transactionActions.updateTransactionWithoutWatch(updatedTransaction)) + status = getFinalizedTransactionStatus(transaction.status, ethersReceipt?.status) + if (status === TransactionStatus.Success) { + // Only the send part was successful, wait for receive part to be confirmed on chain. + // Bridge swaps become non-cancellable after the send transaction is confirmed on chain. + if (!transaction.sendConfirmed) { + const updatedTransaction: BridgeTransactionDetails = { ...transaction, sendConfirmed: true } + yield* put(transactionActions.updateTransaction(updatedTransaction)) + } - // Poll for bridging status from BE - status = yield* call(waitForBridgingStatus, transaction) + // Poll for bridging status from BE + status = yield* call(waitForBridgingStatus, transaction) + } } // Classic transaction status is based on receipt, while UniswapX status is based backend response. if (isClassic(transaction)) { status = getFinalizedTransactionStatus(transaction.status, ethersReceipt?.status) } + return { ...transaction, status, receipt, hash } } @@ -408,7 +415,7 @@ function* waitForBridgingStatus(transaction: TransactionDetails) { return swapStatus ? SWAP_STATUS_TO_TX_STATUS[swapStatus] : TransactionStatus.Failed } -function* waitForCancellation(chainId: WalletChainId, id: string) { +function* waitForCancellation(chainId: UniverseChainId, id: string) { while (true) { const { payload } = yield* take>(cancelTransaction.type) if (payload.cancelRequest && payload.chainId === chainId && payload.id === id) { @@ -417,7 +424,7 @@ function* waitForCancellation(chainId: WalletChainId, id: string) { } } -function* waitForReplacement(chainId: WalletChainId, id: string) { +function* waitForReplacement(chainId: UniverseChainId, id: string) { while (true) { const { payload } = yield* take>(replaceTransaction.type) if (payload.chainId === chainId && payload.id === id) { @@ -425,11 +432,21 @@ function* waitForReplacement(chainId: WalletChainId, id: string) { } } } + /** * Monitor for transactions with the same nonce as the current transaction. If any duplicate is finalized, it means * the current transaction has been invalidated and wont be picked up on chain. */ -export function* waitForTxnInvalidated(chainId: WalletChainId, id: string, nonce: BigNumberish | undefined) { +export function* waitForTxnInvalidated(chainId: UniverseChainId, id: string, nonce: BigNumberish | undefined) { + yield* race({ + sameNonceFinalized: call(waitForSameNonceFinalized, chainId, id, nonce), + bridgeSendCompleted: call(waitForBridgeSendCompleted, chainId, id, nonce), + }) + + return true +} + +export function* waitForSameNonceFinalized(chainId: UniverseChainId, id: string, nonce: BigNumberish | undefined) { while (true) { const { payload } = yield* take>( transactionActions.finalizeTransaction.type, @@ -446,6 +463,28 @@ export function* waitForTxnInvalidated(chainId: WalletChainId, id: string, nonce } } +/** + * When we're canceling a bridge tx, we should invalidate the cancel tx as soon as the send part + * of the bridge is confirmed on chain, instead of waiting for the full completion of the bridge. + */ +export function* waitForBridgeSendCompleted(chainId: UniverseChainId, id: string, nonce: BigNumberish | undefined) { + while (true) { + const { payload } = yield* take>( + transactionActions.updateTransaction.type, + ) + + if ( + isBridge(payload) && + payload.sendConfirmed && + payload.chainId === chainId && + payload.id !== id && + payload.options.request.nonce === nonce + ) { + return true + } + } +} + /** * Send analytics events for finalized transactions */ diff --git a/packages/wallet/src/features/transactions/utils.ts b/packages/wallet/src/features/transactions/utils.ts index 519cb8fcb90..1f207c376f0 100644 --- a/packages/wallet/src/features/transactions/utils.ts +++ b/packages/wallet/src/features/transactions/utils.ts @@ -1,5 +1,5 @@ import { BigNumber, providers } from 'ethers' -import { isUniswapX } from 'uniswap/src/features/transactions/swap/utils/routing' +import { isBridge, isUniswapX } from 'uniswap/src/features/transactions/swap/utils/routing' import { FinalizedTransactionStatus, TransactionDetails, @@ -7,11 +7,11 @@ import { TransactionStatus, TransactionType, } from 'uniswap/src/features/transactions/types/transactionDetails' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' export function getSerializableTransactionRequest( request: providers.TransactionRequest, - chainId?: WalletChainId, + chainId?: UniverseChainId, ): providers.TransactionRequest { // prettier-ignore const { to, from, nonce, gasLimit, gasPrice, data, value, maxPriorityFeePerGas, maxFeePerGas, type } = request @@ -52,6 +52,9 @@ export function getFinalizedTransactionStatus( } export function getIsCancelable(tx: TransactionDetails): boolean { + if (isBridge(tx) && tx.sendConfirmed) { + return false + } if (tx.status === TransactionStatus.Pending && (isUniswapX(tx) || Object.keys(tx.options?.request).length > 0)) { return true } diff --git a/packages/wallet/src/features/unitags/AvatarSelection.ts b/packages/wallet/src/features/unitags/AvatarSelection.ts index f89d6edf148..9822522b3d9 100644 --- a/packages/wallet/src/features/unitags/AvatarSelection.ts +++ b/packages/wallet/src/features/unitags/AvatarSelection.ts @@ -1,5 +1,6 @@ import { ImageLibraryOptions, launchImageLibrary } from 'react-native-image-picker' import { useNftsTabQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks' +import { useEnabledChains } from 'uniswap/src/features/settings/hooks' import { NUM_FIRST_NFTS } from 'wallet/src/components/nfts/NftsList' import { formatNftItems } from 'wallet/src/features/nfts/utils' @@ -33,8 +34,15 @@ export function useAvatarSelectionHandler({ setAvatarImageUri: (uri: string) => void showModal: () => void }): { avatarSelectionHandler: () => Promise; hasNFTs: boolean } { + const { gqlChains } = useEnabledChains() + const { data: nftsData } = useNftsTabQuery({ - variables: { ownerAddress: address, first: NUM_FIRST_NFTS, filter: { filterSpam: false } }, + variables: { + ownerAddress: address, + first: NUM_FIRST_NFTS, + filter: { filterSpam: false }, + chains: gqlChains, + }, }) const nftItems = formatNftItems(nftsData) diff --git a/packages/wallet/src/features/unitags/ChooseNftModal.tsx b/packages/wallet/src/features/unitags/ChooseNftModal.tsx index 2595ce2bc48..cc92604132c 100644 --- a/packages/wallet/src/features/unitags/ChooseNftModal.tsx +++ b/packages/wallet/src/features/unitags/ChooseNftModal.tsx @@ -1,10 +1,11 @@ import { useTranslation } from 'react-i18next' -import { Flex, FlexProps, SpaceTokens, Text, isWeb, useDeviceInsets, useSporeColors } from 'ui/src' +import { Flex, FlexProps, SpaceTokens, Text, isWeb, useSporeColors } from 'ui/src' import { X } from 'ui/src/components/icons' import { spacing } from 'ui/src/theme' import { Modal } from 'uniswap/src/components/modals/Modal' import { ModalProps } from 'uniswap/src/components/modals/ModalProps' import { ModalName } from 'uniswap/src/features/telemetry/constants' +import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { isMobileApp } from 'utilities/src/platform' import { NftView } from 'wallet/src/components/nfts/NftView' import { NftViewWithContextMenu } from 'wallet/src/components/nfts/NftViewWithContextMenu' @@ -33,7 +34,7 @@ export const ChooseNftModal = ({ onClose, }: ChooseNftModalProps): JSX.Element => { const colors = useSporeColors() - const insets = useDeviceInsets() + const insets = useAppInsets() const { t } = useTranslation() const renderNFT = (item: NFTItem): JSX.Element => { diff --git a/packages/wallet/src/features/wallet/context.tsx b/packages/wallet/src/features/wallet/context.tsx index 28870b6bb0b..33a8b6f4a9e 100644 --- a/packages/wallet/src/features/wallet/context.tsx +++ b/packages/wallet/src/features/wallet/context.tsx @@ -3,7 +3,7 @@ import { Signer } from 'ethers' import { createContext, PropsWithChildren, useCallback, useContext, useEffect, useState } from 'react' import { call, getContext } from 'typed-redux-saga' import { SignerMnemonicAccountMeta } from 'uniswap/src/features/accounts/types' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { logger } from 'utilities/src/logger/logger' import { ContractManager } from 'wallet/src/features/contracts/ContractManager' import { ProviderManager } from 'wallet/src/features/providers/ProviderManager' @@ -63,7 +63,7 @@ export function useProviderManager(): ProviderManager { return useContext(WalletContext).value.providers } -export function useProvider(chainId: WalletChainId) { +export function useProvider(chainId: UniverseChainId) { return useProviderManager().tryGetProvider(chainId) } @@ -72,13 +72,13 @@ export function* getProviderManager() { return (yield* getContext('providers')) ?? walletContextValue.providers } -export function* getProvider(chainId: WalletChainId) { +export function* getProvider(chainId: UniverseChainId) { const providerManager = yield* call(getProviderManager) // Note, unlike useWalletProvider above, this throws on missing provider return providerManager.getProvider(chainId) } -export function* getPrivateProvider(chainId: WalletChainId, account?: SignerMnemonicAccountMeta) { +export function* getPrivateProvider(chainId: UniverseChainId, account?: SignerMnemonicAccountMeta) { let signer: Signer | undefined if (account) { const signerManager = yield* call(getSignerManager) @@ -91,7 +91,7 @@ export function* getPrivateProvider(chainId: WalletChainId, account?: SignerMnem /** * Non-generator version of getProvider */ -export function getProviderSync(chainId: WalletChainId) { +export function getProviderSync(chainId: UniverseChainId) { return walletContextValue.providers.getProvider(chainId) } diff --git a/packages/wallet/src/features/wallet/signing/NativeSigner.native.ts b/packages/wallet/src/features/wallet/signing/NativeSigner.native.ts index 84f6cfa1e06..5eb0770a41a 100644 --- a/packages/wallet/src/features/wallet/signing/NativeSigner.native.ts +++ b/packages/wallet/src/features/wallet/signing/NativeSigner.native.ts @@ -45,6 +45,7 @@ export class NativeSigner extends Signer { const signature = await Keyring.signHashForAddress( this.address, _TypedDataEncoder.hash(domain, types, value).slice(2), + // TODO: WALL-4919: Remove hardcoded Mainnet toSupportedChainId(domain.chainId) || UniverseChainId.Mainnet, ) return signature diff --git a/packages/wallet/src/features/wallet/signing/NativeSigner.ts b/packages/wallet/src/features/wallet/signing/NativeSigner.ts index f12428fcab0..2b75c93276c 100644 --- a/packages/wallet/src/features/wallet/signing/NativeSigner.ts +++ b/packages/wallet/src/features/wallet/signing/NativeSigner.ts @@ -1,11 +1,7 @@ import { TypedDataDomain, TypedDataField } from '@ethersproject/abstract-signer' -import { _TypedDataEncoder } from '@ethersproject/hash' -import { Signer, UnsignedTransaction, providers, utils } from 'ethers' -import { toSupportedChainId } from 'uniswap/src/features/chains/utils' +import { Signer, providers } from 'ethers' import { SignsTypedData } from 'uniswap/src/features/transactions/signing' -import { UniverseChainId } from 'uniswap/src/types/chains' -import { areAddressesEqual } from 'uniswap/src/utils/addresses' -import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring' +import { PlatformSplitStubError } from 'utilities/src/errors' /** * A signer that uses a native keyring to access keys @@ -14,66 +10,36 @@ import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring' export class NativeSigner extends Signer implements SignsTypedData { constructor( - private readonly address: string, - provider?: providers.Provider, + private readonly _address: string, + _provider?: providers.Provider, ) { super() - if (provider && !providers.Provider.isProvider(provider)) { - throw new Error(`Invalid provider: ${provider}`) - } - - utils.defineReadOnly(this, 'provider', provider) + throw new PlatformSplitStubError('NativeSigner') } getAddress(): Promise { - return Promise.resolve(this.address) + throw new PlatformSplitStubError('getAddress') } - signMessage(message: string): Promise { - return Keyring.signMessageForAddress(this.address, message) + signMessage(_message: string): Promise { + throw new PlatformSplitStubError('signMessage') } // reference: https://github.com/ethers-io/ethers.js/blob/ce8f1e4015c0f27bf178238770b1325136e3351a/packages/wallet/src.ts/index.ts#L135 async _signTypedData( - domain: TypedDataDomain, - types: Record>, - value: Record, + _domain: TypedDataDomain, + _types: Record>, + _value: Record, ): Promise { - const signature = await Keyring.signHashForAddress( - this.address, - _TypedDataEncoder.hash(domain, types, value), - toSupportedChainId(domain.chainId) || UniverseChainId.Mainnet, - ) - return signature + throw new PlatformSplitStubError('_signTypedData') } - async signTransaction(transaction: providers.TransactionRequest): Promise { - const tx = await utils.resolveProperties(transaction) - - if (tx.chainId === undefined) { - throw new Error('Attempted to sign transaction with an undefined chain') - } - - if (tx.from != null) { - if (!areAddressesEqual(tx.from, this.address)) { - throw new Error(`Signing address does not match the tx 'from' address`) - } - delete tx.from - } - - const ut = tx - const hashedTx = utils.keccak256(utils.serializeTransaction(ut)) - const signature = await Keyring.signTransactionHashForAddress( - this.address, - hashedTx, - tx.chainId || UniverseChainId.Mainnet, - ) - - return utils.serializeTransaction(ut, signature) + async signTransaction(_transaction: providers.TransactionRequest): Promise { + throw new PlatformSplitStubError('signTransaction') } - connect(provider: providers.Provider): NativeSigner { - return new NativeSigner(this.address, provider) + connect(_provider: providers.Provider): NativeSigner { + throw new PlatformSplitStubError('connect') } } diff --git a/packages/wallet/src/features/wallet/signing/NativeSigner.web.ts b/packages/wallet/src/features/wallet/signing/NativeSigner.web.ts new file mode 100644 index 00000000000..4ae69339d82 --- /dev/null +++ b/packages/wallet/src/features/wallet/signing/NativeSigner.web.ts @@ -0,0 +1,81 @@ +import { TypedDataDomain, TypedDataField } from '@ethersproject/abstract-signer' +import { _TypedDataEncoder } from '@ethersproject/hash' +import { Signer, UnsignedTransaction, providers, utils } from 'ethers' +import { toSupportedChainId } from 'uniswap/src/features/chains/utils' +import { SignsTypedData } from 'uniswap/src/features/transactions/signing' +import { UniverseChainId } from 'uniswap/src/types/chains' +import { areAddressesEqual } from 'uniswap/src/utils/addresses' +import { Keyring } from 'wallet/src/features/wallet/Keyring/Keyring' + +/** + * A signer that uses a native keyring to access keys + * NOTE: provide Keyring.platform.ts at runtime. + */ + +export class NativeSigner extends Signer implements SignsTypedData { + constructor( + private readonly address: string, + provider?: providers.Provider, + ) { + super() + + if (provider && !providers.Provider.isProvider(provider)) { + throw new Error(`Invalid provider: ${provider}`) + } + + utils.defineReadOnly(this, 'provider', provider) + } + + getAddress(): Promise { + return Promise.resolve(this.address) + } + + signMessage(message: string): Promise { + return Keyring.signMessageForAddress(this.address, message) + } + + // reference: https://github.com/ethers-io/ethers.js/blob/ce8f1e4015c0f27bf178238770b1325136e3351a/packages/wallet/src.ts/index.ts#L135 + async _signTypedData( + domain: TypedDataDomain, + types: Record>, + value: Record, + ): Promise { + const signature = await Keyring.signHashForAddress( + this.address, + _TypedDataEncoder.hash(domain, types, value), + // TODO: WALL-4919: Remove hardcoded Mainnet + toSupportedChainId(domain.chainId) || UniverseChainId.Mainnet, + ) + return signature + } + + async signTransaction(transaction: providers.TransactionRequest): Promise { + const tx = await utils.resolveProperties(transaction) + + if (tx.chainId === undefined) { + throw new Error('Attempted to sign transaction with an undefined chain') + } + + if (tx.from != null) { + if (!areAddressesEqual(tx.from, this.address)) { + throw new Error(`Signing address does not match the tx 'from' address`) + } + delete tx.from + } + + const ut = tx + const hashedTx = utils.keccak256(utils.serializeTransaction(ut)) + const signature = await Keyring.signTransactionHashForAddress( + this.address, + hashedTx, + // TODO: WALL-4919: Remove hardcoded Mainnet + tx.chainId || UniverseChainId.Mainnet, + ) + + return utils.serializeTransaction(ut, signature) + } + + connect(provider: providers.Provider): NativeSigner { + return new NativeSigner(this.address, provider) + } +} diff --git a/packages/wallet/src/test/fixtures/wallet/notifications.ts b/packages/wallet/src/test/fixtures/wallet/notifications.ts index 9c7741f67cb..ec62c2dabdd 100644 --- a/packages/wallet/src/test/fixtures/wallet/notifications.ts +++ b/packages/wallet/src/test/fixtures/wallet/notifications.ts @@ -8,7 +8,7 @@ import { WrapType } from 'uniswap/src/features/transactions/types/wrap' import { currencyInfo } from 'uniswap/src/test/fixtures/wallet/currencies' import { faker } from 'uniswap/src/test/shared' import { createFixture, randomChoice, randomEnumValue } from 'uniswap/src/test/utils' -import { WALLET_SUPPORTED_CHAIN_IDS } from 'uniswap/src/types/chains' +import { SUPPORTED_CHAIN_IDS } from 'uniswap/src/types/chains' import { WalletConnectEvent } from 'uniswap/src/types/walletConnect' import { AppErrorNotification, @@ -74,7 +74,7 @@ const transactionNotificationBase = createFixture() txType: randomEnumValue(TransactionType), txStatus: randomChoice(FINALIZED_TRANSACTION_STATUSES), txId: faker.datatype.uuid(), - chainId: randomChoice(WALLET_SUPPORTED_CHAIN_IDS), + chainId: randomChoice(SUPPORTED_CHAIN_IDS), })) export const approveTxNotification = createFixture()(() => ({ @@ -160,7 +160,7 @@ export const successNotification = createFixture()(() => ({ export const swapNetworkNotification = createFixture()(() => ({ ...appNotificationBase(), type: AppNotificationType.NetworkChanged, - chainId: randomChoice(WALLET_SUPPORTED_CHAIN_IDS), + chainId: randomChoice(SUPPORTED_CHAIN_IDS), flow: 'swap', })) diff --git a/packages/wallet/src/test/mocks/providers.ts b/packages/wallet/src/test/mocks/providers.ts index a481d6c3a19..d2c0eb1fa19 100644 --- a/packages/wallet/src/test/mocks/providers.ts +++ b/packages/wallet/src/test/mocks/providers.ts @@ -6,7 +6,7 @@ import WETH_ABI from 'uniswap/src/abis/weth.json' import { getWrappedNativeAddress } from 'uniswap/src/constants/addresses' import { DAI } from 'uniswap/src/constants/tokens' import { ethersTransactionReceipt } from 'uniswap/src/test/fixtures' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { ContractManager } from 'wallet/src/features/contracts/ContractManager' import { SignerManager } from 'wallet/src/features/wallet/signing/SignerManager' @@ -37,7 +37,7 @@ export const getTxProvidersMocks = (txReceipt?: TransactionReceipt): TxProviders getTransactionCount: (): number => 1000, estimateGas: (): BigNumber => BigNumber.from('30000'), sendTransaction: (): { hash: string } => ({ hash: '0xabcdef' }), - detectNetwork: (): { name: string; chainId: WalletChainId } => ({ + detectNetwork: (): { name: string; chainId: UniverseChainId } => ({ name: 'mainnet', chainId: 1, }), diff --git a/packages/wallet/src/utils/currencyId.ts b/packages/wallet/src/utils/currencyId.ts index ac060f784e7..a2122533957 100644 --- a/packages/wallet/src/utils/currencyId.ts +++ b/packages/wallet/src/utils/currencyId.ts @@ -2,7 +2,7 @@ import { Currency } from '@uniswap/sdk-core' import { getNativeAddress, getWrappedNativeAddress } from 'uniswap/src/constants/addresses' import { DEFAULT_NATIVE_ADDRESS } from 'uniswap/src/constants/chains' import { toSupportedChainId } from 'uniswap/src/features/chains/utils' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { CurrencyId } from 'uniswap/src/types/currency' import { areAddressesEqual } from 'uniswap/src/utils/addresses' @@ -10,15 +10,15 @@ export function currencyId(currency: Currency): CurrencyId { return buildCurrencyId(currency.chainId, currencyAddress(currency)) } -export function buildCurrencyId(chainId: WalletChainId, address: string): string { +export function buildCurrencyId(chainId: UniverseChainId, address: string): string { return `${chainId}-${address}` } -export function buildNativeCurrencyId(chainId: WalletChainId): string { +export function buildNativeCurrencyId(chainId: UniverseChainId): string { return buildCurrencyId(chainId, getNativeAddress(chainId)) } -export function buildWrappedNativeCurrencyId(chainId: WalletChainId): string { +export function buildWrappedNativeCurrencyId(chainId: UniverseChainId): string { return buildCurrencyId(chainId, getWrappedNativeAddress(chainId)) } @@ -44,7 +44,7 @@ export function getCurrencyAddressForAnalytics(currency: Currency): string { return currency.address } -export const isNativeCurrencyAddress = (chainId: WalletChainId, address: Maybe
): boolean => { +export const isNativeCurrencyAddress = (chainId: UniverseChainId, address: Maybe
): boolean => { if (!address) { return true } @@ -61,8 +61,8 @@ export function currencyIdToAddress(_currencyId: string): Address { return currencyIdParts[1] } -function isPolygonChain(chainId: number): chainId is UniverseChainId.Polygon | UniverseChainId.PolygonMumbai { - return chainId === UniverseChainId.PolygonMumbai || chainId === UniverseChainId.Polygon +function isPolygonChain(chainId: number): chainId is UniverseChainId.Polygon { + return chainId === UniverseChainId.Polygon } function isCeloChain(chainId: number): chainId is UniverseChainId.Celo { @@ -90,7 +90,7 @@ export function currencyIdToGraphQLAddress(_currencyId?: string): Address | null return address.toLowerCase() } -export function currencyIdToChain(_currencyId: string): WalletChainId | null { +export function currencyIdToChain(_currencyId: string): UniverseChainId | null { return toSupportedChainId(_currencyId.split('-')[0]) } diff --git a/packages/wallet/src/utils/linking.ts b/packages/wallet/src/utils/linking.ts index 5a7d3fdb113..8953b1b4ba5 100644 --- a/packages/wallet/src/utils/linking.ts +++ b/packages/wallet/src/utils/linking.ts @@ -5,7 +5,7 @@ import { uniswapUrls } from 'uniswap/src/constants/urls' import { toUniswapWebAppLink } from 'uniswap/src/features/chains/utils' import { BACKEND_NATIVE_CHAIN_ADDRESS_STRING } from 'uniswap/src/features/search/utils' import { ServiceProviderInfo } from 'uniswap/src/features/transactions/types/transactionDetails' -import { UniverseChainId, WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { currencyIdToChain, currencyIdToGraphQLAddress } from 'uniswap/src/utils/currencyId' import { ExplorerDataType, getExplorerLink, openUri } from 'uniswap/src/utils/linking' @@ -13,7 +13,7 @@ export function dismissInAppBrowser(): void { WebBrowser.dismissBrowser() } -export async function openTransactionLink(hash: string | undefined, chainId: WalletChainId): Promise { +export async function openTransactionLink(hash: string | undefined, chainId: UniverseChainId): Promise { if (!hash) { return undefined } @@ -59,7 +59,7 @@ export function getProfileUrl(walletAddress: string): string { const UTM_TAGS_MOBILE = 'utm_medium=mobile&utm_source=share-tdp' export function getTokenUrl(currencyId: string, addMobileUTMTags: boolean = false): string | undefined { - const chainId = currencyIdToChain(currencyId) as WalletChainId + const chainId = currencyIdToChain(currencyId) as UniverseChainId if (!chainId) { return undefined } diff --git a/packages/wallet/src/utils/useNoYoloParser.ts b/packages/wallet/src/utils/useNoYoloParser.ts index b014b8731a1..e5f4e9a9b30 100644 --- a/packages/wallet/src/utils/useNoYoloParser.ts +++ b/packages/wallet/src/utils/useNoYoloParser.ts @@ -2,13 +2,13 @@ import { JsonRpcProvider } from '@ethersproject/providers' import { ExplorerAbiFetcher, Parser, ProxyAbiFetcher, Transaction, TransactionDescription } from 'no-yolo-signatures' import { useEffect, useMemo, useState } from 'react' import { UNIVERSE_CHAIN_INFO } from 'uniswap/src/constants/chains' -import { RPCType, WalletChainId } from 'uniswap/src/types/chains' +import { RPCType, UniverseChainId } from 'uniswap/src/types/chains' import { EthTransaction } from 'uniswap/src/types/walletConnect' import { logger } from 'utilities/src/logger/logger' export function useNoYoloParser( transaction: EthTransaction, - chainId?: WalletChainId, + chainId?: UniverseChainId, ): { parsedTransactionData: TransactionDescription | undefined; isLoading: boolean } { const [isLoading, setIsLoading] = useState(true) const [parsedTransactionData, setParsedTransactionData] = useState(undefined) diff --git a/packages/wallet/src/utils/useTransactionCurrencies.ts b/packages/wallet/src/utils/useTransactionCurrencies.ts index ece3154e7e9..6a61fdaba7c 100644 --- a/packages/wallet/src/utils/useTransactionCurrencies.ts +++ b/packages/wallet/src/utils/useTransactionCurrencies.ts @@ -2,12 +2,12 @@ import { Result } from 'ethers/lib/utils' import { TransactionDescription } from 'no-yolo-signatures' import { useTokenProjects } from 'uniswap/src/features/dataApi/tokenProjects' import { CurrencyInfo } from 'uniswap/src/features/dataApi/types' -import { WalletChainId } from 'uniswap/src/types/chains' +import { UniverseChainId } from 'uniswap/src/types/chains' import { buildCurrencyId } from 'uniswap/src/utils/currencyId' import { isAddress } from 'utilities/src/addresses' export function useTransactionCurrencies(args: { - chainId?: WalletChainId + chainId?: UniverseChainId to?: string parsedTransactionData?: TransactionDescription }): CurrencyInfo[] { diff --git a/turbo.json b/turbo.json index d32c8c90df2..aa723639111 100644 --- a/turbo.json +++ b/turbo.json @@ -74,7 +74,8 @@ "outputs": [ "schema.graphql" ], - "dependsOn": [] + "dependsOn": [], + "cache": false }, "graphql:generate": { "inputs": [ diff --git a/yarn.lock b/yarn.lock index 3e611a42b8a..cd199495c45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7887,6 +7887,13 @@ __metadata: languageName: node linkType: hard +"@openzeppelin/contracts@npm:5.0.2": + version: 5.0.2 + resolution: "@openzeppelin/contracts@npm:5.0.2" + checksum: 0cce6fc284bd1d89e2a447027832a62f1356b44ee31088899453e10349a63a62df2f07da63d76e4c41aad9c86b96b650b2b6fc85439ef276850dda1170a047fd + languageName: node + linkType: hard + "@parcel/watcher-android-arm64@npm:2.4.0": version: 2.4.0 resolution: "@parcel/watcher-android-arm64@npm:2.4.0" @@ -15144,10 +15151,10 @@ __metadata: languageName: node linkType: hard -"@uniswap/analytics-events@npm:2.37.0": - version: 2.37.0 - resolution: "@uniswap/analytics-events@npm:2.37.0" - checksum: 5cdf2ba599c31e15588effcf0f311a82afc674545f770f4fe7761a08baae0c7433830d855cb64cb9c6e36ff5e9c5ef063bea40c7bee0e5a497ed6582fd4cb933 +"@uniswap/analytics-events@npm:2.38.0": + version: 2.38.0 + resolution: "@uniswap/analytics-events@npm:2.38.0" + checksum: 9f9d8f61f4e2c77137f74b14a3b9d4958e71e0f0862da54e7cd3fadad879b6cfd465171cb8e5b310985ebe8c7e7421dd67585eb12c695986c9952240fd689748 languageName: node linkType: hard @@ -15254,11 +15261,12 @@ __metadata: "@types/redux-persist-webextension-storage": 1.0.3 "@types/ua-parser-js": 0.7.31 "@types/uuid": 9.0.1 - "@uniswap/analytics-events": 2.37.0 + "@uniswap/analytics-events": 2.38.0 "@uniswap/eslint-config": "workspace:^" "@uniswap/uniswapx-sdk": ^2.1.0-beta.14 - "@uniswap/universal-router-sdk": 2.2.0 - "@uniswap/v3-sdk": 3.14.0 + "@uniswap/universal-router-sdk": 4.2.0 + "@uniswap/v3-sdk": 3.17.0 + "@uniswap/v4-sdk": 1.10.0 "@welldone-software/why-did-you-render": 8.0.1 clean-webpack-plugin: ^4.0.0 concurrently: ^8.0.1 @@ -15380,7 +15388,7 @@ __metadata: "@types/wcag-contrast": 3.0.0 "@types/xml2js": 0.4.14 "@uniswap/analytics": 1.7.0 - "@uniswap/analytics-events": 2.37.0 + "@uniswap/analytics-events": 2.38.0 "@uniswap/client-explore": 0.0.9 "@uniswap/client-pools": 0.0.5 "@uniswap/default-token-list": 11.19.0 @@ -15389,18 +15397,19 @@ __metadata: "@uniswap/merkle-distributor": 1.0.1 "@uniswap/permit2-sdk": 1.3.0 "@uniswap/redux-multicall": 1.1.8 - "@uniswap/router-sdk": 1.9.2 - "@uniswap/sdk-core": 5.3.0 + "@uniswap/router-sdk": 1.14.2 + "@uniswap/sdk-core": 5.8.0 "@uniswap/smart-order-router": 3.17.3 "@uniswap/token-lists": 1.0.0-beta.33 "@uniswap/uniswapx-sdk": ^2.1.0-beta.14 - "@uniswap/universal-router-sdk": 2.2.0 + "@uniswap/universal-router-sdk": 4.2.0 "@uniswap/v2-core": 1.0.1 "@uniswap/v2-periphery": 1.1.0-beta.0 - "@uniswap/v2-sdk": 4.3.2 + "@uniswap/v2-sdk": 4.6.0 "@uniswap/v3-core": 1.0.1 "@uniswap/v3-periphery": 1.4.4 - "@uniswap/v3-sdk": 3.14.0 + "@uniswap/v3-sdk": 3.17.0 + "@uniswap/v4-sdk": 1.10.0 "@vanilla-extract/css": 1.14.0 "@vanilla-extract/dynamic": 2.1.0 "@vanilla-extract/jest-transform": 1.1.1 @@ -15608,10 +15617,10 @@ __metadata: "@testing-library/react-native": 11.5.0 "@types/redux-mock-store": 1.0.6 "@uniswap/analytics": 1.7.0 - "@uniswap/analytics-events": 2.37.0 + "@uniswap/analytics-events": 2.38.0 "@uniswap/eslint-config": "workspace:^" "@uniswap/ethers-rs-mobile": 0.0.5 - "@uniswap/sdk-core": 5.3.0 + "@uniswap/sdk-core": 5.8.0 "@walletconnect/core": 2.11.2 "@walletconnect/react-native-compat": 2.11.2 "@walletconnect/types": 2.11.2 @@ -15733,22 +15742,23 @@ __metadata: languageName: node linkType: hard -"@uniswap/router-sdk@npm:1.9.2": - version: 1.9.2 - resolution: "@uniswap/router-sdk@npm:1.9.2" +"@uniswap/router-sdk@npm:1.14.2": + version: 1.14.2 + resolution: "@uniswap/router-sdk@npm:1.14.2" dependencies: "@ethersproject/abi": ^5.5.0 - "@uniswap/sdk-core": ^5.0.0 + "@uniswap/sdk-core": ^5.8.0 "@uniswap/swap-router-contracts": ^1.3.0 - "@uniswap/v2-sdk": ^4.3.2 - "@uniswap/v3-sdk": ^3.11.2 - checksum: e2f3fcd04efd7249bf21edb5be5923c7f59442d10959981f33b0c0acc4c4cfa6db8b94c8815d9c9a0969fd558a290a719c5e9daeb81ac316d8af61ccc8ccc3bc + "@uniswap/v2-sdk": ^4.6.0 + "@uniswap/v3-sdk": ^3.17.0 + "@uniswap/v4-sdk": ^1.9.0 + checksum: 2e4ff16421c02c40462c2082df2eb293724258b31dc9c8b4164880fc80f77de2357c1a38a2241f4c50f8d1fc6de36a244067d444f2507806d266818f82f93215 languageName: node linkType: hard -"@uniswap/sdk-core@npm:5.3.0": - version: 5.3.0 - resolution: "@uniswap/sdk-core@npm:5.3.0" +"@uniswap/sdk-core@npm:5.8.0": + version: 5.8.0 + resolution: "@uniswap/sdk-core@npm:5.8.0" dependencies: "@ethersproject/address": ^5.0.2 "@ethersproject/bytes": ^5.7.0 @@ -15759,7 +15769,7 @@ __metadata: jsbi: ^3.1.4 tiny-invariant: ^1.1.0 toformat: ^2.0.0 - checksum: 82570af027937c998208f8ea0bb8c44bd8a2e541023f3b6a5115d9298c95c5f37b20b892ccedf6cc6582ab01f5683f4a8c078cccac43565a5da3362b7363cee7 + checksum: bba467147d544041cb68cc26243d430beb66a24cfde2c8297d94f56a49e204052d9c709d95e9ec28ef208fa0a38a86e9df5ea5d8ed8dc359284b87734f8d3efc languageName: node linkType: hard @@ -15829,19 +15839,23 @@ __metadata: languageName: node linkType: hard -"@uniswap/universal-router-sdk@npm:2.2.0": - version: 2.2.0 - resolution: "@uniswap/universal-router-sdk@npm:2.2.0" +"@uniswap/universal-router-sdk@npm:4.2.0": + version: 4.2.0 + resolution: "@uniswap/universal-router-sdk@npm:4.2.0" dependencies: + "@openzeppelin/contracts": 4.7.0 "@uniswap/permit2-sdk": ^1.3.0 - "@uniswap/router-sdk": ^1.9.2 - "@uniswap/sdk-core": ^5.3.0 - "@uniswap/universal-router": 1.6.0 - "@uniswap/v2-sdk": ^4.3.2 - "@uniswap/v3-sdk": ^3.13.0 + "@uniswap/router-sdk": ^1.14.0 + "@uniswap/sdk-core": ^5.8.0 + "@uniswap/universal-router": 2.0.0-beta.1 + "@uniswap/v2-core": ^1.0.1 + "@uniswap/v2-sdk": ^4.6.0 + "@uniswap/v3-core": 1.0.0 + "@uniswap/v3-sdk": ^3.17.0 + "@uniswap/v4-sdk": ^1.10.0 bignumber.js: ^9.0.2 ethers: ^5.7.0 - checksum: 8b2863ff867913606ee660610a9e7489d58c07531de6b9062b623a5290a0364ffac15997e2fcd613e7b4b280297b1d667496a2ae41bc3c1f5bbc0ab83944d471 + checksum: fae718b1ec206e5fbcab76da1232c3c50ae4b161fb3c3e7847cdbe2178750d46ba38abe5b7f10cc756fb68328e584a002ece9d7b155cb7536ce3155326ea7e5f languageName: node linkType: hard @@ -15872,6 +15886,17 @@ __metadata: languageName: node linkType: hard +"@uniswap/universal-router@npm:2.0.0-beta.1": + version: 2.0.0-beta.1 + resolution: "@uniswap/universal-router@npm:2.0.0-beta.1" + dependencies: + "@openzeppelin/contracts": 5.0.2 + "@uniswap/v2-core": 1.0.1 + "@uniswap/v3-core": 1.0.0 + checksum: 69a4a294df166f36d07b33f7eb5d519f66574840336d19657c60b42f352d891151258fdb61a6a3266af5fdeafb40f26494a7231605793f526d63f1d5a492f9bb + languageName: node + linkType: hard + "@uniswap/v2-core@npm:1.0.0": version: 1.0.0 resolution: "@uniswap/v2-core@npm:1.0.0" @@ -15896,16 +15921,16 @@ __metadata: languageName: node linkType: hard -"@uniswap/v2-sdk@npm:4.3.2": - version: 4.3.2 - resolution: "@uniswap/v2-sdk@npm:4.3.2" +"@uniswap/v2-sdk@npm:4.6.0": + version: 4.6.0 + resolution: "@uniswap/v2-sdk@npm:4.6.0" dependencies: "@ethersproject/address": ^5.0.2 "@ethersproject/solidity": ^5.0.9 - "@uniswap/sdk-core": ^5.0.0 + "@uniswap/sdk-core": ^5.8.0 tiny-invariant: ^1.1.0 tiny-warning: ^1.0.3 - checksum: aaf01f49a1ba5c8214ce4f4d54526fb289de7ecdc5df08f6be07c925ee7c110769adbf77e29b2dbbe7678c4ed21f384b522bbc9d7a82af55367bd66bfba8eb83 + checksum: 7ec46ca0780892adaadbab546b05d628cb2aaaef7198f3f92021e10e3adbbf42566ae0f6d7ae1c55afa01f894164b4b8f450cc91f718059a19f5a333115a5f68 languageName: node linkType: hard @@ -15936,19 +15961,35 @@ __metadata: languageName: node linkType: hard -"@uniswap/v3-sdk@npm:3.14.0, @uniswap/v3-sdk@npm:^3.10.0, @uniswap/v3-sdk@npm:^3.11.0, @uniswap/v3-sdk@npm:^3.11.2, @uniswap/v3-sdk@npm:^3.13.0": - version: 3.14.0 - resolution: "@uniswap/v3-sdk@npm:3.14.0" +"@uniswap/v3-sdk@npm:3.12.0": + version: 3.12.0 + resolution: "@uniswap/v3-sdk@npm:3.12.0" dependencies: "@ethersproject/abi": ^5.5.0 "@ethersproject/solidity": ^5.0.9 - "@uniswap/sdk-core": ^5.3.1 + "@uniswap/sdk-core": ^5.0.0 + "@uniswap/swap-router-contracts": ^1.3.0 + "@uniswap/v3-periphery": ^1.1.1 + "@uniswap/v3-staker": 1.0.0 + tiny-invariant: ^1.1.0 + tiny-warning: ^1.0.3 + checksum: d8d507a8ed302c983217575bcead36700c4ee823db98ea9281cf8f9e5dfb9a5c49da111199f28f65f43ccb4c4dc2996d8a120128076b622b560fe780f8bb8db5 + languageName: node + linkType: hard + +"@uniswap/v3-sdk@npm:3.17.0, @uniswap/v3-sdk@npm:^3.10.0, @uniswap/v3-sdk@npm:^3.11.0, @uniswap/v3-sdk@npm:^3.17.0": + version: 3.17.0 + resolution: "@uniswap/v3-sdk@npm:3.17.0" + dependencies: + "@ethersproject/abi": ^5.5.0 + "@ethersproject/solidity": ^5.0.9 + "@uniswap/sdk-core": ^5.8.0 "@uniswap/swap-router-contracts": ^1.3.0 "@uniswap/v3-periphery": ^1.1.1 "@uniswap/v3-staker": 1.0.0 tiny-invariant: ^1.1.0 tiny-warning: ^1.0.3 - checksum: 5301e6710d7fec1f1eec0ddc7b424fb92792cb17505f4b5360b96594468e47b5865ff63cba88abaacdd6fd249d72d13c0e3986135bdcbc511e86241dc9339cc8 + checksum: ab434e9661f6fae99492af93412a9236379ba604635e31236adeddfc0b78beecdf4241a57fee60f3a2ab06c84c7e0c516ee032a819118c03935e49d8cf31cc27 languageName: node linkType: hard @@ -15963,6 +16004,19 @@ __metadata: languageName: node linkType: hard +"@uniswap/v4-sdk@npm:1.10.0, @uniswap/v4-sdk@npm:^1.10.0, @uniswap/v4-sdk@npm:^1.9.0": + version: 1.10.0 + resolution: "@uniswap/v4-sdk@npm:1.10.0" + dependencies: + "@ethersproject/solidity": ^5.0.9 + "@uniswap/sdk-core": ^5.3.1 + "@uniswap/v3-sdk": 3.12.0 + tiny-invariant: ^1.1.0 + tiny-warning: ^1.0.3 + checksum: c1a55b0e52e2db986c463e75d9de337c48a76b90076705bc4a50da2a27de2f4c91a7c68f79bb04ac7fc7c7b3244fd8c955645e6dcac7e8b9af70354fd905e164 + languageName: node + linkType: hard + "@urql/core@npm:2.3.6, @urql/core@npm:>=2.3.1": version: 2.3.6 resolution: "@urql/core@npm:2.3.6" @@ -45124,16 +45178,17 @@ __metadata: "@typechain/ethers-v5": 7.2.0 "@types/chrome": 0.0.254 "@types/react-window": 1.8.2 - "@uniswap/analytics-events": 2.37.0 + "@uniswap/analytics-events": 2.38.0 "@uniswap/client-explore": 0.0.9 "@uniswap/client-pools": 0.0.5 "@uniswap/eslint-config": "workspace:^" "@uniswap/permit2-sdk": 1.3.0 - "@uniswap/router-sdk": 1.9.2 - "@uniswap/sdk-core": 5.3.0 + "@uniswap/router-sdk": 1.14.2 + "@uniswap/sdk-core": 5.8.0 "@uniswap/uniswapx-sdk": ^2.1.0-beta.14 - "@uniswap/v2-sdk": 4.3.2 - "@uniswap/v3-sdk": 3.14.0 + "@uniswap/v2-sdk": 4.6.0 + "@uniswap/v3-sdk": 3.17.0 + "@uniswap/v4-sdk": 1.10.0 apollo-link-rest: 0.9.0 axios: 1.6.5 dayjs: 1.11.7 @@ -45170,6 +45225,7 @@ __metadata: react-native-mmkv: 2.10.1 react-native-reanimated: 3.15.0 react-native-restart: 0.0.27 + react-native-svg: 15.1.0 react-redux: 8.0.5 react-test-renderer: 18.2.0 react-virtualized-auto-sizer: 1.0.20 @@ -45674,9 +45730,9 @@ __metadata: "@types/react": ^18.0.15 "@types/uuid": 9.0.1 "@uniswap/analytics": 1.7.0 - "@uniswap/analytics-events": 2.37.0 + "@uniswap/analytics-events": 2.38.0 "@uniswap/eslint-config": "workspace:^" - "@uniswap/sdk-core": 5.3.0 + "@uniswap/sdk-core": 5.8.0 aws-appsync-auth-link: 3.0.7 aws-appsync-subscription-link: 3.1.3 dayjs: 1.11.7 @@ -46205,6 +46261,7 @@ __metadata: resolution: "wallet@workspace:packages/wallet" dependencies: "@apollo/client": 3.10.4 + "@ethersproject/abstract-signer": 5.7.0 "@ethersproject/constants": 5.7.0 "@ethersproject/contracts": 5.7.0 "@ethersproject/providers": 5.7.2 @@ -46220,14 +46277,14 @@ __metadata: "@testing-library/react-native": 11.5.0 "@types/react": ^18.0.15 "@types/zxcvbn": 4.4.2 - "@uniswap/analytics-events": 2.37.0 + "@uniswap/analytics-events": 2.38.0 "@uniswap/eslint-config": "workspace:^" "@uniswap/permit2-sdk": 1.3.0 - "@uniswap/router-sdk": 1.9.2 - "@uniswap/sdk-core": 5.3.0 + "@uniswap/router-sdk": 1.14.2 + "@uniswap/sdk-core": 5.8.0 "@uniswap/uniswapx-sdk": ^2.1.0-beta.14 - "@uniswap/universal-router-sdk": 2.2.0 - "@uniswap/v3-sdk": 3.14.0 + "@uniswap/universal-router-sdk": 4.2.0 + "@uniswap/v3-sdk": 3.17.0 apollo3-cache-persist: 0.14.1 axios: 1.6.5 dayjs: 1.11.7