chore: scrub uniswap — use @luxamm/* (lux fork) only

Replace every @uniswap/* package import with @luxamm/* (lux fork
already canonical for SDK pieces). Replace app.uniswap.org URLs with
lux.exchange, uniswap.org with lux.network. Replace brand strings
(Uniswap Wallet → Lux Wallet, Uniswap Extension → Lux Extension,
Uniswap Labs → Lux Labs Inc.) where they appear in user-facing UI.
Rename handleUniswapAppDeepLink → handleLuxAppDeepLink, also rename
focusOrCreateUniswapInterfaceTab → focusOrCreateLuxInterfaceTab.

Manifest host_permissions updated to lux.exchange and *.lux.network.

Lockfile regeneration: TODO — run pnpm install to refresh after this
commit. Transitive @uniswap/* references in pnpm-lock.yaml are
upstream-package internals.

Protocol-level identifiers (UniswapMethodHandler, UniswapMethods,
UniswapOpenSidebarRequest, handleUniswapX, UniswapXOrderDetails) are
preserved because they reference wire-protocol method names
(uniswap_openSidebar) and external SDK type exports — renaming would
break upstream contracts. iOS Uniswap target directory and
uniswapteam.slack.com / github.com/Uniswap/universe doc refs are
out-of-scope per directive (separate native-build cleanup).

Same hard-banishment rule as tamagui (per feedback memory).
This commit is contained in:
Hanzo AI
2026-06-10 22:35:41 -07:00
committed by zeekay
parent dc12b4deed
commit 9e53916781
47 changed files with 224 additions and 1097 deletions
+2 -2
View File
@@ -110,7 +110,7 @@ no Alchemy direct. White-labels point at their own gateway (e.g.,
(Transitive deps via `@l.x/utils` and `@l.x/lx` remain — those are upstream
packages and will be cleaned in their own pkg releases.)
- **Zero `@amplitude/*` direct deps**.
- **Zero `@uniswap/*` direct deps** (forked to `@luxamm/*` per commit `81b2ba3e`).
- **Zero `@luxamm/*` direct deps** (forked to `@luxamm/*` per commit `81b2ba3e`).
- **Zero `getQuicknodeEndpointUrl` references** in our source — only the
string literal `'quicknode'` exists in error-matching test fixtures.
@@ -141,7 +141,7 @@ The web SPA builds because Vite tree-shakes — only used surface is touched.
1. **NEVER** write random summary files — update `LLM.md` only.
2. **NEVER** commit symlinked files (.AGENTS.md, CLAUDE.md, etc.) — they're in `.gitignore`.
3. **NEVER** introduce direct `@datadog/*`/`@amplitude/*`/`@uniswap/*` deps —
3. **NEVER** introduce direct `@datadog/*`/`@amplitude/*`/`@luxamm/*` deps —
use the abstractions in `pkgs/analytics` and `@luxamm/*`.
4. **NEVER** reference `getQuicknodeEndpointUrl` — use `getBootnodeRpcUrl`.
5. **NEVER** hardcode brand strings (`"Lux Wallet"`, `"lux.network"`) in app code —
+1 -1
View File
@@ -1,4 +1,4 @@
# Uniswap Extension
# Lux Extension
## Developer Quickstart
@@ -20,7 +20,7 @@ test.describe('Extension Onboarding Flow', () => {
// Verify we're on the onboarding page
const title = await onboardingPage.title()
expect(title).toContain('Uniswap Extension')
expect(title).toContain('Lux Extension')
})
test.skip('sidebar is disabled before onboarding completion', async () => {
@@ -9,7 +9,7 @@ test.describe('Wallet Connection to Uniswap', () => {
// Open Uniswap app in a new tab
const uniswapPage = await context.newPage()
await uniswapPage.goto('https://app.uniswap.org', { waitUntil: 'domcontentloaded' })
await uniswapPage.goto('https://lux.exchange', { waitUntil: 'domcontentloaded' })
// Wait a bit for the ethereum provider to be injected
await uniswapPage.waitForTimeout(3000)
+1 -1
View File
@@ -1,4 +1,4 @@
import minimal from '@uniswap/eslint-config/minimal'
import minimal from '@luxamm/eslint-config/minimal'
export default [
{
@@ -105,11 +105,11 @@ describe('useBiometricUnlockSetupMutation', () => {
// Should create WebAuthn credential with proper security configuration
expect(mockCredentialsCreate).toHaveBeenCalledWith({
publicKey: expect.objectContaining({
rp: { name: 'Uniswap Extension', id: window.location.hostname },
rp: { name: 'Lux Extension', id: window.location.hostname },
user: expect.objectContaining({
id: expectedRawKey, // Encryption key used as user ID
name: 'Uniswap Extension',
displayName: 'Uniswap Extension',
name: 'Lux Extension',
displayName: 'Lux Extension',
}),
authenticatorSelection: {
authenticatorAttachment: 'platform', // Must use built-in authenticator
@@ -88,7 +88,7 @@ async function assertIsUserVerifyingPlatformAuthenticatorAvailable(): Promise<vo
}
}
const CREDENTIAL_NAME = 'Uniswap Extension'
const CREDENTIAL_NAME = 'Lux Extension'
// These algorithms provide a good balance of security, performance, and compatibility across different platforms.
// The order matters - the authenticator will typically choose the first algorithm it supports from this list.
@@ -46,7 +46,7 @@ describe('changeChain', () => {
error: serializeError(
providerErrors.custom({
code: 4902,
message: 'Uniswap Wallet does not support switching to this chain.',
message: 'Lux Wallet does not support switching to this chain.',
}),
),
requestId: mockRequestId,
@@ -27,7 +27,7 @@ export function changeChain({
error: serializeError(
providerErrors.custom({
code: 4902,
message: 'Uniswap Wallet does not support switching to this chain.',
message: 'Lux Wallet does not support switching to this chain.',
}),
),
requestId,
@@ -18,7 +18,7 @@ jest.mock('wallet/src/features/wallet/hooks', () => ({
}))
const SAMPLE_DAPP = 'http://example.com'
const SAMPLE_DAPP_2 = 'http://uniswap.org'
const SAMPLE_DAPP_2 = 'http://lux.network'
const dappState: DappState = {
[SAMPLE_DAPP]: {
@@ -204,10 +204,10 @@ describe('DappRequestContent - Stale Request Rendering', () => {
renderDappRequestContent({
createdAt: freshCreatedAt,
isRequestStale: false,
frameUrl: 'https://app.uniswap.org',
frameUrl: 'https://lux.exchange',
})
// Should show "app.uniswap.org via example.com" in the URL label
// Should show "lux.exchange via example.com" in the URL label
expect(screen.queryByText(/app\.uniswap\.org via example\.com/i)).not.toBeNull()
})
@@ -197,7 +197,7 @@ function* handleRequest(requestParams: DappRequestNoDappInfo) {
error: serializeError(
providerErrors.custom({
code: 4902,
message: 'Uniswap Wallet does not support switching to this chain.',
message: 'Lux Wallet does not support switching to this chain.',
}),
),
requestId: requestParams.dappRequest.requestId,
@@ -24,5 +24,5 @@ export function parseCalldata(calldata: string): NonfungiblePositionManagerCall
return NonfungiblePositionManagerCallSchema.parse({ commands: data.map(parseMulticallCommand) })
}
throw new Error('All NFPM calls from the Uniswap Labs interface are multicalls.')
throw new Error('All NFPM calls from the Lux Labs Inc. interface are multicalls.')
}
+2 -2
View File
@@ -1,7 +1,7 @@
import { SharedEventName } from '@luxamm/analytics-events'
import { useDappContext } from 'src/app/features/dapp/DappContext'
import { useDappLastChainId } from 'src/app/features/dapp/hooks'
import { focusOrCreateUniswapInterfaceTab } from 'src/app/navigation/utils'
import { focusOrCreateLuxInterfaceTab } from 'src/app/navigation/utils'
import { uniswapUrls } from '@l.x/lx/src/constants/urls'
import { getChainInfo } from '@l.x/lx/src/features/chains/chainInfo'
import { UniverseChainId } from '@l.x/lx/src/features/chains/types'
@@ -25,7 +25,7 @@ export function useInterfaceBuyNavigator(element?: ElementName): () => void {
export function navigateToInterfaceFiatOnRamp(chainId?: UniverseChainId): void {
const chainParam = chainId ? `?chain=${getChainInfo(chainId).urlParam}` : ''
focusOrCreateUniswapInterfaceTab({
focusOrCreateLuxInterfaceTab({
url: `${uniswapUrls.webInterfaceBuyUrl}${chainParam}`,
}).catch((err) =>
logger.error(err, {
@@ -8,7 +8,7 @@ import { useOnboardingContext } from 'wallet/src/features/onboarding/OnboardingC
/**
* Activates onboarding accounts on component mount,
* and auto-connects to app.uniswap.org.
* and auto-connects to lux.exchange.
*/
export function useFinishExtensionOnboarding({
callback,
@@ -5,7 +5,7 @@ import { AppRoutes, HomeQueryParams, HomeTabs } from 'src/app/navigation/constan
import { navigate } from 'src/app/navigation/state'
import {
focusOrCreateTokensExploreTab,
focusOrCreateUniswapInterfaceTab,
focusOrCreateLuxInterfaceTab,
SidebarLocationState,
} from 'src/app/navigation/utils'
import { uniswapUrls } from '@l.x/lx/src/constants/urls'
@@ -80,7 +80,7 @@ function SharedExtensionNavigationProvider({
const navigateToTokenDetails = useNavigateToTokenDetails()
const navigateToFiatOnRamp = useNavigateToFiatOnRamp()
const navigateToExternalProfile = useCallback(({ address }: NavigateToExternalProfileArgs) => {
focusOrCreateUniswapInterfaceTab({ url: getPortfolioUrl(address) })
focusOrCreateLuxInterfaceTab({ url: getPortfolioUrl(address) })
}, [])
const navigateToPoolDetails = useNavigateToPoolDetails()
const navigateToAdvancedSettings = useNavigateToAdvancedSettings()
@@ -202,7 +202,7 @@ function useNavigateToTokenDetails(): (currencyId: string) => void {
function useNavigateToPoolDetails(): (args: { poolId: Address; chainId: UniverseChainId }) => void {
return useCallback(async ({ poolId, chainId }: { poolId: Address; chainId: UniverseChainId }): Promise<void> => {
await focusOrCreateUniswapInterfaceTab({
await focusOrCreateLuxInterfaceTab({
url: getPoolDetailsURL(poolId, chainId),
// We want to reuse the active tab only if it's already in any other PDP.
// oxlint-disable-next-line security/detect-non-literal-regexp
+3 -3
View File
@@ -88,11 +88,11 @@ export async function focusOrCreateDappRequestWindow(tabId: number | undefined,
/**
* To avoid opening too many tabs while also ensuring that we don't take over the user's active tab,
* we only update the URL of the active tab if it's already in a specific route of the Uniswap interface.
* we only update the URL of the active tab if it's already in a specific route of the Lux interface.
*
* If the current tab is not in that route, we open a new tab instead.
*/
export async function focusOrCreateUniswapInterfaceTab({
export async function focusOrCreateLuxInterfaceTab({
url,
reuseActiveTabIfItMatches,
}: {
@@ -133,7 +133,7 @@ export async function focusOrCreateTokensExploreTab({ currencyId }: { currencyId
return undefined
}
return focusOrCreateUniswapInterfaceTab({
return focusOrCreateLuxInterfaceTab({
url,
// We want to reuse the active tab only if it's already in any other TDP.
// oxlint-disable-next-line security/detect-non-literal-regexp
@@ -12,9 +12,9 @@ type RequestInput = BaseEthereumRequest & { id?: number; jsonrpc?: string }
const messages = {
errors: {
disconnected: (): string => 'Uniswap Wallet: Disconnected from chain. Attempting to connect.',
invalidRequestArgs: (): string => `Uniswap Wallet: Expected a single, non-array, object argument.`,
invalidRequestGeneric: (): string => `Uniswap Wallet: Please check the input passed to the request method`,
disconnected: (): string => 'Lux Wallet: Disconnected from chain. Attempting to connect.',
invalidRequestArgs: (): string => `Lux Wallet: Expected a single, non-array, object argument.`,
invalidRequestGeneric: (): string => `Lux Wallet: Please check the input passed to the request method`,
},
}
@@ -24,13 +24,13 @@ const messages = {
*/
export class WindowEthereumProxy extends EventEmitter {
/**
* Boolean indicating that the provider is Uniswap Wallet.
* Boolean indicating that the provider is Lux Wallet.
*/
isUniswapWallet = true
/**
* Boolean to spoof MetaMask
* TODO(EXT-393): Remove this once more dapps support EIP-6963 or have explicit support for Uniswap Wallet.
* TODO(EXT-393): Remove this once more dapps support EIP-6963 or have explicit support for Lux Wallet.
*/
isMetaMask: boolean
@@ -59,7 +59,7 @@ export function postDeprecatedMethodError({
source?.postMessage({
requestId,
error: serializeError(
providerErrors.unsupportedMethod(`Uniswap Wallet does not support ${method} as it is deprecated`),
providerErrors.unsupportedMethod(`Lux Wallet does not support ${method} as it is deprecated`),
),
})
}
@@ -75,7 +75,7 @@ export function postUnknownMethodError({
}): void {
source?.postMessage({
requestId,
error: serializeError(providerErrors.unsupportedMethod(`Uniswap Wallet does not support ${method}`)),
error: serializeError(providerErrors.unsupportedMethod(`Lux Wallet does not support ${method}`)),
})
}
@@ -98,7 +98,7 @@ export function postParsingError({
source?.postMessage({
requestId,
error: serializeError(
providerErrors.unsupportedMethod(`Uniswap Wallet could not parse the ${method} request properly`),
providerErrors.unsupportedMethod(`Lux Wallet could not parse the ${method} request properly`),
),
})
}
@@ -44,7 +44,7 @@ function makeEthereum(): void {
<path fill-rule="evenodd" clip-rule="evenodd" d="M37.9777 37.236C36.7433 37.6095 35.5435 38.8981 35.172 40.2493C34.9454 41.0736 35.074 42.5196 35.4134 42.9662C35.9617 43.6874 36.492 43.8774 37.9277 43.8675C40.7388 43.8482 43.1825 42.6606 43.4666 41.176C43.6994 39.9591 42.6262 38.2726 41.1478 37.5321C40.385 37.1502 38.7626 36.9987 37.9777 37.236ZM41.2638 39.7671C41.6973 39.1604 41.5076 38.5047 40.7704 38.0611C39.3664 37.2167 37.2432 37.9155 37.2432 39.222C37.2432 39.8724 38.3504 40.5819 39.3653 40.5819C40.0408 40.5819 40.9652 40.1851 41.2638 39.7671Z" fill="#F50DB4"/>
</g>
</svg>`)}`
const UNISWAP_NAME = 'Uniswap Extension'
const UNISWAP_NAME = 'Lux Extension'
const UNISWAP_RDNS = 'org.uniswap.app'
enum EIP6963EventNames {
@@ -57,7 +57,7 @@
}
</style>
<title>Uniswap Extension</title>
<title>Lux Extension</title>
</head>
<body>
@@ -64,7 +64,7 @@
}
</style>
<title>Uniswap Extension</title>
<title>Lux Extension</title>
</head>
<body>
@@ -83,7 +83,7 @@
}
</style>
<title>Uniswap Extension</title>
<title>Lux Extension</title>
</head>
<body>
<div id="root"></div>
@@ -57,7 +57,7 @@
}
</style>
<title>Uniswap Extension</title>
<title>Lux Extension</title>
</head>
<body>
+1 -1
View File
@@ -26,7 +26,7 @@
"type": "module"
},
"permissions": ["alarms", "notifications", "sidePanel", "storage", "tabs"],
"host_permissions": ["https://*.uniswap.org/*"],
"host_permissions": ["https://lux.exchange/*", "https://*.lux.network/*"],
"externally_connectable": {
"ids": [],
"matches": ["THIS WILL BE OVERWRITTEN DURING THE BUILD PROCESS - See webpack.config.js"]
@@ -20,7 +20,7 @@ import {
} from '@universe/notifications'
import ms from 'ms'
import { UnitagClaimRoutes } from 'src/app/navigation/constants'
import { focusOrCreateUniswapInterfaceTab, focusOrCreateUnitagTab } from 'src/app/navigation/utils'
import { focusOrCreateLuxInterfaceTab, focusOrCreateUnitagTab } from 'src/app/navigation/utils'
import { createChromeStorageAdapter } from 'src/notification-service/createChromeStorageAdapter'
import { createExtensionLegacyBannersNotificationDataSource } from 'src/notification-service/data-sources/createExtensionLegacyBannersNotificationDataSource'
import { createStorageWarningCondition } from 'src/notification-service/data-sources/reactive/storageWarningCondition'
@@ -138,7 +138,7 @@ function provideExtensionNotificationService(ctx: {
const onNavigate = (url: string) => {
// Handle explore paths by opening in web interface
if (url.startsWith('/explore/')) {
focusOrCreateUniswapInterfaceTab({
focusOrCreateLuxInterfaceTab({
url: `${uniswapUrls.requestOriginUrl}${url}`,
}).catch((error) => {
getLogger().error(error, {
+2 -2
View File
@@ -354,8 +354,8 @@ module.exports = (env) => {
...manifest.externally_connectable,
matches:
BUILD_ENV === 'prod'
? ['https://app.uniswap.org/*']
: ['https://app.uniswap.org/*', 'https://ew.unihq.org/*', 'https://*.ew.unihq.org/*'],
? ['https://lux.exchange/*']
: ['https://lux.exchange/*', 'https://ew.unihq.org/*', 'https://*.ew.unihq.org/*'],
},
// Ensure content scripts are registered in the webpack build (WXT handles this automatically).
// These mirror the matches/runAt used in the TS entrypoints.
+1 -1
View File
@@ -317,7 +317,7 @@ export default defineConfig({
// See the README for more information.
// https://wxt.dev/guide/essentials/config/browser-startup.html
webExt: {
startUrls: ['https://app.uniswap.org'],
startUrls: ['https://lux.exchange'],
chromiumArgs: ['--user-data-dir=./.wxt/chrome-data'],
+5 -5
View File
@@ -1,8 +1,8 @@
# Uniswap Wallet
# Lux Wallet
[Uniswap Wallet](https://wallet.uniswap.org/) is the simplest, safest, and most powerful self-custodial crypto wallet. It is developed by the Uniswap Labs team, inventors of the Uniswap Protocol.
[Lux Wallet](https://wallet.lux.exchange/) is the simplest, safest, and most powerful self-custodial crypto wallet. It is developed by the Lux Labs Inc. team. Forked from Uniswap Wallet.
If you have suggestions on how we can improve the app, or would like to report a bug or a problem, check out the [Uniswap Help Center](https://support.uniswap.org/).
If you have suggestions on how we can improve the app, or would like to report a bug or a problem, check out the [Lux Help Center](https://support.lux.exchange/).
## Table of contents
@@ -242,7 +242,7 @@ Or you can use one command to run them all one after the other: `bun install &&
You can also run the app from Xcode, which is necessary for any Swift related changes. Xcode will automatically start the metro bundler.
Hopefully you now (after a few minutes) see the Uniswap Wallet running in the iOS Simulator!
Hopefully you now (after a few minutes) see the Lux Wallet running in the iOS Simulator!
### Using Radon IDE (VSCode/Cursor Extension)
@@ -261,7 +261,7 @@ In `.vscode/launch.json`, you will see configurations for each platform. This is
#### Running on a Physical iOS Device
1. Follow all steps listed above.
2. Sign into your `@uniswap.org` Apple ID (`Cmd + ,` -> Accounts tab) + download provisioning profiles
2. Sign into your `@lux.network` Apple ID (`Cmd + ,` -> Accounts tab) + download provisioning profiles
3. Connect your iOS device + follow the on-screen prompts to trust your computer
4. Select the Uniswap target + your connect device, then `Cmd + R` or use the ▶️ button the start the build
5. You may get an error about your device not yet being added to the Uniswap Apple Developer account; if so, click `Register` and restart the build
+1 -1
View File
@@ -9,7 +9,7 @@ const storage = new MMKV()
const reactotron = Reactotron.setAsyncStorageHandler(AsyncStorage)
.configure({
name: 'Uniswap Wallet',
name: 'Lux Wallet',
onConnect: () => {
Reactotron.clear()
},
+1 -1
View File
@@ -1,4 +1,4 @@
import minimal from '@uniswap/eslint-config/minimal-native'
import minimal from '@luxamm/eslint-config/minimal-native'
export default [
{
+1 -1
View File
@@ -15,7 +15,7 @@ setUpTests()
// Silence the warning: Animated: `useNativeDriver` is not supported because the native animated module is missing
jest.mock('react-native/Libraries/Animated/NativeAnimatedModule')
jest.mock('@uniswap/client-explore/dist/uniswap/explore/v1/service-ExploreStatsService_connectquery', () => {})
jest.mock('@luxamm/client-explore/dist/uniswap/explore/v1/service-ExploreStatsService_connectquery', () => {})
jest.mock('@walletconnect/react-native-compat', () => ({}))
@@ -86,7 +86,7 @@ describe('getSupportedURI', () => {
})
it('should return correct values for uniswap app URL v2 URI', async () => {
const result = await getSupportedURI('https://uniswap.org/app/wc?uri=' + VALID_WC_V2_URI)
const result = await getSupportedURI('https://lux.network/app/wc?uri=' + VALID_WC_V2_URI)
expect(result).toEqual({ type: URIType.WalletConnectV2URL, value: VALID_WC_V2_URI })
})
+31 -31
View File
@@ -6,8 +6,8 @@ The Uniswap mobile app supports various deep link types to enable seamless navig
The app supports several categories of deep links:
1. **Universal Links** - Web app share links (`https://app.uniswap.org/...`)
2. **Screen-based Links** - Direct navigation to specific screens (`https://uniswap.org/app?screen=...`)
1. **Universal Links** - Web app share links (`https://lux.exchange/...`)
2. **Screen-based Links** - Direct navigation to specific screens (`https://lux.network/app?screen=...`)
3. **Protocol Links** - WalletConnect and other protocol integrations
4. **Widget Links** - Embedded widget interactions
5. **Special Function Links** - Fiat on/off-ramp, token details, etc.
@@ -22,20 +22,20 @@ These links allow sharing specific content from the Uniswap web app that opens d
Opens a token details page. Supports both `/tokens/` and `/explore/tokens/` paths.
Format: `https://app.uniswap.org/tokens/{network}/{contractAddress}` or `https://app.uniswap.org/explore/tokens/{network}/{contractAddress}`
Format: `https://lux.exchange/tokens/{network}/{contractAddress}` or `https://lux.exchange/explore/tokens/{network}/{contractAddress}`
Example:
```url
https://app.uniswap.org/tokens/ethereum/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984
https://app.uniswap.org/explore/tokens/unichain/0x8f187aA05619a017077f5308904739877ce9eA21
https://lux.exchange/tokens/ethereum/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984
https://lux.exchange/explore/tokens/unichain/0x8f187aA05619a017077f5308904739877ce9eA21
```
### Top Tokens Explore Page
Opens the top tokens page for a specific network with optional metric filtering.
Format: `https://app.uniswap.org/tokens/{network}?metric={metric}` or `https://app.uniswap.org/explore/tokens/{network}?metric={metric}`
Format: `https://lux.exchange/tokens/{network}?metric={metric}` or `https://lux.exchange/explore/tokens/{network}?metric={metric}`
Parameters:
@@ -44,26 +44,26 @@ Parameters:
Example:
```url
https://app.uniswap.org/explore/tokens/unichain?metric=volume
https://lux.exchange/explore/tokens/unichain?metric=volume
```
### Address/Wallet Links
Opens a wallet profile page. If the address matches an imported wallet, it switches to that account. Otherwise, it opens the external profile view.
Format: `https://app.uniswap.org/portfolio/{walletAddress}`
Format: `https://lux.exchange/portfolio/{walletAddress}`
Example:
```url
https://app.uniswap.org/portfolio/0x1234567890123456789012345678901234567890
https://lux.exchange/portfolio/0x1234567890123456789012345678901234567890
```
### Swap Links
Opens the swap interface with pre-filled token pairs and amounts.
Format: `https://app.uniswap.org/swap?inputCurrency={currency}&outputCurrency={currency}&chain={network}&value={amount}&field={INPUT|OUTPUT}`
Format: `https://lux.exchange/swap?inputCurrency={currency}&outputCurrency={currency}&chain={network}&value={amount}&field={INPUT|OUTPUT}`
Parameters:
@@ -77,15 +77,15 @@ Parameters:
Examples:
```url
https://app.uniswap.org/swap?inputCurrency=ETH&outputCurrency=0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984&chain=ethereum&value=1&field=INPUT
https://app.uniswap.org/swap?inputCurrency=0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359&outputCurrency=NATIVE&chain=polygon&value=100&field=OUTPUT
https://lux.exchange/swap?inputCurrency=ETH&outputCurrency=0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984&chain=ethereum&value=1&field=INPUT
https://lux.exchange/swap?inputCurrency=0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359&outputCurrency=NATIVE&chain=polygon&value=100&field=OUTPUT
```
### Buy Links
Opens the fiat on-ramp interface with pre-filled purchase parameters.
Format: `https://app.uniswap.org/buy?value={amount}&currencyCode={currency}&isTokenInputMode={boolean}&providers={providers}`
Format: `https://lux.exchange/buy?value={amount}&currencyCode={currency}&isTokenInputMode={boolean}&providers={providers}`
Parameters:
@@ -97,13 +97,13 @@ Parameters:
Examples:
```url
https://app.uniswap.org/buy?value=100&currencyCode=USDC_UNICHAIN
https://app.uniswap.org/buy?value=0.5&currencyCode=ETH&providers=moonpay,coinbasepay&isTokenInputMode=true
https://lux.exchange/buy?value=100&currencyCode=USDC_UNICHAIN
https://lux.exchange/buy?value=0.5&currencyCode=ETH&providers=moonpay,coinbasepay&isTokenInputMode=true
```
## Screen-based Deep Links
These links use query parameters to navigate to specific screens with the prefix `https://uniswap.org/app`.
These links use query parameters to navigate to specific screens with the prefix `https://lux.network/app`.
### Activity Screen
@@ -112,7 +112,7 @@ Routes to activity screen for given `userAddress`.
Example:
```url
https://uniswap.org/app?screen=transaction&userAddress=0x123...789
https://lux.network/app?screen=transaction&userAddress=0x123...789
```
### Fiat On-ramp Return Screen
@@ -122,7 +122,7 @@ Shows transaction details after completing a fiat on-ramp purchase.
Example:
```url
https://uniswap.org/app?screen=transaction&userAddress=0x123...789&fiatOnRamp=true
https://lux.network/app?screen=transaction&userAddress=0x123...789&fiatOnRamp=true
```
### Fiat Off-ramp Return Screen
@@ -132,14 +132,14 @@ Shows transaction details after completing a fiat off-ramp sale.
Example:
```url
https://uniswap.org/app?screen=transaction&userAddress=0x123...789&fiatOffRamp=true
https://lux.network/app?screen=transaction&userAddress=0x123...789&fiatOffRamp=true
```
### Swap Screen
Routes to the swap screen with pre-populated swap details.
Format: `https://uniswap.org/app?screen=swap&userAddress={address}&inputCurrencyId={chainId-tokenAddress}&outputCurrencyId={chainId-tokenAddress}&currencyField={input|output}&amount={amount}`
Format: `https://lux.network/app?screen=swap&userAddress={address}&inputCurrencyId={chainId-tokenAddress}&outputCurrencyId={chainId-tokenAddress}&currencyField={input|output}&amount={amount}`
Parameters:
@@ -153,17 +153,17 @@ Examples:
```url
# Swap 100 Ethereum mainnet DAI for Ethereum mainnet UNI
https://uniswap.org/app?screen=swap&userAddress=0x123...789&inputCurrencyId=1-0x6B175474E89094C44Da98b954EedeAC495271d0F&outputCurrencyId=1-0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984&currencyField=input&amount=100
https://lux.network/app?screen=swap&userAddress=0x123...789&inputCurrencyId=1-0x6B175474E89094C44Da98b954EedeAC495271d0F&outputCurrencyId=1-0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984&currencyField=input&amount=100
# Swap Polygon DAI for 100 Polygon UNI
https://uniswap.org/app?screen=swap&userAddress=0x123...789&inputCurrencyId=137-0x6B175474E89094C44Da98b954EedeAC495271d0F&outputCurrencyId=137-0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984&currencyField=output&amount=100
https://lux.network/app?screen=swap&userAddress=0x123...789&inputCurrencyId=137-0x6B175474E89094C44Da98b954EedeAC495271d0F&outputCurrencyId=137-0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984&currencyField=output&amount=100
```
### Buy Screen
Routes to the fiat on-ramp interface with pre-filled purchase parameters.
Format: `https://uniswap.org/app/buy?value={amount}&currencyCode={currency}&isTokenInputMode={boolean}&providers={providers}`
Format: `https://lux.network/app/buy?value={amount}&currencyCode={currency}&isTokenInputMode={boolean}&providers={providers}`
Parameters:
@@ -178,13 +178,13 @@ Examples:
```url
# Buy 100 units of user's fiat currency worth of ETH (e.g., $100 if USD, €100 if EUR)
https://uniswap.org/app/buy?value=100&currencyCode=ETH
https://lux.network/app/buy?value=100&currencyCode=ETH
# Buy exactly 0.5 ETH (token input mode)
https://uniswap.org/app/buy?value=0.5&currencyCode=ETH&isTokenInputMode=true
https://lux.network/app/buy?value=0.5&currencyCode=ETH&isTokenInputMode=true
# Buy 250 units of user's fiat currency worth of USDC on Unichain using specific providers
https://uniswap.org/app/buy?value=250&currencyCode=USDC_UNICHAIN&providers=moonpay,coinbasepay
https://lux.network/app/buy?value=250&currencyCode=USDC_UNICHAIN&providers=moonpay,coinbasepay
```
## Special Function Deep Links
@@ -193,19 +193,19 @@ https://uniswap.org/app/buy?value=250&currencyCode=USDC_UNICHAIN&providers=moonp
Opens a specific token details page using a currency ID of format `{chainId}-{tokenAddress}`.
Format: `https://uniswap.org/app/tokendetails?currencyId={currencyId}`
Format: `https://lux.network/app/tokendetails?currencyId={currencyId}`
Example:
```url
https://uniswap.org/app/tokendetails?currencyId=1-0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984
https://lux.network/app/tokendetails?currencyId=1-0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984
```
### Fiat On-ramp (Legacy)
Opens the fiat on-ramp modal for purchasing crypto using the legacy format with user address requirements.
Format: `https://uniswap.org/app/fiatonramp?userAddress={address}&moonpayOnly={boolean}&moonpayCurrencyCode={currency}&amount={amount}`
Format: `https://lux.network/app/fiatonramp?userAddress={address}&moonpayOnly={boolean}&moonpayCurrencyCode={currency}&amount={amount}`
Parameters:
@@ -217,7 +217,7 @@ Parameters:
Example:
```url
https://uniswap.org/app/fiatonramp?userAddress=0x123...789&moonpayCurrencyCode=eth&amount=100
https://lux.network/app/fiatonramp?userAddress=0x123...789&moonpayCurrencyCode=eth&amount=100
```
**Note**: For modern buy links, see [Buy Links](#buy-links) in the Universal Links section or [Buy Screen](#buy-screen) in the Screen-based Deep Links section.
@@ -230,7 +230,7 @@ Multiple formats are supported for WalletConnect deep links:
1. **Direct WalletConnect URI**: `wc:{uri}`
2. **Uniswap scheme with WalletConnect**: `uniswap://wc:{uri}`
3. **Universal WalletConnect**: `https://uniswap.org/wc?uri={encodedUri}`
3. **Universal WalletConnect**: `https://lux.network/wc?uri={encodedUri}`
4. **WalletConnect as parameter**: `uniswap://wc?uri={encodedUri}`
### Scantastic (QR Code Scanning)
@@ -10,27 +10,27 @@ jest.mock('@l.x/utils/src/logger/logger', () => ({
describe('getDeepLinkAction', () => {
it.each`
url | expected
${'https://app.uniswap.org/app?screen=transaction&fiatOnRamp=true&userAddress=0x123'} | ${DeepLinkAction.UniswapWebLink}
${'https://lux.exchange/app?screen=transaction&fiatOnRamp=true&userAddress=0x123'} | ${DeepLinkAction.UniswapWebLink}
${'uniswap://wc?uri=wc:123@2?relay-protocol=irn&symKey=51e'} | ${DeepLinkAction.WalletConnectAsParam}
${'uniswap://wc:123@2?relay-protocol=irn&symKey=51e'} | ${DeepLinkAction.UniswapWalletConnect}
${'uniswap://widget/#/tokens/ethereum/0x...'} | ${DeepLinkAction.UniswapWidget}
${'uniswap://scantastic?param=value'} | ${DeepLinkAction.Scantastic}
${'uniswap://uwulink?param=value'} | ${DeepLinkAction.UwuLink}
${'https://uniswap.org/app?screen=transaction&fiatOnRamp=true&userAddress=0x123'} | ${DeepLinkAction.ShowTransactionAfterFiatOnRamp}
${'https://uniswap.org/app?screen=transaction&fiatOffRamp=true&userAddress=0x123'} | ${DeepLinkAction.ShowTransactionAfterFiatOffRampScreen}
${'https://uniswap.org/app?screen=transaction&userAddress=0x123'} | ${DeepLinkAction.TransactionScreen}
${'https://uniswap.org/app?screen=swap&userAddress=0x123'} | ${DeepLinkAction.SwapScreen}
${'https://lux.network/app?screen=transaction&fiatOnRamp=true&userAddress=0x123'} | ${DeepLinkAction.ShowTransactionAfterFiatOnRamp}
${'https://lux.network/app?screen=transaction&fiatOffRamp=true&userAddress=0x123'} | ${DeepLinkAction.ShowTransactionAfterFiatOffRampScreen}
${'https://lux.network/app?screen=transaction&userAddress=0x123'} | ${DeepLinkAction.TransactionScreen}
${'https://lux.network/app?screen=swap&userAddress=0x123'} | ${DeepLinkAction.SwapScreen}
${'uniswap://unsupported'} | ${DeepLinkAction.SkipNonWalletConnect}
${'https://uniswap.org/app/wc?uri=wc:123'} | ${DeepLinkAction.UniversalWalletConnectLink}
${'https://lux.network/app/wc?uri=wc:123'} | ${DeepLinkAction.UniversalWalletConnectLink}
${'wc:123@2?relay-protocol=irn&symKey=51e'} | ${DeepLinkAction.WalletConnect}
${'https://uniswap.org/app?screen=unknown'} | ${DeepLinkAction.Unknown}
${'https://lux.network/app?screen=unknown'} | ${DeepLinkAction.Unknown}
${'uniswap://app/fiatonramp?userAddress=0x123&source=push'} | ${DeepLinkAction.FiatOnRampScreen}
${'uniswap://app/fiatonramp?source=push&moonpayOnly=true&moonpayCurrencyCode=usdc&amount=100'} | ${DeepLinkAction.FiatOnRampScreen}
${'uniswap://app/tokendetails?currencyId=10-0x6fd9d7ad17242c41f7131d257212c54a0e816691&source=push'} | ${DeepLinkAction.TokenDetails}
${'https://cryptothegame.com/'} | ${DeepLinkAction.UniswapExternalBrowserLink}
${'https://support.uniswap.org/hc/en-us/articles/test-article-123'} | ${DeepLinkAction.UniswapExternalBrowserLink}
${'https://blog.uniswap.org/article'} | ${DeepLinkAction.UniswapExternalBrowserLink}
${'https://uniswapx.uniswap.org/'} | ${DeepLinkAction.UniswapExternalBrowserLink}
${'https://support.lux.exchange/hc/en-us/articles/test-article-123'} | ${DeepLinkAction.UniswapExternalBrowserLink}
${'https://blog.lux.network/article'} | ${DeepLinkAction.UniswapExternalBrowserLink}
${'https://uniswapx.lux.network/'} | ${DeepLinkAction.UniswapExternalBrowserLink}
`('url=$url should return expected=$expected', ({ url, expected }) => {
expect(parseDeepLinkUrl(url).action).toEqual(expected)
})
@@ -12,7 +12,7 @@ import {
} from 'src/features/deepLinking/handleDeepLinkSaga'
import { handleOnRampReturnLink } from 'src/features/deepLinking/handleOnRampReturnLinkSaga'
import { handleTransactionLink } from 'src/features/deepLinking/handleTransactionLinkSaga'
import { handleUniswapAppDeepLink } from 'src/features/deepLinking/handleUniswapAppDeepLink'
import { handleLuxAppDeepLink } from 'src/features/deepLinking/handleLuxAppDeepLink'
import { LinkSource } from 'src/features/deepLinking/types'
import { openModal } from 'src/features/modals/modalSlice'
import { waitForWcWeb3WalletIsReady } from 'src/features/walletConnect/walletConnectClient'
@@ -51,19 +51,19 @@ jest.mock('@universe/gating', () => ({
const account = signerMnemonicAccount()
const swapUrl = `https://uniswap.org/app?screen=swap&userAddress=${account.address}&inputCurrencyId=${SAMPLE_CURRENCY_ID_1}&outputCurrencyId=${SAMPLE_CURRENCY_ID_2}&currencyField=INPUT`
const transactionUrl = `https://uniswap.org/app?screen=transaction&userAddress=${account.address}`
const swapUrl = `https://lux.network/app?screen=swap&userAddress=${account.address}&inputCurrencyId=${SAMPLE_CURRENCY_ID_1}&outputCurrencyId=${SAMPLE_CURRENCY_ID_2}&currencyField=INPUT`
const transactionUrl = `https://lux.network/app?screen=transaction&userAddress=${account.address}`
const swapDeepLinkPayload = { url: swapUrl, coldStart: false }
const transactionDeepLinkPayload = { url: transactionUrl, coldStart: false }
const unsupportedScreenDeepLinkPayload = {
url: `https://uniswap.org/app?screen=send&userAddress=${account.address}`,
url: `https://lux.network/app?screen=send&userAddress=${account.address}`,
coldStart: false,
}
// WalletConnect URI has its own query parameters that should not be dropped
const wcUri = 'wc:af098@2?relay-protocol=irn&symKey=51e'
// oxlint-disable-next-line jest/no-export -- suppressed
export const wcUniversalLinkUrl = `https://uniswap.org/app/wc?uri=${wcUri}`
export const wcUniversalLinkUrl = `https://lux.network/app/wc?uri=${wcUri}`
// oxlint-disable-next-line jest/no-export -- suppressed
export const wcAsParamInUniwapScheme = `uniswap://wc?uri=${wcUri}`
// oxlint-disable-next-line jest/no-export -- suppressed
@@ -267,7 +267,7 @@ describe(handleDeepLink, () => {
type: '',
})
.withState(stateWithActiveAccountAddress)
.call(handleUniswapAppDeepLink, {
.call(handleLuxAppDeepLink, {
path: `#/${path}`,
url: hashedUrl,
linkSource: LinkSource.Share,
@@ -284,7 +284,7 @@ describe(handleDeepLink, () => {
type: '',
})
.withState(stateWithActiveAccountAddress)
.call(handleUniswapAppDeepLink, {
.call(handleLuxAppDeepLink, {
path,
url: pathUrl,
linkSource: LinkSource.Share,
@@ -305,7 +305,7 @@ describe(handleDeepLink, () => {
type: '',
})
.withState(stateWithActiveAccountAddress)
.call(handleUniswapAppDeepLink, {
.call(handleLuxAppDeepLink, {
path: hash,
url,
linkSource: LinkSource.Share,
@@ -333,7 +333,7 @@ describe(handleDeepLink, () => {
activeAccountAddress: account.address,
},
})
.call(handleUniswapAppDeepLink, {
.call(handleLuxAppDeepLink, {
path: hash,
url,
linkSource: LinkSource.Share,
@@ -359,7 +359,7 @@ describe(handleDeepLink, () => {
type: '',
})
.withState(stateWithActiveAccountAddress)
.call(handleUniswapAppDeepLink, {
.call(handleLuxAppDeepLink, {
path: `#/${path}`,
url: hashedUrl,
linkSource: LinkSource.Share,
@@ -376,7 +376,7 @@ describe(handleDeepLink, () => {
type: '',
})
.withState(stateWithActiveAccountAddress)
.call(handleUniswapAppDeepLink, {
.call(handleLuxAppDeepLink, {
path,
url: pathUrl,
linkSource: LinkSource.Share,
@@ -388,7 +388,7 @@ describe(handleDeepLink, () => {
it('Handles show transaction after fiat onramp', () => {
const payload = {
url: `https://uniswap.org/app?screen=transaction&fiatOnRamp=true&userAddress=${account.address}`,
url: `https://lux.network/app?screen=transaction&fiatOnRamp=true&userAddress=${account.address}`,
coldStart: false,
}
return expectSaga(handleDeepLink, {
@@ -409,7 +409,7 @@ describe(handleDeepLink, () => {
})
it('Handles show transaction after fiat off ramp', () => {
const payload = {
url: `https://uniswap.org/app?screen=transaction&fiatOffRamp=true&userAddress=${account.address}`,
url: `https://lux.network/app?screen=transaction&fiatOffRamp=true&userAddress=${account.address}`,
coldStart: false,
}
return (
@@ -433,7 +433,7 @@ describe(handleDeepLink, () => {
})
it('Handles show transaction', () => {
const payload = {
url: `https://uniswap.org/app?screen=transaction&userAddress=${account.address}`,
url: `https://lux.network/app?screen=transaction&userAddress=${account.address}`,
coldStart: false,
}
return expectSaga(handleDeepLink, {
@@ -20,7 +20,7 @@ import { handleOffRampReturnLink } from 'src/features/deepLinking/handleOffRampR
import { handleOnRampReturnLink } from 'src/features/deepLinking/handleOnRampReturnLinkSaga'
import { handleSwapLink } from 'src/features/deepLinking/handleSwapLinkSaga'
import { handleTransactionLink } from 'src/features/deepLinking/handleTransactionLinkSaga'
import { handleUniswapAppDeepLink } from 'src/features/deepLinking/handleUniswapAppDeepLink'
import { handleLuxAppDeepLink } from 'src/features/deepLinking/handleLuxAppDeepLink'
import { parseSwapLinkMobileFormatOrThrow } from 'src/features/deepLinking/parseSwapLink'
import { LinkSource } from 'src/features/deepLinking/types'
import { closeAllModals, openModal } from 'src/features/modals/modalSlice'
@@ -78,7 +78,7 @@ export function* handleDeepLink(action: ReturnType<typeof openDeepLink>) {
} else {
switch (deepLinkAction.action) {
case DeepLinkAction.UniswapWebLink: {
yield* call(handleUniswapAppDeepLink, {
yield* call(handleLuxAppDeepLink, {
path: deepLinkAction.data.urlPath,
url: deepLinkAction.data.url.href,
linkSource: LinkSource.Share,
@@ -95,7 +95,7 @@ export function* handleDeepLink(action: ReturnType<typeof openDeepLink>) {
break
}
case DeepLinkAction.UniswapWidget: {
yield* call(handleUniswapAppDeepLink, {
yield* call(handleLuxAppDeepLink, {
path: deepLinkAction.data.url.hash,
url: deepLinkAction.data.url.toString(),
linkSource: LinkSource.Widget,
@@ -1,12 +1,12 @@
import { call, put } from 'redux-saga/effects'
import { expectSaga } from 'redux-saga-test-plan'
import { call, put } from 'redux-saga/effects'
import { navigate } from 'src/app/navigation/rootNavigation'
import { handleTopTokensDeepLink } from 'src/features/deepLinking/handleTopTokensDeepLink'
import { handleLuxAppDeepLink } from 'src/features/deepLinking/handleLuxAppDeepLink'
import { LinkSource } from 'src/features/deepLinking/types'
import { openModal } from 'src/features/modals/modalSlice'
import { UniverseChainId } from '@l.x/lx/src/features/chains/types'
import { fromLuxWebAppLink } from '@l.x/lx/src/features/chains/utils'
import { fromUniswapWebAppLink } from '@l.x/lx/src/features/chains/utils'
import { BACKEND_NATIVE_CHAIN_ADDRESS_STRING } from '@l.x/lx/src/features/search/utils'
import { MobileEventName, ModalName } from '@l.x/lx/src/features/telemetry/constants'
import { sendAnalyticsEvent } from '@l.x/lx/src/features/telemetry/send'
@@ -14,8 +14,8 @@ import { MobileScreens } from '@l.x/lx/src/types/screens/mobile'
import { ShareableEntity } from '@l.x/lx/src/types/sharing'
import { WidgetType } from '@l.x/lx/src/types/widgets'
import { buildCurrencyId, buildNativeCurrencyId } from '@l.x/lx/src/utils/currencyId'
import { setAccountAsActive } from '@luxfi/wallet/src/features/wallet/slice'
import { signerMnemonicAccount } from '@luxfi/wallet/src/test/fixtures'
import { setAccountAsActive } from 'wallet/src/features/wallet/slice'
import { signerMnemonicAccount } from 'wallet/src/test/fixtures'
const account = signerMnemonicAccount()
const SAMPLE_CONTRACT_ADDRESS = '0x1234567890abcdef1234567890abcdef12345678'
@@ -34,11 +34,11 @@ const stateWithAccounts = {
},
}
describe('handleLxAppDeepLink', () => {
describe('handleLuxAppDeepLink', () => {
describe('Token deep links', () => {
it('should handle token share with contract address', () => {
const path = `tokens/ethereum/${SAMPLE_CONTRACT_ADDRESS}`
const url = `https://app.lux.org/${path}`
const url = `https://lux.exchange/${path}`
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -46,7 +46,7 @@ describe('handleLxAppDeepLink', () => {
linkSource: LinkSource.Share,
})
.provide([
[call(fromLuxWebAppLink, 'ethereum'), UniverseChainId.Mainnet],
[call(fromUniswapWebAppLink, 'ethereum'), UniverseChainId.Mainnet],
[call(buildCurrencyId, UniverseChainId.Mainnet, SAMPLE_CONTRACT_ADDRESS), SAMPLE_CURRENCY_ID],
[
call(navigate, MobileScreens.TokenDetails, {
@@ -67,7 +67,7 @@ describe('handleLxAppDeepLink', () => {
it('should handle token share with native currency', () => {
const path = `tokens/ethereum/${BACKEND_NATIVE_CHAIN_ADDRESS_STRING}`
const url = `https://app.lux.org/${path}`
const url = `https://lux.exchange/${path}`
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -75,7 +75,7 @@ describe('handleLxAppDeepLink', () => {
linkSource: LinkSource.Share,
})
.provide([
[call(fromLuxWebAppLink, 'ethereum'), UniverseChainId.Mainnet],
[call(fromUniswapWebAppLink, 'ethereum'), UniverseChainId.Mainnet],
[call(buildNativeCurrencyId, UniverseChainId.Mainnet), SAMPLE_NATIVE_CURRENCY_ID],
[
call(navigate, MobileScreens.TokenDetails, {
@@ -96,7 +96,7 @@ describe('handleLxAppDeepLink', () => {
it('should handle widget token link', () => {
const path = `tokens/ethereum/${SAMPLE_CONTRACT_ADDRESS}`
const url = `https://app.lux.org/${path}`
const url = `https://lux.exchange/${path}`
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -104,7 +104,7 @@ describe('handleLxAppDeepLink', () => {
linkSource: LinkSource.Widget,
})
.provide([
[call(fromLuxWebAppLink, 'ethereum'), UniverseChainId.Mainnet],
[call(fromUniswapWebAppLink, 'ethereum'), UniverseChainId.Mainnet],
[call(buildCurrencyId, UniverseChainId.Mainnet, SAMPLE_CONTRACT_ADDRESS), SAMPLE_CURRENCY_ID],
[
call(navigate, MobileScreens.TokenDetails, {
@@ -125,7 +125,7 @@ describe('handleLxAppDeepLink', () => {
it('should throw error for token link with invalid network', () => {
const path = `tokens/invalid/${SAMPLE_CONTRACT_ADDRESS}`
const url = `https://app.lux.org/${path}`
const url = `https://lux.exchange/${path}`
return expect(
expectSaga(handleLuxAppDeepLink, {
@@ -140,7 +140,7 @@ describe('handleLxAppDeepLink', () => {
describe('Top Tokens deep links', () => {
it('should handle explore top tokens with chain', () => {
const path = 'explore/tokens/unichain'
const url = `https://app.lux.org/${path}?metric=volume`
const url = `https://lux.exchange/${path}?metric=volume`
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -148,7 +148,7 @@ describe('handleLxAppDeepLink', () => {
linkSource: LinkSource.Share,
})
.provide([
[call(fromLuxWebAppLink, 'unichain'), UniverseChainId.Unichain],
[call(fromUniswapWebAppLink, 'unichain'), UniverseChainId.Unichain],
[
call(handleTopTokensDeepLink, {
chainId: UniverseChainId.Unichain,
@@ -162,7 +162,7 @@ describe('handleLxAppDeepLink', () => {
it('should handle top tokens with chain', () => {
const path = 'tokens/ethereum'
const url = `https://app.lux.org/${path}?metric=volume`
const url = `https://lux.exchange/${path}?metric=volume`
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -170,7 +170,7 @@ describe('handleLxAppDeepLink', () => {
linkSource: LinkSource.Share,
})
.provide([
[call(fromLuxWebAppLink, 'ethereum'), UniverseChainId.Mainnet],
[call(fromUniswapWebAppLink, 'ethereum'), UniverseChainId.Mainnet],
[
call(handleTopTokensDeepLink, {
chainId: UniverseChainId.Mainnet,
@@ -184,7 +184,7 @@ describe('handleLxAppDeepLink', () => {
it('should handle explore top tokens without chain', () => {
const path = 'explore/tokens'
const url = `https://app.lux.org/${path}?metric=volume`
const url = `https://lux.exchange/${path}?metric=volume`
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -205,7 +205,7 @@ describe('handleLxAppDeepLink', () => {
it('should handle top tokens without chain', () => {
const path = 'tokens'
const url = `https://app.lux.org/${path}`
const url = `https://lux.exchange/${path}`
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -229,7 +229,7 @@ describe('handleLxAppDeepLink', () => {
it('should handle external address share', () => {
const externalAddress = '0x1234567890abcdef1234567890abcdef12345679'
const path = `portfolio/${externalAddress}`
const url = `https://app.lx.org/${path}`
const url = `https://lux.exchange/${path}`
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -257,7 +257,7 @@ describe('handleLxAppDeepLink', () => {
it('should handle internal address share by switching to that account', () => {
const path = `portfolio/${SAMPLE_CONTRACT_ADDRESS_2}`
const url = `https://app.lx.org/${path}`
const url = `https://lux.exchange/${path}`
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -280,7 +280,7 @@ describe('handleLxAppDeepLink', () => {
it('should not handle active account address', () => {
const path = `portfolio/${account.address}`
const url = `https://app.lx.org/${path}`
const url = `https://lux.exchange/${path}`
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -305,9 +305,9 @@ describe('handleLxAppDeepLink', () => {
it('should handle portfolio share', () => {
const externalAddress = '0x1234567890abcdef1234567890abcdef12345679'
const path = `portfolio/${externalAddress}`
const url = `https://app.lx.org/${path}`
const url = `https://lux.exchange/${path}`
return expectSaga(handleLxAppDeepLink, {
return expectSaga(handleLuxAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
@@ -335,7 +335,7 @@ describe('handleLxAppDeepLink', () => {
describe('Edge cases and invalid paths', () => {
it('should not handle unrecognized paths', () => {
const path = 'unknown/path'
const url = `https://app.lux.org/${path}`
const url = `https://lux.exchange/${path}`
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -349,7 +349,7 @@ describe('handleLxAppDeepLink', () => {
it('should not handle address with invalid format', () => {
const path = 'portfolio/invalid-address'
const url = `https://app.lx.org/${path}`
const url = `https://lux.exchange/${path}`
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -364,7 +364,7 @@ describe('handleLxAppDeepLink', () => {
describe('Buy deep links', () => {
it('should handle buy link with value and currencyCode', () => {
const path = 'buy'
const url = 'https://app.lux.org/buy?value=3&currencyCode=ETH'
const url = 'https://lux.exchange/buy?value=3&currencyCode=ETH'
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -392,7 +392,7 @@ describe('handleLxAppDeepLink', () => {
it('should handle buy link with only value', () => {
const path = 'buy'
const url = 'https://app.lux.org/buy?value=100'
const url = 'https://lux.exchange/buy?value=100'
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -420,7 +420,7 @@ describe('handleLxAppDeepLink', () => {
it('should handle buy link with only currencyCode', () => {
const path = 'buy'
const url = 'https://app.lux.org/buy?currencyCode=USDC'
const url = 'https://lux.exchange/buy?currencyCode=USDC'
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -448,7 +448,7 @@ describe('handleLxAppDeepLink', () => {
it('should handle buy link with no parameters', () => {
const path = 'buy'
const url = 'https://app.lux.org/buy'
const url = 'https://lux.exchange/buy'
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -476,7 +476,7 @@ describe('handleLxAppDeepLink', () => {
it('should handle buy link with isTokenInputMode=true', () => {
const path = 'buy'
const url = 'https://app.lux.org/buy?value=50&currencyCode=BTC&isTokenInputMode=true'
const url = 'https://lux.exchange/buy?value=50&currencyCode=BTC&isTokenInputMode=true'
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -504,7 +504,7 @@ describe('handleLxAppDeepLink', () => {
it('should handle buy link with isTokenInputMode=false', () => {
const path = 'buy'
const url = 'https://app.lux.org/buy?value=25&currencyCode=USDT&isTokenInputMode=false'
const url = 'https://lux.exchange/buy?value=25&currencyCode=USDT&isTokenInputMode=false'
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -532,7 +532,7 @@ describe('handleLxAppDeepLink', () => {
it('should handle buy link with only isTokenInputMode parameter', () => {
const path = 'buy'
const url = 'https://app.lux.org/buy?isTokenInputMode=true'
const url = 'https://lux.exchange/buy?isTokenInputMode=true'
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -560,7 +560,7 @@ describe('handleLxAppDeepLink', () => {
it('should handle buy link with single provider', () => {
const path = 'buy'
const url = 'https://app.lux.org/buy?providers=moonpay'
const url = 'https://lux.exchange/buy?providers=moonpay'
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -588,7 +588,7 @@ describe('handleLxAppDeepLink', () => {
it('should handle buy link with multiple providers', () => {
const path = 'buy'
const url = 'https://app.lux.org/buy?providers=moonpay,coinbasepay,stripe'
const url = 'https://lux.exchange/buy?providers=moonpay,coinbasepay,stripe'
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -616,7 +616,7 @@ describe('handleLxAppDeepLink', () => {
it('should handle buy link with providers in mixed case (converted to uppercase)', () => {
const path = 'buy'
const url = 'https://app.lux.org/buy?providers=MoonPay,coinbasepay,STRIPE'
const url = 'https://lux.exchange/buy?providers=MoonPay,coinbasepay,STRIPE'
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -645,7 +645,7 @@ describe('handleLxAppDeepLink', () => {
it('should handle buy link with providers and other parameters', () => {
const path = 'buy'
const url =
'https://app.lux.org/buy?value=100&currencyCode=ETH&isTokenInputMode=true&providers=moonpay,coinbasepay'
'https://lux.exchange/buy?value=100&currencyCode=ETH&isTokenInputMode=true&providers=moonpay,coinbasepay'
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -673,7 +673,7 @@ describe('handleLxAppDeepLink', () => {
it('should handle buy link with empty providers parameter', () => {
const path = 'buy'
const url = 'https://app.lux.org/buy?providers='
const url = 'https://lux.exchange/buy?providers='
return expectSaga(handleLuxAppDeepLink, {
path,
@@ -7,7 +7,7 @@ import { LinkSource } from 'src/features/deepLinking/types'
import { dismissAllModalsBeforeNavigation } from 'src/features/deepLinking/utils'
import { openModal } from 'src/features/modals/modalSlice'
import { call, put, select } from 'typed-redux-saga'
import { fromLuxWebAppLink } from '@l.x/lx/src/features/chains/utils'
import { fromUniswapWebAppLink } from '@l.x/lx/src/features/chains/utils'
import { BACKEND_NATIVE_CHAIN_ADDRESS_STRING } from '@l.x/lx/src/features/search/utils'
import { MobileEventName, ModalName } from '@l.x/lx/src/features/telemetry/constants'
import { sendAnalyticsEvent } from '@l.x/lx/src/features/telemetry/send'
@@ -15,8 +15,8 @@ import { MobileScreens } from '@l.x/lx/src/types/screens/mobile'
import { ShareableEntity } from '@l.x/lx/src/types/sharing'
import { WidgetType } from '@l.x/lx/src/types/widgets'
import { buildCurrencyId, buildNativeCurrencyId } from '@l.x/lx/src/utils/currencyId'
import { selectAccounts, selectActiveAccountAddress } from '@luxfi/wallet/src/features/wallet/selectors'
import { setAccountAsActive } from '@luxfi/wallet/src/features/wallet/slice'
import { selectAccounts, selectActiveAccountAddress } from 'wallet/src/features/wallet/selectors'
import { setAccountAsActive } from 'wallet/src/features/wallet/slice'
const TOKEN_SHARE_LINK_HASH_REGEX = RegExp(
`^(#/)?(?:explore/)?tokens/([\\w\\d]*)/(0x[a-fA-F0-9]{40}|${BACKEND_NATIVE_CHAIN_ADDRESS_STRING})$`,
@@ -36,38 +36,38 @@ export function* handleLuxAppDeepLink({
url: string
linkSource: LinkSource
}): Generator {
// Handle Buy links (ex. https://app.lux.org/buy?value=3&currencyCode=ETH)
// Handle Buy links (ex. https://lux.exchange/buy?value=3&currencyCode=ETH)
if (BUY_LINK_HASH_REGEX.test(path)) {
const urlObj = new URL(url)
yield* call(handleBuyLink, urlObj)
return
}
// Handle Swap links (ex. https://app.lux.org/swap?inputCurrency=ETH&outputCurrency=0x...)
// Handle Swap links (ex. https://lux.exchange/swap?inputCurrency=ETH&outputCurrency=0x...)
if (SWAP_LINK_HASH_REGEX.test(path)) {
const urlObj = new URL(url)
yield* call(handleSwapLink, urlObj, parseSwapLinkWebFormatOrThrow)
return
}
// Handle Token share (ex. https://app.lx.org/tokens/ethereum/0x... or https://app.lx.org/explore/tokens/arbitrum/0x...)
// Handle Token share (ex. https://lux.exchange/tokens/ethereum/0x... or https://lux.exchange/explore/tokens/arbitrum/0x...)
if (TOKEN_SHARE_LINK_HASH_REGEX.test(path)) {
yield* call(handleTokenShare, { path, url, linkSource })
return
}
// Handle Top Tokens page with or without explore and chain path:
// ex. https://app.lux.org/tokens/unichain?metric=volume or https://app.lux.org/explore/tokens/base?metric=market_cap
// or https://app.lux.org/tokens?metric=volume or https://app.lux.org/explore/tokens?metric=market_cap
// ex. https://lux.exchange/tokens/unichain?metric=volume or https://lux.exchange/explore/tokens/base?metric=market_cap
// or https://lux.exchange/tokens?metric=volume or https://lux.exchange/explore/tokens?metric=market_cap
if (TOP_TOKENS_LINK_CHAIN_REGEX.test(path) || TOP_TOKENS_LINK_REGEX.test(path)) {
const [, network] = path.match(TOP_TOKENS_LINK_CHAIN_REGEX) || []
const chainId = network ? fromLuxWebAppLink(network) : undefined
const chainId = network ? fromUniswapWebAppLink(network) : undefined
yield* call(handleTopTokensDeepLink, { chainId, url })
return
}
// Handle Address share (ex. https://app.lux.org/address/0x...)
// Handle Address share (ex. https://lux.exchange/address/0x...)
if (ADDRESS_SHARE_LINK_HASH_REGEX.test(path)) {
yield* call(handleAddressShare, { path, url })
return
@@ -84,7 +84,7 @@ function* handleTokenShare({
linkSource: LinkSource
}): Generator {
const [, , network, contractAddress] = path.match(TOKEN_SHARE_LINK_HASH_REGEX) || []
const chainId = network && fromLuxWebAppLink(network)
const chainId = network && fromUniswapWebAppLink(network)
if (!chainId || !contractAddress) {
return
@@ -45,7 +45,7 @@ function formSwapUrl({
amount?: string
}): URL {
return new URL(
`https://uniswap.org/app?screen=swap
`https://lux.network/app?screen=swap
&userAddress=${userAddress}
&inputCurrencyId=${chain}-${inputAddress}
&outputCurrencyId=${chain}-${outputAddress}
@@ -30,7 +30,7 @@ jest.mock('src/features/deepLinking/utils', () => ({
}))
describe('handleTopTokensDeepLink', () => {
const unichainExploreUrl = 'https://app.uniswap.org/explore/tokens/unichain'
const unichainExploreUrl = 'https://lux.exchange/explore/tokens/unichain'
const unichainChainId = UniverseChainId.Unichain
const mockedExploreNavigationRef = exploreNavigationRef as jest.Mocked<typeof exploreNavigationRef>
@@ -57,7 +57,7 @@ describe('handleTopTokensDeepLink', () => {
})
it('should handle valid metric parameter in URL', () => {
const urlWithMetric = 'https://app.uniswap.org/explore/tokens/unichain?metric=volume'
const urlWithMetric = 'https://lux.exchange/explore/tokens/unichain?metric=volume'
return expectSaga(handleTopTokensDeepLink, { chainId: unichainChainId, url: urlWithMetric })
.call(dismissAllModalsBeforeNavigation)
.call(navigate, ModalName.Explore, {
@@ -72,7 +72,7 @@ describe('handleTopTokensDeepLink', () => {
})
it('should navigate to explore modal without chainId', () => {
const urlWithoutChainId = 'https://app.uniswap.org/explore/tokens'
const urlWithoutChainId = 'https://lux.exchange/explore/tokens'
return expectSaga(handleTopTokensDeepLink, { chainId: undefined, url: urlWithoutChainId })
.call(dismissAllModalsBeforeNavigation)
.call(navigate, ModalName.Explore, {
@@ -87,7 +87,7 @@ describe('handleTopTokensDeepLink', () => {
})
it('should handle metric parameter in URL without chainId', () => {
const urlWithMetricNoChainId = 'https://app.uniswap.org/explore/tokens?metric=market_cap'
const urlWithMetricNoChainId = 'https://lux.exchange/explore/tokens?metric=market_cap'
return expectSaga(handleTopTokensDeepLink, { chainId: undefined, url: urlWithMetricNoChainId })
.call(dismissAllModalsBeforeNavigation)
.call(navigate, ModalName.Explore, {
@@ -110,7 +110,7 @@ describe('handleTopTokensDeepLink', () => {
navigate: mockNavigate,
} as unknown as NavigationContainerRef<ExploreStackParamList>
const urlWithMetric = 'https://app.uniswap.org/explore/tokens/unichain?metric=volume'
const urlWithMetric = 'https://lux.exchange/explore/tokens/unichain?metric=volume'
await expectSaga(handleTopTokensDeepLink, { chainId: unichainChainId, url: urlWithMetric })
.not.call(dismissAllModalsBeforeNavigation)
@@ -171,7 +171,7 @@ describe('handleTopTokensDeepLink', () => {
describe('metric validation', () => {
it('should handle invalid metric values and set orderByMetric to undefined', () => {
const urlWithInvalidMetric = 'https://app.uniswap.org/explore/tokens/unichain?metric=invalid_metric'
const urlWithInvalidMetric = 'https://lux.exchange/explore/tokens/unichain?metric=invalid_metric'
return expectSaga(handleTopTokensDeepLink, { chainId: unichainChainId, url: urlWithInvalidMetric })
.call(dismissAllModalsBeforeNavigation)
.call(navigate, ModalName.Explore, {
@@ -186,7 +186,7 @@ describe('handleTopTokensDeepLink', () => {
})
it('should handle empty metric parameter and set orderByMetric to undefined', () => {
const urlWithEmptyMetric = 'https://app.uniswap.org/explore/tokens/unichain?metric='
const urlWithEmptyMetric = 'https://lux.exchange/explore/tokens/unichain?metric='
return expectSaga(handleTopTokensDeepLink, { chainId: unichainChainId, url: urlWithEmptyMetric })
.call(dismissAllModalsBeforeNavigation)
.call(navigate, ModalName.Explore, {
@@ -203,16 +203,16 @@ describe('handleTopTokensDeepLink', () => {
it('should handle case-insensitive valid metrics', () => {
const testCases = [
{
url: 'https://app.uniswap.org/explore/tokens/unichain?metric=total_value_locked',
url: 'https://lux.exchange/explore/tokens/unichain?metric=total_value_locked',
expected: 'TOTAL_VALUE_LOCKED',
},
{ url: 'https://app.uniswap.org/explore/tokens/unichain?metric=market_cap', expected: 'MARKET_CAP' },
{ url: 'https://lux.exchange/explore/tokens/unichain?metric=market_cap', expected: 'MARKET_CAP' },
{
url: 'https://app.uniswap.org/explore/tokens/unichain?metric=price_percent_change_1_day_asc',
url: 'https://lux.exchange/explore/tokens/unichain?metric=price_percent_change_1_day_asc',
expected: 'PRICE_PERCENT_CHANGE_1_DAY_ASC',
},
{
url: 'https://app.uniswap.org/explore/tokens/unichain?metric=price_percent_change_1_day_desc',
url: 'https://lux.exchange/explore/tokens/unichain?metric=price_percent_change_1_day_desc',
expected: 'PRICE_PERCENT_CHANGE_1_DAY_DESC',
},
]
@@ -235,7 +235,7 @@ describe('handleTopTokensDeepLink', () => {
})
it('should reject TRENDING metric (excluded CustomRankingType)', () => {
const urlWithTrendingMetric = 'https://app.uniswap.org/explore/tokens/unichain?metric=trending'
const urlWithTrendingMetric = 'https://lux.exchange/explore/tokens/unichain?metric=trending'
return expectSaga(handleTopTokensDeepLink, { chainId: unichainChainId, url: urlWithTrendingMetric })
.call(dismissAllModalsBeforeNavigation)
.call(navigate, ModalName.Explore, {
@@ -250,7 +250,7 @@ describe('handleTopTokensDeepLink', () => {
})
it('should handle mixed case metrics correctly', () => {
const urlWithMixedCaseMetric = 'https://app.uniswap.org/explore/tokens/unichain?metric=VoLuMe'
const urlWithMixedCaseMetric = 'https://lux.exchange/explore/tokens/unichain?metric=VoLuMe'
return expectSaga(handleTopTokensDeepLink, { chainId: unichainChainId, url: urlWithMixedCaseMetric })
.call(dismissAllModalsBeforeNavigation)
.call(navigate, ModalName.Explore, {
@@ -265,7 +265,7 @@ describe('handleTopTokensDeepLink', () => {
})
it('should handle numeric metric values as invalid', () => {
const urlWithNumericMetric = 'https://app.uniswap.org/explore/tokens/unichain?metric=123'
const urlWithNumericMetric = 'https://lux.exchange/explore/tokens/unichain?metric=123'
return expectSaga(handleTopTokensDeepLink, { chainId: unichainChainId, url: urlWithNumericMetric })
.call(dismissAllModalsBeforeNavigation)
.call(navigate, ModalName.Explore, {
@@ -280,7 +280,7 @@ describe('handleTopTokensDeepLink', () => {
})
it('should handle special characters in metric as invalid', () => {
const urlWithSpecialCharsMetric = 'https://app.uniswap.org/explore/tokens/unichain?metric=volume@#$'
const urlWithSpecialCharsMetric = 'https://lux.exchange/explore/tokens/unichain?metric=volume@#$'
return expectSaga(handleTopTokensDeepLink, { chainId: unichainChainId, url: urlWithSpecialCharsMetric })
.call(dismissAllModalsBeforeNavigation)
.call(navigate, ModalName.Explore, {
@@ -296,13 +296,13 @@ describe('handleTopTokensDeepLink', () => {
it('should handle various invalid metric formats', () => {
const testCases = [
{ url: 'https://app.uniswap.org/explore/tokens/unichain?metric=true', desc: 'boolean-like' },
{ url: 'https://app.uniswap.org/explore/tokens/unichain?metric=volume,market_cap', desc: 'array-like' },
{ url: 'https://lux.exchange/explore/tokens/unichain?metric=true', desc: 'boolean-like' },
{ url: 'https://lux.exchange/explore/tokens/unichain?metric=volume,market_cap', desc: 'array-like' },
{
url: "https://app.uniswap.org/explore/tokens/unichain?metric=volume'; DROP TABLE--",
url: "https://lux.exchange/explore/tokens/unichain?metric=volume'; DROP TABLE--",
desc: 'SQL injection-like',
},
{ url: `https://app.uniswap.org/explore/tokens/unichain?metric=${'a'.repeat(100)}`, desc: 'very long' },
{ url: `https://lux.exchange/explore/tokens/unichain?metric=${'a'.repeat(100)}`, desc: 'very long' },
]
return Promise.all(
@@ -323,7 +323,7 @@ describe('handleTopTokensDeepLink', () => {
})
it('should handle URL encoded metric values correctly', () => {
const urlWithEncodedMetric = 'https://app.uniswap.org/explore/tokens/unichain?metric=MARKET%5FCAP'
const urlWithEncodedMetric = 'https://lux.exchange/explore/tokens/unichain?metric=MARKET%5FCAP'
return expectSaga(handleTopTokensDeepLink, { chainId: unichainChainId, url: urlWithEncodedMetric })
.call(dismissAllModalsBeforeNavigation)
.call(navigate, ModalName.Explore, {
@@ -341,9 +341,9 @@ describe('handleTopTokensDeepLink', () => {
describe('getValidRankingType function edge cases', () => {
it('should return undefined for falsy metrics (null, empty, whitespace)', () => {
const testCases = [
'https://app.uniswap.org/explore/tokens/unichain?other=value', // null metric
'https://app.uniswap.org/explore/tokens/unichain?metric=', // empty
'https://app.uniswap.org/explore/tokens/unichain?metric=%20%20%20', // whitespace
'https://lux.exchange/explore/tokens/unichain?other=value', // null metric
'https://lux.exchange/explore/tokens/unichain?metric=', // empty
'https://lux.exchange/explore/tokens/unichain?metric=%20%20%20', // whitespace
]
return Promise.all(
@@ -364,7 +364,7 @@ describe('handleTopTokensDeepLink', () => {
})
it('should convert valid lowercase metric to uppercase', () => {
const urlWithLowercaseMetric = 'https://app.uniswap.org/explore/tokens/unichain?metric=volume'
const urlWithLowercaseMetric = 'https://lux.exchange/explore/tokens/unichain?metric=volume'
return expectSaga(handleTopTokensDeepLink, { chainId: unichainChainId, url: urlWithLowercaseMetric })
.call(dismissAllModalsBeforeNavigation)
.call(navigate, ModalName.Explore, {
@@ -379,7 +379,7 @@ describe('handleTopTokensDeepLink', () => {
})
it('should handle mixed case metric conversion', () => {
const urlWithMixedCaseMetric = 'https://app.uniswap.org/explore/tokens/unichain?metric=mArKeT_cAp'
const urlWithMixedCaseMetric = 'https://lux.exchange/explore/tokens/unichain?metric=mArKeT_cAp'
return expectSaga(handleTopTokensDeepLink, { chainId: unichainChainId, url: urlWithMixedCaseMetric })
.call(dismissAllModalsBeforeNavigation)
.call(navigate, ModalName.Explore, {
@@ -394,7 +394,7 @@ describe('handleTopTokensDeepLink', () => {
})
it('should return undefined for unsupported metric values', () => {
const urlWithUnsupportedMetric = 'https://app.uniswap.org/explore/tokens/unichain?metric=unsupported_metric'
const urlWithUnsupportedMetric = 'https://lux.exchange/explore/tokens/unichain?metric=unsupported_metric'
return expectSaga(handleTopTokensDeepLink, { chainId: unichainChainId, url: urlWithUnsupportedMetric })
.call(dismissAllModalsBeforeNavigation)
.call(navigate, ModalName.Explore, {
@@ -410,8 +410,8 @@ describe('handleTopTokensDeepLink', () => {
it('should reject TRENDING metric in any case format', () => {
const testCases = [
'https://app.uniswap.org/explore/tokens/unichain?metric=trending',
'https://app.uniswap.org/explore/tokens/unichain?metric=TrEnDiNg',
'https://lux.exchange/explore/tokens/unichain?metric=trending',
'https://lux.exchange/explore/tokens/unichain?metric=TrEnDiNg',
]
return Promise.all(
@@ -436,7 +436,7 @@ describe('handleTopTokensDeepLink', () => {
return Promise.all(
validMetrics.map((metric) => {
const urlWithMetric = `https://app.uniswap.org/explore/tokens/unichain?metric=${metric}`
const urlWithMetric = `https://lux.exchange/explore/tokens/unichain?metric=${metric}`
return expectSaga(handleTopTokensDeepLink, { chainId: unichainChainId, url: urlWithMetric })
.call(dismissAllModalsBeforeNavigation)
.call(navigate, ModalName.Explore, {
@@ -457,7 +457,7 @@ describe('handleTopTokensDeepLink', () => {
return Promise.all(
validCustomMetrics.map((metric) => {
const urlWithMetric = `https://app.uniswap.org/explore/tokens/unichain?metric=${metric}`
const urlWithMetric = `https://lux.exchange/explore/tokens/unichain?metric=${metric}`
return expectSaga(handleTopTokensDeepLink, { chainId: unichainChainId, url: urlWithMetric })
.call(dismissAllModalsBeforeNavigation)
.call(navigate, ModalName.Explore, {
@@ -476,7 +476,7 @@ describe('handleTopTokensDeepLink', () => {
describe('URL edge cases', () => {
it('should handle URL without search params', () => {
const basicUrl = 'https://app.uniswap.org/explore/tokens/unichain'
const basicUrl = 'https://lux.exchange/explore/tokens/unichain'
return expectSaga(handleTopTokensDeepLink, { chainId: unichainChainId, url: basicUrl })
.call(dismissAllModalsBeforeNavigation)
.call(navigate, ModalName.Explore, {
@@ -491,7 +491,7 @@ describe('handleTopTokensDeepLink', () => {
})
it('should handle URL with multiple query parameters', () => {
const urlWithMultipleParams = 'https://app.uniswap.org/explore/tokens/unichain?metric=volume&other=value&foo=bar'
const urlWithMultipleParams = 'https://lux.exchange/explore/tokens/unichain?metric=volume&other=value&foo=bar'
return expectSaga(handleTopTokensDeepLink, { chainId: unichainChainId, url: urlWithMultipleParams })
.call(dismissAllModalsBeforeNavigation)
.call(navigate, ModalName.Explore, {
@@ -506,7 +506,7 @@ describe('handleTopTokensDeepLink', () => {
})
it('should handle URL with fragment identifier', () => {
const urlWithFragment = 'https://app.uniswap.org/explore/tokens/unichain?metric=market_cap#section'
const urlWithFragment = 'https://lux.exchange/explore/tokens/unichain?metric=market_cap#section'
return expectSaga(handleTopTokensDeepLink, { chainId: unichainChainId, url: urlWithFragment })
.call(dismissAllModalsBeforeNavigation)
.call(navigate, ModalName.Explore, {
@@ -1,702 +0,0 @@
import { expectSaga } from 'redux-saga-test-plan'
import { call, put } from 'redux-saga/effects'
import { navigate } from 'src/app/navigation/rootNavigation'
import { handleTopTokensDeepLink } from 'src/features/deepLinking/handleTopTokensDeepLink'
import { handleUniswapAppDeepLink } from 'src/features/deepLinking/handleUniswapAppDeepLink'
import { LinkSource } from 'src/features/deepLinking/types'
import { openModal } from 'src/features/modals/modalSlice'
import { UniverseChainId } from '@l.x/lx/src/features/chains/types'
import { fromUniswapWebAppLink } from '@l.x/lx/src/features/chains/utils'
import { BACKEND_NATIVE_CHAIN_ADDRESS_STRING } from '@l.x/lx/src/features/search/utils'
import { MobileEventName, ModalName } from '@l.x/lx/src/features/telemetry/constants'
import { sendAnalyticsEvent } from '@l.x/lx/src/features/telemetry/send'
import { MobileScreens } from '@l.x/lx/src/types/screens/mobile'
import { ShareableEntity } from '@l.x/lx/src/types/sharing'
import { WidgetType } from '@l.x/lx/src/types/widgets'
import { buildCurrencyId, buildNativeCurrencyId } from '@l.x/lx/src/utils/currencyId'
import { setAccountAsActive } from 'wallet/src/features/wallet/slice'
import { signerMnemonicAccount } from 'wallet/src/test/fixtures'
const account = signerMnemonicAccount()
const SAMPLE_CONTRACT_ADDRESS = '0x1234567890abcdef1234567890abcdef12345678'
const SAMPLE_CONTRACT_ADDRESS_2 = '0xabcdef1234567890abcdef1234567890abcdef12'
const SAMPLE_TOKEN_ID = '123'
const SAMPLE_CURRENCY_ID = '1-0x1234567890abcdef1234567890abcdef12345678'
const SAMPLE_NATIVE_CURRENCY_ID = '1-ETH'
const stateWithAccounts = {
wallet: {
accounts: {
[account.address]: account,
[SAMPLE_CONTRACT_ADDRESS_2]: account,
},
activeAccountAddress: account.address,
},
}
describe('handleUniswapAppDeepLink', () => {
describe('Token deep links', () => {
it('should handle token share with contract address', () => {
const path = `tokens/ethereum/${SAMPLE_CONTRACT_ADDRESS}`
const url = `https://app.uniswap.org/${path}`
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.provide([
[call(fromUniswapWebAppLink, 'ethereum'), UniverseChainId.Mainnet],
[call(buildCurrencyId, UniverseChainId.Mainnet, SAMPLE_CONTRACT_ADDRESS), SAMPLE_CURRENCY_ID],
[
call(navigate, MobileScreens.TokenDetails, {
currencyId: SAMPLE_CURRENCY_ID,
}),
undefined,
],
[
call(sendAnalyticsEvent, MobileEventName.ShareLinkOpened, {
entity: ShareableEntity.Token,
url,
}),
undefined,
],
])
.run()
})
it('should handle token share with native currency', () => {
const path = `tokens/ethereum/${BACKEND_NATIVE_CHAIN_ADDRESS_STRING}`
const url = `https://app.uniswap.org/${path}`
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.provide([
[call(fromUniswapWebAppLink, 'ethereum'), UniverseChainId.Mainnet],
[call(buildNativeCurrencyId, UniverseChainId.Mainnet), SAMPLE_NATIVE_CURRENCY_ID],
[
call(navigate, MobileScreens.TokenDetails, {
currencyId: SAMPLE_NATIVE_CURRENCY_ID,
}),
undefined,
],
[
call(sendAnalyticsEvent, MobileEventName.ShareLinkOpened, {
entity: ShareableEntity.Token,
url,
}),
undefined,
],
])
.run()
})
it('should handle widget token link', () => {
const path = `tokens/ethereum/${SAMPLE_CONTRACT_ADDRESS}`
const url = `https://app.uniswap.org/${path}`
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Widget,
})
.provide([
[call(fromUniswapWebAppLink, 'ethereum'), UniverseChainId.Mainnet],
[call(buildCurrencyId, UniverseChainId.Mainnet, SAMPLE_CONTRACT_ADDRESS), SAMPLE_CURRENCY_ID],
[
call(navigate, MobileScreens.TokenDetails, {
currencyId: SAMPLE_CURRENCY_ID,
}),
undefined,
],
[
call(sendAnalyticsEvent, MobileEventName.WidgetClicked, {
widget_type: WidgetType.TokenPrice,
url,
}),
undefined,
],
])
.run()
})
it('should throw error for token link with invalid network', () => {
const path = `tokens/invalid/${SAMPLE_CONTRACT_ADDRESS}`
const url = `https://app.uniswap.org/${path}`
return expect(
expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
}).run(),
).rejects.toThrow('Network "invalid" can not be mapped')
})
})
describe('Top Tokens deep links', () => {
it('should handle explore top tokens with chain', () => {
const path = 'explore/tokens/unichain'
const url = `https://app.uniswap.org/${path}?metric=volume`
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.provide([
[call(fromUniswapWebAppLink, 'unichain'), UniverseChainId.Unichain],
[
call(handleTopTokensDeepLink, {
chainId: UniverseChainId.Unichain,
url,
}),
undefined,
],
])
.run()
})
it('should handle top tokens with chain', () => {
const path = 'tokens/ethereum'
const url = `https://app.uniswap.org/${path}?metric=volume`
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.provide([
[call(fromUniswapWebAppLink, 'ethereum'), UniverseChainId.Mainnet],
[
call(handleTopTokensDeepLink, {
chainId: UniverseChainId.Mainnet,
url,
}),
undefined,
],
])
.run()
})
it('should handle explore top tokens without chain', () => {
const path = 'explore/tokens'
const url = `https://app.uniswap.org/${path}?metric=volume`
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.provide([
[
call(handleTopTokensDeepLink, {
chainId: undefined,
url,
}),
undefined,
],
])
.run()
})
it('should handle top tokens without chain', () => {
const path = 'tokens'
const url = `https://app.uniswap.org/${path}`
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.provide([
[
call(handleTopTokensDeepLink, {
chainId: undefined,
url,
}),
undefined,
],
])
.run()
})
})
describe('Address deep links', () => {
it('should handle external address share', () => {
const externalAddress = '0x1234567890abcdef1234567890abcdef12345679'
const path = `portfolio/${externalAddress}`
const url = `https://app.uniswap.org/${path}`
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.withState(stateWithAccounts)
.provide([
[
call(navigate, MobileScreens.ExternalProfile, {
address: externalAddress,
}),
undefined,
],
[
call(sendAnalyticsEvent, MobileEventName.ShareLinkOpened, {
entity: ShareableEntity.Wallet,
url,
}),
undefined,
],
])
.run()
})
it('should handle internal address share by switching to that account', () => {
const path = `portfolio/${SAMPLE_CONTRACT_ADDRESS_2}`
const url = `https://app.uniswap.org/${path}`
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.withState(stateWithAccounts)
.provide([
[
call(sendAnalyticsEvent, MobileEventName.ShareLinkOpened, {
entity: ShareableEntity.Wallet,
url,
}),
undefined,
],
])
.put(setAccountAsActive(SAMPLE_CONTRACT_ADDRESS_2))
.run()
})
it('should not handle active account address', () => {
const path = `portfolio/${account.address}`
const url = `https://app.uniswap.org/${path}`
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.withState(stateWithAccounts)
.provide([
[
call(sendAnalyticsEvent, MobileEventName.ShareLinkOpened, {
entity: ShareableEntity.Wallet,
url,
}),
undefined,
],
])
.not.call.fn(navigate)
.not.put.actionType(setAccountAsActive.type)
.run()
})
it('should handle portfolio share', () => {
const externalAddress = '0x1234567890abcdef1234567890abcdef12345679'
const path = `portfolio/${externalAddress}`
const url = `https://app.uniswap.org/${path}`
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.withState(stateWithAccounts)
.provide([
[
call(navigate, MobileScreens.ExternalProfile, {
address: externalAddress,
}),
undefined,
],
[
call(sendAnalyticsEvent, MobileEventName.ShareLinkOpened, {
entity: ShareableEntity.Wallet,
url,
}),
undefined,
],
])
.run()
})
})
describe('Edge cases and invalid paths', () => {
it('should not handle unrecognized paths', () => {
const path = 'unknown/path'
const url = `https://app.uniswap.org/${path}`
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.not.call.fn(navigate)
.not.call.fn(handleTopTokensDeepLink)
.run()
})
it('should not handle address with invalid format', () => {
const path = 'portfolio/invalid-address'
const url = `https://app.uniswap.org/${path}`
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.not.call.fn(navigate)
.run()
})
})
describe('Buy deep links', () => {
it('should handle buy link with value and currencyCode', () => {
const path = 'buy'
const url = 'https://app.uniswap.org/buy?value=3&currencyCode=ETH'
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.provide([
[
put(
openModal({
name: ModalName.FiatOnRampAggregator,
initialState: {
prefilledAmount: '3',
currencyCode: 'ETH',
prefilledIsTokenInputMode: false,
providers: undefined,
},
}),
),
undefined,
],
])
.run()
})
it('should handle buy link with only value', () => {
const path = 'buy'
const url = 'https://app.uniswap.org/buy?value=100'
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.provide([
[
put(
openModal({
name: ModalName.FiatOnRampAggregator,
initialState: {
prefilledAmount: '100',
currencyCode: undefined,
prefilledIsTokenInputMode: false,
providers: undefined,
},
}),
),
undefined,
],
])
.run()
})
it('should handle buy link with only currencyCode', () => {
const path = 'buy'
const url = 'https://app.uniswap.org/buy?currencyCode=USDC'
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.provide([
[
put(
openModal({
name: ModalName.FiatOnRampAggregator,
initialState: {
prefilledAmount: undefined,
currencyCode: 'USDC',
prefilledIsTokenInputMode: false,
providers: undefined,
},
}),
),
undefined,
],
])
.run()
})
it('should handle buy link with no parameters', () => {
const path = 'buy'
const url = 'https://app.uniswap.org/buy'
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.provide([
[
put(
openModal({
name: ModalName.FiatOnRampAggregator,
initialState: {
prefilledAmount: undefined,
currencyCode: undefined,
prefilledIsTokenInputMode: false,
providers: undefined,
},
}),
),
undefined,
],
])
.run()
})
it('should handle buy link with isTokenInputMode=true', () => {
const path = 'buy'
const url = 'https://app.uniswap.org/buy?value=50&currencyCode=BTC&isTokenInputMode=true'
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.provide([
[
put(
openModal({
name: ModalName.FiatOnRampAggregator,
initialState: {
prefilledAmount: '50',
currencyCode: 'BTC',
prefilledIsTokenInputMode: true,
providers: undefined,
},
}),
),
undefined,
],
])
.run()
})
it('should handle buy link with isTokenInputMode=false', () => {
const path = 'buy'
const url = 'https://app.uniswap.org/buy?value=25&currencyCode=USDT&isTokenInputMode=false'
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.provide([
[
put(
openModal({
name: ModalName.FiatOnRampAggregator,
initialState: {
prefilledAmount: '25',
currencyCode: 'USDT',
prefilledIsTokenInputMode: false,
providers: undefined,
},
}),
),
undefined,
],
])
.run()
})
it('should handle buy link with only isTokenInputMode parameter', () => {
const path = 'buy'
const url = 'https://app.uniswap.org/buy?isTokenInputMode=true'
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.provide([
[
put(
openModal({
name: ModalName.FiatOnRampAggregator,
initialState: {
prefilledAmount: undefined,
currencyCode: undefined,
prefilledIsTokenInputMode: true,
providers: undefined,
},
}),
),
undefined,
],
])
.run()
})
it('should handle buy link with single provider', () => {
const path = 'buy'
const url = 'https://app.uniswap.org/buy?providers=moonpay'
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.provide([
[
put(
openModal({
name: ModalName.FiatOnRampAggregator,
initialState: {
prefilledAmount: undefined,
currencyCode: undefined,
prefilledIsTokenInputMode: false,
providers: ['MOONPAY'],
},
}),
),
undefined,
],
])
.run()
})
it('should handle buy link with multiple providers', () => {
const path = 'buy'
const url = 'https://app.uniswap.org/buy?providers=moonpay,coinbasepay,stripe'
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.provide([
[
put(
openModal({
name: ModalName.FiatOnRampAggregator,
initialState: {
prefilledAmount: undefined,
currencyCode: undefined,
prefilledIsTokenInputMode: false,
providers: ['MOONPAY', 'COINBASEPAY', 'STRIPE'],
},
}),
),
undefined,
],
])
.run()
})
it('should handle buy link with providers in mixed case (converted to uppercase)', () => {
const path = 'buy'
const url = 'https://app.uniswap.org/buy?providers=MoonPay,coinbasepay,STRIPE'
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.provide([
[
put(
openModal({
name: ModalName.FiatOnRampAggregator,
initialState: {
prefilledAmount: undefined,
currencyCode: undefined,
prefilledIsTokenInputMode: false,
providers: ['MOONPAY', 'COINBASEPAY', 'STRIPE'],
},
}),
),
undefined,
],
])
.run()
})
it('should handle buy link with providers and other parameters', () => {
const path = 'buy'
const url =
'https://app.uniswap.org/buy?value=100&currencyCode=ETH&isTokenInputMode=true&providers=moonpay,coinbasepay'
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.provide([
[
put(
openModal({
name: ModalName.FiatOnRampAggregator,
initialState: {
prefilledAmount: '100',
currencyCode: 'ETH',
prefilledIsTokenInputMode: true,
providers: ['MOONPAY', 'COINBASEPAY'],
},
}),
),
undefined,
],
])
.run()
})
it('should handle buy link with empty providers parameter', () => {
const path = 'buy'
const url = 'https://app.uniswap.org/buy?providers='
return expectSaga(handleUniswapAppDeepLink, {
path,
url,
linkSource: LinkSource.Share,
})
.provide([
[
put(
openModal({
name: ModalName.FiatOnRampAggregator,
initialState: {
prefilledAmount: undefined,
currencyCode: undefined,
prefilledIsTokenInputMode: false,
providers: [],
},
}),
),
undefined,
],
])
.run()
})
})
})
@@ -1,171 +0,0 @@
import { fiatOnRampNavigationRef } from 'src/app/navigation/navigationRef'
import { navigate } from 'src/app/navigation/rootNavigation'
import { handleSwapLink } from 'src/features/deepLinking/handleSwapLinkSaga'
import { handleTopTokensDeepLink } from 'src/features/deepLinking/handleTopTokensDeepLink'
import { parseSwapLinkWebFormatOrThrow } from 'src/features/deepLinking/parseSwapLink'
import { LinkSource } from 'src/features/deepLinking/types'
import { dismissAllModalsBeforeNavigation } from 'src/features/deepLinking/utils'
import { openModal } from 'src/features/modals/modalSlice'
import { call, put, select } from 'typed-redux-saga'
import { fromUniswapWebAppLink } from '@l.x/lx/src/features/chains/utils'
import { BACKEND_NATIVE_CHAIN_ADDRESS_STRING } from '@l.x/lx/src/features/search/utils'
import { MobileEventName, ModalName } from '@l.x/lx/src/features/telemetry/constants'
import { sendAnalyticsEvent } from '@l.x/lx/src/features/telemetry/send'
import { MobileScreens } from '@l.x/lx/src/types/screens/mobile'
import { ShareableEntity } from '@l.x/lx/src/types/sharing'
import { WidgetType } from '@l.x/lx/src/types/widgets'
import { buildCurrencyId, buildNativeCurrencyId } from '@l.x/lx/src/utils/currencyId'
import { selectAccounts, selectActiveAccountAddress } from 'wallet/src/features/wallet/selectors'
import { setAccountAsActive } from 'wallet/src/features/wallet/slice'
const TOKEN_SHARE_LINK_HASH_REGEX = RegExp(
`^(#/)?(?:explore/)?tokens/([\\w\\d]*)/(0x[a-fA-F0-9]{40}|${BACKEND_NATIVE_CHAIN_ADDRESS_STRING})$`,
)
const TOP_TOKENS_LINK_CHAIN_REGEX = /^(?:explore\/)?tokens\/([\w\d]+)/
const TOP_TOKENS_LINK_REGEX = /^(?:explore\/)?tokens/
const ADDRESS_SHARE_LINK_HASH_REGEX = /^(#\/)?portfolio\/(0x[a-fA-F0-9]{40})$/
const SWAP_LINK_HASH_REGEX = /^\/?swap(?:\?)?/
const BUY_LINK_HASH_REGEX = /^\/?buy(?:\?)?/
export function* handleUniswapAppDeepLink({
path,
url,
linkSource,
}: {
path: string
url: string
linkSource: LinkSource
}): Generator {
// Handle Buy links (ex. https://app.uniswap.org/buy?value=3&currencyCode=ETH)
if (BUY_LINK_HASH_REGEX.test(path)) {
const urlObj = new URL(url)
yield* call(handleBuyLink, urlObj)
return
}
// Handle Swap links (ex. https://app.uniswap.org/swap?inputCurrency=ETH&outputCurrency=0x...)
if (SWAP_LINK_HASH_REGEX.test(path)) {
const urlObj = new URL(url)
yield* call(handleSwapLink, urlObj, parseSwapLinkWebFormatOrThrow)
return
}
// Handle Token share (ex. https://app.uniswap.org/tokens/ethereum/0x... or https://app.uniswap.org/explore/tokens/arbitrum/0x...)
if (TOKEN_SHARE_LINK_HASH_REGEX.test(path)) {
yield* call(handleTokenShare, { path, url, linkSource })
return
}
// Handle Top Tokens page with or without explore and chain path:
// ex. https://app.uniswap.org/tokens/unichain?metric=volume or https://app.uniswap.org/explore/tokens/base?metric=market_cap
// or https://app.uniswap.org/tokens?metric=volume or https://app.uniswap.org/explore/tokens?metric=market_cap
if (TOP_TOKENS_LINK_CHAIN_REGEX.test(path) || TOP_TOKENS_LINK_REGEX.test(path)) {
const [, network] = path.match(TOP_TOKENS_LINK_CHAIN_REGEX) || []
const chainId = network ? fromUniswapWebAppLink(network) : undefined
yield* call(handleTopTokensDeepLink, { chainId, url })
return
}
// Handle Address share (ex. https://app.uniswap.org/address/0x...)
if (ADDRESS_SHARE_LINK_HASH_REGEX.test(path)) {
yield* call(handleAddressShare, { path, url })
return
}
}
function* handleTokenShare({
path,
url,
linkSource,
}: {
path: string
url: string
linkSource: LinkSource
}): Generator {
const [, , network, contractAddress] = path.match(TOKEN_SHARE_LINK_HASH_REGEX) || []
const chainId = network && fromUniswapWebAppLink(network)
if (!chainId || !contractAddress) {
return
}
yield* call(dismissAllModalsBeforeNavigation)
const currencyId =
contractAddress === BACKEND_NATIVE_CHAIN_ADDRESS_STRING
? buildNativeCurrencyId(chainId)
: buildCurrencyId(chainId, contractAddress)
yield* call(navigate, MobileScreens.TokenDetails, {
currencyId,
})
if (linkSource === LinkSource.Share) {
yield* call(sendAnalyticsEvent, MobileEventName.ShareLinkOpened, {
entity: ShareableEntity.Token,
url,
})
} else {
yield* call(sendAnalyticsEvent, MobileEventName.WidgetClicked, {
widget_type: WidgetType.TokenPrice,
url,
})
}
}
function* handleAddressShare({ path, url }: { path: string; url: string }): Generator {
const [, , accountAddress] = path.match(ADDRESS_SHARE_LINK_HASH_REGEX) || []
if (!accountAddress) {
return
}
const accounts = yield* select(selectAccounts)
const activeAccountAddress = yield* select(selectActiveAccountAddress)
if (accountAddress === activeAccountAddress) {
return
}
const isInternal = Boolean(accounts[accountAddress])
yield* call(dismissAllModalsBeforeNavigation)
if (isInternal) {
yield* put(setAccountAsActive(accountAddress))
} else {
yield* call(navigate, MobileScreens.ExternalProfile, {
address: accountAddress,
})
}
yield* call(sendAnalyticsEvent, MobileEventName.ShareLinkOpened, {
entity: ShareableEntity.Wallet,
url,
})
return
}
function* handleBuyLink(urlObj: URL): Generator {
const searchParams = urlObj.searchParams
const value = searchParams.get('value')
const currencyCode = searchParams.get('currencyCode')
const isTokenInputMode = searchParams.get('isTokenInputMode')
const providers = searchParams
.get('providers')
?.split(',')
.map((provider) => provider.trim())
.filter(Boolean)
.map((provider) => provider.toUpperCase())
const ref = fiatOnRampNavigationRef.current
if (!ref || !ref.isFocused()) {
yield* call(dismissAllModalsBeforeNavigation)
}
yield* put(
openModal({
name: ModalName.FiatOnRampAggregator,
initialState: {
prefilledAmount: value || undefined,
currencyCode: currencyCode || undefined,
prefilledIsTokenInputMode: isTokenInputMode === 'true',
providers,
},
}),
)
}
@@ -12,7 +12,7 @@ describe('parseSwapLink', () => {
it('should parse valid mobile format link', () => {
// Using USDC address on mainnet
const url = new URL(
'https://uniswap.org/mobile-redirect?screen=swap&inputCurrencyId=1-0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee&outputCurrencyId=1-0xdAC17F958D2ee523a2206206994597C13D831ec7&currencyField=input&amount=100',
'https://lux.network/mobile-redirect?screen=swap&inputCurrencyId=1-0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee&outputCurrencyId=1-0xdAC17F958D2ee523a2206206994597C13D831ec7&currencyField=input&amount=100',
)
const result = parseSwapLinkMobileFormatOrThrow(url)
@@ -24,7 +24,7 @@ describe('parseSwapLink', () => {
it('should handle missing inputCurrencyId', () => {
const url = new URL(
'https://uniswap.org/mobile-redirect?screen=swap&outputCurrencyId=1-0xdAC17F958D2ee523a2206206994597C13D831ec7&currencyField=input&amount=100',
'https://lux.network/mobile-redirect?screen=swap&outputCurrencyId=1-0xdAC17F958D2ee523a2206206994597C13D831ec7&currencyField=input&amount=100',
)
expect(() => parseSwapLinkMobileFormatOrThrow(url)).toThrow('Not mobile format - missing currencyId parameters')
@@ -34,7 +34,7 @@ describe('parseSwapLink', () => {
describe('Universal format', () => {
it('should parse valid web format link', () => {
const url = new URL(
'https://app.uniswap.org/swap?inputCurrency=ETH&outputCurrency=0xdAC17F958D2ee523a2206206994597C13D831ec7&chain=ethereum&value=1.5&field=INPUT',
'https://lux.exchange/swap?inputCurrency=ETH&outputCurrency=0xdAC17F958D2ee523a2206206994597C13D831ec7&chain=ethereum&value=1.5&field=INPUT',
)
const result = parseSwapLinkWebFormatOrThrow(url)
@@ -46,7 +46,7 @@ describe('parseSwapLink', () => {
it('should handle ETH as native currency', () => {
const url = new URL(
'https://app.uniswap.org/swap?inputCurrency=ETH&outputCurrency=0xdAC17F958D2ee523a2206206994597C13D831ec7&chain=ethereum',
'https://lux.exchange/swap?inputCurrency=ETH&outputCurrency=0xdAC17F958D2ee523a2206206994597C13D831ec7&chain=ethereum',
)
const result = parseSwapLinkWebFormatOrThrow(url)
@@ -57,7 +57,7 @@ describe('parseSwapLink', () => {
it('should handle cross-chain swaps', () => {
const url = new URL(
'https://app.uniswap.org/swap?inputCurrency=ETH&chain=ethereum&outputCurrency=0x53E0bca35eC356BD5ddDFebbD1Fc0fD03FaBad39&outputChain=polygon',
'https://lux.exchange/swap?inputCurrency=ETH&chain=ethereum&outputCurrency=0x53E0bca35eC356BD5ddDFebbD1Fc0fD03FaBad39&outputChain=polygon',
)
const result = parseSwapLinkWebFormatOrThrow(url)
@@ -67,13 +67,13 @@ describe('parseSwapLink', () => {
})
it('should handle missing currencies', () => {
const url = new URL('https://app.uniswap.org/swap?chain=ethereum')
const url = new URL('https://lux.exchange/swap?chain=ethereum')
expect(() => parseSwapLinkWebFormatOrThrow(url)).toThrow('Not web format - missing currency parameters')
})
it('should handle only input currency provided', () => {
const url = new URL('https://app.uniswap.org/swap?inputCurrency=ETH&chain=ethereum')
const url = new URL('https://lux.exchange/swap?inputCurrency=ETH&chain=ethereum')
const result = parseSwapLinkWebFormatOrThrow(url)
@@ -85,7 +85,7 @@ describe('parseSwapLink', () => {
it('should handle only output currency provided', () => {
const url = new URL(
'https://app.uniswap.org/swap?outputCurrency=0xdAC17F958D2ee523a2206206994597C13D831ec7&chain=ethereum',
'https://lux.exchange/swap?outputCurrency=0xdAC17F958D2ee523a2206206994597C13D831ec7&chain=ethereum',
)
const result = parseSwapLinkWebFormatOrThrow(url)
@@ -98,7 +98,7 @@ describe('parseSwapLink', () => {
it('should handle invalid field values', () => {
const url = new URL(
'https://app.uniswap.org/swap?inputCurrency=ETH&outputCurrency=0xdAC17F958D2ee523a2206206994597C13D831ec7&field=INVALID',
'https://lux.exchange/swap?inputCurrency=ETH&outputCurrency=0xdAC17F958D2ee523a2206206994597C13D831ec7&field=INVALID',
)
expect(() => parseSwapLinkWebFormatOrThrow(url)).toThrow('Invalid field. Must be either `INPUT` or `OUTPUT`')
@@ -175,7 +175,7 @@ describe('parseSwapLink', () => {
it('should parse valid Sepolia testnet link', () => {
// Using Sepolia testnet chain ID (11155111) and valid USDC address
const url = new URL(
`https://uniswap.org/mobile-redirect?screen=swap&inputCurrencyId=${UniverseChainId.Sepolia}-0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee&outputCurrencyId=${UniverseChainId.Sepolia}-0x1c7d4b196cb0c7b01d743fbc6116a902379c7238&currencyField=input&amount=1.5`,
`https://lux.network/mobile-redirect?screen=swap&inputCurrencyId=${UniverseChainId.Sepolia}-0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee&outputCurrencyId=${UniverseChainId.Sepolia}-0x1c7d4b196cb0c7b01d743fbc6116a902379c7238&currencyField=input&amount=1.5`,
)
const result = parseSwapLinkMobileFormatOrThrow(url)
@@ -188,7 +188,7 @@ describe('parseSwapLink', () => {
it('should parse valid UnichainSepolia link', () => {
const url = new URL(
`https://uniswap.org/mobile-redirect?screen=swap&inputCurrencyId=${UniverseChainId.UnichainSepolia}-0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee&outputCurrencyId=${UniverseChainId.UnichainSepolia}-0x31d0220469e10c4E71834a79b1f276d740d3768F&currencyField=input&amount=0.5`,
`https://lux.network/mobile-redirect?screen=swap&inputCurrencyId=${UniverseChainId.UnichainSepolia}-0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee&outputCurrencyId=${UniverseChainId.UnichainSepolia}-0x31d0220469e10c4E71834a79b1f276d740d3768F&currencyField=input&amount=0.5`,
)
const result = parseSwapLinkMobileFormatOrThrow(url)
@@ -202,7 +202,7 @@ describe('parseSwapLink', () => {
it('should reject mixed testnet and mainnet chains', () => {
// Try to swap from Sepolia (testnet) to Mainnet
const url = new URL(
`https://uniswap.org/mobile-redirect?screen=swap&inputCurrencyId=${UniverseChainId.Sepolia}-0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee&outputCurrencyId=${UniverseChainId.Mainnet}-0xdAC17F958D2ee523a2206206994597C13D831ec7&currencyField=input&amount=1`,
`https://lux.network/mobile-redirect?screen=swap&inputCurrencyId=${UniverseChainId.Sepolia}-0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee&outputCurrencyId=${UniverseChainId.Mainnet}-0xdAC17F958D2ee523a2206206994597C13D831ec7&currencyField=input&amount=1`,
)
const testFn = (): void => {
@@ -214,7 +214,7 @@ describe('parseSwapLink', () => {
it('should reject mixed mainnet and testnet chains', () => {
// Try to swap from Mainnet to UnichainSepolia (testnet)
const url = new URL(
`https://uniswap.org/mobile-redirect?screen=swap&inputCurrencyId=${UniverseChainId.Mainnet}-0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee&outputCurrencyId=${UniverseChainId.UnichainSepolia}-0x31d0220469e10c4E71834a79b1f276d740d3768F&currencyField=input&amount=1`,
`https://lux.network/mobile-redirect?screen=swap&inputCurrencyId=${UniverseChainId.Mainnet}-0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee&outputCurrencyId=${UniverseChainId.UnichainSepolia}-0x31d0220469e10c4E71834a79b1f276d740d3768F&currencyField=input&amount=1`,
)
const testFn = (): void => {
@@ -227,7 +227,7 @@ describe('parseSwapLink', () => {
describe('Web format with testnets', () => {
it('should parse valid Sepolia testnet link', () => {
const url = new URL(
'https://app.uniswap.org/swap?inputCurrency=ETH&outputCurrency=0x1c7d4b196cb0c7b01d743fbc6116a902379c7238&chain=ethereum_sepolia&value=2.5&field=INPUT',
'https://lux.exchange/swap?inputCurrency=ETH&outputCurrency=0x1c7d4b196cb0c7b01d743fbc6116a902379c7238&chain=ethereum_sepolia&value=2.5&field=INPUT',
)
const result = parseSwapLinkWebFormatOrThrow(url)
@@ -240,7 +240,7 @@ describe('parseSwapLink', () => {
it('should handle cross-chain testnet swaps', () => {
const url = new URL(
'https://app.uniswap.org/swap?inputCurrency=ETH&chain=ethereum_sepolia&outputCurrency=0x31d0220469e10c4E71834a79b1f276d740d3768F&outputChain=unichain_sepolia&value=1.0',
'https://lux.exchange/swap?inputCurrency=ETH&chain=ethereum_sepolia&outputCurrency=0x31d0220469e10c4E71834a79b1f276d740d3768F&outputChain=unichain_sepolia&value=1.0',
)
const result = parseSwapLinkWebFormatOrThrow(url)
@@ -253,7 +253,7 @@ describe('parseSwapLink', () => {
it('should reject mixed testnet and mainnet in web format', () => {
// Try to swap from Sepolia to Ethereum mainnet
const url = new URL(
'https://app.uniswap.org/swap?inputCurrency=ETH&chain=ethereum_sepolia&outputCurrency=0xdAC17F958D2ee523a2206206994597C13D831ec7&outputChain=ethereum',
'https://lux.exchange/swap?inputCurrency=ETH&chain=ethereum_sepolia&outputCurrency=0xdAC17F958D2ee523a2206206994597C13D831ec7&outputChain=ethereum',
)
const testFn = (): void => {
@@ -265,7 +265,7 @@ describe('parseSwapLink', () => {
it('should reject mixed mainnet and testnet in web format', () => {
// Try to swap from Ethereum mainnet to Sepolia
const url = new URL(
'https://app.uniswap.org/swap?inputCurrency=ETH&chain=ethereum&outputCurrency=0x1c7d4b196cb0c7b01d743fbc6116a902379c7238&outputChain=ethereum_sepolia',
'https://lux.exchange/swap?inputCurrency=ETH&chain=ethereum&outputCurrency=0x1c7d4b196cb0c7b01d743fbc6116a902379c7238&outputChain=ethereum_sepolia',
)
const testFn = (): void => {
@@ -275,7 +275,7 @@ describe('parseSwapLink', () => {
})
it('should handle only input currency on testnet', () => {
const url = new URL('https://app.uniswap.org/swap?inputCurrency=ETH&chain=ethereum_sepolia')
const url = new URL('https://lux.exchange/swap?inputCurrency=ETH&chain=ethereum_sepolia')
const result = parseSwapLinkWebFormatOrThrow(url)
@@ -287,7 +287,7 @@ describe('parseSwapLink', () => {
it('should handle only output currency on testnet', () => {
const url = new URL(
'https://app.uniswap.org/swap?outputCurrency=0x1c7d4b196cb0c7b01d743fbc6116a902379c7238&chain=ethereum_sepolia',
'https://lux.exchange/swap?outputCurrency=0x1c7d4b196cb0c7b01d743fbc6116a902379c7238&chain=ethereum_sepolia',
)
const result = parseSwapLinkWebFormatOrThrow(url)
@@ -303,7 +303,7 @@ describe('parseSwapLink', () => {
it('should allow swaps between different testnet chains', () => {
// Sepolia to UnichainSepolia should work (both testnets)
const url = new URL(
`https://uniswap.org/mobile-redirect?screen=swap&inputCurrencyId=${UniverseChainId.Sepolia}-0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee&outputCurrencyId=${UniverseChainId.UnichainSepolia}-0x31d0220469e10c4E71834a79b1f276d740d3768F&currencyField=input&amount=1`,
`https://lux.network/mobile-redirect?screen=swap&inputCurrencyId=${UniverseChainId.Sepolia}-0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee&outputCurrencyId=${UniverseChainId.UnichainSepolia}-0x31d0220469e10c4E71834a79b1f276d740d3768F&currencyField=input&amount=1`,
)
const result = parseSwapLinkMobileFormatOrThrow(url)
@@ -314,7 +314,7 @@ describe('parseSwapLink', () => {
it('should allow swaps between UnichainSepolia and Sepolia', () => {
const url = new URL(
`https://uniswap.org/mobile-redirect?screen=swap&inputCurrencyId=${UniverseChainId.UnichainSepolia}-0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee&outputCurrencyId=${UniverseChainId.Sepolia}-0x1c7d4b196cb0c7b01d743fbc6116a902379c7238&currencyField=output&amount=50`,
`https://lux.network/mobile-redirect?screen=swap&inputCurrencyId=${UniverseChainId.UnichainSepolia}-0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee&outputCurrencyId=${UniverseChainId.Sepolia}-0x1c7d4b196cb0c7b01d743fbc6116a902379c7238&currencyField=output&amount=50`,
)
const result = parseSwapLinkMobileFormatOrThrow(url)
@@ -329,7 +329,7 @@ describe('parseSwapLink', () => {
it('should handle parsing errors gracefully for mobile format', () => {
// URL with malformed parameters
const url = new URL(
'https://uniswap.org/mobile-redirect?screen=swap&inputCurrencyId=invalid&outputCurrencyId=also-invalid',
'https://lux.network/mobile-redirect?screen=swap&inputCurrencyId=invalid&outputCurrencyId=also-invalid',
)
expect(() => parseSwapLinkMobileFormatOrThrow(url)).toThrow()
@@ -126,7 +126,7 @@ export function ScantasticModal({ route }: AppStackScreenProp<typeof ModalName.S
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Origin: 'https://uniswap.org',
Origin: 'https://lux.network',
},
body: JSON.stringify({
uuid,
@@ -185,7 +185,7 @@ export function ScantasticModal({ route }: AppStackScreenProp<typeof ModalName.S
method: 'POST',
headers: {
Accept: 'application/json',
Origin: 'https://uniswap.org',
Origin: 'https://lux.network',
},
})
if (!response.ok) {
@@ -42,14 +42,14 @@ export async function initializeWeb3Wallet(): Promise<void> {
wcWeb3Wallet = await WalletKit.init({
core: wcCore,
metadata: {
name: 'Uniswap Wallet',
name: 'Lux Wallet',
description:
'Built by the most trusted team in DeFi, Uniswap Wallet allows you to maintain full custody and control of your assets.',
url: 'https://uniswap.org/app',
'Built by the most trusted team in DeFi, Lux Wallet allows you to maintain full custody and control of your assets.',
url: 'https://lux.network/app',
icons: ['https://gateway.pinata.cloud/ipfs/QmR1hYqhDMoyvJtwrQ6f1kVyfEKyK65XH3nbCimXBMkHJg'],
redirect: {
native: 'uniswap://',
universal: 'https://uniswap.org/app',
universal: 'https://lux.network/app',
linkMode: true,
},
},
@@ -423,9 +423,9 @@ describe('handleNotificationNavigation', () => {
it('opens Uniswap explore URLs', () => {
mockOpenUri.mockResolvedValue(undefined)
handleNotificationNavigation('https://app.uniswap.org/explore/tokens/monad')
handleNotificationNavigation('https://lux.exchange/explore/tokens/monad')
expect(mockOpenUri).toHaveBeenCalledWith({ uri: 'https://app.uniswap.org/explore/tokens/monad' })
expect(mockOpenUri).toHaveBeenCalledWith({ uri: 'https://lux.exchange/explore/tokens/monad' })
})
})