Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
627b773a26 | ||
|
|
0904399245 | ||
|
|
aec4c5a44a | ||
|
|
2a3fdf6df3 | ||
|
|
fc342495e3 | ||
|
|
8318981cb2 | ||
|
|
7b553be1cd | ||
|
|
bbe42b81de | ||
|
|
e9f469d399 | ||
|
|
4c58258f01 | ||
|
|
f6e6db6430 | ||
|
|
dab445f237 | ||
|
|
da90738ba1 | ||
|
|
e4dbef80eb | ||
|
|
e169c9d900 | ||
|
|
c8a17f5fe9 | ||
|
|
552a38994b | ||
|
|
b313ac9843 | ||
|
|
88aec2c894 | ||
|
|
86677ed193 | ||
|
|
accf0905f8 | ||
|
|
ef5065de48 | ||
|
|
bcb45cded6 | ||
|
|
dfe50b4bee | ||
|
|
90f72e05b9 | ||
|
|
3a0f6920d0 | ||
|
|
07eb9eb9a2 | ||
|
|
a9e8e8b275 | ||
|
|
4708d3d3d7 | ||
|
|
27acddc0e7 | ||
|
|
102a935ff8 | ||
|
|
614c15243e | ||
|
|
082db21308 | ||
|
|
0f75c6a52b | ||
|
|
1c2ed1d9e4 | ||
|
|
0e956fb7c4 | ||
|
|
b49dd03e25 | ||
|
|
82211a888c | ||
|
|
5dd8cbbdc9 | ||
|
|
5640c115de | ||
|
|
7b8114b401 | ||
|
|
8e36361866 | ||
|
|
0a04cff7eb | ||
|
|
d3dd1d4ebd |
3
.env
3
.env
@@ -11,4 +11,5 @@ REACT_APP_MOONPAY_PUBLISHABLE_KEY="pk_test_DycfESRid31UaSxhI5yWKe1r5E5kKSz"
|
||||
REACT_APP_SENTRY_DSN="https://a3c62e400b8748b5a8d007150e2f38b7@o1037921.ingest.sentry.io/4504255148851200"
|
||||
REACT_APP_STATSIG_PROXY_URL="https://api.uniswap.org/v1/statsig-proxy"
|
||||
REACT_APP_TEMP_API_URL="https://temp.api.uniswap.org/v1"
|
||||
REACT_APP_WALLET_CONNECT_PROJECT_ID="c6c9bacd35afa3eb9e6cccf6d8464395"
|
||||
REACT_APP_UNISWAP_API_URL="https://api.uniswap.org/v2"
|
||||
REACT_APP_WALLET_CONNECT_PROJECT_ID="c6c9bacd35afa3eb9e6cccf6d8464395"
|
||||
|
||||
@@ -13,7 +13,6 @@ module.exports = {
|
||||
files: ['**/*'],
|
||||
rules: {
|
||||
'multiline-comment-style': ['error', 'separate-lines'],
|
||||
'rulesdir/enforce-retry-on-import': 'error',
|
||||
'rulesdir/no-undefined-or': 'error',
|
||||
},
|
||||
},
|
||||
|
||||
15
.github/workflows/1-main-to-staging.yml
vendored
15
.github/workflows/1-main-to-staging.yml
vendored
@@ -14,6 +14,21 @@ jobs:
|
||||
environment:
|
||||
name: push/staging
|
||||
steps:
|
||||
- name: Check test status
|
||||
uses: actions/github-script@v6.4.1
|
||||
with:
|
||||
script: |
|
||||
const statuses = await github.rest.repos.listCommitStatusesForRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: context.sha
|
||||
})
|
||||
const status = statuses.data.find(status => status.context === 'Test / promotion')?.state || 'missing'
|
||||
core.info('Status: ' + status)
|
||||
if (status !== 'success') {
|
||||
core.setFailed('"Test / promotion" must be successful before pushing')
|
||||
}
|
||||
|
||||
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
with:
|
||||
token: ${{ secrets.RELEASE_SERVICE_ACCESS_TOKEN }}
|
||||
|
||||
47
.github/workflows/test.yml
vendored
47
.github/workflows/test.yml
vendored
@@ -9,6 +9,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- releases/staging
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
@@ -108,7 +109,6 @@ jobs:
|
||||
cypress-test-matrix:
|
||||
needs: [build-e2e, cypress-rerun]
|
||||
runs-on: ubuntu-latest
|
||||
container: cypress/browsers:node-18.14.1-chrome-111.0.5563.64-1-ff-111.0-edge-111.0.1661.43-1
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -142,8 +142,9 @@ jobs:
|
||||
parallel: true
|
||||
start: yarn serve
|
||||
wait-on: 'http://localhost:3000'
|
||||
browser: chrome
|
||||
browser: electron
|
||||
group: e2e
|
||||
spec: ${{ github.ref_name == 'releases/staging' && 'cypress/{e2e,staging}/**/*.test.ts' || 'cypress/e2e/**/*.test.ts' }}
|
||||
env:
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -167,3 +168,45 @@ jobs:
|
||||
with:
|
||||
name: Cypress tests
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
|
||||
|
||||
pre:
|
||||
if: ${{ github.ref_name == 'main' || github.ref_name == 'releases/staging' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v6.4.1
|
||||
with:
|
||||
script: |
|
||||
github.rest.repos.createCommitStatus({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
sha: context.sha,
|
||||
state: 'pending',
|
||||
context: 'Test / promotion',
|
||||
description: 'Running tests...',
|
||||
target_url: 'https://github.com/Uniswap/interface/actions/runs/' + context.runId
|
||||
})
|
||||
|
||||
post:
|
||||
if: ${{ github.ref_name == 'main' || github.ref_name == 'releases/staging' }}
|
||||
needs: [pre, lint, typecheck, deps-tests, unit-tests, cypress-test-matrix]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v6.4.1
|
||||
with:
|
||||
script: |
|
||||
github.rest.repos.createCommitStatus({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
sha: context.sha,
|
||||
state: ${{ env.STATUS }} ? 'success' : 'failure',
|
||||
context: 'Test / promotion',
|
||||
description: ${{ env.STATUS }} ? 'All tests passed' : 'One or more tests failed and are blocking promotion',
|
||||
target_url: 'https://github.com/Uniswap/interface/actions/runs/' + context.runId
|
||||
})
|
||||
env:
|
||||
STATUS: |
|
||||
${{ needs.lint.result == 'success' }} &&
|
||||
${{ needs.typecheck.result == 'success' }} &&
|
||||
${{ needs.deps-tests.result == 'success' }} &&
|
||||
${{ needs.unit-tests.result == 'success' }} &&
|
||||
${{ needs.cypress-test-matrix.result == 'success' }}
|
||||
|
||||
1
CODEOWNERS
Normal file
1
CODEOWNERS
Normal file
@@ -0,0 +1 @@
|
||||
@uniswap/web-admins
|
||||
@@ -6,6 +6,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const path = require('path')
|
||||
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin')
|
||||
const { DefinePlugin, IgnorePlugin, ProvidePlugin } = require('webpack')
|
||||
const { RetryChunkLoadPlugin } = require('webpack-retry-chunk-load-plugin')
|
||||
|
||||
const commitHash = execSync('git rev-parse HEAD').toString().trim()
|
||||
const isProduction = process.env.NODE_ENV === 'production'
|
||||
@@ -93,6 +94,16 @@ module.exports = {
|
||||
// See https://vanilla-extract.style/documentation/integrations/webpack/#identifiers for docs.
|
||||
// See https://github.com/vanilla-extract-css/vanilla-extract/issues/771#issuecomment-1249524366.
|
||||
new VanillaExtractPlugin({ identifiers: 'short' }),
|
||||
new RetryChunkLoadPlugin({
|
||||
cacheBust: `function() {
|
||||
return 'cache-bust=' + Date.now();
|
||||
}`,
|
||||
// Retries with exponential backoff (500ms, 1000ms, 2000ms).
|
||||
retryDelay: `function(retryAttempt) {
|
||||
return 2 ** (retryAttempt - 1) * 500;
|
||||
}`,
|
||||
maxRetries: 3,
|
||||
}),
|
||||
],
|
||||
configure: (webpackConfig) => {
|
||||
// Configure webpack plugins:
|
||||
|
||||
@@ -25,14 +25,9 @@ export default defineConfig({
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
...config,
|
||||
// Only enable Chrome.
|
||||
// Electron (the default) has issues injecting window.ethereum before pageload, so it is not viable.
|
||||
browsers: config.browsers.filter(({ name }) => name === 'chrome'),
|
||||
}
|
||||
return config
|
||||
},
|
||||
baseUrl: 'http://localhost:3000',
|
||||
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
|
||||
specPattern: 'cypress/{e2e,staging}/**/*.test.ts',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { FeatureFlag } from '../../src/featureFlags'
|
||||
import { getTestSelector } from '../utils'
|
||||
|
||||
describe('Buy Crypto Modal', () => {
|
||||
it('should open and close', () => {
|
||||
cy.visit('/')
|
||||
cy.visit('/', { featureFlags: [FeatureFlag.fiatOnRampButtonOnSwap] })
|
||||
|
||||
// Open the fiat onramp modal
|
||||
cy.get(getTestSelector('buy-fiat-button')).click()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { getTestSelector } from '../utils'
|
||||
import { CONNECTED_WALLET_USER_STATE } from '../utils/user-state'
|
||||
import { CONNECTED_WALLET_USER_STATE, DISCONNECTED_WALLET_USER_STATE } from '../utils/user-state'
|
||||
|
||||
describe('Landing Page', () => {
|
||||
it('shows landing page when no user state exists', () => {
|
||||
cy.visit('/', { userState: {} })
|
||||
cy.visit('/', { userState: DISCONNECTED_WALLET_USER_STATE })
|
||||
cy.get(getTestSelector('landing-page'))
|
||||
cy.screenshot()
|
||||
})
|
||||
|
||||
@@ -28,15 +28,19 @@ describe('Permit2', () => {
|
||||
// check token approval
|
||||
cy.hardhat()
|
||||
.then(({ approval, wallet }) => approval.getTokenAllowanceForPermit2({ owner: wallet, token: inputToken }))
|
||||
.should('deep.equal', MaxUint256)
|
||||
.then((allowance) => {
|
||||
Cypress.log({ name: `Token allowace: ${allowance.toString()}` })
|
||||
cy.wrap(allowance).should('deep.equal', MaxUint256)
|
||||
})
|
||||
}
|
||||
|
||||
/** Asserts the universal router has a max permit2 approval for spend of the input token on-chain. */
|
||||
function expectPermit2AllowanceForUniversalRouterToBeMax(inputToken: Token) {
|
||||
cy.hardhat()
|
||||
.then((hardhat) => hardhat.approval.getPermit2Allowance({ owner: hardhat.wallet, token: inputToken }))
|
||||
.then(({ approval, wallet }) => approval.getPermit2Allowance({ owner: wallet, token: inputToken }))
|
||||
.then((allowance) => {
|
||||
cy.wrap(MaxUint160.eq(allowance.amount)).should('eq', true)
|
||||
Cypress.log({ name: `Permit2 allowace: ${allowance.amount.toString()}` })
|
||||
cy.wrap(allowance.amount).should('deep.equal', MaxUint160)
|
||||
// Asserts that the on-chain expiration is in 30 days, within a tolerance of 40 seconds.
|
||||
const THIRTY_DAYS_SECONDS = 2_592_000
|
||||
const expected = Math.floor(Date.now() / 1000 + THIRTY_DAYS_SECONDS)
|
||||
@@ -44,6 +48,13 @@ describe('Permit2', () => {
|
||||
})
|
||||
}
|
||||
|
||||
beforeEach(() =>
|
||||
cy.hardhat().then(async (hardhat) => {
|
||||
await hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(DAI, 1e18))
|
||||
await hardhat.mine()
|
||||
})
|
||||
)
|
||||
|
||||
describe('approval process (with intermediate screens)', () => {
|
||||
// Turn off automine so that intermediate screens are available to assert on.
|
||||
beforeEach(() => cy.hardhat({ automine: false }))
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { SupportedChainId } from '@uniswap/sdk-core'
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
|
||||
import { DEFAULT_DEADLINE_FROM_NOW } from '../../../src/constants/misc'
|
||||
import { UNI, USDC_MAINNET } from '../../../src/constants/tokens'
|
||||
import { getBalance, getTestSelector } from '../../utils'
|
||||
|
||||
const UNI_MAINNET = UNI[SupportedChainId.MAINNET]
|
||||
const UNI_MAINNET = UNI[ChainId.MAINNET]
|
||||
|
||||
describe('Swap errors', () => {
|
||||
it('wallet rejection', () => {
|
||||
@@ -41,7 +41,7 @@ describe('Swap errors', () => {
|
||||
cy.get('#swap-button').click()
|
||||
cy.contains('Confirm swap').click()
|
||||
cy.wait('@eth_estimateGas').wait('@eth_sendRawTransaction').wait('@eth_getTransactionReceipt')
|
||||
cy.contains('Transaction submitted')
|
||||
cy.contains('Swap submitted')
|
||||
cy.get(getTestSelector('confirmation-close-icon')).click()
|
||||
cy.get(getTestSelector('web3-status-connected')).should('contain', '1 Pending')
|
||||
|
||||
@@ -89,7 +89,7 @@ describe('Swap errors', () => {
|
||||
cy.get('#swap-button').click()
|
||||
cy.contains('Confirm swap').click()
|
||||
cy.wait('@eth_sendRawTransaction').wait('@eth_getTransactionReceipt')
|
||||
cy.contains('Transaction submitted')
|
||||
cy.contains('Swap submitted')
|
||||
cy.get(getTestSelector('confirmation-close-icon')).click()
|
||||
}
|
||||
cy.get(getTestSelector('web3-status-connected')).should('contain', '2 Pending')
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { FeatureFlag } from '../../../src/featureFlags'
|
||||
import { getTestSelector } from '../../utils'
|
||||
|
||||
describe('Swap settings', () => {
|
||||
it('Opens and closes the settings menu', () => {
|
||||
cy.visit('/swap')
|
||||
cy.visit('/swap', { featureFlags: [FeatureFlag.uniswapXEnabled], ethereum: 'hardhat' })
|
||||
cy.contains('Settings').should('not.exist')
|
||||
cy.get(getTestSelector('open-settings-dialog-button')).click()
|
||||
cy.contains('Max slippage').should('exist')
|
||||
cy.contains('Transaction deadline').should('exist')
|
||||
cy.contains('Auto Router API').should('exist')
|
||||
cy.contains('UniswapX').should('exist')
|
||||
cy.contains('Local routing').should('exist')
|
||||
cy.get(getTestSelector('open-settings-dialog-button')).click()
|
||||
cy.contains('Settings').should('not.exist')
|
||||
})
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { SupportedChainId } from '@uniswap/sdk-core'
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
|
||||
import { UNI, USDC_MAINNET } from '../../../src/constants/tokens'
|
||||
import { getBalance, getTestSelector } from '../../utils'
|
||||
|
||||
const UNI_MAINNET = UNI[SupportedChainId.MAINNET]
|
||||
const UNI_MAINNET = UNI[ChainId.MAINNET]
|
||||
|
||||
describe('Swap', () => {
|
||||
describe('Swap on main page', () => {
|
||||
@@ -69,9 +69,9 @@ describe('Swap', () => {
|
||||
cy.contains('Review swap')
|
||||
cy.contains('Confirm swap').click()
|
||||
cy.wait('@eth_estimateGas').wait('@eth_sendRawTransaction').wait('@eth_getTransactionReceipt')
|
||||
cy.contains('Transaction submitted')
|
||||
cy.contains('Swap submitted')
|
||||
cy.get(getTestSelector('confirmation-close-icon')).click()
|
||||
cy.contains('Transaction submitted').should('not.exist')
|
||||
cy.contains('Swap submitted').should('not.exist')
|
||||
cy.get(getTestSelector('web3-status-connected')).should('contain', '1 Pending')
|
||||
|
||||
// Mine transaction
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { CurrencyAmount, SupportedChainId, WETH9 } from '@uniswap/sdk-core'
|
||||
import { ChainId, CurrencyAmount, WETH9 } from '@uniswap/sdk-core'
|
||||
|
||||
import { getBalance, getTestSelector } from '../../utils'
|
||||
|
||||
const WETH = WETH9[SupportedChainId.MAINNET]
|
||||
const WETH = WETH9[ChainId.MAINNET]
|
||||
|
||||
describe('Swap wrap', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { SupportedChainId, WETH9 } from '@uniswap/sdk-core'
|
||||
import { ChainId, WETH9 } from '@uniswap/sdk-core'
|
||||
|
||||
import { UNI } from '../../src/constants/tokens'
|
||||
import { ARB, UNI } from '../../src/constants/tokens'
|
||||
import { getTestSelector } from '../utils'
|
||||
|
||||
const UNI_MAINNET = UNI[SupportedChainId.MAINNET]
|
||||
const UNI_MAINNET = UNI[ChainId.MAINNET]
|
||||
|
||||
const UNI_ADDRESS = '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984'
|
||||
|
||||
@@ -149,7 +149,7 @@ describe('Token details', () => {
|
||||
cy.get(getTestSelector('tokens-network-filter-selected')).click()
|
||||
cy.get(getTestSelector('tokens-network-filter-option-arbitrum')).click()
|
||||
cy.get(getTestSelector('tokens-network-filter-selected')).should('contain', 'Arbitrum')
|
||||
cy.get(getTestSelector('token-table-row-ARB')).click()
|
||||
cy.get(getTestSelector(`token-table-row-${ARB.address.toLowerCase()}`)).click()
|
||||
cy.get(`#swap-currency-output .token-symbol-container`).should('contain.text', 'ARB')
|
||||
cy.get(getTestSelector('open-settings-dialog-button')).should('be.disabled')
|
||||
cy.contains('Connect to Arbitrum').should('exist')
|
||||
|
||||
@@ -10,11 +10,11 @@ describe('Token explore', () => {
|
||||
cy.get(getTestSelectorStartsWith('token-table')).its('length').should('be.greaterThan', 0)
|
||||
// check sorted svg icon is present in volume cell, since tokens are sorted by volume by default
|
||||
cy.get(getTestSelector('header-row')).find(getTestSelector('volume-cell')).find('svg').should('exist')
|
||||
cy.get(getTestSelector('token-table-row-ETH')).find(getTestSelector('name-cell')).should('include.text', 'Ether')
|
||||
cy.get(getTestSelector('token-table-row-ETH')).find(getTestSelector('volume-cell')).should('include.text', '$')
|
||||
cy.get(getTestSelector('token-table-row-ETH')).find(getTestSelector('price-cell')).should('include.text', '$')
|
||||
cy.get(getTestSelector('token-table-row-ETH')).find(getTestSelector('tvl-cell')).should('include.text', '$')
|
||||
cy.get(getTestSelector('token-table-row-ETH'))
|
||||
cy.get(getTestSelector('token-table-row-NATIVE')).find(getTestSelector('name-cell')).should('include.text', 'Ether')
|
||||
cy.get(getTestSelector('token-table-row-NATIVE')).find(getTestSelector('volume-cell')).should('include.text', '$')
|
||||
cy.get(getTestSelector('token-table-row-NATIVE')).find(getTestSelector('price-cell')).should('include.text', '$')
|
||||
cy.get(getTestSelector('token-table-row-NATIVE')).find(getTestSelector('tvl-cell')).should('include.text', '$')
|
||||
cy.get(getTestSelector('token-table-row-NATIVE'))
|
||||
.find(getTestSelector('percent-change-cell'))
|
||||
.should('include.text', '%')
|
||||
cy.get(getTestSelector('header-row')).find(getTestSelector('price-cell')).click()
|
||||
@@ -24,14 +24,14 @@ describe('Token explore', () => {
|
||||
it('should update when time window toggled', () => {
|
||||
cy.visit('/tokens/ethereum')
|
||||
cy.get(getTestSelector('time-selector')).should('contain', '1D')
|
||||
cy.get(getTestSelector('token-table-row-ETH'))
|
||||
cy.get(getTestSelector('token-table-row-NATIVE'))
|
||||
.find(getTestSelector('volume-cell'))
|
||||
.then(function ($elem) {
|
||||
cy.wrap($elem.text()).as('dailyEthVol')
|
||||
})
|
||||
cy.get(getTestSelector('time-selector')).click()
|
||||
cy.get(getTestSelector('1Y')).click()
|
||||
cy.get(getTestSelector('token-table-row-ETH'))
|
||||
cy.get(getTestSelector('token-table-row-NATIVE'))
|
||||
.find(getTestSelector('volume-cell'))
|
||||
.then(function ($elem) {
|
||||
cy.wrap($elem.text()).as('yearlyEthVol')
|
||||
@@ -41,7 +41,7 @@ describe('Token explore', () => {
|
||||
|
||||
it('should navigate to token detail page when row clicked', () => {
|
||||
cy.visit('/tokens/ethereum')
|
||||
cy.get(getTestSelector('token-table-row-ETH')).click()
|
||||
cy.get(getTestSelector('token-table-row-NATIVE')).click()
|
||||
cy.get(getTestSelector('token-details-about-section')).should('exist')
|
||||
cy.get(getTestSelector('token-details-stats')).should('exist')
|
||||
cy.get(getTestSelector('token-info-container')).should('exist')
|
||||
@@ -53,13 +53,15 @@ describe('Token explore', () => {
|
||||
it('should update when global network changed', () => {
|
||||
cy.visit('/tokens/ethereum')
|
||||
cy.get(getTestSelector('tokens-network-filter-selected')).should('contain', 'Ethereum')
|
||||
cy.get(getTestSelector('token-table-row-ETH')).should('exist')
|
||||
cy.get(getTestSelector('token-table-row-NATIVE')).should('exist')
|
||||
|
||||
// note: cannot switch global chain via UI because we cannot approve the network switch
|
||||
// in metamask modal using plain cypress. this is a workaround.
|
||||
cy.visit('/tokens/polygon')
|
||||
cy.get(getTestSelector('tokens-network-filter-selected')).should('contain', 'Polygon')
|
||||
cy.get(getTestSelector('token-table-row-MATIC')).should('exist')
|
||||
cy.get(getTestSelector('token-table-row-NATIVE'))
|
||||
.find(getTestSelector('name-cell'))
|
||||
.should('include.text', 'Polygon Matic')
|
||||
})
|
||||
|
||||
it('should update when token explore table network changed', () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getTestSelector } from '../../utils'
|
||||
import { DISCONNECTED_WALLET_USER_STATE } from '../../utils/user-state'
|
||||
|
||||
describe('disconnect wallet', () => {
|
||||
it('should clear state', () => {
|
||||
@@ -27,7 +28,7 @@ describe('disconnect wallet', () => {
|
||||
|
||||
describe('connect wallet', () => {
|
||||
it('should load state', () => {
|
||||
cy.visit('/swap', { ethereum: 'hardhat', userState: {} })
|
||||
cy.visit('/swap', { ethereum: 'hardhat', userState: DISCONNECTED_WALLET_USER_STATE })
|
||||
|
||||
// Connect the wallet
|
||||
cy.get(getTestSelector('navbar-connect-wallet')).contains('Connect').click()
|
||||
|
||||
@@ -119,3 +119,12 @@ describe('network switching', () => {
|
||||
cy.get(`#swap-currency-output .token-symbol-container`).should('contain.text', 'Select token')
|
||||
})
|
||||
})
|
||||
|
||||
describe('network switching from URL param', () => {
|
||||
it('should switch network from URL param', () => {
|
||||
cy.visit('/swap?chain=polygon', { ethereum: 'hardhat' })
|
||||
cy.get(getTestSelector('web3-status-connected'))
|
||||
cy.wait('@wallet_switchEthereumChain')
|
||||
waitsForActiveChain('Polygon')
|
||||
})
|
||||
})
|
||||
|
||||
File diff suppressed because one or more lines are too long
19
cypress/staging/t9n.test.ts
Normal file
19
cypress/staging/t9n.test.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { getTestSelector } from '../utils'
|
||||
|
||||
describe('translations', () => {
|
||||
it('loads locale from the query param', () => {
|
||||
cy.visit('/?lng=fr-FR')
|
||||
cy.contains('Échanger')
|
||||
cy.contains('Uniswap disponible en : English')
|
||||
})
|
||||
|
||||
it('loads locale from menu', () => {
|
||||
cy.visit('/')
|
||||
cy.get(getTestSelector('web3-status-connected')).click()
|
||||
cy.get(getTestSelector('wallet-settings')).click()
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('français').click({ force: true })
|
||||
cy.location('hash').should('match', /\?lng=fr-FR$/)
|
||||
cy.contains('Échanger')
|
||||
cy.contains('Uniswap disponible en : English')
|
||||
})
|
||||
})
|
||||
@@ -58,7 +58,7 @@ Cypress.Commands.overwrite(
|
||||
// Set initial user state.
|
||||
win.localStorage.setItem(
|
||||
'redux_localstorage_simple_user', // storage key for the user reducer using 'redux-localstorage-simple'
|
||||
JSON.stringify(options?.userState ?? CONNECTED_WALLET_USER_STATE)
|
||||
JSON.stringify({ ...CONNECTED_WALLET_USER_STATE, ...(options?.userState ?? {}) })
|
||||
)
|
||||
|
||||
// Set feature flags, if configured.
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
*/
|
||||
|
||||
import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
import { JsonRpcProvider } from '@ethersproject/providers'
|
||||
import { Wallet } from '@ethersproject/wallet'
|
||||
|
||||
import { SupportedChainId } from '../../src/constants/chains'
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
|
||||
// todo: figure out how env vars actually work in CI
|
||||
// const TEST_PRIVATE_KEY = Cypress.env('INTEGRATION_TEST_PRIVATE_KEY')
|
||||
@@ -15,7 +13,7 @@ const TEST_PRIVATE_KEY = '0xe580410d7c37d26c6ad1a837bbae46bc27f9066a466fb3a66e77
|
||||
|
||||
// address of the above key
|
||||
const TEST_ADDRESS_NEVER_USE = new Wallet(TEST_PRIVATE_KEY).address
|
||||
const CHAIN_ID = SupportedChainId.GOERLI
|
||||
const CHAIN_ID = ChainId.GOERLI
|
||||
const HEXLIFIED_CHAIN_ID = `0x${CHAIN_ID.toString(16)}`
|
||||
|
||||
const provider = new JsonRpcProvider('https://goerli.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 5)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { UserState } from '../../src/state/user/reducer'
|
||||
|
||||
export const CONNECTED_WALLET_USER_STATE: Partial<UserState> = { selectedWallet: 'INJECTED' }
|
||||
|
||||
export const DISCONNECTED_WALLET_USER_STATE: Partial<UserState> = { selectedWallet: undefined }
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/* eslint-env node */
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description: 'enforce use of retry() for dynamic imports',
|
||||
category: 'Best Practices',
|
||||
recommended: false,
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
create(context) {
|
||||
return {
|
||||
ImportExpression(node) {
|
||||
const grandParent = node.parent.parent
|
||||
if (
|
||||
!(
|
||||
grandParent &&
|
||||
grandParent.type === 'CallExpression' &&
|
||||
// Technically, we are only checking that a function named `retry` wraps the dynamic import.
|
||||
// We do not go as far as enforcing that it is import('utils/retry').retry
|
||||
grandParent.callee.name === 'retry' &&
|
||||
grandParent.arguments.length === 1 &&
|
||||
grandParent.arguments[0].type === 'ArrowFunctionExpression'
|
||||
)
|
||||
) {
|
||||
context.report({
|
||||
node,
|
||||
message: 'Dynamic import should be wrapped in retry (see `utils/retry.ts`): `retry(() => import(...))`',
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
1
functions/babel.config.js
Normal file
1
functions/babel.config.js
Normal file
@@ -0,0 +1 @@
|
||||
export const presets = ['@babel/preset-env']
|
||||
9
functions/global-setup.ts
Normal file
9
functions/global-setup.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { setup } from 'jest-dev-server'
|
||||
|
||||
module.exports = async function globalSetup() {
|
||||
globalThis.servers = await setup({
|
||||
command: `yarn start:cloud`,
|
||||
port: 3000,
|
||||
launchTimeout: 50000,
|
||||
})
|
||||
}
|
||||
5
functions/global-teardown.ts
Normal file
5
functions/global-teardown.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { teardown } from 'jest-dev-server'
|
||||
|
||||
module.exports = async function globalTeardown() {
|
||||
await teardown(globalThis.servers)
|
||||
}
|
||||
6
functions/global.d.ts
vendored
Normal file
6
functions/global.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import { setup } from 'jest-dev-server'
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var servers: Awaited<ReturnType<typeof setup>>
|
||||
}
|
||||
9
functions/jest.config.json
Normal file
9
functions/jest.config.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"globalSetup": "<rootDir>/global-setup.ts",
|
||||
"globalTeardown": "<rootDir>/global-teardown.ts",
|
||||
"preset": "ts-jest",
|
||||
"transform": {
|
||||
"'^.+\\.(ts|tsx)?$'": "ts-jest",
|
||||
"^.+\\.(js|jsx)$": "babel-jest"
|
||||
}
|
||||
}
|
||||
3
functions/nft.test.ts
Normal file
3
functions/nft.test.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
test('example', async () => {
|
||||
expect(true).toBe(true)
|
||||
})
|
||||
19
functions/tsconfig.json
Normal file
19
functions/tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"incremental": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"target": "ES6",
|
||||
"tsBuildInfoFile": "../node_modules/.cache/tsbuildinfo/functions", // avoid clobbering the build tsbuildinfo
|
||||
"types": ["jest", "node"],
|
||||
"jsx": "react",
|
||||
"moduleResolution": "NodeNext",
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["**/*.ts"],
|
||||
"watchOptions": {
|
||||
"excludeDirectories": ["node_modules"]
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ const config: CodegenConfig = {
|
||||
withHooks: true,
|
||||
// This avoid all generated schemas being wrapped in Maybe https://the-guild.dev/graphql/codegen/plugins/typescript/typescript#maybevalue-string-default-value-t--null
|
||||
maybeValue: 'T',
|
||||
immutableTypes: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SupportedChainId } from '@uniswap/sdk-core'
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
|
||||
/* eslint-env node */
|
||||
require('dotenv').config()
|
||||
@@ -6,7 +6,7 @@ require('dotenv').config()
|
||||
// Block selection is arbitrary, as e2e tests will build up their own state.
|
||||
// The only requirement is that all infrastructure under test (eg Permit2 contracts) are already deployed.
|
||||
// TODO(WEB-2187): Make more dynamic to avoid manually updating
|
||||
const BLOCK_NUMBER = 17388567
|
||||
const BLOCK_NUMBER = 17693163
|
||||
const POLYGON_BLOCK_NUMBER = 43600000
|
||||
|
||||
const forkingConfig = {
|
||||
@@ -16,12 +16,12 @@ const forkingConfig = {
|
||||
}
|
||||
|
||||
const forks = {
|
||||
[SupportedChainId.MAINNET]: {
|
||||
[ChainId.MAINNET]: {
|
||||
url: `https://mainnet.infura.io/v3/${process.env.REACT_APP_INFURA_KEY}`,
|
||||
blockNumber: BLOCK_NUMBER,
|
||||
...forkingConfig,
|
||||
},
|
||||
[SupportedChainId.POLYGON]: {
|
||||
[ChainId.POLYGON]: {
|
||||
url: `https://polygon-mainnet.infura.io/v3/${process.env.REACT_APP_INFURA_KEY}`,
|
||||
blockNumber: POLYGON_BLOCK_NUMBER,
|
||||
...forkingConfig,
|
||||
@@ -32,8 +32,8 @@ module.exports = {
|
||||
forks,
|
||||
networks: {
|
||||
hardhat: {
|
||||
chainId: SupportedChainId.MAINNET,
|
||||
forking: forks[SupportedChainId.MAINNET],
|
||||
chainId: ChainId.MAINNET,
|
||||
forking: forks[ChainId.MAINNET],
|
||||
accounts: {
|
||||
count: 2,
|
||||
},
|
||||
|
||||
35
package.json
35
package.json
@@ -19,6 +19,7 @@
|
||||
"i18n": "yarn i18n:extract --clean && yarn i18n:compile",
|
||||
"prepare": "concurrently \"npm:ajv\" \"npm:contracts\" \"npm:graphql\" \"npm:i18n\"",
|
||||
"start": "craco start",
|
||||
"start:cloud": "NODE_OPTIONS=--dns-result-order=ipv4first PORT=3001 npx wrangler pages dev --proxy=3001 --port=3000 -- yarn start",
|
||||
"build": "craco build",
|
||||
"build:e2e": "REACT_APP_CSP_ALLOW_UNSAFE_EVAL=true REACT_APP_ADD_COVERAGE_INSTRUMENTATION=true craco build",
|
||||
"analyze": "source-map-explorer 'build/static/js/*.js' --only-mapped",
|
||||
@@ -26,6 +27,7 @@
|
||||
"lint": "yarn eslint --ignore-path .gitignore --cache --cache-location node_modules/.cache/eslint/ .",
|
||||
"typecheck": "tsc",
|
||||
"test": "craco test",
|
||||
"test:cloud": "NODE_OPTIONS=--experimental-vm-modules yarn jest functions --watch --config=functions/jest.config.json",
|
||||
"cypress:open": "cypress open --browser chrome --e2e",
|
||||
"cypress:run": "cypress run --browser chrome --e2e",
|
||||
"deduplicate": "yarn-deduplicate --strategy=highest"
|
||||
@@ -66,6 +68,8 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.22.7",
|
||||
"@cloudflare/workers-types": "^4.20230518.0",
|
||||
"@craco/craco": "^7.1.0",
|
||||
"@ethersproject/experimental": "^5.4.0",
|
||||
"@lingui/cli": "^3.9.0",
|
||||
@@ -97,36 +101,42 @@
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/wcag-contrast": "^3.0.0",
|
||||
"@uniswap/default-token-list": "^9.6.0",
|
||||
"@uniswap/default-token-list": "^11.2.0",
|
||||
"@uniswap/eslint-config": "^1.2.0",
|
||||
"@vanilla-extract/babel-plugin": "^1.1.7",
|
||||
"@vanilla-extract/jest-transform": "^1.1.1",
|
||||
"@vanilla-extract/webpack-plugin": "^2.1.11",
|
||||
"@walletconnect/types": "^2.8.6",
|
||||
"babel-jest": "^29.6.1",
|
||||
"babel-plugin-istanbul": "^6.1.1",
|
||||
"buffer": "^6.0.3",
|
||||
"concurrently": "^8.0.1",
|
||||
"cypress": "12.12.0",
|
||||
"cypress-hardhat": "^2.4.1",
|
||||
"cypress-hardhat": "^2.5.0",
|
||||
"env-cmd": "^10.1.0",
|
||||
"eslint": "^7.11.0",
|
||||
"eslint-plugin-import": "^2.27",
|
||||
"eslint-plugin-rulesdir": "^0.2.2",
|
||||
"hardhat": "^2.14.0",
|
||||
"jest": "^29.6.1",
|
||||
"jest-dev-server": "^9.0.0",
|
||||
"jest-fail-on-console": "^3.1.1",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"jest-styled-components": "^7.0.8",
|
||||
"ms.macro": "^2.0.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier": "^2.8.8",
|
||||
"process": "^0.11.10",
|
||||
"react-scripts": "^5.0.1",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"serve": "^11.3.2",
|
||||
"source-map-explorer": "^2.5.3",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-transform-graphql-tag": "^0.2.1",
|
||||
"typechain": "^5.0.0",
|
||||
"typescript": "^4.4.3",
|
||||
"webpack-retry-chunk-load-plugin": "^3.1.1",
|
||||
"wrangler": "https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/4925945367/npm-package-wrangler-3048",
|
||||
"yarn-deduplicate": "^6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -158,23 +168,24 @@
|
||||
"@types/react-window-infinite-loader": "^1.0.6",
|
||||
"@uniswap/analytics": "^1.3.1",
|
||||
"@uniswap/analytics-events": "^2.13.0",
|
||||
"@uniswap/conedison": "^1.7.1",
|
||||
"@uniswap/conedison": "^1.8.0",
|
||||
"@uniswap/governance": "^1.0.2",
|
||||
"@uniswap/liquidity-staker": "^1.0.2",
|
||||
"@uniswap/merkle-distributor": "1.0.1",
|
||||
"@uniswap/permit2-sdk": "1.2.0",
|
||||
"@uniswap/redux-multicall": "^1.1.8",
|
||||
"@uniswap/router-sdk": "^1.3.0",
|
||||
"@uniswap/sdk-core": "^3.2.3",
|
||||
"@uniswap/smart-order-router": "^3.12.1",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.31",
|
||||
"@uniswap/universal-router-sdk": "^1.5.1",
|
||||
"@uniswap/router-sdk": "^1.6.0",
|
||||
"@uniswap/sdk-core": "^4.0.3",
|
||||
"@uniswap/smart-order-router": "3.13.7",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.33",
|
||||
"@uniswap/uniswapx-sdk": "^1.0.1",
|
||||
"@uniswap/universal-router-sdk": "^1.5.4",
|
||||
"@uniswap/v2-core": "1.0.0",
|
||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||
"@uniswap/v2-sdk": "^3.0.1",
|
||||
"@uniswap/v2-sdk": "^3.2.0",
|
||||
"@uniswap/v3-core": "1.0.0",
|
||||
"@uniswap/v3-periphery": "^1.1.1",
|
||||
"@uniswap/v3-sdk": "^3.9.0",
|
||||
"@uniswap/v3-sdk": "^3.10.0",
|
||||
"@vanilla-extract/css": "^1.7.2",
|
||||
"@vanilla-extract/css-utils": "^0.1.2",
|
||||
"@vanilla-extract/dynamic": "^2.0.2",
|
||||
@@ -186,7 +197,6 @@
|
||||
"@visx/react-spring": "^2.12.2",
|
||||
"@visx/responsive": "^2.10.0",
|
||||
"@visx/shape": "^2.11.1",
|
||||
"@walletconnect/modal": "^2.5.4",
|
||||
"@web3-react/coinbase-wallet": "^8.2.0",
|
||||
"@web3-react/core": "^8.2.0",
|
||||
"@web3-react/eip1193": "^8.2.0",
|
||||
@@ -196,7 +206,6 @@
|
||||
"@web3-react/network": "^8.2.0",
|
||||
"@web3-react/types": "^8.2.0",
|
||||
"@web3-react/url": "^8.2.0",
|
||||
"@web3-react/walletconnect": "^8.2.0",
|
||||
"@web3-react/walletconnect-v2": "^8.3.7",
|
||||
"ajv": "^8.11.0",
|
||||
"ajv-formats": "^2.1.1",
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
<% if (process.env.REACT_APP_CSP_ALLOW_UNSAFE_EVAL) { %>
|
||||
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline' 'unsafe-eval'"
|
||||
content="script-src 'self' 'unsafe-inline' 'unsafe-eval'"
|
||||
<% } else { %>
|
||||
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
|
||||
content="script-src 'self' 'unsafe-inline'"
|
||||
<% } %>
|
||||
/>
|
||||
|
||||
@@ -37,8 +37,6 @@
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
|
||||
<link rel="preconnect" href="https://www.google-analytics.com/" />
|
||||
|
||||
<link rel="preload" href="%PUBLIC_URL%/fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
|
||||
11
src/assets/svg/avax_logo.svg
Normal file
11
src/assets/svg/avax_logo.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_13871_12533)">
|
||||
<path d="M12.9341 2.74118H3.05524V11.7259H12.9341V2.74118Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.9947 8C15.9947 12.4154 12.4154 15.9947 7.99999 15.9947C3.58465 15.9947 0.00531006 12.4154 0.00531006 8C0.00531006 3.58466 3.58465 0.00532532 7.99999 0.00532532C12.4154 0.00532532 15.9947 3.58466 15.9947 8ZM5.73452 11.1815H4.18298C3.85696 11.1815 3.69591 11.1815 3.59772 11.1187C3.49166 11.0499 3.42685 10.936 3.41899 10.8103C3.4131 10.6945 3.49363 10.553 3.65467 10.2702L7.48562 3.51761C7.64864 3.23086 7.73112 3.08749 7.83521 3.03447C7.94715 2.97751 8.08071 2.97751 8.19266 3.03447C8.29675 3.08749 8.37924 3.23086 8.54224 3.51761L9.32981 4.89239L9.33382 4.89941C9.50989 5.20703 9.59917 5.36303 9.63815 5.52675C9.68135 5.70548 9.68135 5.89402 9.63815 6.07274C9.59887 6.23771 9.51049 6.39484 9.33177 6.70711L7.31946 10.2643L7.31426 10.2734C7.13703 10.5836 7.04722 10.7408 6.92274 10.8594C6.78722 10.989 6.62421 11.0832 6.44549 11.1363C6.28247 11.1815 6.09983 11.1815 5.73452 11.1815ZM9.65268 11.1815H11.8759C12.2038 11.1815 12.3689 11.1815 12.4671 11.1168C12.5731 11.048 12.6399 10.9321 12.6458 10.8064C12.6515 10.6943 12.5727 10.5584 12.4184 10.292C12.413 10.283 12.4077 10.2737 12.4023 10.2643L11.2887 8.35928L11.276 8.33783C11.1195 8.07321 11.0405 7.93958 10.9391 7.88793C10.8272 7.83096 10.6955 7.83096 10.5836 7.88793C10.4815 7.94095 10.399 8.0804 10.236 8.36124L9.12633 10.2663L9.12253 10.2729C8.96009 10.5533 8.87891 10.6934 8.88477 10.8084C8.89262 10.9341 8.95743 11.0499 9.06348 11.1187C9.15973 11.1815 9.3247 11.1815 9.65268 11.1815Z" fill="#E84142"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_13871_12533">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
11
src/assets/svg/avax_square_logo.svg
Normal file
11
src/assets/svg/avax_square_logo.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_298_130193)">
|
||||
<path d="M12.9346 2.74121H3.05566V11.7259H12.9346V2.74121Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.9998 0C15.9998 4.41538 15.9998 15.9947 15.9998 15.9947C11.5844 15.9947 0.00590861 15.9947 0.00590861 15.9947L0.00488281 5.43594e-05C4.42027 5.43594e-05 15.9998 0 15.9998 0ZM5.73493 11.1815H4.18339C3.85736 11.1815 3.69632 11.1815 3.59813 11.1187C3.49207 11.0499 3.42726 10.936 3.4194 10.8103C3.41351 10.6945 3.49404 10.553 3.65508 10.2702L7.48603 3.51765C7.64905 3.2309 7.73153 3.08753 7.83562 3.03451C7.94756 2.97755 8.08112 2.97755 8.19307 3.03451C8.29716 3.08753 8.37965 3.2309 8.54265 3.51765L9.33022 4.89243L9.33423 4.89945C9.51029 5.20707 9.59958 5.36307 9.63856 5.52679C9.68176 5.70552 9.68176 5.89406 9.63856 6.07278C9.59928 6.23775 9.5109 6.39488 9.33218 6.70715L7.31987 10.2643L7.31466 10.2734C7.13744 10.5836 7.04762 10.7408 6.92315 10.8594C6.78763 10.9891 6.62462 11.0833 6.44589 11.1364C6.28288 11.1815 6.10024 11.1815 5.73493 11.1815ZM9.65309 11.1815H11.8763C12.2043 11.1815 12.3693 11.1815 12.4675 11.1168C12.5735 11.048 12.6403 10.9321 12.6463 10.8065C12.6519 10.6944 12.5731 10.5584 12.4188 10.2921C12.4134 10.283 12.4081 10.2738 12.4027 10.2644L11.2891 8.35932L11.2764 8.33787C11.1199 8.07325 11.0409 7.93962 10.9395 7.88797C10.8276 7.831 10.6959 7.831 10.584 7.88797C10.4819 7.94099 10.3994 8.08044 10.2364 8.36128L9.12674 10.2663L9.12294 10.2729C8.9605 10.5533 8.87932 10.6934 8.88518 10.8084C8.89303 10.9341 8.95784 11.0499 9.06389 11.1187C9.16014 11.1815 9.32511 11.1815 9.65309 11.1815Z" fill="#E84142"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_298_130193">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
9
src/assets/svg/bolt.svg
Normal file
9
src/assets/svg/bolt.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="10" height="14" viewBox="0 0 10 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.97119 6.19815C9.91786 6.07749 9.79854 6.00016 9.66654 6.00016H6.66654V1.00016C6.66654 0.862156 6.58189 0.738159 6.45255 0.688826C6.32255 0.638826 6.17787 0.674818 6.0852 0.776818L0.0852016 7.44349C-0.00279838 7.54149 -0.025439 7.68149 0.028561 7.80216C0.0818943 7.92283 0.201208 8.00016 0.333208 8.00016H3.33321V13.0002C3.33321 13.1382 3.41786 13.2622 3.5472 13.3115C3.58653 13.3262 3.62654 13.3335 3.66654 13.3335C3.75921 13.3335 3.84988 13.2948 3.91455 13.2228L9.91455 6.55616C10.0025 6.45882 10.0245 6.31815 9.97119 6.19815Z" fill="url(#paint0_linear_1816_1801)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_1816_1801" x1="-10.1808" y1="-12.0005" x2="10.6572" y2="-11.6015" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4673FA"/>
|
||||
<stop offset="1" stop-color="#9646FA"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 917 B |
37
src/assets/svg/uniswapx_error.svg
Normal file
37
src/assets/svg/uniswapx_error.svg
Normal file
@@ -0,0 +1,37 @@
|
||||
<svg width="74" height="72" viewBox="0 0 74 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M24.3747 21.5043C24.671 21.5043 24.9274 21.7105 24.991 21.9998C25.1664 22.7982 25.1791 24.1098 24.6141 25.1601C24.322 25.703 23.8683 26.1865 23.2031 26.4658C22.5432 26.7429 21.7375 26.7931 20.7823 26.5871C19.5609 26.3237 18.3363 25.6271 17.1951 24.7335C16.0477 23.8349 14.9472 22.7077 13.9724 21.5319C12.9967 20.3551 12.137 19.1177 11.4739 17.9903C10.8173 16.8739 10.3306 15.8269 10.132 15.0366C10.059 14.7459 10.2005 14.444 10.4706 14.3141C10.7407 14.1843 11.0649 14.2624 11.2463 14.501C12.087 15.6072 14.0213 17.3714 16.4539 18.8598C18.8877 20.3489 21.7272 21.5043 24.3747 21.5043Z" fill="white" stroke="black" stroke-width="1.26191" stroke-linejoin="round"/>
|
||||
<mask id="path-2-outside-1_1930_115608" maskUnits="userSpaceOnUse" x="0.0805664" y="18.0469" width="56" height="43" fill="black">
|
||||
<rect fill="white" x="0.0805664" y="18.0469" width="56" height="43"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.4887 52.6749C51.8537 49.2159 53.8899 44.7033 53.8899 39.7671C53.8899 28.8759 43.9775 20.0469 31.7499 20.0469C19.5224 20.0469 9.60998 28.8759 9.60998 39.7671C9.60998 40.805 9.69999 41.8241 9.87343 42.8186C5.25341 44.2149 2.08057 47.1039 2.08057 50.4413C2.08057 55.1444 8.38116 58.9569 16.1533 58.9569C18.6361 58.9569 20.9687 58.5679 22.9937 57.885C25.6793 58.9161 28.6397 59.4874 31.7499 59.4874C38.4356 59.4874 44.4292 56.8479 48.4887 52.6749Z"/>
|
||||
</mask>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.4887 52.6749C51.8537 49.2159 53.8899 44.7033 53.8899 39.7671C53.8899 28.8759 43.9775 20.0469 31.7499 20.0469C19.5224 20.0469 9.60998 28.8759 9.60998 39.7671C9.60998 40.805 9.69999 41.8241 9.87343 42.8186C5.25341 44.2149 2.08057 47.1039 2.08057 50.4413C2.08057 55.1444 8.38116 58.9569 16.1533 58.9569C18.6361 58.9569 20.9687 58.5679 22.9937 57.885C25.6793 58.9161 28.6397 59.4874 31.7499 59.4874C38.4356 59.4874 44.4292 56.8479 48.4887 52.6749Z" fill="white"/>
|
||||
<path d="M48.4887 52.6749L49.3932 53.5549L48.4887 52.6749ZM9.87343 42.8186L10.2385 44.0265L11.3086 43.7031L11.1166 42.6018L9.87343 42.8186ZM22.9937 57.885L23.446 56.707L23.0214 56.544L22.5905 56.6893L22.9937 57.885ZM52.628 39.7671C52.628 44.3432 50.743 48.548 47.5842 51.795L49.3932 53.5549C52.9645 49.8838 55.1518 45.0634 55.1518 39.7671H52.628ZM31.7499 21.3088C43.4219 21.3088 52.628 29.7062 52.628 39.7671H55.1518C55.1518 28.0457 44.5332 18.785 31.7499 18.785V21.3088ZM10.8719 39.7671C10.8719 29.7062 20.078 21.3088 31.7499 21.3088V18.785C18.9667 18.785 8.34807 28.0457 8.34807 39.7671H10.8719ZM11.1166 42.6018C10.9555 41.6784 10.8719 40.7318 10.8719 39.7671H8.34807C8.34807 40.8781 8.44444 41.9697 8.63029 43.0354L11.1166 42.6018ZM3.34248 50.4413C3.34248 47.9707 5.77784 45.3747 10.2385 44.0265L9.50835 41.6106C4.72899 43.0551 0.818657 46.2371 0.818657 50.4413H3.34248ZM16.1533 57.695C12.4585 57.695 9.17438 56.7862 6.85568 55.3831C4.5135 53.9658 3.34248 52.1807 3.34248 50.4413H0.818657C0.818657 53.405 2.79793 55.8776 5.54909 57.5424C8.32373 59.2214 12.076 60.2188 16.1533 60.2188V57.695ZM22.5905 56.6893C20.7028 57.3258 18.5071 57.695 16.1533 57.695V60.2188C18.7651 60.2188 21.2345 59.8099 23.3969 59.0808L22.5905 56.6893ZM31.7499 58.2255C28.7946 58.2255 25.9875 57.6827 23.446 56.707L22.5414 59.0631C25.3711 60.1496 28.4849 60.7493 31.7499 60.7493V58.2255ZM47.5842 51.795C43.7692 55.7165 38.1051 58.2255 31.7499 58.2255V60.7493C38.7662 60.7493 45.0891 57.9792 49.3932 53.5549L47.5842 51.795Z" fill="black" mask="url(#path-2-outside-1_1930_115608)"/>
|
||||
<path d="M39.0719 21.5043C38.7762 21.5043 38.5201 21.7097 38.456 21.9984C38.2783 22.7979 38.2654 24.1112 38.8376 25.1627C39.1334 25.7063 39.592 26.1886 40.2622 26.4668C40.9266 26.7426 41.7385 26.793 42.7026 26.5874C43.9355 26.3246 45.1727 25.6291 46.3268 24.7356C47.487 23.8374 48.6 22.7104 49.586 21.5347C50.5728 20.3579 51.4423 19.1205 52.1131 17.993C52.7773 16.8766 53.2698 15.8293 53.4709 15.0382C53.5446 14.7481 53.4043 14.4461 53.135 14.3153C52.8657 14.1845 52.5416 14.2609 52.3592 14.4982C51.5085 15.6046 49.5517 17.3692 47.0903 18.8581C44.6282 20.3475 41.7536 21.5043 39.0719 21.5043Z" fill="white" stroke="black" stroke-width="1.26191" stroke-linejoin="round"/>
|
||||
<path d="M9.27317 46.8448L9.50259 46.4782C9.93019 45.7949 9.38096 45.0323 8.64133 45.3527C7.81405 45.7111 6.82196 46.2689 5.87019 47.1255C5.44297 47.51 5.12141 47.8905 4.88077 48.2545C3.88061 49.7674 5.42411 51.2381 7.23768 51.2541C8.19162 51.2626 9.28694 51.2174 10.3799 51.0555C11.8292 50.8408 13.0434 50.1513 13.916 49.4901C14.561 49.0012 14.1621 48.1301 13.3534 48.1626L10.1134 48.2925C9.35325 48.323 8.8696 47.4897 9.27317 46.8448Z" fill="black"/>
|
||||
<path d="M22.3604 55.8761C23.8648 55.9852 25.2756 55.8261 26.3472 55.4561C26.8819 55.2714 27.362 55.024 27.7263 54.7048C28.0935 54.3831 28.3749 53.9571 28.4127 53.4357C28.4505 52.9142 28.2335 52.4521 27.9166 52.0807C27.6022 51.7123 27.1628 51.3982 26.6604 51.1384C25.6534 50.6176 24.2803 50.2565 22.7758 50.1474C21.2714 50.0383 19.8606 50.1974 18.789 50.5675C18.2544 50.7521 17.7742 50.9995 17.4099 51.3187C17.0428 51.6404 16.7613 52.0665 16.7235 52.5879C16.6857 53.1093 16.9027 53.5714 17.2196 53.9428C17.534 54.3112 17.9735 54.6253 18.4759 54.8852C19.4829 55.4059 20.8559 55.767 22.3604 55.8761Z" fill="white" stroke="black" stroke-width="1.26191"/>
|
||||
<line y1="-0.630955" x2="10.458" y2="-0.630955" transform="matrix(0.99738 0.0723377 -0.0723344 0.99738 17.2944 53.4453)" stroke="black" stroke-width="1.26191"/>
|
||||
<line y1="-0.630955" x2="5.19898" y2="-0.630955" transform="matrix(-0.0723344 0.99738 -0.99738 -0.0723377 23.6921 50.0703)" stroke="black" stroke-width="1.26191"/>
|
||||
<line y1="-0.630955" x2="5.19898" y2="-0.630955" transform="matrix(-0.0723344 0.99738 -0.99738 -0.0723377 19.9366 50.5742)" stroke="black" stroke-width="1.26191"/>
|
||||
<path d="M23.6113 12.1879C23.4573 11.9346 23.1478 11.8226 22.8674 11.9188C22.587 12.015 22.4114 12.2934 22.4453 12.5878L23.4514 21.3197C23.618 22.7653 24.9556 23.7808 26.3928 23.5526C28.1885 23.2675 29.1214 21.2523 28.1769 19.6986L23.6113 12.1879Z" fill="url(#paint0_linear_1930_115608)" stroke="black" stroke-width="1.26191" stroke-linejoin="round"/>
|
||||
<mask id="path-11-outside-2_1930_115608" maskUnits="userSpaceOnUse" x="53.3934" y="34.8259" width="22.6706" height="24.8274" fill="black">
|
||||
<rect fill="white" x="53.3934" y="34.8259" width="22.6706" height="24.8274"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M57.6261 54.012C57.135 53.9721 56.7516 53.8134 56.5049 53.5249C55.2906 52.1043 57.8521 48.0728 62.2261 44.5205C66.6002 40.9681 71.1305 39.24 72.3449 40.6606C73.078 41.5183 72.435 43.3274 70.8479 45.4082C71.665 45.4935 72.234 45.7574 72.4517 46.2109C73.1324 47.6291 70.1196 50.3641 65.7223 52.3195C62.4196 53.7883 59.2752 54.3996 57.6261 54.012Z"/>
|
||||
</mask>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M57.6261 54.012C57.135 53.9721 56.7516 53.8134 56.5049 53.5249C55.2906 52.1043 57.8521 48.0728 62.2261 44.5205C66.6002 40.9681 71.1305 39.24 72.3449 40.6606C73.078 41.5183 72.435 43.3274 70.8479 45.4082C71.665 45.4935 72.234 45.7574 72.4517 46.2109C73.1324 47.6291 70.1196 50.3641 65.7223 52.3195C62.4196 53.7883 59.2752 54.3996 57.6261 54.012Z" fill="url(#paint1_linear_1930_115608)"/>
|
||||
<path d="M57.6261 54.012L57.9148 52.7835C57.8534 52.7691 57.791 52.7593 57.7282 52.7542L57.6261 54.012ZM56.5049 53.5249L55.5457 54.3449L55.5457 54.3449L56.5049 53.5249ZM62.2261 44.5205L61.4306 43.5409L62.2261 44.5205ZM72.3449 40.6606L71.3857 41.4806L71.3857 41.4806L72.3449 40.6606ZM70.8479 45.4082L69.8445 44.6428C69.5678 45.0056 69.5085 45.4895 69.6893 45.9084C69.8702 46.3272 70.2631 46.6159 70.7169 46.6633L70.8479 45.4082ZM72.4517 46.2109L71.314 46.757L72.4517 46.2109ZM65.7223 52.3195L66.2351 53.4726L65.7223 52.3195ZM57.7282 52.7542C57.5875 52.7428 57.5078 52.7177 57.4715 52.7021C57.44 52.6886 57.4475 52.6855 57.4642 52.705L55.5457 54.3449C56.0816 54.9718 56.8316 55.2135 57.524 55.2697L57.7282 52.7542ZM57.4642 52.705C57.5255 52.7768 57.4073 52.7335 57.5045 52.2864C57.5986 51.8541 57.859 51.248 58.335 50.4988C59.279 49.0131 60.9099 47.2151 63.0217 45.5L61.4306 43.5409C59.1683 45.3782 57.3314 47.3721 56.2048 49.1453C55.6454 50.0257 55.2198 50.9159 55.0384 51.75C54.8602 52.5693 54.8772 53.5627 55.5457 54.3449L57.4642 52.705ZM63.0217 45.5C65.1329 43.7855 67.244 42.5441 68.9202 41.9047C69.7645 41.5827 70.4326 41.4388 70.9004 41.4263C71.3906 41.4132 71.4384 41.5422 71.3857 41.4806L73.3041 39.8407C72.6442 39.0687 71.6703 38.881 70.8329 38.9034C69.973 38.9264 69.0075 39.1702 68.0207 39.5466C66.0346 40.3042 63.6935 41.7031 61.4306 43.5409L63.0217 45.5ZM71.3857 41.4806C71.3132 41.3958 71.4919 41.4945 71.2668 42.1842C71.0603 42.8169 70.5933 43.6612 69.8445 44.6428L71.8512 46.1735C72.6896 45.0744 73.3376 43.9737 73.6661 42.9673C73.976 42.0178 74.1096 40.7831 73.3041 39.8407L71.3857 41.4806ZM70.7169 46.6633C71.0333 46.6963 71.2227 46.7572 71.3185 46.8042C71.4057 46.8471 71.3576 46.8477 71.314 46.757L73.5893 45.6649C73.0698 44.5825 71.8893 44.2481 70.9789 44.1531L70.7169 46.6633ZM71.314 46.757C71.2372 46.5968 71.3566 46.582 71.1885 46.902C71.0285 47.2068 70.6953 47.6281 70.1431 48.1294C69.05 49.1216 67.321 50.2275 65.2095 51.1665L66.2351 53.4726C68.5208 52.4561 70.4969 51.2168 71.8394 49.9981C72.5049 49.394 73.0722 48.7434 73.423 48.0755C73.7658 47.4227 74.0066 46.5342 73.5893 45.6649L71.314 46.757ZM65.2095 51.1665C63.6302 51.8688 62.1053 52.3583 60.8007 52.6257C59.4622 52.9 58.4759 52.9154 57.9148 52.7835L57.3374 55.2404C58.4254 55.4961 59.8358 55.3997 61.3075 55.0981C62.813 54.7896 64.5117 54.239 66.2351 53.4726L65.2095 51.1665Z" fill="black" mask="url(#path-11-outside-2_1930_115608)"/>
|
||||
<ellipse cx="14.8446" cy="37.3595" rx="3.31248" ry="4.96883" fill="black"/>
|
||||
<ellipse cx="14.9235" cy="37.3605" rx="1.4985" ry="2.60272" fill="white"/>
|
||||
<ellipse cx="27.7795" cy="37.3595" rx="3.31248" ry="4.96883" fill="black"/>
|
||||
<ellipse cx="27.8581" cy="37.3605" rx="1.4985" ry="2.60272" fill="white"/>
|
||||
<path d="M34.2463 54.3156C34.2463 51.2381 35.3257 47.5016 37.9302 45.6216C40.1969 43.9853 43.0541 43.828 45.7611 44.284" stroke="black" stroke-width="1.26191"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_1930_115608" x1="27.4989" y1="4.1016" x2="19.0152" y2="20.2113" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF24A7"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_1930_115608" x1="66.7203" y1="38.9751" x2="63.2492" y2="55.4808" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF24A7"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 10 KiB |
@@ -1,24 +0,0 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_13571_129912)">
|
||||
<rect width="40" height="40" fill="url(#paint0_linear_13571_129912)"/>
|
||||
<path d="M20 40C31.0457 40 40 31.0457 40 20C40 8.9543 31.0457 0 20 0C8.9543 0 0 8.9543 0 20C0 31.0457 8.9543 40 20 40Z" fill="url(#paint1_linear_13571_129912)"/>
|
||||
<path d="M34.5575 20.2857H30.9819C30.9819 13.0516 25.0541 7.1875 17.7414 7.1875C10.5191 7.1875 4.64737 12.908 4.50368 20.0182C4.35499 27.3678 11.3253 33.75 18.7558 33.75H19.6904C26.2413 33.75 35.0216 28.6771 36.3934 22.4961C36.6469 21.3567 35.7369 20.2857 34.5575 20.2857ZM12.4278 20.6079C12.4278 21.5753 11.628 22.3665 10.6501 22.3665C9.67215 22.3665 8.87237 21.575 8.87237 20.6079V17.7629C8.87237 16.7955 9.67215 16.0043 10.6501 16.0043C11.628 16.0043 12.4278 16.7955 12.4278 17.7629V20.6079ZM18.6007 20.6079C18.6007 21.5753 17.801 22.3665 16.8231 22.3665C15.8451 22.3665 15.0453 21.575 15.0453 20.6079V17.7629C15.0453 16.7955 15.8455 16.0043 16.8231 16.0043C17.801 16.0043 18.6007 16.7955 18.6007 17.7629V20.6079Z" fill="url(#paint2_linear_13571_129912)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_13571_129912" x1="20" y1="0" x2="20" y2="40" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#534BB1"/>
|
||||
<stop offset="1" stop-color="#551BF9"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_13571_129912" x1="20" y1="0" x2="20" y2="40" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#534BB1"/>
|
||||
<stop offset="1" stop-color="#551BF9"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_13571_129912" x1="20.4687" y1="7.1875" x2="20.4687" y2="33.75" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.82"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_13571_129912">
|
||||
<rect width="40" height="40" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -1,5 +1,6 @@
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events'
|
||||
import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes'
|
||||
import styled from 'styled-components/macro'
|
||||
import { BREAKPOINTS, ExternalLink, StyledRouterLink } from 'theme'
|
||||
import { useIsDarkMode } from 'theme/components/ThemeToggle'
|
||||
@@ -137,6 +138,7 @@ const LogoSectionContent = () => {
|
||||
}
|
||||
|
||||
export const AboutFooter = () => {
|
||||
const shouldDisableNFTRoutes = useDisableNFTRoutes()
|
||||
return (
|
||||
<Footer>
|
||||
<LogoSectionLeft>
|
||||
@@ -148,7 +150,7 @@ export const AboutFooter = () => {
|
||||
<LinkGroupTitle>App</LinkGroupTitle>
|
||||
<TextLink to="/swap">Swap</TextLink>
|
||||
<TextLink to="/tokens">Tokens</TextLink>
|
||||
<TextLink to="/nfts">NFTs</TextLink>
|
||||
{!shouldDisableNFTRoutes && <TextLink to="/nfts">NFTs</TextLink>}
|
||||
<TextLink to="/pools">Pools</TextLink>
|
||||
</LinkGroup>
|
||||
<LinkGroup>
|
||||
|
||||
@@ -283,7 +283,7 @@ function SwapSummary({ info }: { info: ExactInputSwapTransactionInfo | ExactOutp
|
||||
/>{' '}
|
||||
for{' '}
|
||||
<FormattedCurrencyAmountManaged
|
||||
rawAmount={info.expectedOutputCurrencyAmountRaw}
|
||||
rawAmount={info.settledOutputCurrencyAmountRaw ?? info.expectedOutputCurrencyAmountRaw}
|
||||
currencyId={info.outputCurrencyId}
|
||||
sigFigs={6}
|
||||
/>
|
||||
|
||||
@@ -12,14 +12,14 @@ import { formatDelta } from 'components/Tokens/TokenDetails/PriceChart'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import { getConnection } from 'connection'
|
||||
import { usePortfolioBalancesQuery } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { GQL_MAINNET_CHAINS } from 'graphql/data/util'
|
||||
import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes'
|
||||
import { useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks'
|
||||
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
|
||||
import { ProfilePageStateType } from 'nft/types'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { ArrowDownRight, ArrowUpRight, CreditCard, IconProps, Info, LogOut, Settings } from 'react-feather'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
import { updateSelectedWallet } from 'state/user/reducer'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
@@ -171,7 +171,7 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
|
||||
const clearCollectionFilters = useWalletCollections((state) => state.clearCollectionFilters)
|
||||
const isClaimAvailable = useIsNftClaimAvailable((state) => state.isClaimAvailable)
|
||||
|
||||
const shouldDisableNFTRoutes = useAtomValue(shouldDisableNFTRoutesAtom)
|
||||
const shouldDisableNFTRoutes = useDisableNFTRoutes()
|
||||
|
||||
const unclaimedAmount: CurrencyAmount<Token> | undefined = useUserUnclaimedAmount(account)
|
||||
const isUnclaimed = useUserHasAvailableClaim(account)
|
||||
@@ -227,9 +227,10 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
|
||||
const closeFiatOnrampUnavailableTooltip = useCallback(() => setShow(false), [setShow])
|
||||
|
||||
const { data: portfolioBalances } = usePortfolioBalancesQuery({
|
||||
variables: { ownerAddress: account ?? '' },
|
||||
variables: { ownerAddress: account ?? '', chains: GQL_MAINNET_CHAINS },
|
||||
fetchPolicy: 'cache-only', // PrefetchBalancesWrapper handles balance fetching/staleness; this component only reads from cache
|
||||
})
|
||||
|
||||
const portfolio = portfolioBalances?.portfolios?.[0]
|
||||
const totalBalance = portfolio?.tokensTotalDenominatedValue?.value
|
||||
const absoluteChange = portfolio?.tokensTotalDenominatedValueChange?.absolute?.value
|
||||
|
||||
@@ -3,8 +3,10 @@ import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/an
|
||||
import Column from 'components/Column'
|
||||
import AlertTriangleFilled from 'components/Icons/AlertTriangleFilled'
|
||||
import { LoaderV2 } from 'components/Icons/LoadingSpinner'
|
||||
import Row from 'components/Row'
|
||||
import { TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import useENSName from 'hooks/useENSName'
|
||||
import { useCallback } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { EllipsisStyle, ThemedText } from 'theme'
|
||||
import { shortenAddress } from 'utils'
|
||||
@@ -12,6 +14,7 @@ import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
|
||||
|
||||
import { PortfolioLogo } from '../PortfolioLogo'
|
||||
import PortfolioRow from '../PortfolioRow'
|
||||
import { useOpenOffchainActivityModal } from './OffchainActivityModal'
|
||||
import { useTimeSince } from './parseRemote'
|
||||
import { Activity } from './types'
|
||||
|
||||
@@ -26,16 +29,36 @@ const StyledTimestamp = styled(ThemedText.Caption)`
|
||||
font-feature-settings: 'tnum' on, 'lnum' on, 'ss02' on;
|
||||
`
|
||||
|
||||
export function ActivityRow({
|
||||
activity: { chainId, status, title, descriptor, logos, otherAccount, currencies, timestamp, hash },
|
||||
}: {
|
||||
activity: Activity
|
||||
}) {
|
||||
const { ENSName } = useENSName(otherAccount)
|
||||
function StatusIndicator({ activity: { status, timestamp } }: { activity: Activity }) {
|
||||
const timeSince = useTimeSince(timestamp)
|
||||
|
||||
switch (status) {
|
||||
case TransactionStatus.Pending:
|
||||
return <LoaderV2 />
|
||||
case TransactionStatus.Confirmed:
|
||||
return <StyledTimestamp>{timeSince}</StyledTimestamp>
|
||||
case TransactionStatus.Failed:
|
||||
return <AlertTriangleFilled />
|
||||
}
|
||||
}
|
||||
|
||||
export function ActivityRow({ activity }: { activity: Activity }) {
|
||||
const { chainId, title, descriptor, logos, otherAccount, currencies, hash, prefixIconSrc, offchainOrderStatus } =
|
||||
activity
|
||||
const openOffchainActivityModal = useOpenOffchainActivityModal()
|
||||
|
||||
const { ENSName } = useENSName(otherAccount)
|
||||
const explorerUrl = getExplorerLink(chainId, hash, ExplorerDataType.TRANSACTION)
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
if (offchainOrderStatus) {
|
||||
openOffchainActivityModal({ orderHash: hash, status: offchainOrderStatus })
|
||||
return
|
||||
}
|
||||
|
||||
window.open(getExplorerLink(chainId, hash, ExplorerDataType.TRANSACTION), '_blank')
|
||||
}, [offchainOrderStatus, chainId, hash, openOffchainActivityModal])
|
||||
|
||||
return (
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
@@ -49,23 +72,20 @@ export function ActivityRow({
|
||||
<PortfolioLogo chainId={chainId} currencies={currencies} images={logos} accountAddress={otherAccount} />
|
||||
</Column>
|
||||
}
|
||||
title={<ThemedText.SubHeader>{title}</ThemedText.SubHeader>}
|
||||
title={
|
||||
<Row gap="4px">
|
||||
{prefixIconSrc && <img height="14px" width="14px" src={prefixIconSrc} alt="" />}
|
||||
<ThemedText.SubHeader>{title}</ThemedText.SubHeader>
|
||||
</Row>
|
||||
}
|
||||
descriptor={
|
||||
<ActivityRowDescriptor color="textSecondary">
|
||||
{descriptor}
|
||||
{ENSName ?? shortenAddress(otherAccount)}
|
||||
</ActivityRowDescriptor>
|
||||
}
|
||||
right={
|
||||
status === TransactionStatus.Pending ? (
|
||||
<LoaderV2 />
|
||||
) : status === TransactionStatus.Confirmed ? (
|
||||
<StyledTimestamp>{timeSince}</StyledTimestamp>
|
||||
) : (
|
||||
<AlertTriangleFilled />
|
||||
)
|
||||
}
|
||||
onClick={() => window.open(explorerUrl, '_blank')}
|
||||
right={<StatusIndicator activity={activity} />}
|
||||
onClick={onClick}
|
||||
/>
|
||||
</TraceEvent>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
||||
import { ReactComponent as ErrorContent } from 'assets/svg/uniswapx_error.svg'
|
||||
import Column, { AutoColumn } from 'components/Column'
|
||||
import { OpacityHoverState } from 'components/Common'
|
||||
import { LoaderV3 } from 'components/Icons/LoadingSpinner'
|
||||
import Modal from 'components/Modal'
|
||||
import { AnimatedEntranceConfirmationIcon, FadePresence } from 'components/swap/PendingModalContent/Logos'
|
||||
import { TradeSummary } from 'components/swap/PendingModalContent/TradeSummary'
|
||||
import { useCurrency } from 'hooks/Tokens'
|
||||
import { atom } from 'jotai'
|
||||
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
|
||||
import { UniswapXOrderStatus } from 'lib/hooks/orders/types'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
import { useOrder } from 'state/signatures/hooks'
|
||||
import { UniswapXOrderDetails } from 'state/signatures/types'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText } from 'theme'
|
||||
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
|
||||
|
||||
type SelectedOrderInfo = {
|
||||
modalOpen?: boolean
|
||||
orderHash: string
|
||||
status: UniswapXOrderStatus
|
||||
details?: UniswapXOrderDetails
|
||||
}
|
||||
|
||||
const selectedOrderAtom = atom<SelectedOrderInfo | undefined>(undefined)
|
||||
|
||||
export function useOpenOffchainActivityModal() {
|
||||
const setSelectedOrder = useUpdateAtom(selectedOrderAtom)
|
||||
|
||||
return useCallback(
|
||||
(order: { orderHash: string; status: UniswapXOrderStatus }) => setSelectedOrder({ ...order, modalOpen: true }),
|
||||
[setSelectedOrder]
|
||||
)
|
||||
}
|
||||
|
||||
const Wrapper = styled(AutoColumn).attrs({ gap: 'md', grow: true })`
|
||||
padding: 16px;
|
||||
`
|
||||
|
||||
const ContentContainer = styled(AutoColumn).attrs({ justify: 'center', gap: 'md' })`
|
||||
padding: 28px 44px 24px 44px;
|
||||
`
|
||||
|
||||
const StyledXButton = styled(X)`
|
||||
cursor: pointer;
|
||||
justify-self: flex-end;
|
||||
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
${OpacityHoverState};
|
||||
`
|
||||
|
||||
const LoadingWrapper = styled.div`
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
position: relative;
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
const LoadingIndicator = styled(LoaderV3)`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
`
|
||||
|
||||
function Loader() {
|
||||
return (
|
||||
<LoadingWrapper>
|
||||
<FadePresence>
|
||||
<LoadingIndicator />
|
||||
</FadePresence>
|
||||
</LoadingWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
const Success = styled(AnimatedEntranceConfirmationIcon)`
|
||||
margin-bottom: 10px;
|
||||
`
|
||||
|
||||
const LearnMoreLink = styled(ExternalLink)`
|
||||
font-weight: 600;
|
||||
`
|
||||
const DescriptionText = styled(ThemedText.LabelMicro)`
|
||||
text-align: center;
|
||||
`
|
||||
|
||||
function useOrderAmounts(
|
||||
orderDetails?: UniswapXOrderDetails
|
||||
): Pick<InterfaceTrade, 'inputAmount' | 'outputAmount'> | undefined {
|
||||
const inputCurrency = useCurrency(orderDetails?.swapInfo?.inputCurrencyId, orderDetails?.chainId)
|
||||
const outputCurrency = useCurrency(orderDetails?.swapInfo?.outputCurrencyId, orderDetails?.chainId)
|
||||
|
||||
if (!orderDetails) return undefined
|
||||
|
||||
if (!inputCurrency || !outputCurrency) {
|
||||
console.error(`Could not find token(s) for order ${orderDetails.orderHash}`)
|
||||
return undefined
|
||||
}
|
||||
|
||||
const { swapInfo } = orderDetails
|
||||
|
||||
if (swapInfo.tradeType === TradeType.EXACT_INPUT) {
|
||||
return {
|
||||
inputAmount: CurrencyAmount.fromRawAmount(inputCurrency, swapInfo.inputCurrencyAmountRaw),
|
||||
outputAmount: CurrencyAmount.fromRawAmount(
|
||||
outputCurrency,
|
||||
swapInfo.settledOutputCurrencyAmountRaw ?? swapInfo.expectedOutputCurrencyAmountRaw
|
||||
),
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
inputAmount: CurrencyAmount.fromRawAmount(inputCurrency, swapInfo.expectedInputCurrencyAmountRaw),
|
||||
outputAmount: CurrencyAmount.fromRawAmount(outputCurrency, swapInfo.outputCurrencyAmountRaw),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function OrderContent({ order }: { order: SelectedOrderInfo }) {
|
||||
const amounts = useOrderAmounts(order.details)
|
||||
|
||||
const explorerLink = order?.details?.txHash
|
||||
? getExplorerLink(order.details.chainId, order.details.txHash, ExplorerDataType.TRANSACTION)
|
||||
: undefined
|
||||
|
||||
switch (order.status) {
|
||||
case UniswapXOrderStatus.OPEN: {
|
||||
return (
|
||||
<ContentContainer>
|
||||
<Loader />
|
||||
<ThemedText.SubHeaderLarge>
|
||||
<Trans>Swapping</Trans>
|
||||
</ThemedText.SubHeaderLarge>
|
||||
<Column>
|
||||
{amounts && <TradeSummary trade={amounts} />}
|
||||
<ThemedText.Caption paddingTop="48px" textAlign="center">
|
||||
<ExternalLink href="https://support.uniswap.org/hc/en-us/articles/17515415311501">
|
||||
<Trans>Learn more about swapping with UniswapX</Trans>
|
||||
</ExternalLink>
|
||||
</ThemedText.Caption>
|
||||
</Column>
|
||||
</ContentContainer>
|
||||
)
|
||||
}
|
||||
case UniswapXOrderStatus.FILLED:
|
||||
return (
|
||||
<ContentContainer>
|
||||
<Success />
|
||||
<ThemedText.SubHeaderLarge>
|
||||
<Trans>Swapped</Trans>
|
||||
</ThemedText.SubHeaderLarge>
|
||||
<Column>
|
||||
{amounts && <TradeSummary trade={amounts} />}
|
||||
<ThemedText.Caption paddingTop="48px" textAlign="center">
|
||||
{explorerLink && (
|
||||
<ExternalLink href={explorerLink}>
|
||||
<Trans>View on Explorer</Trans>
|
||||
</ExternalLink>
|
||||
)}
|
||||
</ThemedText.Caption>
|
||||
</Column>
|
||||
</ContentContainer>
|
||||
)
|
||||
case UniswapXOrderStatus.CANCELLED:
|
||||
return (
|
||||
<ContentContainer>
|
||||
<ErrorContent />
|
||||
<ThemedText.SubHeaderLarge>
|
||||
<Trans>Cancelled</Trans>
|
||||
</ThemedText.SubHeaderLarge>
|
||||
<ThemedText.LabelSmall textAlign="center">
|
||||
<Trans>This order was cancelled</Trans>
|
||||
</ThemedText.LabelSmall>
|
||||
</ContentContainer>
|
||||
)
|
||||
case UniswapXOrderStatus.EXPIRED:
|
||||
return (
|
||||
<ContentContainer>
|
||||
<ErrorContent />
|
||||
<ThemedText.SubHeaderLarge>
|
||||
<Trans>Swap expired</Trans>
|
||||
</ThemedText.SubHeaderLarge>
|
||||
<DescriptionText>
|
||||
{/* TODO: Improve translation grammar by not having to break up the string */}
|
||||
<Trans>Your swap expired before it could be filled. Try again or</Trans>{' '}
|
||||
<LearnMoreLink href="https://support.uniswap.org/hc/en-us/articles/17515426867213">
|
||||
<Trans>learn more.</Trans>
|
||||
</LearnMoreLink>
|
||||
</DescriptionText>
|
||||
</ContentContainer>
|
||||
)
|
||||
case UniswapXOrderStatus.ERROR:
|
||||
return (
|
||||
<ContentContainer>
|
||||
<ErrorContent />
|
||||
<ThemedText.SubHeaderLarge>
|
||||
<Trans>Error</Trans>
|
||||
</ThemedText.SubHeaderLarge>
|
||||
<ThemedText.LabelSmall textAlign="center">
|
||||
{/* TODO: Improve translation grammar by not having to break up the string */}
|
||||
<Trans>Your swap couldn't be filled at this time. Try again or </Trans>{' '}
|
||||
<LearnMoreLink href="https://support.uniswap.org/hc/en-us/articles/17515489874189">
|
||||
<Trans>learn more.</Trans>
|
||||
</LearnMoreLink>
|
||||
</ThemedText.LabelSmall>
|
||||
</ContentContainer>
|
||||
)
|
||||
case UniswapXOrderStatus.INSUFFICIENT_FUNDS:
|
||||
return (
|
||||
<ContentContainer>
|
||||
<ErrorContent />
|
||||
<ThemedText.SubHeaderLarge>
|
||||
<Trans>Insufficient funds for swap</Trans>
|
||||
</ThemedText.SubHeaderLarge>
|
||||
<ThemedText.LabelSmall textAlign="center">{t`You didn't have enough ${
|
||||
amounts?.inputAmount.currency.symbol ?? amounts?.inputAmount.currency.name ?? t`of the input token`
|
||||
} to complete this swap.`}</ThemedText.LabelSmall>
|
||||
</ContentContainer>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns the order currently selected in the UI synced with updates from order status polling */
|
||||
function useSyncedSelectedOrder(): SelectedOrderInfo | undefined {
|
||||
const selectedOrder = useAtomValue(selectedOrderAtom)
|
||||
const localPendingOrder = useOrder(selectedOrder?.orderHash ?? '')
|
||||
|
||||
return useMemo(() => {
|
||||
if (!selectedOrder) return undefined
|
||||
|
||||
return {
|
||||
...selectedOrder,
|
||||
status: localPendingOrder?.status ?? selectedOrder.status,
|
||||
details: localPendingOrder,
|
||||
}
|
||||
}, [localPendingOrder, selectedOrder])
|
||||
}
|
||||
|
||||
export function OffchainActivityModal() {
|
||||
const syncedSelectedOrder = useSyncedSelectedOrder()
|
||||
const setSelectedOrder = useUpdateAtom(selectedOrderAtom)
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setSelectedOrder((order) => order && { ...order, modalOpen: false })
|
||||
}, [setSelectedOrder])
|
||||
|
||||
return (
|
||||
<Modal isOpen={!!syncedSelectedOrder?.modalOpen} onDismiss={reset}>
|
||||
<Wrapper>
|
||||
<StyledXButton onClick={reset} />
|
||||
{syncedSelectedOrder && <OrderContent order={syncedSelectedOrder} />}
|
||||
</Wrapper>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { useAccountDrawer } from 'components/AccountDrawer'
|
||||
import Column from 'components/Column'
|
||||
import { LoadingBubble } from 'components/Tokens/loading'
|
||||
import { getYear, isSameDay, isSameMonth, isSameWeek, isSameYear } from 'date-fns'
|
||||
import { TransactionStatus, useTransactionListQuery } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { TransactionStatus, useActivityQuery } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { PollingInterval } from 'graphql/data/util'
|
||||
import { atom, useAtom } from 'jotai'
|
||||
import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletContent'
|
||||
@@ -91,7 +91,7 @@ function wasTxCancelled(localActivity: Activity, remoteMap: ActivityMap, account
|
||||
if (!remoteTx) return false
|
||||
|
||||
// Cancellations are only possible when both nonce and tx.from are the same
|
||||
if (remoteTx.nonce === localActivity.nonce && remoteTx.receipt?.from.toLowerCase() === account.toLowerCase()) {
|
||||
if (remoteTx.nonce === localActivity.nonce && remoteTx.from.toLowerCase() === account.toLowerCase()) {
|
||||
// If the remote tx has a different hash than the local tx, the local tx was cancelled
|
||||
return remoteTx.hash.toLowerCase() !== localActivity.hash.toLowerCase()
|
||||
}
|
||||
@@ -126,11 +126,7 @@ export function ActivityTab({ account }: { account: string }) {
|
||||
|
||||
const localMap = useLocalActivities(account)
|
||||
|
||||
const { data, loading, refetch } = useTransactionListQuery({
|
||||
variables: { account },
|
||||
errorPolicy: 'all',
|
||||
fetchPolicy: 'cache-first',
|
||||
})
|
||||
const { data, loading, refetch } = useActivityQuery({ variables: { account } })
|
||||
|
||||
// We only refetch remote activity if the user renavigates to the activity tab by changing tabs or opening the drawer
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SupportedChainId, Token, TradeType as MockTradeType } from '@uniswap/sdk-core'
|
||||
import { ChainId, Token, TradeType as MockTradeType } from '@uniswap/sdk-core'
|
||||
import { PERMIT2_ADDRESS } from '@uniswap/universal-router-sdk'
|
||||
import { DAI as MockDAI, nativeOnChain, USDC_MAINNET as MockUSDC_MAINNET, USDT as MockUSDT } from 'constants/tokens'
|
||||
import { TransactionStatus as MockTxStatus } from 'graphql/data/__generated__/types-and-hooks'
|
||||
@@ -12,7 +12,9 @@ import {
|
||||
} from 'state/transactions/types'
|
||||
import { renderHook } from 'test-utils/render'
|
||||
|
||||
import { parseLocalActivity, useLocalActivities } from './parseLocal'
|
||||
import { UniswapXOrderStatus } from '../../../../lib/hooks/orders/types'
|
||||
import { SignatureDetails, SignatureType } from '../../../../state/signatures/types'
|
||||
import { signatureToActivity, transactionToActivity, useLocalActivities } from './parseLocal'
|
||||
|
||||
function mockSwapInfo(
|
||||
type: MockTradeType,
|
||||
@@ -30,6 +32,7 @@ function mockSwapInfo(
|
||||
outputCurrencyId: outputCurrency.address,
|
||||
expectedOutputCurrencyAmountRaw: outputCurrencyAmountRaw,
|
||||
minimumOutputCurrencyAmountRaw: outputCurrencyAmountRaw,
|
||||
isUniswapXOrder: false,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
@@ -40,13 +43,14 @@ function mockSwapInfo(
|
||||
maximumInputCurrencyAmountRaw: inputCurrencyAmountRaw,
|
||||
outputCurrencyId: outputCurrency.address,
|
||||
outputCurrencyAmountRaw,
|
||||
isUniswapXOrder: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mockAccount1 = '0x000000000000000000000000000000000000000001'
|
||||
const mockAccount2 = '0x000000000000000000000000000000000000000002'
|
||||
const mockChainId = SupportedChainId.MAINNET
|
||||
const mockChainId = ChainId.MAINNET
|
||||
const mockSpenderAddress = PERMIT2_ADDRESS[mockChainId]
|
||||
const mockCurrencyAmountRaw = '1000000000000000000'
|
||||
const mockCurrencyAmountRawUSDC = '1000000'
|
||||
@@ -246,27 +250,13 @@ describe('parseLocalActivity', () => {
|
||||
status: 1,
|
||||
},
|
||||
} as TransactionDetails
|
||||
const chainId = SupportedChainId.MAINNET
|
||||
expect(parseLocalActivity(details, chainId, mockTokenAddressMap)).toEqual({
|
||||
const chainId = ChainId.MAINNET
|
||||
expect(transactionToActivity(details, chainId, mockTokenAddressMap)).toEqual({
|
||||
chainId: 1,
|
||||
currencies: [MockUSDC_MAINNET, MockDAI],
|
||||
descriptor: '1.00 USDC for 1.00 DAI',
|
||||
hash: undefined,
|
||||
receipt: {
|
||||
id: '0x123',
|
||||
info: {
|
||||
type: 1,
|
||||
tradeType: MockTradeType.EXACT_INPUT,
|
||||
inputCurrencyId: MockUSDC_MAINNET.address,
|
||||
inputCurrencyAmountRaw: mockCurrencyAmountRawUSDC,
|
||||
outputCurrencyId: MockDAI.address,
|
||||
expectedOutputCurrencyAmountRaw: mockCurrencyAmountRaw,
|
||||
minimumOutputCurrencyAmountRaw: mockCurrencyAmountRaw,
|
||||
},
|
||||
receipt: { status: 1, transactionHash: '0x123' },
|
||||
status: 'CONFIRMED',
|
||||
transactionHash: '0x123',
|
||||
},
|
||||
from: undefined,
|
||||
status: 'CONFIRMED',
|
||||
timestamp: NaN,
|
||||
title: 'Swapped',
|
||||
@@ -287,8 +277,8 @@ describe('parseLocalActivity', () => {
|
||||
status: 1,
|
||||
},
|
||||
} as TransactionDetails
|
||||
const chainId = SupportedChainId.MAINNET
|
||||
expect(parseLocalActivity(details, chainId, mockTokenAddressMap)).toMatchObject({
|
||||
const chainId = ChainId.MAINNET
|
||||
expect(transactionToActivity(details, chainId, mockTokenAddressMap)).toMatchObject({
|
||||
chainId: 1,
|
||||
currencies: [MockUSDC_MAINNET, MockDAI],
|
||||
descriptor: '1.00 USDC for 1.00 DAI',
|
||||
@@ -311,9 +301,9 @@ describe('parseLocalActivity', () => {
|
||||
status: 1,
|
||||
},
|
||||
} as TransactionDetails
|
||||
const chainId = SupportedChainId.MAINNET
|
||||
const chainId = ChainId.MAINNET
|
||||
const tokens = {} as ChainTokenMap
|
||||
expect(parseLocalActivity(details, chainId, tokens)).toMatchObject({
|
||||
expect(transactionToActivity(details, chainId, tokens)).toMatchObject({
|
||||
chainId: 1,
|
||||
currencies: [undefined, undefined],
|
||||
descriptor: 'Unknown for Unknown',
|
||||
@@ -349,10 +339,7 @@ describe('parseLocalActivity', () => {
|
||||
descriptor: `1.00 ${MockUSDC_MAINNET.symbol} for 1.00 ${MockDAI.symbol}`,
|
||||
hash,
|
||||
status: MockTxStatus.Confirmed,
|
||||
receipt: {
|
||||
id: hash,
|
||||
status: MockTxStatus.Confirmed,
|
||||
},
|
||||
from: mockAccount2,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -367,10 +354,7 @@ describe('parseLocalActivity', () => {
|
||||
descriptor: `1.00 ${MockUSDC_MAINNET.symbol} for 1.00 ${MockDAI.symbol}`,
|
||||
hash,
|
||||
status: MockTxStatus.Confirmed,
|
||||
receipt: {
|
||||
id: hash,
|
||||
status: MockTxStatus.Confirmed,
|
||||
},
|
||||
from: mockAccount2,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -385,10 +369,7 @@ describe('parseLocalActivity', () => {
|
||||
descriptor: MockDAI.symbol,
|
||||
hash,
|
||||
status: MockTxStatus.Confirmed,
|
||||
receipt: {
|
||||
id: hash,
|
||||
status: MockTxStatus.Confirmed,
|
||||
},
|
||||
from: mockAccount2,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -402,10 +383,6 @@ describe('parseLocalActivity', () => {
|
||||
descriptor: MockUSDT.symbol,
|
||||
hash,
|
||||
status: MockTxStatus.Confirmed,
|
||||
receipt: {
|
||||
id: hash,
|
||||
status: MockTxStatus.Confirmed,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@@ -422,10 +399,7 @@ describe('parseLocalActivity', () => {
|
||||
descriptor: `1.00 ${native.symbol} for 1.00 ${native.wrapped.symbol}`,
|
||||
hash,
|
||||
status: MockTxStatus.Confirmed,
|
||||
receipt: {
|
||||
id: hash,
|
||||
status: MockTxStatus.Confirmed,
|
||||
},
|
||||
from: mockAccount2,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -442,10 +416,7 @@ describe('parseLocalActivity', () => {
|
||||
descriptor: `1.00 ${native.wrapped.symbol} for 1.00 ${native.symbol}`,
|
||||
hash,
|
||||
status: MockTxStatus.Confirmed,
|
||||
receipt: {
|
||||
id: hash,
|
||||
status: MockTxStatus.Confirmed,
|
||||
},
|
||||
from: mockAccount2,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -460,10 +431,7 @@ describe('parseLocalActivity', () => {
|
||||
descriptor: `1.00 ${MockUSDC_MAINNET.symbol} and 1.00 ${MockDAI.symbol}`,
|
||||
hash,
|
||||
status: MockTxStatus.Confirmed,
|
||||
receipt: {
|
||||
id: hash,
|
||||
status: MockTxStatus.Confirmed,
|
||||
},
|
||||
from: mockAccount2,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -478,10 +446,7 @@ describe('parseLocalActivity', () => {
|
||||
descriptor: `1.00 ${MockUSDC_MAINNET.symbol} and 1.00 ${MockDAI.symbol}`,
|
||||
hash,
|
||||
status: MockTxStatus.Confirmed,
|
||||
receipt: {
|
||||
id: hash,
|
||||
status: MockTxStatus.Confirmed,
|
||||
},
|
||||
from: mockAccount2,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -496,10 +461,7 @@ describe('parseLocalActivity', () => {
|
||||
descriptor: `1.00 ${MockUSDC_MAINNET.symbol} and 1.00 ${MockDAI.symbol}`,
|
||||
hash,
|
||||
status: MockTxStatus.Confirmed,
|
||||
receipt: {
|
||||
id: hash,
|
||||
status: MockTxStatus.Confirmed,
|
||||
},
|
||||
from: mockAccount2,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -514,10 +476,7 @@ describe('parseLocalActivity', () => {
|
||||
descriptor: `1.00 ${MockUSDC_MAINNET.symbol} and 1.00 ${MockDAI.symbol}`,
|
||||
hash,
|
||||
status: MockTxStatus.Confirmed,
|
||||
receipt: {
|
||||
id: hash,
|
||||
status: MockTxStatus.Confirmed,
|
||||
},
|
||||
from: mockAccount2,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -532,10 +491,29 @@ describe('parseLocalActivity', () => {
|
||||
descriptor: `${MockUSDC_MAINNET.symbol} and ${MockDAI.symbol}`,
|
||||
hash,
|
||||
status: MockTxStatus.Confirmed,
|
||||
receipt: {
|
||||
id: hash,
|
||||
status: MockTxStatus.Confirmed,
|
||||
},
|
||||
from: mockAccount2,
|
||||
})
|
||||
})
|
||||
|
||||
it('Signature to activity - returns undefined if is on chain order', () => {
|
||||
expect(
|
||||
signatureToActivity(
|
||||
{
|
||||
type: SignatureType.SIGN_UNISWAPX_ORDER,
|
||||
status: UniswapXOrderStatus.FILLED,
|
||||
} as SignatureDetails,
|
||||
{}
|
||||
)
|
||||
).toBeUndefined()
|
||||
|
||||
expect(
|
||||
signatureToActivity(
|
||||
{
|
||||
type: SignatureType.SIGN_UNISWAPX_ORDER,
|
||||
status: UniswapXOrderStatus.CANCELLED,
|
||||
} as SignatureDetails,
|
||||
{}
|
||||
)
|
||||
).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { t } from '@lingui/macro'
|
||||
import { formatCurrencyAmount } from '@uniswap/conedison/format'
|
||||
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
||||
import { ChainId, Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
||||
import { nativeOnChain } from '@uniswap/smart-order-router'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import UniswapXBolt from 'assets/svg/bolt.svg'
|
||||
import { TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { ChainTokenMap, useAllTokensMultichain } from 'hooks/Tokens'
|
||||
import { useMemo } from 'react'
|
||||
import { isOnChainOrder, useAllSignatures } from 'state/signatures/hooks'
|
||||
import { SignatureDetails, SignatureType } from 'state/signatures/types'
|
||||
import { useMultichainTransactions } from 'state/transactions/hooks'
|
||||
import {
|
||||
AddLiquidityV2PoolTransactionInfo,
|
||||
@@ -23,10 +25,10 @@ import {
|
||||
WrapTransactionInfo,
|
||||
} from 'state/transactions/types'
|
||||
|
||||
import { getActivityTitle } from '../constants'
|
||||
import { getActivityTitle, OrderTextTable } from '../constants'
|
||||
import { Activity, ActivityMap } from './types'
|
||||
|
||||
function getCurrency(currencyId: string, chainId: SupportedChainId, tokens: ChainTokenMap): Currency | undefined {
|
||||
function getCurrency(currencyId: string, chainId: ChainId, tokens: ChainTokenMap): Currency | undefined {
|
||||
return currencyId === 'ETH' ? nativeOnChain(chainId) : tokens[chainId]?.[currencyId]
|
||||
}
|
||||
|
||||
@@ -46,23 +48,24 @@ function buildCurrencyDescriptor(
|
||||
|
||||
function parseSwap(
|
||||
swap: ExactInputSwapTransactionInfo | ExactOutputSwapTransactionInfo,
|
||||
chainId: SupportedChainId,
|
||||
chainId: ChainId,
|
||||
tokens: ChainTokenMap
|
||||
): Partial<Activity> {
|
||||
const tokenIn = getCurrency(swap.inputCurrencyId, chainId, tokens)
|
||||
const tokenOut = getCurrency(swap.outputCurrencyId, chainId, tokens)
|
||||
const [inputRaw, outputRaw] =
|
||||
swap.tradeType === TradeType.EXACT_INPUT
|
||||
? [swap.inputCurrencyAmountRaw, swap.expectedOutputCurrencyAmountRaw]
|
||||
? [swap.inputCurrencyAmountRaw, swap.settledOutputCurrencyAmountRaw ?? swap.expectedOutputCurrencyAmountRaw]
|
||||
: [swap.expectedInputCurrencyAmountRaw, swap.outputCurrencyAmountRaw]
|
||||
|
||||
return {
|
||||
descriptor: buildCurrencyDescriptor(tokenIn, inputRaw, tokenOut, outputRaw),
|
||||
currencies: [tokenIn, tokenOut],
|
||||
prefixIconSrc: swap.isUniswapXOrder ? UniswapXBolt : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
function parseWrap(wrap: WrapTransactionInfo, chainId: SupportedChainId, status: TransactionStatus): Partial<Activity> {
|
||||
function parseWrap(wrap: WrapTransactionInfo, chainId: ChainId, status: TransactionStatus): Partial<Activity> {
|
||||
const native = nativeOnChain(chainId)
|
||||
const wrapped = native.wrapped
|
||||
const [input, output] = wrap.unwrapped ? [wrapped, native] : [native, wrapped]
|
||||
@@ -76,7 +79,7 @@ function parseWrap(wrap: WrapTransactionInfo, chainId: SupportedChainId, status:
|
||||
|
||||
function parseApproval(
|
||||
approval: ApproveTransactionInfo,
|
||||
chainId: SupportedChainId,
|
||||
chainId: ChainId,
|
||||
tokens: ChainTokenMap,
|
||||
status: TransactionStatus
|
||||
): Partial<Activity> {
|
||||
@@ -97,7 +100,7 @@ type GenericLPInfo = Omit<
|
||||
AddLiquidityV3PoolTransactionInfo | RemoveLiquidityV3TransactionInfo | AddLiquidityV2PoolTransactionInfo,
|
||||
'type'
|
||||
>
|
||||
function parseLP(lp: GenericLPInfo, chainId: SupportedChainId, tokens: ChainTokenMap): Partial<Activity> {
|
||||
function parseLP(lp: GenericLPInfo, chainId: ChainId, tokens: ChainTokenMap): Partial<Activity> {
|
||||
const baseCurrency = getCurrency(lp.baseCurrencyId, chainId, tokens)
|
||||
const quoteCurrency = getCurrency(lp.quoteCurrencyId, chainId, tokens)
|
||||
const [baseRaw, quoteRaw] = [lp.expectedAmountBaseRaw, lp.expectedAmountQuoteRaw]
|
||||
@@ -108,7 +111,7 @@ function parseLP(lp: GenericLPInfo, chainId: SupportedChainId, tokens: ChainToke
|
||||
|
||||
function parseCollectFees(
|
||||
collect: CollectFeesTransactionInfo,
|
||||
chainId: SupportedChainId,
|
||||
chainId: ChainId,
|
||||
tokens: ChainTokenMap
|
||||
): Partial<Activity> {
|
||||
// Adapts CollectFeesTransactionInfo to generic LP type
|
||||
@@ -123,7 +126,7 @@ function parseCollectFees(
|
||||
|
||||
function parseMigrateCreateV3(
|
||||
lp: MigrateV2LiquidityToV3TransactionInfo | CreateV3PoolTransactionInfo,
|
||||
chainId: SupportedChainId,
|
||||
chainId: ChainId,
|
||||
tokens: ChainTokenMap
|
||||
): Partial<Activity> {
|
||||
const baseCurrency = getCurrency(lp.baseCurrencyId, chainId, tokens)
|
||||
@@ -135,9 +138,9 @@ function parseMigrateCreateV3(
|
||||
return { descriptor, currencies: [baseCurrency, quoteCurrency] }
|
||||
}
|
||||
|
||||
export function parseLocalActivity(
|
||||
export function transactionToActivity(
|
||||
details: TransactionDetails,
|
||||
chainId: SupportedChainId,
|
||||
chainId: ChainId,
|
||||
tokens: ChainTokenMap
|
||||
): Activity | undefined {
|
||||
try {
|
||||
@@ -147,22 +150,13 @@ export function parseLocalActivity(
|
||||
? TransactionStatus.Confirmed
|
||||
: TransactionStatus.Failed
|
||||
|
||||
const receipt = details.receipt
|
||||
? {
|
||||
id: details.receipt.transactionHash,
|
||||
...details.receipt,
|
||||
...details,
|
||||
status,
|
||||
}
|
||||
: undefined
|
||||
|
||||
const defaultFields = {
|
||||
hash: details.hash,
|
||||
chainId,
|
||||
title: getActivityTitle(details.info.type, status),
|
||||
status,
|
||||
timestamp: (details.confirmedTime ?? details.addedTime) / 1000,
|
||||
receipt,
|
||||
from: details.from,
|
||||
nonce: details.nonce,
|
||||
}
|
||||
|
||||
@@ -193,17 +187,53 @@ export function parseLocalActivity(
|
||||
}
|
||||
}
|
||||
|
||||
export function signatureToActivity(signature: SignatureDetails, tokens: ChainTokenMap): Activity | undefined {
|
||||
switch (signature.type) {
|
||||
case SignatureType.SIGN_UNISWAPX_ORDER: {
|
||||
// Only returns Activity items for orders that don't have an on-chain counterpart
|
||||
if (isOnChainOrder(signature.status)) return undefined
|
||||
|
||||
const { title, statusMessage, status } = OrderTextTable[signature.status]
|
||||
|
||||
return {
|
||||
hash: signature.orderHash,
|
||||
chainId: signature.chainId,
|
||||
title,
|
||||
status,
|
||||
offchainOrderStatus: signature.status,
|
||||
timestamp: signature.addedTime / 1000,
|
||||
from: signature.offerer,
|
||||
statusMessage,
|
||||
prefixIconSrc: UniswapXBolt,
|
||||
...parseSwap(signature.swapInfo, signature.chainId, tokens),
|
||||
}
|
||||
}
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export function useLocalActivities(account: string): ActivityMap {
|
||||
const allTransactions = useMultichainTransactions()
|
||||
const allSignatures = useAllSignatures()
|
||||
const tokens = useAllTokensMultichain()
|
||||
|
||||
return useMemo(() => {
|
||||
const activityByHash: ActivityMap = {}
|
||||
const activityMap: ActivityMap = {}
|
||||
for (const [transaction, chainId] of allTransactions) {
|
||||
if (transaction.from !== account) continue
|
||||
|
||||
activityByHash[transaction.hash] = parseLocalActivity(transaction, chainId, tokens)
|
||||
const activity = transactionToActivity(transaction, chainId, tokens)
|
||||
if (activity) activityMap[transaction.hash] = activity
|
||||
}
|
||||
return activityByHash
|
||||
}, [account, allTransactions, tokens])
|
||||
|
||||
for (const signature of Object.values(allSignatures)) {
|
||||
if (signature.offerer !== account) continue
|
||||
|
||||
const activity = signatureToActivity(signature, tokens)
|
||||
if (activity) activityMap[signature.id] = activity
|
||||
}
|
||||
|
||||
return activityMap
|
||||
}, [account, allSignatures, allTransactions, tokens])
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { t } from '@lingui/macro'
|
||||
import { formatFiatPrice, formatNumberOrString, NumberType } from '@uniswap/conedison/format'
|
||||
import { SupportedChainId } from '@uniswap/sdk-core'
|
||||
import { ChainId, NONFUNGIBLE_POSITION_MANAGER_ADDRESSES, UNI_ADDRESSES } from '@uniswap/sdk-core'
|
||||
import UniswapXBolt from 'assets/svg/bolt.svg'
|
||||
import moonpayLogoSrc from 'assets/svg/moonpay.svg'
|
||||
import { NONFUNGIBLE_POSITION_MANAGER_ADDRESSES, UNI_ADDRESS } from 'constants/addresses'
|
||||
import { nativeOnChain } from 'constants/tokens'
|
||||
import {
|
||||
ActivityType,
|
||||
@@ -11,15 +11,19 @@ import {
|
||||
NftApprovalPartsFragment,
|
||||
NftApproveForAllPartsFragment,
|
||||
NftTransferPartsFragment,
|
||||
SwapOrderDetailsPartsFragment,
|
||||
SwapOrderStatus,
|
||||
TokenApprovalPartsFragment,
|
||||
TokenAssetPartsFragment,
|
||||
TokenTransferPartsFragment,
|
||||
TransactionDetailsPartsFragment,
|
||||
} from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { fromGraphQLChain } from 'graphql/data/util'
|
||||
import { logSentryErrorForUnsupportedChain, supportedChainIdFromGQLChain } from 'graphql/data/util'
|
||||
import ms from 'ms.macro'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { isAddress } from 'utils'
|
||||
|
||||
import { MOONPAY_SENDER_ADDRESSES } from '../constants'
|
||||
import { MOONPAY_SENDER_ADDRESSES, OrderStatusTable, OrderTextTable } from '../constants'
|
||||
import { Activity } from './types'
|
||||
|
||||
type TransactionChanges = {
|
||||
@@ -38,7 +42,7 @@ const ENS_IMG =
|
||||
'https://464911102-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/collections%2F2TjMAeHSzwlQgcOdL48E%2Ficon%2FKWP0gk2C6bdRPliWIA6o%2Fens%20transparent%20background.png?alt=media&token=bd28b063-5a75-4971-890c-97becea09076'
|
||||
|
||||
const COMMON_CONTRACTS: { [key: string]: Partial<Activity> | undefined } = {
|
||||
[UNI_ADDRESS[SupportedChainId.MAINNET].toLowerCase()]: {
|
||||
[UNI_ADDRESSES[ChainId.MAINNET].toLowerCase()]: {
|
||||
title: t`UNI Governance`,
|
||||
descriptor: t`Contract Interaction`,
|
||||
logos: [UNI_IMG],
|
||||
@@ -75,11 +79,10 @@ function isSameAddress(a?: string, b?: string) {
|
||||
return a === b || a?.toLowerCase() === b?.toLowerCase() // Lazy-lowercases the addresses
|
||||
}
|
||||
|
||||
function callsPositionManagerContract(assetActivity: AssetActivityPartsFragment) {
|
||||
return isSameAddress(
|
||||
assetActivity.transaction.to,
|
||||
NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[fromGraphQLChain(assetActivity.chain)]
|
||||
)
|
||||
function callsPositionManagerContract(assetActivity: TransactionActivity) {
|
||||
const supportedChain = supportedChainIdFromGQLChain(assetActivity.chain)
|
||||
if (!supportedChain) return false
|
||||
return isSameAddress(assetActivity.details.to, NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[supportedChain])
|
||||
}
|
||||
|
||||
// Gets counts for number of NFTs in each collection present
|
||||
@@ -93,15 +96,24 @@ function getCollectionCounts(nftTransfers: NftTransferPartsFragment[]): { [key:
|
||||
}, {} as { [key: string]: number | undefined })
|
||||
}
|
||||
|
||||
function getSwapTitle(sent: TokenTransferPartsFragment, received: TokenTransferPartsFragment) {
|
||||
function getSwapTitle(sent: TokenTransferPartsFragment, received: TokenTransferPartsFragment): string | undefined {
|
||||
const supportedSentChain = supportedChainIdFromGQLChain(sent.asset.chain)
|
||||
const supportedReceivedChain = supportedChainIdFromGQLChain(received.asset.chain)
|
||||
if (!supportedSentChain || !supportedReceivedChain) {
|
||||
logSentryErrorForUnsupportedChain({
|
||||
extras: { sentAsset: sent.asset, receivedAsset: received.asset },
|
||||
errorMessage: 'Invalid activity from unsupported chain received from GQL',
|
||||
})
|
||||
return undefined
|
||||
}
|
||||
if (
|
||||
sent.tokenStandard === 'NATIVE' &&
|
||||
isSameAddress(nativeOnChain(fromGraphQLChain(sent.asset.chain)).wrapped.address, received.asset.address)
|
||||
isSameAddress(nativeOnChain(supportedSentChain).wrapped.address, received.asset.address)
|
||||
)
|
||||
return t`Wrapped`
|
||||
else if (
|
||||
received.tokenStandard === 'NATIVE' &&
|
||||
isSameAddress(nativeOnChain(fromGraphQLChain(received.asset.chain)).wrapped.address, received.asset.address)
|
||||
isSameAddress(nativeOnChain(supportedReceivedChain).wrapped.address, received.asset.address)
|
||||
) {
|
||||
return t`Unwrapped`
|
||||
} else {
|
||||
@@ -109,6 +121,20 @@ function getSwapTitle(sent: TokenTransferPartsFragment, received: TokenTransferP
|
||||
}
|
||||
}
|
||||
|
||||
function getSwapDescriptor({
|
||||
tokenIn,
|
||||
inputAmount,
|
||||
tokenOut,
|
||||
outputAmount,
|
||||
}: {
|
||||
tokenIn: TokenAssetPartsFragment
|
||||
outputAmount: string
|
||||
tokenOut: TokenAssetPartsFragment
|
||||
inputAmount: string
|
||||
}) {
|
||||
return `${inputAmount} ${tokenIn.symbol} for ${outputAmount} ${tokenOut.symbol}`
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param transactedValue Transacted value amount from TokenTransfer API response
|
||||
@@ -138,13 +164,17 @@ function parseSwap(changes: TransactionChanges) {
|
||||
const outputAmount = formatNumberOrString(received.quantity, NumberType.TokenNonTx)
|
||||
return {
|
||||
title: getSwapTitle(sent, received),
|
||||
descriptor: `${inputAmount} ${sent.asset.symbol} for ${outputAmount} ${received.asset.symbol}`,
|
||||
descriptor: getSwapDescriptor({ tokenIn: sent.asset, inputAmount, tokenOut: received.asset, outputAmount }),
|
||||
}
|
||||
}
|
||||
}
|
||||
return { title: t`Unknown Swap` }
|
||||
}
|
||||
|
||||
function parseSwapOrder(changes: TransactionChanges) {
|
||||
return { ...parseSwap(changes), prefixIconSrc: UniswapXBolt }
|
||||
}
|
||||
|
||||
function parseApprove(changes: TransactionChanges) {
|
||||
if (changes.TokenApproval.length === 1) {
|
||||
const title = parseInt(changes.TokenApproval[0].quantity) === 0 ? t`Revoked Approval` : t`Approved`
|
||||
@@ -167,7 +197,10 @@ function parseLPTransfers(changes: TransactionChanges) {
|
||||
}
|
||||
}
|
||||
|
||||
function parseSendReceive(changes: TransactionChanges, assetActivity: AssetActivityPartsFragment) {
|
||||
type TransactionActivity = AssetActivityPartsFragment & { details: TransactionDetailsPartsFragment }
|
||||
type OrderActivity = AssetActivityPartsFragment & { details: SwapOrderDetailsPartsFragment }
|
||||
|
||||
function parseSendReceive(changes: TransactionChanges, assetActivity: TransactionActivity) {
|
||||
// TODO(cartcrom): remove edge cases after backend implements
|
||||
// Edge case: Receiving two token transfers in interaction w/ V3 manager === removing liquidity. These edge cases should potentially be moved to backend
|
||||
if (changes.TokenTransfer.length === 2 && callsPositionManagerContract(assetActivity)) {
|
||||
@@ -214,7 +247,7 @@ function parseSendReceive(changes: TransactionChanges, assetActivity: AssetActiv
|
||||
return { title: t`Unknown Send` }
|
||||
}
|
||||
|
||||
function parseMint(changes: TransactionChanges, assetActivity: AssetActivityPartsFragment) {
|
||||
function parseMint(changes: TransactionChanges, assetActivity: TransactionActivity) {
|
||||
const collectionMap = getCollectionCounts(changes.NftTransfer)
|
||||
if (Object.keys(collectionMap).length === 1) {
|
||||
const collectionName = Object.keys(collectionMap)[0]
|
||||
@@ -228,13 +261,14 @@ function parseMint(changes: TransactionChanges, assetActivity: AssetActivityPart
|
||||
return { title: t`Unknown Mint` }
|
||||
}
|
||||
|
||||
function parseUnknown(_changes: TransactionChanges, assetActivity: AssetActivityPartsFragment) {
|
||||
return { title: t`Contract Interaction`, ...COMMON_CONTRACTS[assetActivity.transaction.to.toLowerCase()] }
|
||||
function parseUnknown(_changes: TransactionChanges, assetActivity: TransactionActivity) {
|
||||
return { title: t`Contract Interaction`, ...COMMON_CONTRACTS[assetActivity.details.to.toLowerCase()] }
|
||||
}
|
||||
|
||||
type ActivityTypeParser = (changes: TransactionChanges, assetActivity: AssetActivityPartsFragment) => Partial<Activity>
|
||||
type ActivityTypeParser = (changes: TransactionChanges, assetActivity: TransactionActivity) => Partial<Activity>
|
||||
const ActivityParserByType: { [key: string]: ActivityTypeParser | undefined } = {
|
||||
[ActivityType.Swap]: parseSwap,
|
||||
[ActivityType.SwapOrder]: parseSwapOrder,
|
||||
[ActivityType.Approve]: parseApprove,
|
||||
[ActivityType.Send]: parseSendReceive,
|
||||
[ActivityType.Receive]: parseSendReceive,
|
||||
@@ -255,8 +289,51 @@ function getLogoSrcs(changes: TransactionChanges): string[] {
|
||||
return Array.from(logoSet).filter(Boolean) as string[]
|
||||
}
|
||||
|
||||
function parseUniswapXOrder({ details, chain, timestamp }: OrderActivity): Activity | undefined {
|
||||
// We currently only have a polling mechanism for locally-sent pending orders, so we hide remote pending orders since they won't update upon completion
|
||||
// TODO(WEB-2487): Add polling mechanism for remote orders to allow displaying remote pending orders
|
||||
if (details.orderStatus === SwapOrderStatus.Open) return undefined
|
||||
|
||||
const { inputToken, inputTokenQuantity, outputToken, outputTokenQuantity, orderStatus } = details
|
||||
const uniswapXOrderStatus = OrderStatusTable[orderStatus]
|
||||
const { status, statusMessage, title } = OrderTextTable[uniswapXOrderStatus]
|
||||
const descriptor = getSwapDescriptor({
|
||||
tokenIn: inputToken,
|
||||
inputAmount: inputTokenQuantity,
|
||||
tokenOut: outputToken,
|
||||
outputAmount: outputTokenQuantity,
|
||||
})
|
||||
|
||||
const supportedChain = supportedChainIdFromGQLChain(chain)
|
||||
if (!supportedChain) {
|
||||
logSentryErrorForUnsupportedChain({
|
||||
extras: { details },
|
||||
errorMessage: 'Invalid activity from unsupported chain received from GQL',
|
||||
})
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
hash: details.hash,
|
||||
chainId: supportedChain,
|
||||
status,
|
||||
statusMessage,
|
||||
offchainOrderStatus: uniswapXOrderStatus,
|
||||
timestamp,
|
||||
logos: [inputToken.project?.logo?.url, outputToken.project?.logo?.url],
|
||||
title,
|
||||
descriptor,
|
||||
from: details.offerer,
|
||||
prefixIconSrc: UniswapXBolt,
|
||||
}
|
||||
}
|
||||
|
||||
function parseRemoteActivity(assetActivity: AssetActivityPartsFragment): Activity | undefined {
|
||||
try {
|
||||
if (assetActivity.details.__typename === 'SwapOrderDetails') {
|
||||
return parseUniswapXOrder(assetActivity as OrderActivity)
|
||||
}
|
||||
|
||||
const changes = assetActivity.assetChanges.reduce(
|
||||
(acc: TransactionChanges, assetChange) => {
|
||||
if (assetChange.__typename === 'NftApproval') acc.NftApproval.push(assetChange)
|
||||
@@ -269,19 +346,27 @@ function parseRemoteActivity(assetActivity: AssetActivityPartsFragment): Activit
|
||||
},
|
||||
{ NftTransfer: [], TokenTransfer: [], TokenApproval: [], NftApproval: [], NftApproveForAll: [] }
|
||||
)
|
||||
const supportedChain = supportedChainIdFromGQLChain(assetActivity.chain)
|
||||
if (!supportedChain) {
|
||||
logSentryErrorForUnsupportedChain({
|
||||
extras: { assetActivity },
|
||||
errorMessage: 'Invalid activity from unsupported chain received from GQL',
|
||||
})
|
||||
return undefined
|
||||
}
|
||||
const defaultFields = {
|
||||
hash: assetActivity.transaction.hash,
|
||||
chainId: fromGraphQLChain(assetActivity.chain),
|
||||
status: assetActivity.transaction.status,
|
||||
hash: assetActivity.details.hash,
|
||||
chainId: supportedChain,
|
||||
status: assetActivity.details.status,
|
||||
timestamp: assetActivity.timestamp,
|
||||
logos: getLogoSrcs(changes),
|
||||
title: assetActivity.type,
|
||||
descriptor: assetActivity.transaction.to,
|
||||
receipt: assetActivity.transaction,
|
||||
nonce: assetActivity.transaction.nonce,
|
||||
descriptor: assetActivity.details.to,
|
||||
from: assetActivity.details.from,
|
||||
nonce: assetActivity.details.nonce,
|
||||
}
|
||||
const parsedFields = ActivityParserByType[assetActivity.type]?.(changes, assetActivity)
|
||||
|
||||
const parsedFields = ActivityParserByType[assetActivity.type]?.(changes, assetActivity as TransactionActivity)
|
||||
return { ...defaultFields, ...parsedFields }
|
||||
} catch (e) {
|
||||
console.error('Failed to parse activity', e, assetActivity)
|
||||
@@ -289,7 +374,7 @@ function parseRemoteActivity(assetActivity: AssetActivityPartsFragment): Activit
|
||||
}
|
||||
}
|
||||
|
||||
export function parseRemoteActivities(assetActivities?: AssetActivityPartsFragment[]) {
|
||||
export function parseRemoteActivities(assetActivities?: readonly AssetActivityPartsFragment[]) {
|
||||
return assetActivities?.reduce((acc: { [hash: string]: Activity }, assetActivity) => {
|
||||
const activity = parseRemoteActivity(assetActivity)
|
||||
if (activity) acc[activity.hash] = activity
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { AssetActivityPartsFragment, TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
|
||||
|
||||
type Receipt = AssetActivityPartsFragment['transaction']
|
||||
import { ChainId, Currency } from '@uniswap/sdk-core'
|
||||
import { TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { UniswapXOrderStatus } from 'lib/hooks/orders/types'
|
||||
|
||||
export type Activity = {
|
||||
hash: string
|
||||
chainId: SupportedChainId
|
||||
chainId: ChainId
|
||||
status: TransactionStatus
|
||||
// TODO (UniswapX): decouple Activity from UniswapXOrderStatus once we can link UniswapXScan instead of needing data for modal
|
||||
offchainOrderStatus?: UniswapXOrderStatus
|
||||
statusMessage?: string
|
||||
timestamp: number
|
||||
title: string
|
||||
descriptor?: string
|
||||
logos?: Array<string | undefined>
|
||||
currencies?: Array<Currency | undefined>
|
||||
otherAccount?: string
|
||||
receipt?: Omit<Receipt, 'nonce'>
|
||||
from: string
|
||||
nonce?: number | null
|
||||
prefixIconSrc?: string
|
||||
}
|
||||
|
||||
export type ActivityMap = { [hash: string]: Activity | undefined }
|
||||
export type ActivityMap = { [id: string]: Activity | undefined }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
import { ChainId, Token } from '@uniswap/sdk-core'
|
||||
import { Pool, Position } from '@uniswap/v3-sdk'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { useAllTokensMultichain } from 'hooks/Tokens'
|
||||
import { atom, useAtom } from 'jotai'
|
||||
import { atomWithStorage } from 'jotai/utils'
|
||||
@@ -16,7 +15,7 @@ import { useInterfaceMulticallContracts } from './hooks'
|
||||
|
||||
export type PositionInfo = {
|
||||
owner: string
|
||||
chainId: SupportedChainId
|
||||
chainId: ChainId
|
||||
position: Position
|
||||
pool: Pool
|
||||
details: PositionDetails
|
||||
@@ -59,7 +58,7 @@ export function useCachedPositions(account: string): UseCachedPositionsReturnTyp
|
||||
return [cachedPositions[account], setPositionsAndStaleTimeout]
|
||||
}
|
||||
|
||||
const poolAddressKey = (details: PositionDetails, chainId: SupportedChainId) =>
|
||||
const poolAddressKey = (details: PositionDetails, chainId: ChainId) =>
|
||||
`${chainId}-${details.token0}-${details.token1}-${details.fee}`
|
||||
|
||||
type PoolAddressMap = { [key: string]: string | undefined }
|
||||
@@ -71,11 +70,11 @@ const poolAddressCacheAtom = atomWithStorage<PoolAddressMap>('poolCache', {})
|
||||
export function usePoolAddressCache() {
|
||||
const [cache, updateCache] = useAtom(poolAddressCacheAtom)
|
||||
const get = useCallback(
|
||||
(details: PositionDetails, chainId: SupportedChainId) => cache[poolAddressKey(details, chainId)],
|
||||
(details: PositionDetails, chainId: ChainId) => cache[poolAddressKey(details, chainId)],
|
||||
[cache]
|
||||
)
|
||||
const set = useCallback(
|
||||
(details: PositionDetails, chainId: SupportedChainId, address: string) =>
|
||||
(details: PositionDetails, chainId: ChainId, address: string) =>
|
||||
updateCache((c) => ({ ...c, [poolAddressKey(details, chainId)]: address })),
|
||||
[updateCache]
|
||||
)
|
||||
@@ -104,8 +103,8 @@ function useTokenCache() {
|
||||
return { get, set }
|
||||
}
|
||||
|
||||
type TokenGetterFn = (addresses: string[], chainId: SupportedChainId) => Promise<{ [key: string]: Token | undefined }>
|
||||
export function useGetCachedTokens(chains: SupportedChainId[]): TokenGetterFn {
|
||||
type TokenGetterFn = (addresses: string[], chainId: ChainId) => Promise<{ [key: string]: Token | undefined }>
|
||||
export function useGetCachedTokens(chains: ChainId[]): TokenGetterFn {
|
||||
const allTokens = useAllTokensMultichain()
|
||||
const multicallContracts = useInterfaceMulticallContracts(chains)
|
||||
const tokenCache = useTokenCache()
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
import { ChainId, Token } from '@uniswap/sdk-core'
|
||||
import ERC20_ABI from 'abis/erc20.json'
|
||||
import { Erc20Interface } from 'abis/types/Erc20'
|
||||
import { Erc20Bytes32Interface } from 'abis/types/Erc20Bytes32'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { DEFAULT_ERC20_DECIMALS } from 'constants/tokens'
|
||||
import { Interface } from 'ethers/lib/utils'
|
||||
import { UniswapInterfaceMulticall } from 'types/v3'
|
||||
@@ -38,7 +37,7 @@ async function fetchChunk(multicall: UniswapInterfaceMulticall, chunk: Call[]):
|
||||
}
|
||||
}
|
||||
|
||||
function tryParseToken(address: string, chainId: SupportedChainId, data: CallResult[]) {
|
||||
function tryParseToken(address: string, chainId: ChainId, data: CallResult[]) {
|
||||
try {
|
||||
const [nameData, symbolData, decimalsData, nameDataBytes32, symbolDataBytes32] = data
|
||||
|
||||
@@ -61,7 +60,7 @@ function tryParseToken(address: string, chainId: SupportedChainId, data: CallRes
|
||||
}
|
||||
}
|
||||
|
||||
function parseTokens(addresses: string[], chainId: SupportedChainId, returnData: CallResult[]) {
|
||||
function parseTokens(addresses: string[], chainId: ChainId, returnData: CallResult[]) {
|
||||
const tokenDataSlices = arrayToSlices(returnData, 5)
|
||||
|
||||
return tokenDataSlices.reduce((acc: TokenMap, slice, index) => {
|
||||
@@ -90,7 +89,7 @@ const TokenPromiseCache: { [key: CurrencyKey]: Promise<Token | undefined> | unde
|
||||
// Returns tokens using a single RPC call to the multicall contract
|
||||
export async function getTokensAsync(
|
||||
addresses: string[],
|
||||
chainId: SupportedChainId,
|
||||
chainId: ChainId,
|
||||
multicall: UniswapInterfaceMulticall
|
||||
): Promise<TokenMap> {
|
||||
if (addresses.length === 0) return {}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
import {
|
||||
ChainId,
|
||||
MULTICALL_ADDRESSES,
|
||||
NONFUNGIBLE_POSITION_MANAGER_ADDRESSES as V3NFT_ADDRESSES,
|
||||
Token,
|
||||
} from '@uniswap/sdk-core'
|
||||
import { AddressMap } from '@uniswap/smart-order-router'
|
||||
import MulticallJSON from '@uniswap/v3-periphery/artifacts/contracts/lens/UniswapInterfaceMulticall.sol/UniswapInterfaceMulticall.json'
|
||||
import NFTPositionManagerJSON from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { MULTICALL_ADDRESS, NONFUNGIBLE_POSITION_MANAGER_ADDRESSES as V3NFT_ADDRESSES } from 'constants/addresses'
|
||||
import { isSupportedChain, SupportedChainId } from 'constants/chains'
|
||||
import { isSupportedChain } from 'constants/chains'
|
||||
import { RPC_PROVIDERS } from 'constants/providers'
|
||||
import { BaseContract } from 'ethers/lib/ethers'
|
||||
import { ContractInput, useUniswapPricesQuery } from 'graphql/data/__generated__/types-and-hooks'
|
||||
@@ -23,7 +27,7 @@ type ContractMap<T extends BaseContract> = { [key: number]: T }
|
||||
function useContractMultichain<T extends BaseContract>(
|
||||
addressMap: AddressMap,
|
||||
ABI: any,
|
||||
chainIds?: SupportedChainId[]
|
||||
chainIds?: ChainId[]
|
||||
): ContractMap<T> {
|
||||
const { chainId: walletChainId, provider: walletProvider } = useWeb3React()
|
||||
|
||||
@@ -35,19 +39,26 @@ function useContractMultichain<T extends BaseContract>(
|
||||
.filter(isSupportedChain)
|
||||
|
||||
return relevantChains.reduce((acc: ContractMap<T>, chainId) => {
|
||||
const provider = walletProvider && walletChainId === chainId ? walletProvider : RPC_PROVIDERS[chainId]
|
||||
acc[chainId] = getContract(addressMap[chainId], ABI, provider) as T
|
||||
const provider =
|
||||
walletProvider && walletChainId === chainId
|
||||
? walletProvider
|
||||
: isSupportedChain(chainId)
|
||||
? RPC_PROVIDERS[chainId]
|
||||
: undefined
|
||||
if (provider) {
|
||||
acc[chainId] = getContract(addressMap[chainId] ?? '', ABI, provider) as T
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
}, [ABI, addressMap, chainIds, walletChainId, walletProvider])
|
||||
}
|
||||
|
||||
export function useV3ManagerContracts(chainIds: SupportedChainId[]): ContractMap<NonfungiblePositionManager> {
|
||||
export function useV3ManagerContracts(chainIds: ChainId[]): ContractMap<NonfungiblePositionManager> {
|
||||
return useContractMultichain<NonfungiblePositionManager>(V3NFT_ADDRESSES, NFTPositionManagerJSON.abi, chainIds)
|
||||
}
|
||||
|
||||
export function useInterfaceMulticallContracts(chainIds: SupportedChainId[]): ContractMap<UniswapInterfaceMulticall> {
|
||||
return useContractMultichain<UniswapInterfaceMulticall>(MULTICALL_ADDRESS, MulticallJSON.abi, chainIds)
|
||||
export function useInterfaceMulticallContracts(chainIds: ChainId[]): ContractMap<UniswapInterfaceMulticall> {
|
||||
return useContractMultichain<UniswapInterfaceMulticall>(MULTICALL_ADDRESSES, MulticallJSON.abi, chainIds)
|
||||
}
|
||||
|
||||
type PriceMap = { [key: CurrencyKey]: number | undefined }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { SupportedChainId, WETH9 } from '@uniswap/sdk-core'
|
||||
import { ChainId, WETH9 } from '@uniswap/sdk-core'
|
||||
import { FeeAmount, Pool, Position } from '@uniswap/v3-sdk'
|
||||
import { USDC_MAINNET } from 'constants/tokens'
|
||||
import { mocked } from 'test-utils/mocked'
|
||||
@@ -16,7 +16,7 @@ const owner = '0xf5b6bb25f5beaea03dd014c6ef9fa9f3926bf36c'
|
||||
|
||||
const pool = new Pool(
|
||||
USDC_MAINNET,
|
||||
WETH9[SupportedChainId.MAINNET],
|
||||
WETH9[ChainId.MAINNET],
|
||||
FeeAmount.MEDIUM,
|
||||
'1851127709498178402383049949138810',
|
||||
'7076437181775065414',
|
||||
@@ -34,7 +34,7 @@ const details = {
|
||||
tokenId: BigNumber.from('0'),
|
||||
operator: '0x0',
|
||||
token0: USDC_MAINNET.address,
|
||||
token1: WETH9[SupportedChainId.MAINNET].address,
|
||||
token1: WETH9[ChainId.MAINNET].address,
|
||||
fee: FeeAmount.MEDIUM,
|
||||
tickLower: -100,
|
||||
tickUpper: 100,
|
||||
@@ -48,7 +48,7 @@ const useMultiChainPositionsReturnValue = {
|
||||
positions: [
|
||||
{
|
||||
owner,
|
||||
chainId: SupportedChainId.MAINNET,
|
||||
chainId: ChainId.MAINNET,
|
||||
position,
|
||||
pool,
|
||||
details,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { ChainId, CurrencyAmount, Token, V3_CORE_FACTORY_ADDRESSES } from '@uniswap/sdk-core'
|
||||
import IUniswapV3PoolStateJSON from '@uniswap/v3-core/artifacts/contracts/interfaces/pool/IUniswapV3PoolState.sol/IUniswapV3PoolState.json'
|
||||
import { computePoolAddress, Pool, Position } from '@uniswap/v3-sdk'
|
||||
import { V3_CORE_FACTORY_ADDRESSES } from 'constants/addresses'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { DEFAULT_ERC20_DECIMALS } from 'constants/tokens'
|
||||
import { BigNumber } from 'ethers/lib/ethers'
|
||||
import { Interface } from 'ethers/lib/utils'
|
||||
@@ -18,7 +16,7 @@ import { useInterfaceMulticallContracts, usePoolPriceMap, useV3ManagerContracts
|
||||
|
||||
function createPositionInfo(
|
||||
owner: string,
|
||||
chainId: SupportedChainId,
|
||||
chainId: ChainId,
|
||||
details: PositionDetails,
|
||||
slot0: any,
|
||||
tokenA: Token,
|
||||
@@ -42,11 +40,13 @@ type FeeAmounts = [BigNumber, BigNumber]
|
||||
const MAX_UINT128 = BigNumber.from(2).pow(128).sub(1)
|
||||
|
||||
const DEFAULT_CHAINS = [
|
||||
SupportedChainId.MAINNET,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.POLYGON,
|
||||
SupportedChainId.CELO,
|
||||
ChainId.MAINNET,
|
||||
ChainId.ARBITRUM_ONE,
|
||||
ChainId.OPTIMISM,
|
||||
ChainId.POLYGON,
|
||||
ChainId.CELO,
|
||||
ChainId.BNB,
|
||||
ChainId.AVALANCHE,
|
||||
]
|
||||
|
||||
type UseMultiChainPositionsData = { positions?: PositionInfo[]; loading: boolean }
|
||||
@@ -117,7 +117,7 @@ export default function useMultiChainPositions(account: string, chains = DEFAULT
|
||||
|
||||
// Combines PositionDetails with Pool data to build our return type
|
||||
const fetchPositionInfo = useCallback(
|
||||
async (positionDetails: PositionDetails[], chainId: SupportedChainId, multicall: UniswapInterfaceMulticall) => {
|
||||
async (positionDetails: PositionDetails[], chainId: ChainId, multicall: UniswapInterfaceMulticall) => {
|
||||
const poolInterface = new Interface(IUniswapV3PoolStateJSON.abi) as UniswapV3PoolInterface
|
||||
const tokens = await getTokens(
|
||||
positionDetails.flatMap((details) => [details.token0, details.token1]),
|
||||
@@ -158,7 +158,7 @@ export default function useMultiChainPositions(account: string, chains = DEFAULT
|
||||
)
|
||||
|
||||
const fetchPositionsForChain = useCallback(
|
||||
async (chainId: SupportedChainId): Promise<PositionInfo[]> => {
|
||||
async (chainId: ChainId): Promise<PositionInfo[]> => {
|
||||
try {
|
||||
const pm = pms[chainId]
|
||||
const multicall = multicalls[chainId]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SupportedChainId } from '@uniswap/sdk-core'
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
import { DAI_ARBITRUM } from '@uniswap/smart-order-router'
|
||||
import { BRIDGED_USDC_ARBITRUM, DAI, USDC_MAINNET } from 'constants/tokens'
|
||||
import { render } from 'test-utils/render'
|
||||
@@ -7,13 +7,13 @@ import { PortfolioLogo } from './PortfolioLogo'
|
||||
|
||||
describe('PortfolioLogo', () => {
|
||||
it('renders without L2 icon', () => {
|
||||
const { container } = render(<PortfolioLogo chainId={SupportedChainId.MAINNET} currencies={[DAI, USDC_MAINNET]} />)
|
||||
const { container } = render(<PortfolioLogo chainId={ChainId.MAINNET} currencies={[DAI, USDC_MAINNET]} />)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('renders with L2 icon', () => {
|
||||
const { container } = render(
|
||||
<PortfolioLogo chainId={SupportedChainId.ARBITRUM_ONE} currencies={[DAI_ARBITRUM, BRIDGED_USDC_ARBITRUM]} />
|
||||
<PortfolioLogo chainId={ChainId.ARBITRUM_ONE} currencies={[DAI_ARBITRUM, BRIDGED_USDC_ARBITRUM]} />
|
||||
)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { ChainId, Currency } from '@uniswap/sdk-core'
|
||||
import blankTokenUrl from 'assets/svg/blank_token.svg'
|
||||
import { ReactComponent as UnknownStatus } from 'assets/svg/contract-interaction.svg'
|
||||
import { LogoImage, MissingImageLogo } from 'components/Logo/AssetLogo'
|
||||
import { Unicon } from 'components/Unicon'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import useTokenLogoSource from 'hooks/useAssetLogoSource'
|
||||
import useENSAvatar from 'hooks/useENSAvatar'
|
||||
import React from 'react'
|
||||
@@ -37,7 +36,7 @@ const DoubleLogoContainer = styled.div`
|
||||
`
|
||||
|
||||
type MultiLogoProps = {
|
||||
chainId: SupportedChainId
|
||||
chainId: ChainId
|
||||
accountAddress?: string
|
||||
currencies?: Array<Currency | undefined>
|
||||
images?: (string | undefined)[]
|
||||
@@ -85,7 +84,7 @@ const L2LogoContainer = styled.div<{ $backgroundColor?: string }>`
|
||||
* Renders an image by prioritizing a list of sources, and then eventually a fallback triangle alert
|
||||
*/
|
||||
export function PortfolioLogo({
|
||||
chainId = SupportedChainId.MAINNET,
|
||||
chainId = ChainId.MAINNET,
|
||||
accountAddress,
|
||||
currencies,
|
||||
images,
|
||||
@@ -142,7 +141,7 @@ export function PortfolioLogo({
|
||||
}
|
||||
|
||||
const L2Logo =
|
||||
chainId !== SupportedChainId.MAINNET && chainLogo ? (
|
||||
chainId !== ChainId.MAINNET && chainLogo ? (
|
||||
<L2LogoContainer $backgroundColor={squareLogoUrl ? theme.backgroundSurface : theme.textPrimary}>
|
||||
{squareLogoUrl ? (
|
||||
<SquareChainLogo src={chainLogo} alt="chainLogo" />
|
||||
|
||||
@@ -4,7 +4,12 @@ import { formatNumber, NumberType } from '@uniswap/conedison/format'
|
||||
import Row from 'components/Row'
|
||||
import { formatDelta } from 'components/Tokens/TokenDetails/PriceChart'
|
||||
import { PortfolioBalancesQuery, usePortfolioBalancesQuery } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { getTokenDetailsURL, gqlToCurrency } from 'graphql/data/util'
|
||||
import {
|
||||
getTokenDetailsURL,
|
||||
GQL_MAINNET_CHAINS,
|
||||
gqlToCurrency,
|
||||
logSentryErrorForUnsupportedChain,
|
||||
} from 'graphql/data/util'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletContent'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
@@ -31,7 +36,7 @@ export default function Tokens({ account }: { account: string }) {
|
||||
const [showHiddenTokens, setShowHiddenTokens] = useState(false)
|
||||
|
||||
const { data } = usePortfolioBalancesQuery({
|
||||
variables: { ownerAddress: account },
|
||||
variables: { ownerAddress: account, chains: GQL_MAINNET_CHAINS },
|
||||
fetchPolicy: 'cache-only', // PrefetchBalancesWrapper handles balance fetching/staleness; this component only reads from cache
|
||||
errorPolicy: 'all',
|
||||
})
|
||||
@@ -103,6 +108,13 @@ function TokenRow({ token, quantity, denominatedValue, tokenProjectMarket }: Tok
|
||||
}, [navigate, token, toggleWalletDrawer])
|
||||
|
||||
const currency = gqlToCurrency(token)
|
||||
if (!currency) {
|
||||
logSentryErrorForUnsupportedChain({
|
||||
extras: { token },
|
||||
errorMessage: 'Token from unsupported chain received from Mini Portfolio Token Balance Query',
|
||||
})
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
|
||||
@@ -80,7 +80,7 @@ exports[`PortfolioLogo renders with L2 icon 1`] = `
|
||||
>
|
||||
<img
|
||||
class="c2 c3"
|
||||
src="blank_token.svg"
|
||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/arbitrum/assets/0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1/logo.png"
|
||||
/>
|
||||
<img
|
||||
class="c2 c3"
|
||||
@@ -152,11 +152,11 @@ exports[`PortfolioLogo renders without L2 icon 1`] = `
|
||||
>
|
||||
<img
|
||||
class="c2 c3"
|
||||
src="blank_token.svg"
|
||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x6B175474E89094C44Da98b954EedeAC495271d0F/logo.png"
|
||||
/>
|
||||
<img
|
||||
class="c2 c3"
|
||||
src="blank_token.svg"
|
||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { t } from '@lingui/macro'
|
||||
import { TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { SwapOrderStatus, TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { UniswapXOrderStatus } from 'lib/hooks/orders/types'
|
||||
import { TransactionType } from 'state/transactions/types'
|
||||
|
||||
// use even number because rows are in groups of 2
|
||||
@@ -159,6 +160,38 @@ export function getActivityTitle(type: TransactionType, status: TransactionStatu
|
||||
return TransactionTitleTable[type][status]
|
||||
}
|
||||
|
||||
const SwapTitleTable = TransactionTitleTable[TransactionType.SWAP]
|
||||
export const OrderTextTable: {
|
||||
[status in UniswapXOrderStatus]: { title: string; status: TransactionStatus; statusMessage?: string }
|
||||
} = {
|
||||
[UniswapXOrderStatus.OPEN]: {
|
||||
title: SwapTitleTable.PENDING,
|
||||
status: TransactionStatus.Pending,
|
||||
},
|
||||
[UniswapXOrderStatus.FILLED]: {
|
||||
title: SwapTitleTable.CONFIRMED,
|
||||
status: TransactionStatus.Confirmed,
|
||||
},
|
||||
[UniswapXOrderStatus.EXPIRED]: {
|
||||
title: t`Swap expired`,
|
||||
statusMessage: t`Your swap could not be fulfilled at this time. Please try again.`,
|
||||
status: TransactionStatus.Failed,
|
||||
},
|
||||
[UniswapXOrderStatus.ERROR]: {
|
||||
title: SwapTitleTable.FAILED,
|
||||
status: TransactionStatus.Failed,
|
||||
},
|
||||
[UniswapXOrderStatus.INSUFFICIENT_FUNDS]: {
|
||||
title: SwapTitleTable.FAILED,
|
||||
statusMessage: t`Your account had insufficent funds to complete this swap.`,
|
||||
status: TransactionStatus.Failed,
|
||||
},
|
||||
[UniswapXOrderStatus.CANCELLED]: {
|
||||
title: t`Swap cancelled`,
|
||||
status: TransactionStatus.Failed,
|
||||
},
|
||||
}
|
||||
|
||||
// Non-exhaustive list of addresses Moonpay uses when sending purchased tokens
|
||||
export const MOONPAY_SENDER_ADDRESSES = [
|
||||
'0x8216874887415e2650d12d53ff53516f04a74fd7',
|
||||
@@ -166,3 +199,11 @@ export const MOONPAY_SENDER_ADDRESSES = [
|
||||
'0xb287eac48ab21c5fb1d3723830d60b4c797555b0',
|
||||
'0xd108fd0e8c8e71552a167e7a44ff1d345d233ba6',
|
||||
]
|
||||
|
||||
// Converts GQL backend orderStatus enum to the enum used by the frontend and UniswapX backend
|
||||
export const OrderStatusTable: { [key in SwapOrderStatus]: UniswapXOrderStatus } = {
|
||||
[SwapOrderStatus.Open]: UniswapXOrderStatus.OPEN,
|
||||
[SwapOrderStatus.Expired]: UniswapXOrderStatus.EXPIRED,
|
||||
[SwapOrderStatus.Error]: UniswapXOrderStatus.ERROR,
|
||||
[SwapOrderStatus.InsufficientFunds]: UniswapXOrderStatus.INSUFFICIENT_FUNDS,
|
||||
}
|
||||
@@ -4,10 +4,9 @@ import { BrowserEvent, InterfaceElementName, InterfaceSectionName, SharedEventNa
|
||||
import Column from 'components/Column'
|
||||
import { LoaderV2 } from 'components/Icons/LoadingSpinner'
|
||||
import { AutoRow } from 'components/Row'
|
||||
import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes'
|
||||
import { useIsNftPage } from 'hooks/useIsNftPage'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
|
||||
import { useHasPendingTransactions } from 'state/transactions/hooks'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { BREAKPOINTS, ThemedText } from 'theme'
|
||||
@@ -99,8 +98,8 @@ export default function MiniPortfolio({ account }: { account: string }) {
|
||||
const isNftPage = useIsNftPage()
|
||||
const theme = useTheme()
|
||||
const [currentPage, setCurrentPage] = useState(isNftPage ? 1 : 0)
|
||||
const shouldDisableNFTRoutes = useDisableNFTRoutes()
|
||||
const [activityUnread, setActivityUnread] = useState(false)
|
||||
const shouldDisableNFTRoutes = useAtomValue(shouldDisableNFTRoutesAtom)
|
||||
|
||||
const { component: Page, key: currentKey } = Pages[currentPage]
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { usePortfolioBalancesLazyQuery } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { GQL_MAINNET_CHAINS } from 'graphql/data/util'
|
||||
import usePrevious from 'hooks/usePrevious'
|
||||
import { PropsWithChildren, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useAllTransactions } from 'state/transactions/hooks'
|
||||
@@ -44,7 +45,7 @@ export default function PrefetchBalancesWrapper({ children }: PropsWithChildren)
|
||||
const [hasUnfetchedBalances, setHasUnfetchedBalances] = useState(true)
|
||||
const fetchBalances = useCallback(() => {
|
||||
if (account) {
|
||||
prefetchPortfolioBalances({ variables: { ownerAddress: account } })
|
||||
prefetchPortfolioBalances({ variables: { ownerAddress: account, chains: GQL_MAINNET_CHAINS } })
|
||||
setHasUnfetchedBalances(false)
|
||||
}
|
||||
}, [account, prefetchPortfolioBalances])
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { InterfaceElementName } from '@uniswap/analytics-events'
|
||||
import { WalletConnect } from '@web3-react/walletconnect'
|
||||
import { WalletConnect as WalletConnectv2 } from '@web3-react/walletconnect-v2'
|
||||
import Column, { AutoColumn } from 'components/Column'
|
||||
import Modal from 'components/Modal'
|
||||
import { RowBetween } from 'components/Row'
|
||||
import { uniwalletConnectConnection, uniwalletWCV2ConnectConnection } from 'connection'
|
||||
import { uniwalletWCV2ConnectConnection } from 'connection'
|
||||
import { ActivationStatus, useActivationState } from 'connection/activate'
|
||||
import { ConnectionType } from 'connection/types'
|
||||
import { UniwalletConnect } from 'connection/WalletConnect'
|
||||
import { UniwalletConnect as UniwalletConnectV2 } from 'connection/WalletConnectV2'
|
||||
import { useWalletConnectV2AsDefault } from 'featureFlags/flags/walletConnectV2'
|
||||
import { QRCodeSVG } from 'qrcode.react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
@@ -45,27 +42,17 @@ export default function UniwalletModal() {
|
||||
const [uri, setUri] = useState<string>()
|
||||
|
||||
// Displays the modal if a Uniswap Wallet Connection is pending & qrcode URI is available
|
||||
const uniswapWalletConnectors = [ConnectionType.UNISWAP_WALLET, ConnectionType.UNISWAP_WALLET_V2]
|
||||
const open =
|
||||
activationState.status === ActivationStatus.PENDING &&
|
||||
uniswapWalletConnectors.includes(activationState.connection.type) &&
|
||||
activationState.connection.type === ConnectionType.UNISWAP_WALLET_V2 &&
|
||||
!!uri
|
||||
|
||||
const walletConnectV2AsDefault = useWalletConnectV2AsDefault()
|
||||
|
||||
useEffect(() => {
|
||||
if (walletConnectV2AsDefault) {
|
||||
const connectorV2 = uniwalletWCV2ConnectConnection.connector as WalletConnectv2
|
||||
connectorV2.events.addListener(UniwalletConnectV2.UNI_URI_AVAILABLE, (uri: string) => {
|
||||
uri && setUri(uri)
|
||||
})
|
||||
} else {
|
||||
const connectorV1 = uniwalletConnectConnection.connector as WalletConnect
|
||||
connectorV1.events.addListener(UniwalletConnect.UNI_URI_AVAILABLE, (uri: string) => {
|
||||
uri && setUri(uri)
|
||||
})
|
||||
}
|
||||
}, [walletConnectV2AsDefault])
|
||||
const connectorV2 = uniwalletWCV2ConnectConnection.connector as WalletConnectv2
|
||||
connectorV2.events.addListener(UniwalletConnectV2.UNI_URI_AVAILABLE, (uri: string) => {
|
||||
uri && setUri(uri)
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (open) sendAnalyticsEvent('Uniswap wallet modal opened')
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import type { TransactionResponse } from '@ethersproject/providers'
|
||||
import { UNISWAP_NFT_AIRDROP_CLAIM_ADDRESS } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import uniswapNftAirdropClaim from 'abis/uniswap-nft-airdrop-claim.json'
|
||||
import airdropBackgroundv2 from 'assets/images/airdopBackground.png'
|
||||
import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button'
|
||||
import { OpacityHoverState } from 'components/Common'
|
||||
import Loader from 'components/Icons/LoadingSpinner'
|
||||
import { UNISWAP_NFT_AIRDROP_CLAIM_ADDRESS } from 'constants/addresses'
|
||||
import { useContract } from 'hooks/useContract'
|
||||
import { ChevronRightIcon } from 'nft/components/icons'
|
||||
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
|
||||
|
||||
@@ -21,7 +21,6 @@ export default function AnimatedDropdown({ open, children }: React.PropsWithChil
|
||||
velocity: 0.01,
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<animated.div style={{ ...props, overflow: 'hidden', width: '100%', willChange: 'height' }}>
|
||||
<div ref={ref}>{children}</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
|
||||
import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format'
|
||||
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
@@ -13,7 +14,6 @@ import { ReactNode, useCallback, useState } from 'react'
|
||||
import { Lock } from 'react-feather'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { flexColumnNoWrap, flexRowNoWrap } from 'theme/styles'
|
||||
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||
|
||||
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
|
||||
import { useCurrencyBalance } from '../../state/connection/hooks'
|
||||
@@ -323,7 +323,7 @@ export default function SwapCurrencyInputPanel({
|
||||
renderBalance ? (
|
||||
renderBalance(selectedCurrencyBalance)
|
||||
) : (
|
||||
<Trans>Balance: {formatCurrencyAmount(selectedCurrencyBalance, 4)}</Trans>
|
||||
<Trans>Balance: {formatCurrencyAmount(selectedCurrencyBalance, NumberType.TokenNonTx)}</Trans>
|
||||
)
|
||||
) : null}
|
||||
</ThemedText.DeprecatedBody>
|
||||
|
||||
@@ -2,9 +2,8 @@ import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'fe
|
||||
import { DetailsV2Variant, useDetailsV2Flag } from 'featureFlags/flags/nftDetails'
|
||||
import { useRoutingAPIForPriceFlag } from 'featureFlags/flags/priceRoutingApi'
|
||||
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
|
||||
import { UnifiedRouterVariant, useRoutingAPIV2Flag } from 'featureFlags/flags/unifiedRouter'
|
||||
import { useWalletConnectFallbackFlag } from 'featureFlags/flags/walletConnectPopover'
|
||||
import { useWalletConnectV2Flag } from 'featureFlags/flags/walletConnectV2'
|
||||
import { UniswapXVariant, useUniswapXFlag } from 'featureFlags/flags/uniswapx'
|
||||
import { useUniswapXSyntheticQuoteFlag } from 'featureFlags/flags/uniswapXUseSyntheticQuote'
|
||||
import { useUpdateAtom } from 'jotai/utils'
|
||||
import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
@@ -212,28 +211,22 @@ export default function FeatureFlagModal() {
|
||||
label="Use the new details page for nfts"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={UnifiedRouterVariant}
|
||||
value={useRoutingAPIV2Flag()}
|
||||
featureFlag={FeatureFlag.uraEnabled}
|
||||
label="Enable the Unified Routing API"
|
||||
variant={UniswapXVariant}
|
||||
value={useUniswapXFlag()}
|
||||
featureFlag={FeatureFlag.uniswapXEnabled}
|
||||
label="Enable UniswapX"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={BaseVariant}
|
||||
value={useUniswapXSyntheticQuoteFlag()}
|
||||
featureFlag={FeatureFlag.uniswapXSyntheticQuote}
|
||||
label="Force synthetic quotes for UniswapX"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={BaseVariant}
|
||||
value={useRoutingAPIForPriceFlag()}
|
||||
featureFlag={FeatureFlag.routingAPIPrice}
|
||||
label="Use the URA or routing-api for price fetches"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={BaseVariant}
|
||||
value={useWalletConnectV2Flag()}
|
||||
featureFlag={FeatureFlag.walletConnectV2}
|
||||
label="Uses WalletConnect V2 as default wallet connect connection"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={BaseVariant}
|
||||
value={useWalletConnectFallbackFlag()}
|
||||
featureFlag={FeatureFlag.walletConnectFallback}
|
||||
label="Adds a ... menu to the connection option"
|
||||
label="Use the routing-api v2 for price fetches"
|
||||
/>
|
||||
<FeatureFlagGroup name="Debug">
|
||||
<FeatureFlagOption
|
||||
|
||||
@@ -1,39 +1,40 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { ChainId, SUPPORTED_CHAINS } from '@uniswap/sdk-core'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { ALL_SUPPORTED_CHAIN_IDS, SupportedChainId } from 'constants/chains'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
export const FEE_AMOUNT_DETAIL: Record<
|
||||
FeeAmount,
|
||||
{ label: string; description: ReactNode; supportedChains: SupportedChainId[] }
|
||||
{ label: string; description: ReactNode; supportedChains: readonly ChainId[] }
|
||||
> = {
|
||||
[FeeAmount.LOWEST]: {
|
||||
label: '0.01',
|
||||
description: <Trans>Best for very stable pairs.</Trans>,
|
||||
supportedChains: [
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
SupportedChainId.BNB,
|
||||
SupportedChainId.CELO,
|
||||
SupportedChainId.CELO_ALFAJORES,
|
||||
SupportedChainId.MAINNET,
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.POLYGON,
|
||||
SupportedChainId.POLYGON_MUMBAI,
|
||||
ChainId.ARBITRUM_ONE,
|
||||
ChainId.BNB,
|
||||
ChainId.CELO,
|
||||
ChainId.CELO_ALFAJORES,
|
||||
ChainId.MAINNET,
|
||||
ChainId.OPTIMISM,
|
||||
ChainId.POLYGON,
|
||||
ChainId.POLYGON_MUMBAI,
|
||||
ChainId.AVALANCHE,
|
||||
],
|
||||
},
|
||||
[FeeAmount.LOW]: {
|
||||
label: '0.05',
|
||||
description: <Trans>Best for stable pairs.</Trans>,
|
||||
supportedChains: ALL_SUPPORTED_CHAIN_IDS,
|
||||
supportedChains: SUPPORTED_CHAINS,
|
||||
},
|
||||
[FeeAmount.MEDIUM]: {
|
||||
label: '0.3',
|
||||
description: <Trans>Best for most pairs.</Trans>,
|
||||
supportedChains: ALL_SUPPORTED_CHAIN_IDS,
|
||||
supportedChains: SUPPORTED_CHAINS,
|
||||
},
|
||||
[FeeAmount.HIGH]: {
|
||||
label: '1',
|
||||
description: <Trans>Best for exotic pairs.</Trans>,
|
||||
supportedChains: ALL_SUPPORTED_CHAIN_IDS,
|
||||
supportedChains: SUPPORTED_CHAINS,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -112,9 +112,9 @@ exports[`StatusIcon with account renders children in correct order 1`] = `
|
||||
class="c1"
|
||||
>
|
||||
<img
|
||||
alt="Install MetaMask icon"
|
||||
alt="WalletConnect icon"
|
||||
class="c2"
|
||||
src="metamask-icon.svg"
|
||||
src="walletconnect-icon.svg"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
@@ -240,9 +240,9 @@ exports[`StatusIcon with no account renders children in correct order 1`] = `
|
||||
class="c1"
|
||||
>
|
||||
<img
|
||||
alt="Install MetaMask icon"
|
||||
alt="WalletConnect icon"
|
||||
class="c2"
|
||||
src="metamask-icon.svg"
|
||||
src="walletconnect-icon.svg"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import useTokenLogoSource from 'hooks/useAssetLogoSource'
|
||||
import React from 'react'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
@@ -63,7 +63,7 @@ const L2NetworkLogo = styled.div<{ networkUrl?: string; parentSize: string }>`
|
||||
export default function AssetLogo({
|
||||
isNative,
|
||||
address,
|
||||
chainId = SupportedChainId.MAINNET,
|
||||
chainId = ChainId.MAINNET,
|
||||
symbol,
|
||||
backupImg,
|
||||
size = '24px',
|
||||
|
||||
@@ -3,7 +3,7 @@ import { TokenStandard } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { SearchToken } from 'graphql/data/SearchTokens'
|
||||
import { TokenQueryData } from 'graphql/data/Token'
|
||||
import { TopToken } from 'graphql/data/TopTokens'
|
||||
import { CHAIN_NAME_TO_CHAIN_ID } from 'graphql/data/util'
|
||||
import { supportedChainIdFromGQLChain } from 'graphql/data/util'
|
||||
|
||||
import AssetLogo, { AssetLogoBaseProps } from './AssetLogo'
|
||||
|
||||
@@ -12,7 +12,7 @@ export default function QueryTokenLogo(
|
||||
token?: TopToken | TokenQueryData | SearchToken
|
||||
}
|
||||
) {
|
||||
const chainId = props.token?.chain ? CHAIN_NAME_TO_CHAIN_ID[props.token?.chain] : undefined
|
||||
const chainId = props.token?.chain ? supportedChainIdFromGQLChain(props.token?.chain) : undefined
|
||||
|
||||
return (
|
||||
<AssetLogo
|
||||
|
||||
23
src/components/Logo/UniswapXBrandMark.tsx
Normal file
23
src/components/Logo/UniswapXBrandMark.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
import UniswapXRouterLabel, { UnswapXRouterLabelProps } from '../RouterLabel/UniswapXRouterLabel'
|
||||
|
||||
type UniswapXBrandMarkProps = Omit<UnswapXRouterLabelProps, 'children' | 'fontWeight'> & {
|
||||
fontWeight?: 'bold'
|
||||
}
|
||||
|
||||
export default function UniswapXBrandMark({ fontWeight, ...props }: UniswapXBrandMarkProps): JSX.Element {
|
||||
return (
|
||||
<UniswapXRouterLabel {...props}>
|
||||
<ThemedText.BodySecondary
|
||||
fontSize="inherit"
|
||||
{...(fontWeight === 'bold' && {
|
||||
fontWeight: '500',
|
||||
})}
|
||||
>
|
||||
<Trans>UniswapX</Trans>
|
||||
</ThemedText.BodySecondary>
|
||||
</UniswapXRouterLabel>
|
||||
)
|
||||
}
|
||||
@@ -1,18 +1,13 @@
|
||||
import { t } from '@lingui/macro'
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { WalletConnect } from '@web3-react/walletconnect-v2'
|
||||
import { showTestnetsAtom } from 'components/AccountDrawer/TestnetsToggle'
|
||||
import { MouseoverTooltip } from 'components/Tooltip'
|
||||
import { getConnection } from 'connection'
|
||||
import { ConnectionType } from 'connection/types'
|
||||
import { WalletConnectV2 } from 'connection/WalletConnectV2'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import {
|
||||
L1_CHAIN_IDS,
|
||||
L2_CHAIN_IDS,
|
||||
SupportedChainId,
|
||||
TESTNET_CHAIN_IDS,
|
||||
UniWalletSupportedChains,
|
||||
} from 'constants/chains'
|
||||
import { L1_CHAIN_IDS, L2_CHAIN_IDS, TESTNET_CHAIN_IDS, UniWalletSupportedChains } from 'constants/chains'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import useSelectChain from 'hooks/useSelectChain'
|
||||
import useSyncChainQuery from 'hooks/useSyncChainQuery'
|
||||
@@ -36,15 +31,15 @@ interface ChainSelectorProps {
|
||||
leftAlign?: boolean
|
||||
}
|
||||
|
||||
function useWalletSupportedChains(): SupportedChainId[] {
|
||||
function useWalletSupportedChains(): ChainId[] {
|
||||
const { connector } = useWeb3React()
|
||||
|
||||
const connectionType = getConnection(connector).type
|
||||
|
||||
switch (connectionType) {
|
||||
case ConnectionType.WALLET_CONNECT_V2:
|
||||
return getSupportedChainIdsFromWalletConnectSession((connector as WalletConnect).provider?.session)
|
||||
case ConnectionType.UNISWAP_WALLET:
|
||||
return getSupportedChainIdsFromWalletConnectSession((connector as WalletConnectV2).provider?.session)
|
||||
case ConnectionType.UNISWAP_WALLET_V2:
|
||||
return UniWalletSupportedChains
|
||||
default:
|
||||
return NETWORK_SELECTOR_CHAINS
|
||||
@@ -63,7 +58,7 @@ export const ChainSelector = ({ leftAlign }: ChainSelectorProps) => {
|
||||
|
||||
const [supportedChains, unsupportedChains] = useMemo(() => {
|
||||
const { supported, unsupported } = NETWORK_SELECTOR_CHAINS.filter(
|
||||
(chain) => showTestnets || !TESTNET_CHAIN_IDS.has(chain)
|
||||
(chain: number) => showTestnets || !TESTNET_CHAIN_IDS.includes(chain)
|
||||
).reduce(
|
||||
(acc, chain) => {
|
||||
if (walletSupportsChain.includes(chain)) {
|
||||
@@ -73,7 +68,7 @@ export const ChainSelector = ({ leftAlign }: ChainSelectorProps) => {
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{ supported: [], unsupported: [] } as Record<string, SupportedChainId[]>
|
||||
{ supported: [], unsupported: [] } as Record<string, ChainId[]>
|
||||
)
|
||||
return [supported, unsupported]
|
||||
}, [showTestnets, walletSupportsChain])
|
||||
@@ -87,10 +82,10 @@ export const ChainSelector = ({ leftAlign }: ChainSelectorProps) => {
|
||||
const selectChain = useSelectChain()
|
||||
useSyncChainQuery()
|
||||
|
||||
const [pendingChainId, setPendingChainId] = useState<SupportedChainId | undefined>(undefined)
|
||||
const [pendingChainId, setPendingChainId] = useState<ChainId | undefined>(undefined)
|
||||
|
||||
const onSelectChain = useCallback(
|
||||
async (targetChainId: SupportedChainId) => {
|
||||
async (targetChainId: ChainId) => {
|
||||
setPendingChainId(targetChainId)
|
||||
await selectChain(targetChainId)
|
||||
setPendingChainId(undefined)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import Loader from 'components/Icons/LoadingSpinner'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { CheckMarkIcon } from 'nft/components/icons'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
@@ -63,7 +63,7 @@ const Logo = styled.img`
|
||||
`
|
||||
interface ChainSelectorRowProps {
|
||||
disabled?: boolean
|
||||
targetChain: SupportedChainId
|
||||
targetChain: ChainId
|
||||
onSelectChain: (targetChain: number) => void
|
||||
isPending: boolean
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens'
|
||||
import { Chain, NftCollection, useRecentlySearchedAssetsQuery } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { SearchToken } from 'graphql/data/SearchTokens'
|
||||
import { CHAIN_NAME_TO_CHAIN_ID } from 'graphql/data/util'
|
||||
import { logSentryErrorForUnsupportedChain, supportedChainIdFromGQLChain } from 'graphql/data/util'
|
||||
import { useAtom } from 'jotai'
|
||||
import { atomWithStorage, useAtomValue } from 'jotai/utils'
|
||||
import { GenieCollection } from 'nft/types'
|
||||
@@ -86,7 +85,15 @@ export function useRecentlySearchedAssets() {
|
||||
shortenedHistory.forEach((asset) => {
|
||||
if (asset.address === 'NATIVE') {
|
||||
// Handles special case where wMATIC data needs to be used for MATIC
|
||||
const native = nativeOnChain(CHAIN_NAME_TO_CHAIN_ID[asset.chain] ?? SupportedChainId.MAINNET)
|
||||
const chain = supportedChainIdFromGQLChain(asset.chain)
|
||||
if (!chain) {
|
||||
logSentryErrorForUnsupportedChain({
|
||||
extras: { asset },
|
||||
errorMessage: 'Invalid chain retrieved from Seach Token/Collection Query',
|
||||
})
|
||||
return
|
||||
}
|
||||
const native = nativeOnChain(chain)
|
||||
const queryAddress = getQueryAddress(asset.chain)?.toLowerCase() ?? `NATIVE-${asset.chain}`
|
||||
const result = resultsMap[queryAddress]
|
||||
if (result) data.push({ ...result, address: 'NATIVE', ...native })
|
||||
|
||||
32
src/components/NavBar/SearchBar.test.tsx
Normal file
32
src/components/NavBar/SearchBar.test.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes'
|
||||
import { useIsMobile, useIsTablet } from 'nft/hooks'
|
||||
import { useIsNavSearchInputVisible } from 'nft/hooks/useIsNavSearchInputVisible'
|
||||
import { mocked } from 'test-utils/mocked'
|
||||
import { render, screen } from 'test-utils/render'
|
||||
|
||||
import { SearchBar } from './SearchBar'
|
||||
|
||||
jest.mock('hooks/useDisableNFTRoutes')
|
||||
jest.mock('nft/hooks')
|
||||
jest.mock('nft/hooks/useIsNavSearchInputVisible')
|
||||
|
||||
describe('disable nft on searchbar', () => {
|
||||
beforeEach(() => {
|
||||
mocked(useIsMobile).mockReturnValue(false)
|
||||
mocked(useIsTablet).mockReturnValue(false)
|
||||
mocked(useIsNavSearchInputVisible).mockReturnValue(true)
|
||||
})
|
||||
|
||||
it('should render text with nfts', () => {
|
||||
mocked(useDisableNFTRoutes).mockReturnValue(false)
|
||||
const { container } = render(<SearchBar />)
|
||||
expect(container).toMatchSnapshot()
|
||||
expect(screen.queryByPlaceholderText('Search tokens and NFT collections')).toBeVisible()
|
||||
})
|
||||
it('should render text without nfts', () => {
|
||||
mocked(useDisableNFTRoutes).mockReturnValue(true)
|
||||
const { container } = render(<SearchBar />)
|
||||
expect(container).toMatchSnapshot()
|
||||
expect(screen.queryByPlaceholderText('Search tokens')).toBeVisible()
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { t } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent, Trace, TraceEvent, useTrace } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, InterfaceEventName, InterfaceSectionName } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
@@ -7,6 +7,7 @@ import clsx from 'clsx'
|
||||
import { useCollectionSearch } from 'graphql/data/nft/CollectionSearch'
|
||||
import { useSearchTokens } from 'graphql/data/SearchTokens'
|
||||
import useDebounce from 'hooks/useDebounce'
|
||||
import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes'
|
||||
import { useIsNftPage } from 'hooks/useIsNftPage'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import { organizeSearchResults } from 'lib/utils/searchBar'
|
||||
@@ -50,6 +51,7 @@ export const SearchBar = () => {
|
||||
const isMobile = useIsMobile()
|
||||
const isTablet = useIsTablet()
|
||||
const isNavSearchInputVisible = useIsNavSearchInputVisible()
|
||||
const shouldDisableNFTRoutes = useDisableNFTRoutes()
|
||||
|
||||
useOnClickOutside(searchRef, () => {
|
||||
isOpen && toggleOpen()
|
||||
@@ -102,8 +104,16 @@ export const SearchBar = () => {
|
||||
...trace,
|
||||
}
|
||||
const placeholderText = useMemo(() => {
|
||||
return isMobileOrTablet ? `Search` : `Search tokens and NFT collections`
|
||||
}, [isMobileOrTablet])
|
||||
if (isMobileOrTablet) {
|
||||
return t`Search`
|
||||
} else {
|
||||
if (shouldDisableNFTRoutes) {
|
||||
return t`Search tokens`
|
||||
} else {
|
||||
return t`Search tokens and NFT collections`
|
||||
}
|
||||
}
|
||||
}, [isMobileOrTablet, shouldDisableNFTRoutes])
|
||||
|
||||
const handleKeyPress = useCallback(
|
||||
(event: any) => {
|
||||
@@ -174,26 +184,19 @@ export const SearchBar = () => {
|
||||
element={InterfaceElementName.NAVBAR_SEARCH_INPUT}
|
||||
properties={{ ...trace }}
|
||||
>
|
||||
<Trans
|
||||
id={placeholderText}
|
||||
render={({ translation }) => (
|
||||
<Box
|
||||
as="input"
|
||||
data-cy="search-bar-input"
|
||||
placeholder={translation as string}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
!isOpen && toggleOpen()
|
||||
setSearchValue(event.target.value)
|
||||
}}
|
||||
onBlur={() =>
|
||||
sendAnalyticsEvent(InterfaceEventName.NAVBAR_SEARCH_EXITED, navbarSearchEventProperties)
|
||||
}
|
||||
className={`${styles.searchBarInput} ${styles.searchContentLeftAlign}`}
|
||||
value={searchValue}
|
||||
ref={inputRef}
|
||||
width="full"
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
as="input"
|
||||
data-cy="search-bar-input"
|
||||
placeholder={placeholderText}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
!isOpen && toggleOpen()
|
||||
setSearchValue(event.target.value)
|
||||
}}
|
||||
onBlur={() => sendAnalyticsEvent(InterfaceEventName.NAVBAR_SEARCH_EXITED, navbarSearchEventProperties)}
|
||||
className={`${styles.searchBarInput} ${styles.searchContentLeftAlign}`}
|
||||
value={searchValue}
|
||||
ref={inputRef}
|
||||
width="full"
|
||||
/>
|
||||
</TraceEvent>
|
||||
{!isOpen && <KeyShortCut>/</KeyShortCut>}
|
||||
|
||||
32
src/components/NavBar/SearchBarDropdown.test.tsx
Normal file
32
src/components/NavBar/SearchBarDropdown.test.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes'
|
||||
import { mocked } from 'test-utils/mocked'
|
||||
import { render } from 'test-utils/render'
|
||||
|
||||
import { SearchBarDropdown } from './SearchBarDropdown'
|
||||
|
||||
jest.mock('hooks/useDisableNFTRoutes')
|
||||
|
||||
const SearchBarDropdownProps = {
|
||||
toggleOpen: () => void 0,
|
||||
tokens: [],
|
||||
collections: [],
|
||||
queryText: '',
|
||||
hasInput: false,
|
||||
isLoading: false,
|
||||
}
|
||||
|
||||
describe('disable nft on searchbar dropdown', () => {
|
||||
it('should render popular nft collections', () => {
|
||||
mocked(useDisableNFTRoutes).mockReturnValue(false)
|
||||
const { container } = render(<SearchBarDropdown {...SearchBarDropdownProps} />)
|
||||
expect(container).toMatchSnapshot()
|
||||
expect(container).toHaveTextContent('Popular NFT collections')
|
||||
})
|
||||
it('should not render popular nft collections', () => {
|
||||
mocked(useDisableNFTRoutes).mockReturnValue(true)
|
||||
const { container } = render(<SearchBarDropdown {...SearchBarDropdownProps} />)
|
||||
expect(container).toMatchSnapshot()
|
||||
expect(container).not.toHaveTextContent('Popular NFT collections')
|
||||
expect(container).not.toHaveTextContent('NFT')
|
||||
})
|
||||
})
|
||||
@@ -1,14 +1,17 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useTrace } from '@uniswap/analytics'
|
||||
import { InterfaceSectionName, NavBarSearchTypes } from '@uniswap/analytics-events'
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import clsx from 'clsx'
|
||||
import Badge from 'components/Badge'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { HistoryDuration, SafetyLevel } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { useTrendingCollections } from 'graphql/data/nft/TrendingCollections'
|
||||
import { SearchToken } from 'graphql/data/SearchTokens'
|
||||
import useTrendingTokens from 'graphql/data/TrendingTokens'
|
||||
import { BACKEND_NOT_YET_SUPPORTED_CHAIN_IDS } from 'graphql/data/util'
|
||||
import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes'
|
||||
import { useIsNftPage } from 'hooks/useIsNftPage'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
@@ -19,7 +22,6 @@ import { useLocation } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
import BnbLogoURI from '../../assets/svg/bnb-logo.svg'
|
||||
import { ClockIcon, TrendingArrow } from '../../nft/components/icons'
|
||||
import { useRecentlySearchedAssets } from './RecentlySearchedAssets'
|
||||
import * as styles from './SearchBar.css'
|
||||
@@ -103,12 +105,12 @@ function isKnownToken(token: SearchToken) {
|
||||
return token.project?.safetyLevel == SafetyLevel.Verified || token.project?.safetyLevel == SafetyLevel.MediumWarning
|
||||
}
|
||||
|
||||
const BNBLogo = styled.img`
|
||||
const ChainLogo = styled.img`
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
margin-right: 8px;
|
||||
`
|
||||
const BNBComingSoonBadge = styled(Badge)`
|
||||
const ChainComingSoonBadge = styled(Badge)`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.backgroundModule};
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
@@ -148,6 +150,7 @@ export const SearchBarDropdown = ({
|
||||
const isNFTPage = useIsNftPage()
|
||||
const isTokenPage = pathname.includes('/tokens')
|
||||
const [resultsState, setResultsState] = useState<ReactNode>()
|
||||
const shouldDisableNFTRoutes = useDisableNFTRoutes()
|
||||
|
||||
const { data: trendingCollections, loading: trendingCollectionsAreLoading } = useTrendingCollections(
|
||||
3,
|
||||
@@ -311,7 +314,7 @@ export const SearchBarDropdown = ({
|
||||
isLoading={!trendingTokenData}
|
||||
/>
|
||||
)}
|
||||
{!isTokenPage && (
|
||||
{Boolean(!isTokenPage && !shouldDisableNFTRoutes) && (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={shortenedHistory.length + (isNFTPage ? 0 : trendingTokens?.length ?? 0)}
|
||||
@@ -351,23 +354,36 @@ export const SearchBarDropdown = ({
|
||||
trace,
|
||||
searchHistory,
|
||||
trendingCollectionsAreLoading,
|
||||
shouldDisableNFTRoutes,
|
||||
])
|
||||
|
||||
const showBNBComingSoonBadge = chainId === SupportedChainId.BNB && !isLoading
|
||||
const showChainComingSoonBadge = chainId && BACKEND_NOT_YET_SUPPORTED_CHAIN_IDS.includes(chainId) && !isLoading
|
||||
const logoUri = getChainInfo(chainId)?.logoUrl
|
||||
|
||||
return (
|
||||
<Column overflow="hidden" className={clsx(styles.searchBarDropdownNft, styles.searchBarScrollable)}>
|
||||
<Box opacity={isLoading ? '0.3' : '1'} transition="125">
|
||||
{resultsState}
|
||||
{showBNBComingSoonBadge && (
|
||||
<BNBComingSoonBadge>
|
||||
<BNBLogo src={BnbLogoURI} />
|
||||
{showChainComingSoonBadge && (
|
||||
<ChainComingSoonBadge>
|
||||
<ChainLogo src={logoUri} />
|
||||
<ThemedText.BodySmall color="textSecondary" fontSize="14px" fontWeight="400" lineHeight="20px">
|
||||
<Trans>Coming soon: search and explore tokens on BNB Chain</Trans>
|
||||
<ComingSoonText chainId={chainId} />
|
||||
</ThemedText.BodySmall>
|
||||
</BNBComingSoonBadge>
|
||||
</ChainComingSoonBadge>
|
||||
)}
|
||||
</Box>
|
||||
</Column>
|
||||
)
|
||||
}
|
||||
|
||||
function ComingSoonText({ chainId }: { chainId: ChainId }) {
|
||||
switch (chainId) {
|
||||
case ChainId.BNB:
|
||||
return <Trans>Coming soon: search and explore tokens on BNB Chain</Trans>
|
||||
case ChainId.AVALANCHE:
|
||||
return <Trans>Coming soon: search and explore tokens on Avalanche Chain</Trans>
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
187
src/components/NavBar/__snapshots__/SearchBar.test.tsx.snap
Normal file
187
src/components/NavBar/__snapshots__/SearchBar.test.tsx.snap
Normal file
@@ -0,0 +1,187 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`disable nft on searchbar should render text with nfts 1`] = `
|
||||
.c0 {
|
||||
background-color: #ADBCFF3d;
|
||||
color: #7780A0;
|
||||
padding: 0px 8px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
line-height: 16px;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
opacity: 0.6;
|
||||
-webkit-backdrop-filter: blur(60px);
|
||||
backdrop-filter: blur(60px);
|
||||
}
|
||||
|
||||
<div>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_column_sm__rgw6ez477 sprinkles_position_relative_sm__rgw6ez491 sprinkles_width_auto_sm__rgw6ez17v sprinkles_width_auto_md__rgw6ez17w SearchBar_searchBarContainerNft__1fbf9sz4 SearchBar__1fbf9sz3 sprinkles_right_0_sm__rgw6ez39p sprinkles_top_0_sm__rgw6ez3f7 sprinkles_zIndex_3_sm__rgw6ez3qj sprinkles_display_flex_sm__rgw6ez44v sprinkles_maxHeight_searchResultsMaxHeight_sm__rgw6ez1zd sprinkles_overflow_hidden_default__rgw6ez7m3 searchBarContainerDisableBlur"
|
||||
data-cy="search-bar"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_borderRadius_12_default__rgw6ez7bj sprinkles_borderBottomWidth_1px_default__rgw6ez7kj sprinkles_backgroundColor_searchBackground_default__rgw6ez6d1 sprinkles_gap_12_sm__rgw6ez3tj SearchBar_nftSearchBar__1fbf9sz9 SearchBar_baseSearchNftStyle__1fbf9sz2 SearchBar_baseSearchStyle__1fbf9sz1 SearchBar__1fbf9sz0 sprinkles_paddingTop_12_sm__rgw6ez2ov sprinkles_paddingBottom_12_sm__rgw6ez28d sprinkles_width_viewWidth_sm__rgw6ez17p sprinkles_borderStyle_solid_default__rgw6ez7ab sprinkles_borderWidth_1px_default__rgw6ez7jr sprinkles_borderColor_searchOutline_default__rgw6ez51v SearchBar__1fbf9sz8 sprinkles_paddingLeft_16_sm__rgw6ez2e7 sprinkles_paddingRight_16_sm__rgw6ez2jp sprinkles_color_textSecondary_default__rgw6ez4ep common_magicalGradientOnHover__127l8hdb common_magicalGradient__127l8hda"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 SearchBar_searchContentLeftAlign__1fbf9sz10"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_none_sm__rgw6ez44j sprinkles_display_flex_md__rgw6ez44w"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M15 15L11.2439 11.2439M12.3821 6.69106C12.3821 9.83414 9.83414 12.3821 6.69106 12.3821C3.54797 12.3821 1 9.83414 1 6.69106C1 3.54797 3.54797 1 6.69106 1C9.83414 1 12.3821 3.54797 12.3821 6.69106Z"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_display_none_md__rgw6ez44k sprinkles_color_textTertiary_default__rgw6ez4ev"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="16"
|
||||
viewBox="0 0 8 16"
|
||||
width="8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M7 1L1 7L7 13"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
class="reset_base__1klryar0 reset_input__1klryar8 reset_field__1klryar5 reset_appearance__1klryar4 sprinkles_width_full_sm__rgw6ez16v SearchBar_searchBarInput__1fbf9szb SearchBar__1fbf9sza sprinkles_padding_0_sm__rgw6ez2t7 sprinkles_fontWeight_normal_sm__rgw6ezcp sprinkles_fontSize_16_sm__rgw6ezb1 sprinkles_color_textPrimary_default__rgw6ez4ej sprinkles_color_textSecondary_placeholder__rgw6ez4eu sprinkles_border_none_default__rgw6ez7iz sprinkles_background_none_default__rgw6ez4sj sprinkles_lineHeight_24_sm__rgw6ezed sprinkles_height_full_sm__rgw6ez1dv SearchBar_searchContentLeftAlign__1fbf9sz10"
|
||||
data-cy="search-bar-input"
|
||||
placeholder="Search tokens and NFT collections"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
/
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_column_sm__rgw6ez477 sprinkles_overflow_hidden_default__rgw6ez7m3 SearchBar_hidden__1fbf9szx SearchBar__1fbf9szw sprinkles_visibility_hidden_sm__rgw6ez46v sprinkles_opacity_0_sm__rgw6ez4ad sprinkles_padding_0_sm__rgw6ez2t7 sprinkles_height_0_sm__rgw6ez187"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`disable nft on searchbar should render text without nfts 1`] = `
|
||||
.c0 {
|
||||
background-color: #ADBCFF3d;
|
||||
color: #7780A0;
|
||||
padding: 0px 8px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
line-height: 16px;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
opacity: 0.6;
|
||||
-webkit-backdrop-filter: blur(60px);
|
||||
backdrop-filter: blur(60px);
|
||||
}
|
||||
|
||||
<div>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_column_sm__rgw6ez477 sprinkles_position_relative_sm__rgw6ez491 sprinkles_width_auto_sm__rgw6ez17v sprinkles_width_auto_md__rgw6ez17w SearchBar_searchBarContainerNft__1fbf9sz4 SearchBar__1fbf9sz3 sprinkles_right_0_sm__rgw6ez39p sprinkles_top_0_sm__rgw6ez3f7 sprinkles_zIndex_3_sm__rgw6ez3qj sprinkles_display_flex_sm__rgw6ez44v sprinkles_maxHeight_searchResultsMaxHeight_sm__rgw6ez1zd sprinkles_overflow_hidden_default__rgw6ez7m3 searchBarContainerDisableBlur"
|
||||
data-cy="search-bar"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_borderRadius_12_default__rgw6ez7bj sprinkles_borderBottomWidth_1px_default__rgw6ez7kj sprinkles_backgroundColor_searchBackground_default__rgw6ez6d1 sprinkles_gap_12_sm__rgw6ez3tj SearchBar_nftSearchBar__1fbf9sz9 SearchBar_baseSearchNftStyle__1fbf9sz2 SearchBar_baseSearchStyle__1fbf9sz1 SearchBar__1fbf9sz0 sprinkles_paddingTop_12_sm__rgw6ez2ov sprinkles_paddingBottom_12_sm__rgw6ez28d sprinkles_width_viewWidth_sm__rgw6ez17p sprinkles_borderStyle_solid_default__rgw6ez7ab sprinkles_borderWidth_1px_default__rgw6ez7jr sprinkles_borderColor_searchOutline_default__rgw6ez51v SearchBar__1fbf9sz8 sprinkles_paddingLeft_16_sm__rgw6ez2e7 sprinkles_paddingRight_16_sm__rgw6ez2jp sprinkles_color_textSecondary_default__rgw6ez4ep common_magicalGradientOnHover__127l8hdb common_magicalGradient__127l8hda"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 SearchBar_searchContentLeftAlign__1fbf9sz10"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_none_sm__rgw6ez44j sprinkles_display_flex_md__rgw6ez44w"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M15 15L11.2439 11.2439M12.3821 6.69106C12.3821 9.83414 9.83414 12.3821 6.69106 12.3821C3.54797 12.3821 1 9.83414 1 6.69106C1 3.54797 3.54797 1 6.69106 1C9.83414 1 12.3821 3.54797 12.3821 6.69106Z"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_display_none_md__rgw6ez44k sprinkles_color_textTertiary_default__rgw6ez4ev"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="16"
|
||||
viewBox="0 0 8 16"
|
||||
width="8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M7 1L1 7L7 13"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
class="reset_base__1klryar0 reset_input__1klryar8 reset_field__1klryar5 reset_appearance__1klryar4 sprinkles_width_full_sm__rgw6ez16v SearchBar_searchBarInput__1fbf9szb SearchBar__1fbf9sza sprinkles_padding_0_sm__rgw6ez2t7 sprinkles_fontWeight_normal_sm__rgw6ezcp sprinkles_fontSize_16_sm__rgw6ezb1 sprinkles_color_textPrimary_default__rgw6ez4ej sprinkles_color_textSecondary_placeholder__rgw6ez4eu sprinkles_border_none_default__rgw6ez7iz sprinkles_background_none_default__rgw6ez4sj sprinkles_lineHeight_24_sm__rgw6ezed sprinkles_height_full_sm__rgw6ez1dv SearchBar_searchContentLeftAlign__1fbf9sz10"
|
||||
data-cy="search-bar-input"
|
||||
placeholder="Search tokens"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
/
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_column_sm__rgw6ez477 sprinkles_overflow_hidden_default__rgw6ez7m3 SearchBar_hidden__1fbf9szx SearchBar__1fbf9szw sprinkles_visibility_hidden_sm__rgw6ez46v sprinkles_opacity_0_sm__rgw6ez4ad sprinkles_padding_0_sm__rgw6ez2t7 sprinkles_height_0_sm__rgw6ez187"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,344 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`disable nft on searchbar dropdown should not render popular nft collections 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_column_sm__rgw6ez477 sprinkles_overflow_hidden_default__rgw6ez7m3 SearchBar_searchBarDropdownNft__1fbf9szd SearchBar_baseSearchNftStyle__1fbf9sz2 SearchBar_baseSearchStyle__1fbf9sz1 SearchBar__1fbf9sz0 sprinkles_paddingTop_12_sm__rgw6ez2ov sprinkles_paddingBottom_12_sm__rgw6ez28d sprinkles_width_viewWidth_sm__rgw6ez17p sprinkles_borderStyle_solid_default__rgw6ez7ab sprinkles_borderWidth_1px_default__rgw6ez7jr sprinkles_borderColor_searchOutline_default__rgw6ez51v SearchBar__1fbf9szc sprinkles_borderBottomLeftRadius_12_default__rgw6ez7g7 sprinkles_borderBottomRightRadius_12_default__rgw6ez7hr sprinkles_height_viewHeight_sm__rgw6ez1ej sprinkles_height_auto_md__rgw6ez1ew sprinkles_backgroundColor_backgroundSurface_default__rgw6ez6cj SearchBar__1fbf9sze sprinkles_overflowY_auto_default__rgw6ez7nn"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_opacity_1_sm__rgw6ez4b7 sprinkles_transition_125_default__rgw6ez7oj"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_column_sm__rgw6ez477 sprinkles_gap_20_sm__rgw6ez3u7"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_column_sm__rgw6ez477 sprinkles_gap_12_sm__rgw6ez3tj"
|
||||
data-cy="searchbar-dropdown"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_paddingLeft_16_sm__rgw6ez2e7 sprinkles_paddingRight_16_sm__rgw6ez2jp sprinkles_paddingTop_4_sm__rgw6ez2o7 sprinkles_paddingBottom_4_sm__rgw6ez27p sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_gap_8_sm__rgw6ez3t7 sprinkles_color_gray300_default__rgw6ez4k1 common__127l8hd4 sprinkles_fontWeight_medium_sm__rgw6ezcv sprinkles_fontSize_14_sm__rgw6ezav sprinkles_lineHeight_14_sm__rgw6ezdv"
|
||||
style="line-height: 20px;"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M17.5 5.8335H18.25C18.25 5.41928 17.9142 5.0835 17.5 5.0835V5.8335ZM11.0227 12.4307L10.4876 12.9562C10.6286 13.0998 10.8214 13.1807 11.0227 13.1807C11.224 13.1807 11.4169 13.0998 11.5579 12.9562L11.0227 12.4307ZM7.61364 8.9585L8.14881 8.43305C8.00778 8.28941 7.81493 8.2085 7.61364 8.2085C7.41234 8.2085 7.21949 8.28941 7.07846 8.43305L7.61364 8.9585ZM1.96483 13.6414C1.67463 13.937 1.67899 14.4118 1.97456 14.702C2.27013 14.9922 2.74498 14.9878 3.03517 14.6923L1.96483 13.6414ZM13.4091 5.0835C12.9949 5.0835 12.6591 5.41928 12.6591 5.8335C12.6591 6.24771 12.9949 6.5835 13.4091 6.5835V5.0835ZM16.75 10.0002C16.75 10.4144 17.0858 10.7502 17.5 10.7502C17.9142 10.7502 18.25 10.4144 18.25 10.0002H16.75ZM16.9648 5.30805L10.4876 11.9053L11.5579 12.9562L18.0352 6.35894L16.9648 5.30805ZM11.5579 11.9053L8.14881 8.43305L7.07846 9.48394L10.4876 12.9562L11.5579 11.9053ZM7.07846 8.43305L1.96483 13.6414L3.03517 14.6923L8.14881 9.48394L7.07846 8.43305ZM13.4091 6.5835H17.5V5.0835H13.4091V6.5835ZM16.75 5.8335V10.0002H18.25V5.8335H16.75Z"
|
||||
fill="var(--color-gray300__rgw6ez1g)"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="reset_base__1klryar0"
|
||||
>
|
||||
Popular tokens
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_column_sm__rgw6ez477 sprinkles_gap_12_sm__rgw6ez3tj"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j SearchBar_suggestionRow__1fbf9szg SearchBar__1fbf9szf sprinkles_paddingLeft_16_sm__rgw6ez2e7 sprinkles_paddingRight_16_sm__rgw6ez2jp sprinkles_paddingTop_8_sm__rgw6ez2oj sprinkles_paddingBottom_8_sm__rgw6ez281 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_justifyContent_space-between_sm__rgw6ez48j sprinkles_cursor_pointer_default__rgw6ez79z"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_width_full_sm__rgw6ez16v"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 SearchBar_imageHolder__1fbf9szq SearchBar__1fbf9szh sprinkles_width_36_sm__rgw6ez137 sprinkles_height_36_sm__rgw6ez1a7 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_marginRight_8_sm__rgw6ezr1 SearchBar__1fbf9szp sprinkles_background_backgroundModule_default__rgw6ez4p1 sprinkles_flexShrink_0_sm__rgw6ez3xv"
|
||||
/>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_column_sm__rgw6ez477 sprinkles_gap_4_sm__rgw6ez3sv sprinkles_width_full_sm__rgw6ez16v"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_justifyContent_space-between_sm__rgw6ez48j"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_20_sm__rgw6ez19d sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
style="width: 180px;"
|
||||
/>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_20_sm__rgw6ez19d sprinkles_width_48_sm__rgw6ez13v sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_justifyContent_space-between_sm__rgw6ez48j"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_16_sm__rgw6ez191 sprinkles_width_120_sm__rgw6ez15j sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
/>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_16_sm__rgw6ez191 sprinkles_width_48_sm__rgw6ez13v sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j SearchBar_suggestionRow__1fbf9szg SearchBar__1fbf9szf sprinkles_paddingLeft_16_sm__rgw6ez2e7 sprinkles_paddingRight_16_sm__rgw6ez2jp sprinkles_paddingTop_8_sm__rgw6ez2oj sprinkles_paddingBottom_8_sm__rgw6ez281 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_justifyContent_space-between_sm__rgw6ez48j sprinkles_cursor_pointer_default__rgw6ez79z"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_width_full_sm__rgw6ez16v"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 SearchBar_imageHolder__1fbf9szq SearchBar__1fbf9szh sprinkles_width_36_sm__rgw6ez137 sprinkles_height_36_sm__rgw6ez1a7 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_marginRight_8_sm__rgw6ezr1 SearchBar__1fbf9szp sprinkles_background_backgroundModule_default__rgw6ez4p1 sprinkles_flexShrink_0_sm__rgw6ez3xv"
|
||||
/>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_column_sm__rgw6ez477 sprinkles_gap_4_sm__rgw6ez3sv sprinkles_width_full_sm__rgw6ez16v"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_justifyContent_space-between_sm__rgw6ez48j"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_20_sm__rgw6ez19d sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
style="width: 180px;"
|
||||
/>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_20_sm__rgw6ez19d sprinkles_width_48_sm__rgw6ez13v sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_justifyContent_space-between_sm__rgw6ez48j"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_16_sm__rgw6ez191 sprinkles_width_120_sm__rgw6ez15j sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
/>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_16_sm__rgw6ez191 sprinkles_width_48_sm__rgw6ez13v sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`disable nft on searchbar dropdown should render popular nft collections 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_column_sm__rgw6ez477 sprinkles_overflow_hidden_default__rgw6ez7m3 SearchBar_searchBarDropdownNft__1fbf9szd SearchBar_baseSearchNftStyle__1fbf9sz2 SearchBar_baseSearchStyle__1fbf9sz1 SearchBar__1fbf9sz0 sprinkles_paddingTop_12_sm__rgw6ez2ov sprinkles_paddingBottom_12_sm__rgw6ez28d sprinkles_width_viewWidth_sm__rgw6ez17p sprinkles_borderStyle_solid_default__rgw6ez7ab sprinkles_borderWidth_1px_default__rgw6ez7jr sprinkles_borderColor_searchOutline_default__rgw6ez51v SearchBar__1fbf9szc sprinkles_borderBottomLeftRadius_12_default__rgw6ez7g7 sprinkles_borderBottomRightRadius_12_default__rgw6ez7hr sprinkles_height_viewHeight_sm__rgw6ez1ej sprinkles_height_auto_md__rgw6ez1ew sprinkles_backgroundColor_backgroundSurface_default__rgw6ez6cj SearchBar__1fbf9sze sprinkles_overflowY_auto_default__rgw6ez7nn"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_opacity_1_sm__rgw6ez4b7 sprinkles_transition_125_default__rgw6ez7oj"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_column_sm__rgw6ez477 sprinkles_gap_20_sm__rgw6ez3u7"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_column_sm__rgw6ez477 sprinkles_gap_12_sm__rgw6ez3tj"
|
||||
data-cy="searchbar-dropdown"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_paddingLeft_16_sm__rgw6ez2e7 sprinkles_paddingRight_16_sm__rgw6ez2jp sprinkles_paddingTop_4_sm__rgw6ez2o7 sprinkles_paddingBottom_4_sm__rgw6ez27p sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_gap_8_sm__rgw6ez3t7 sprinkles_color_gray300_default__rgw6ez4k1 common__127l8hd4 sprinkles_fontWeight_medium_sm__rgw6ezcv sprinkles_fontSize_14_sm__rgw6ezav sprinkles_lineHeight_14_sm__rgw6ezdv"
|
||||
style="line-height: 20px;"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M17.5 5.8335H18.25C18.25 5.41928 17.9142 5.0835 17.5 5.0835V5.8335ZM11.0227 12.4307L10.4876 12.9562C10.6286 13.0998 10.8214 13.1807 11.0227 13.1807C11.224 13.1807 11.4169 13.0998 11.5579 12.9562L11.0227 12.4307ZM7.61364 8.9585L8.14881 8.43305C8.00778 8.28941 7.81493 8.2085 7.61364 8.2085C7.41234 8.2085 7.21949 8.28941 7.07846 8.43305L7.61364 8.9585ZM1.96483 13.6414C1.67463 13.937 1.67899 14.4118 1.97456 14.702C2.27013 14.9922 2.74498 14.9878 3.03517 14.6923L1.96483 13.6414ZM13.4091 5.0835C12.9949 5.0835 12.6591 5.41928 12.6591 5.8335C12.6591 6.24771 12.9949 6.5835 13.4091 6.5835V5.0835ZM16.75 10.0002C16.75 10.4144 17.0858 10.7502 17.5 10.7502C17.9142 10.7502 18.25 10.4144 18.25 10.0002H16.75ZM16.9648 5.30805L10.4876 11.9053L11.5579 12.9562L18.0352 6.35894L16.9648 5.30805ZM11.5579 11.9053L8.14881 8.43305L7.07846 9.48394L10.4876 12.9562L11.5579 11.9053ZM7.07846 8.43305L1.96483 13.6414L3.03517 14.6923L8.14881 9.48394L7.07846 8.43305ZM13.4091 6.5835H17.5V5.0835H13.4091V6.5835ZM16.75 5.8335V10.0002H18.25V5.8335H16.75Z"
|
||||
fill="var(--color-gray300__rgw6ez1g)"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="reset_base__1klryar0"
|
||||
>
|
||||
Popular tokens
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_column_sm__rgw6ez477 sprinkles_gap_12_sm__rgw6ez3tj"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j SearchBar_suggestionRow__1fbf9szg SearchBar__1fbf9szf sprinkles_paddingLeft_16_sm__rgw6ez2e7 sprinkles_paddingRight_16_sm__rgw6ez2jp sprinkles_paddingTop_8_sm__rgw6ez2oj sprinkles_paddingBottom_8_sm__rgw6ez281 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_justifyContent_space-between_sm__rgw6ez48j sprinkles_cursor_pointer_default__rgw6ez79z"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_width_full_sm__rgw6ez16v"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 SearchBar_imageHolder__1fbf9szq SearchBar__1fbf9szh sprinkles_width_36_sm__rgw6ez137 sprinkles_height_36_sm__rgw6ez1a7 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_marginRight_8_sm__rgw6ezr1 SearchBar__1fbf9szp sprinkles_background_backgroundModule_default__rgw6ez4p1 sprinkles_flexShrink_0_sm__rgw6ez3xv"
|
||||
/>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_column_sm__rgw6ez477 sprinkles_gap_4_sm__rgw6ez3sv sprinkles_width_full_sm__rgw6ez16v"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_justifyContent_space-between_sm__rgw6ez48j"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_20_sm__rgw6ez19d sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
style="width: 180px;"
|
||||
/>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_20_sm__rgw6ez19d sprinkles_width_48_sm__rgw6ez13v sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_justifyContent_space-between_sm__rgw6ez48j"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_16_sm__rgw6ez191 sprinkles_width_120_sm__rgw6ez15j sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
/>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_16_sm__rgw6ez191 sprinkles_width_48_sm__rgw6ez13v sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j SearchBar_suggestionRow__1fbf9szg SearchBar__1fbf9szf sprinkles_paddingLeft_16_sm__rgw6ez2e7 sprinkles_paddingRight_16_sm__rgw6ez2jp sprinkles_paddingTop_8_sm__rgw6ez2oj sprinkles_paddingBottom_8_sm__rgw6ez281 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_justifyContent_space-between_sm__rgw6ez48j sprinkles_cursor_pointer_default__rgw6ez79z"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_width_full_sm__rgw6ez16v"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 SearchBar_imageHolder__1fbf9szq SearchBar__1fbf9szh sprinkles_width_36_sm__rgw6ez137 sprinkles_height_36_sm__rgw6ez1a7 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_marginRight_8_sm__rgw6ezr1 SearchBar__1fbf9szp sprinkles_background_backgroundModule_default__rgw6ez4p1 sprinkles_flexShrink_0_sm__rgw6ez3xv"
|
||||
/>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_column_sm__rgw6ez477 sprinkles_gap_4_sm__rgw6ez3sv sprinkles_width_full_sm__rgw6ez16v"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_justifyContent_space-between_sm__rgw6ez48j"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_20_sm__rgw6ez19d sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
style="width: 180px;"
|
||||
/>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_20_sm__rgw6ez19d sprinkles_width_48_sm__rgw6ez13v sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_justifyContent_space-between_sm__rgw6ez48j"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_16_sm__rgw6ez191 sprinkles_width_120_sm__rgw6ez15j sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
/>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_16_sm__rgw6ez191 sprinkles_width_48_sm__rgw6ez13v sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_column_sm__rgw6ez477 sprinkles_gap_12_sm__rgw6ez3tj"
|
||||
data-cy="searchbar-dropdown"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_paddingLeft_16_sm__rgw6ez2e7 sprinkles_paddingRight_16_sm__rgw6ez2jp sprinkles_paddingTop_4_sm__rgw6ez2o7 sprinkles_paddingBottom_4_sm__rgw6ez27p sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_gap_8_sm__rgw6ez3t7 sprinkles_color_gray300_default__rgw6ez4k1 common__127l8hd4 sprinkles_fontWeight_medium_sm__rgw6ezcv sprinkles_fontSize_14_sm__rgw6ezav sprinkles_lineHeight_14_sm__rgw6ezdv"
|
||||
style="line-height: 20px;"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M17.5 5.8335H18.25C18.25 5.41928 17.9142 5.0835 17.5 5.0835V5.8335ZM11.0227 12.4307L10.4876 12.9562C10.6286 13.0998 10.8214 13.1807 11.0227 13.1807C11.224 13.1807 11.4169 13.0998 11.5579 12.9562L11.0227 12.4307ZM7.61364 8.9585L8.14881 8.43305C8.00778 8.28941 7.81493 8.2085 7.61364 8.2085C7.41234 8.2085 7.21949 8.28941 7.07846 8.43305L7.61364 8.9585ZM1.96483 13.6414C1.67463 13.937 1.67899 14.4118 1.97456 14.702C2.27013 14.9922 2.74498 14.9878 3.03517 14.6923L1.96483 13.6414ZM13.4091 5.0835C12.9949 5.0835 12.6591 5.41928 12.6591 5.8335C12.6591 6.24771 12.9949 6.5835 13.4091 6.5835V5.0835ZM16.75 10.0002C16.75 10.4144 17.0858 10.7502 17.5 10.7502C17.9142 10.7502 18.25 10.4144 18.25 10.0002H16.75ZM16.9648 5.30805L10.4876 11.9053L11.5579 12.9562L18.0352 6.35894L16.9648 5.30805ZM11.5579 11.9053L8.14881 8.43305L7.07846 9.48394L10.4876 12.9562L11.5579 11.9053ZM7.07846 8.43305L1.96483 13.6414L3.03517 14.6923L8.14881 9.48394L7.07846 8.43305ZM13.4091 6.5835H17.5V5.0835H13.4091V6.5835ZM16.75 5.8335V10.0002H18.25V5.8335H16.75Z"
|
||||
fill="var(--color-gray300__rgw6ez1g)"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="reset_base__1klryar0"
|
||||
>
|
||||
Popular NFT collections
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_column_sm__rgw6ez477 sprinkles_gap_12_sm__rgw6ez3tj"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j SearchBar_suggestionRow__1fbf9szg SearchBar__1fbf9szf sprinkles_paddingLeft_16_sm__rgw6ez2e7 sprinkles_paddingRight_16_sm__rgw6ez2jp sprinkles_paddingTop_8_sm__rgw6ez2oj sprinkles_paddingBottom_8_sm__rgw6ez281 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_justifyContent_space-between_sm__rgw6ez48j sprinkles_cursor_pointer_default__rgw6ez79z"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_width_full_sm__rgw6ez16v"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 SearchBar_imageHolder__1fbf9szq SearchBar__1fbf9szh sprinkles_width_36_sm__rgw6ez137 sprinkles_height_36_sm__rgw6ez1a7 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_marginRight_8_sm__rgw6ezr1 SearchBar__1fbf9szp sprinkles_background_backgroundModule_default__rgw6ez4p1 sprinkles_flexShrink_0_sm__rgw6ez3xv"
|
||||
/>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_column_sm__rgw6ez477 sprinkles_gap_4_sm__rgw6ez3sv sprinkles_width_full_sm__rgw6ez16v"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_justifyContent_space-between_sm__rgw6ez48j"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_20_sm__rgw6ez19d sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
style="width: 180px;"
|
||||
/>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_20_sm__rgw6ez19d sprinkles_width_48_sm__rgw6ez13v sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_justifyContent_space-between_sm__rgw6ez48j"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_16_sm__rgw6ez191 sprinkles_width_120_sm__rgw6ez15j sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
/>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_16_sm__rgw6ez191 sprinkles_width_48_sm__rgw6ez13v sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j SearchBar_suggestionRow__1fbf9szg SearchBar__1fbf9szf sprinkles_paddingLeft_16_sm__rgw6ez2e7 sprinkles_paddingRight_16_sm__rgw6ez2jp sprinkles_paddingTop_8_sm__rgw6ez2oj sprinkles_paddingBottom_8_sm__rgw6ez281 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_justifyContent_space-between_sm__rgw6ez48j sprinkles_cursor_pointer_default__rgw6ez79z"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_width_full_sm__rgw6ez16v"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 SearchBar_imageHolder__1fbf9szq SearchBar__1fbf9szh sprinkles_width_36_sm__rgw6ez137 sprinkles_height_36_sm__rgw6ez1a7 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_marginRight_8_sm__rgw6ezr1 SearchBar__1fbf9szp sprinkles_background_backgroundModule_default__rgw6ez4p1 sprinkles_flexShrink_0_sm__rgw6ez3xv"
|
||||
/>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_column_sm__rgw6ez477 sprinkles_gap_4_sm__rgw6ez3sv sprinkles_width_full_sm__rgw6ez16v"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_justifyContent_space-between_sm__rgw6ez48j"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_20_sm__rgw6ez19d sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
style="width: 180px;"
|
||||
/>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_20_sm__rgw6ez19d sprinkles_width_48_sm__rgw6ez13v sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_display_flex_sm__rgw6ez44v sprinkles_flexDirection_row_sm__rgw6ez471 sprinkles_alignItems_center_sm__rgw6ez3j sprinkles_justifyContent_space-between_sm__rgw6ez48j"
|
||||
>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_16_sm__rgw6ez191 sprinkles_width_120_sm__rgw6ez15j sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
/>
|
||||
<div
|
||||
class="reset_base__1klryar0 sprinkles_borderRadius_round_default__rgw6ez7cj sprinkles_height_16_sm__rgw6ez191 sprinkles_width_48_sm__rgw6ez13v sprinkles_background_backgroundModule_default__rgw6ez4p1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -3,9 +3,9 @@ import { useWeb3React } from '@web3-react/core'
|
||||
import { useAccountDrawer } from 'components/AccountDrawer'
|
||||
import Web3Status from 'components/Web3Status'
|
||||
import { chainIdToBackendName } from 'graphql/data/util'
|
||||
import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes'
|
||||
import { useIsNftPage } from 'hooks/useIsNftPage'
|
||||
import { useIsPoolsPage } from 'hooks/useIsPoolsPage'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Row } from 'nft/components/Flex'
|
||||
import { UniIcon } from 'nft/components/icons'
|
||||
@@ -13,7 +13,6 @@ import { useProfilePageState } from 'nft/hooks'
|
||||
import { ProfilePageStateType } from 'nft/types'
|
||||
import { ReactNode, useCallback } from 'react'
|
||||
import { NavLink, NavLinkProps, useLocation, useNavigate } from 'react-router-dom'
|
||||
import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { useIsNavSearchInputVisible } from '../../nft/hooks/useIsNavSearchInputVisible'
|
||||
@@ -61,7 +60,7 @@ export const PageTabs = () => {
|
||||
const isPoolActive = useIsPoolsPage()
|
||||
const isNftPage = useIsNftPage()
|
||||
|
||||
const shouldDisableNFTRoutes = useAtomValue(shouldDisableNFTRoutesAtom)
|
||||
const shouldDisableNFTRoutes = useDisableNFTRoutes()
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { ArrowUpRight } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, HideSmall } from 'theme'
|
||||
@@ -30,15 +30,16 @@ const RootWrapper = styled.div`
|
||||
`
|
||||
|
||||
const SHOULD_SHOW_ALERT = {
|
||||
[SupportedChainId.OPTIMISM]: true,
|
||||
[SupportedChainId.OPTIMISM_GOERLI]: true,
|
||||
[SupportedChainId.ARBITRUM_ONE]: true,
|
||||
[SupportedChainId.ARBITRUM_GOERLI]: true,
|
||||
[SupportedChainId.POLYGON]: true,
|
||||
[SupportedChainId.POLYGON_MUMBAI]: true,
|
||||
[SupportedChainId.CELO]: true,
|
||||
[SupportedChainId.CELO_ALFAJORES]: true,
|
||||
[SupportedChainId.BNB]: true,
|
||||
[ChainId.OPTIMISM]: true,
|
||||
[ChainId.OPTIMISM_GOERLI]: true,
|
||||
[ChainId.ARBITRUM_ONE]: true,
|
||||
[ChainId.ARBITRUM_GOERLI]: true,
|
||||
[ChainId.POLYGON]: true,
|
||||
[ChainId.POLYGON_MUMBAI]: true,
|
||||
[ChainId.CELO]: true,
|
||||
[ChainId.CELO_ALFAJORES]: true,
|
||||
[ChainId.BNB]: true,
|
||||
[ChainId.AVALANCHE]: true,
|
||||
}
|
||||
|
||||
type NetworkAlertChains = keyof typeof SHOULD_SHOW_ALERT
|
||||
@@ -47,44 +48,48 @@ const BG_COLORS_BY_DARK_MODE_AND_CHAIN_ID: {
|
||||
[darkMode in 'dark' | 'light']: { [chainId in NetworkAlertChains]: string }
|
||||
} = {
|
||||
dark: {
|
||||
[SupportedChainId.POLYGON]:
|
||||
[ChainId.POLYGON]:
|
||||
'radial-gradient(100% 93.36% at 0% 6.64%, rgba(160, 108, 247, 0.1) 0%, rgba(82, 32, 166, 0.1) 100%)',
|
||||
[SupportedChainId.POLYGON_MUMBAI]:
|
||||
[ChainId.POLYGON_MUMBAI]:
|
||||
'radial-gradient(100% 93.36% at 0% 6.64%, rgba(160, 108, 247, 0.1) 0%, rgba(82, 32, 166, 0.1) 100%)',
|
||||
[SupportedChainId.CELO]:
|
||||
[ChainId.CELO]:
|
||||
'radial-gradient(182.71% 150.59% at 2.81% 7.69%, rgba(90, 190, 170, 0.15) 0%, rgba(80, 160, 40, 0.15) 100%)',
|
||||
[SupportedChainId.CELO_ALFAJORES]:
|
||||
[ChainId.CELO_ALFAJORES]:
|
||||
'radial-gradient(182.71% 150.59% at 2.81% 7.69%, rgba(90, 190, 170, 0.15) 0%, rgba(80, 160, 40, 0.15) 100%)',
|
||||
[SupportedChainId.BNB]:
|
||||
[ChainId.BNB]:
|
||||
'radial-gradient(182.71% 150.59% at 2.81% 7.69%, rgba(240, 185, 11, 0.16) 0%, rgba(255, 168, 0, 0.16) 100%)',
|
||||
[SupportedChainId.OPTIMISM]:
|
||||
[ChainId.OPTIMISM]:
|
||||
'radial-gradient(948% 292% at 42% 0%, rgba(255, 58, 212, 0.01) 0%, rgba(255, 255, 255, 0.04) 100%),radial-gradient(98% 96% at 2% 0%, rgba(255, 39, 39, 0.01) 0%, rgba(235, 0, 255, 0.01) 96%)',
|
||||
[SupportedChainId.OPTIMISM_GOERLI]:
|
||||
[ChainId.OPTIMISM_GOERLI]:
|
||||
'radial-gradient(948% 292% at 42% 0%, rgba(255, 58, 212, 0.04) 0%, rgba(255, 255, 255, 0.04) 100%),radial-gradient(98% 96% at 2% 0%, rgba(255, 39, 39, 0.04) 0%, rgba(235, 0, 255, 0.01 96%)',
|
||||
[SupportedChainId.ARBITRUM_ONE]:
|
||||
[ChainId.ARBITRUM_ONE]:
|
||||
'radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.01) 0%, rgba(219, 255, 0, 0) 100%),radial-gradient(75% 75% at 0% 0%, rgba(150, 190, 220, 0.05) 0%, rgba(33, 114, 229, 0.05) 100%), hsla(0, 0%, 100%, 0.05)',
|
||||
[SupportedChainId.ARBITRUM_GOERLI]:
|
||||
[ChainId.ARBITRUM_GOERLI]:
|
||||
'radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.05) 0%, rgba(219, 255, 0, 0) 100%),radial-gradient(75% 75% at 0% 0%, rgba(150, 190, 220, 0.05) 0%, rgba(33, 114, 229, 0.1) 100%), hsla(0, 0%, 100%, 0.05)',
|
||||
[ChainId.AVALANCHE]:
|
||||
'radial-gradient(948% 292% at 42% 0%, rgba(255, 58, 212, 0.01) 0%, rgba(255, 255, 255, 0.04) 100%),radial-gradient(98% 96% at 2% 0%, rgba(255, 39, 39, 0.01) 0%, rgba(235, 0, 255, 0.01) 96%)',
|
||||
},
|
||||
light: {
|
||||
[SupportedChainId.POLYGON]:
|
||||
[ChainId.POLYGON]:
|
||||
'radial-gradient(182.71% 205.59% at 2.81% 7.69%, rgba(130, 71, 229, 0.2) 0%, rgba(167, 202, 255, 0.2) 100%)',
|
||||
[SupportedChainId.POLYGON_MUMBAI]:
|
||||
[ChainId.POLYGON_MUMBAI]:
|
||||
'radial-gradient(182.71% 205.59% at 2.81% 7.69%, rgba(130, 71, 229, 0.2) 0%, rgba(167, 202, 255, 0.2) 100%)',
|
||||
[SupportedChainId.CELO]:
|
||||
[ChainId.CELO]:
|
||||
'radial-gradient(182.71% 150.59% at 2.81% 7.69%, rgba(63, 208, 137, 0.15) 0%, rgba(49, 205, 50, 0.15) 100%)',
|
||||
[SupportedChainId.CELO_ALFAJORES]:
|
||||
[ChainId.CELO_ALFAJORES]:
|
||||
'radial-gradient(182.71% 150.59% at 2.81% 7.69%, rgba(63, 208, 137, 0.15) 0%, rgba(49, 205, 50, 0.15) 100%)',
|
||||
[SupportedChainId.BNB]:
|
||||
[ChainId.BNB]:
|
||||
'radial-gradient(182.71% 150.59% at 2.81% 7.69%, rgba(240, 185, 11, 0.16) 0%, rgba(255, 168, 0, 0.16) 100%)',
|
||||
[SupportedChainId.OPTIMISM]:
|
||||
[ChainId.OPTIMISM]:
|
||||
'radial-gradient(92% 105% at 50% 7%, rgba(255, 58, 212, 0.04) 0%, rgba(255, 255, 255, 0.03) 100%),radial-gradient(100% 97% at 0% 12%, rgba(235, 0, 255, 0.1) 0%, rgba(243, 19, 19, 0.1) 100%), hsla(0, 0%, 100%, 0.1)',
|
||||
[SupportedChainId.OPTIMISM_GOERLI]:
|
||||
[ChainId.OPTIMISM_GOERLI]:
|
||||
'radial-gradient(92% 105% at 50% 7%, rgba(255, 58, 212, 0.04) 0%, rgba(255, 255, 255, 0.03) 100%),radial-gradient(100% 97% at 0% 12%, rgba(235, 0, 255, 0.1) 0%, rgba(243, 19, 19, 0.1) 100%), hsla(0, 0%, 100%, 0.1)',
|
||||
[SupportedChainId.ARBITRUM_ONE]:
|
||||
[ChainId.ARBITRUM_ONE]:
|
||||
'radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.1) 0%, rgba(219, 255, 0, 0) 100%),radial-gradient(circle at top left, hsla(206, 50%, 75%, 0.01), hsla(215, 79%, 51%, 0.12)), hsla(0, 0%, 100%, 0.1)',
|
||||
[SupportedChainId.ARBITRUM_GOERLI]:
|
||||
[ChainId.ARBITRUM_GOERLI]:
|
||||
'radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.1) 0%, rgba(219, 255, 0, 0) 100%),radial-gradient(circle at top left, hsla(206, 50%, 75%, 0.01), hsla(215, 79%, 51%, 0.12)), hsla(0, 0%, 100%, 0.1)',
|
||||
[ChainId.AVALANCHE]:
|
||||
'radial-gradient(92% 105% at 50% 7%, rgba(255, 58, 212, 0.04) 0%, rgba(255, 255, 255, 0.03) 100%),radial-gradient(100% 97% at 0% 12%, rgba(235, 0, 255, 0.1) 0%, rgba(243, 19, 19, 0.1) 100%), hsla(0, 0%, 100%, 0.1)',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -135,15 +140,16 @@ const StyledArrowUpRight = styled(ArrowUpRight)`
|
||||
`
|
||||
|
||||
const TEXT_COLORS: { [chainId in NetworkAlertChains]: string } = {
|
||||
[SupportedChainId.POLYGON]: 'rgba(130, 71, 229)',
|
||||
[SupportedChainId.POLYGON_MUMBAI]: 'rgba(130, 71, 229)',
|
||||
[SupportedChainId.CELO]: 'rgba(53, 178, 97)',
|
||||
[SupportedChainId.CELO_ALFAJORES]: 'rgba(53, 178, 97)',
|
||||
[SupportedChainId.OPTIMISM]: '#ff3856',
|
||||
[SupportedChainId.OPTIMISM_GOERLI]: '#ff3856',
|
||||
[SupportedChainId.ARBITRUM_ONE]: '#0490ed',
|
||||
[SupportedChainId.BNB]: colors.gold400,
|
||||
[SupportedChainId.ARBITRUM_GOERLI]: '#0490ed',
|
||||
[ChainId.POLYGON]: 'rgba(130, 71, 229)',
|
||||
[ChainId.POLYGON_MUMBAI]: 'rgba(130, 71, 229)',
|
||||
[ChainId.CELO]: 'rgba(53, 178, 97)',
|
||||
[ChainId.CELO_ALFAJORES]: 'rgba(53, 178, 97)',
|
||||
[ChainId.OPTIMISM]: '#ff3856',
|
||||
[ChainId.OPTIMISM_GOERLI]: '#ff3856',
|
||||
[ChainId.ARBITRUM_ONE]: '#0490ed',
|
||||
[ChainId.BNB]: colors.gold400,
|
||||
[ChainId.ARBITRUM_GOERLI]: '#0490ed',
|
||||
[ChainId.AVALANCHE]: '#ff3856',
|
||||
}
|
||||
|
||||
function shouldShowAlert(chainId: number | undefined): chainId is NetworkAlertChains {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { getChainInfoOrDefault, L2ChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
|
||||
@@ -62,7 +62,7 @@ export function ChainConnectivityWarning() {
|
||||
</TitleText>
|
||||
</TitleRow>
|
||||
<BodyRow>
|
||||
{chainId === SupportedChainId.MAINNET ? (
|
||||
{chainId === ChainId.MAINNET ? (
|
||||
<Trans>You may have lost your network connection.</Trans>
|
||||
) : (
|
||||
<Trans>{label} might be down right now, or you may have lost your network connection.</Trans>
|
||||
|
||||
@@ -20,7 +20,7 @@ const ReferenceElement = styled.div`
|
||||
height: inherit;
|
||||
`
|
||||
|
||||
const Arrow = styled.div`
|
||||
export const Arrow = styled.div`
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
z-index: 9998;
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import AlertTriangleFilled from 'components/Icons/AlertTriangleFilled'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { ThemedText } from '../../theme'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { AutoRow } from '../Row'
|
||||
|
||||
const RowNoFlex = styled(AutoRow)`
|
||||
flex-wrap: nowrap;
|
||||
`
|
||||
|
||||
const ColumnContainer = styled(AutoColumn)`
|
||||
margin: 0 12px;
|
||||
`
|
||||
|
||||
export const PopupAlertTriangle = styled(AlertTriangleFilled)`
|
||||
flex-shrink: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
`
|
||||
|
||||
export default function FailedNetworkSwitchPopup({ chainId }: { chainId: SupportedChainId }) {
|
||||
const chainInfo = getChainInfo(chainId)
|
||||
|
||||
return (
|
||||
<RowNoFlex gap="12px">
|
||||
<PopupAlertTriangle />
|
||||
<ColumnContainer gap="sm">
|
||||
<ThemedText.SubHeader color="textSecondary">
|
||||
<Trans>Failed to switch networks</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
|
||||
<ThemedText.BodySmall color="textSecondary">
|
||||
<Trans>To use Uniswap on {chainInfo.label}, switch the network in your wallet’s settings.</Trans>
|
||||
</ThemedText.BodySmall>
|
||||
</ColumnContainer>
|
||||
</RowNoFlex>
|
||||
)
|
||||
}
|
||||
165
src/components/Popups/PopupContent.tsx
Normal file
165
src/components/Popups/PopupContent.tsx
Normal file
@@ -0,0 +1,165 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
import { useOpenOffchainActivityModal } from 'components/AccountDrawer/MiniPortfolio/Activity/OffchainActivityModal'
|
||||
import { signatureToActivity, transactionToActivity } from 'components/AccountDrawer/MiniPortfolio/Activity/parseLocal'
|
||||
import { Activity } from 'components/AccountDrawer/MiniPortfolio/Activity/types'
|
||||
import { PortfolioLogo } from 'components/AccountDrawer/MiniPortfolio/PortfolioLogo'
|
||||
import PortfolioRow from 'components/AccountDrawer/MiniPortfolio/PortfolioRow'
|
||||
import Column, { AutoColumn } from 'components/Column'
|
||||
import AlertTriangleFilled from 'components/Icons/AlertTriangleFilled'
|
||||
import { AutoRow } from 'components/Row'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { useAllTokensMultichain } from 'hooks/Tokens'
|
||||
import useENSName from 'hooks/useENSName'
|
||||
import { X } from 'react-feather'
|
||||
import { useOrder } from 'state/signatures/hooks'
|
||||
import { useTransaction } from 'state/transactions/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { EllipsisStyle, ThemedText } from 'theme'
|
||||
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
|
||||
|
||||
const StyledClose = styled(X)<{ $padding: number }>`
|
||||
position: absolute;
|
||||
right: ${({ $padding }) => `${$padding}px`};
|
||||
top: ${({ $padding }) => `${$padding}px`};
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
const PopupContainer = styled.div<{ padded?: boolean }>`
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
position: relative;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
box-shadow: ${({ theme }) => theme.deepShadow};
|
||||
transition: ${({ theme }) => `visibility ${theme.transition.duration.fast} ease-in-out`};
|
||||
|
||||
padding: ${({ padded }) => (padded ? '20px 35px 20px 20px' : '2px 0px')};
|
||||
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
min-width: 290px;
|
||||
&:not(:last-of-type) {
|
||||
margin-right: 20px;
|
||||
}
|
||||
`}
|
||||
`
|
||||
|
||||
const RowNoFlex = styled(AutoRow)`
|
||||
flex-wrap: nowrap;
|
||||
`
|
||||
|
||||
const ColumnContainer = styled(AutoColumn)`
|
||||
margin: 0 12px;
|
||||
`
|
||||
|
||||
const PopupAlertTriangle = styled(AlertTriangleFilled)`
|
||||
flex-shrink: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
`
|
||||
|
||||
export function FailedNetworkSwitchPopup({ chainId, onClose }: { chainId: ChainId; onClose: () => void }) {
|
||||
const chainInfo = getChainInfo(chainId)
|
||||
|
||||
return (
|
||||
<PopupContainer padded>
|
||||
<StyledClose $padding={20} onClick={onClose} />
|
||||
<RowNoFlex gap="12px">
|
||||
<PopupAlertTriangle />
|
||||
<ColumnContainer gap="sm">
|
||||
<ThemedText.SubHeader color="textSecondary">
|
||||
<Trans>Failed to switch networks</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
|
||||
<ThemedText.BodySmall color="textSecondary">
|
||||
<Trans>To use Uniswap on {chainInfo.label}, switch the network in your wallet’s settings.</Trans>
|
||||
</ThemedText.BodySmall>
|
||||
</ColumnContainer>
|
||||
</RowNoFlex>
|
||||
</PopupContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const Descriptor = styled(ThemedText.BodySmall)`
|
||||
${EllipsisStyle}
|
||||
`
|
||||
|
||||
type ActivityPopupContentProps = { activity: Activity; onClick: () => void; onClose: () => void }
|
||||
function ActivityPopupContent({ activity, onClick, onClose }: ActivityPopupContentProps) {
|
||||
const success = activity.status === TransactionStatus.Confirmed
|
||||
const { ENSName } = useENSName(activity?.otherAccount)
|
||||
|
||||
return (
|
||||
<PopupContainer>
|
||||
<StyledClose $padding={16} onClick={onClose} />
|
||||
<PortfolioRow
|
||||
left={
|
||||
success ? (
|
||||
<Column>
|
||||
<PortfolioLogo
|
||||
chainId={activity.chainId}
|
||||
currencies={activity.currencies}
|
||||
images={activity.logos}
|
||||
accountAddress={activity.otherAccount}
|
||||
/>
|
||||
</Column>
|
||||
) : (
|
||||
<PopupAlertTriangle />
|
||||
)
|
||||
}
|
||||
title={<ThemedText.SubHeader>{activity.title}</ThemedText.SubHeader>}
|
||||
descriptor={
|
||||
<Descriptor color="textSecondary">
|
||||
{activity.descriptor}
|
||||
{ENSName ?? activity.otherAccount}
|
||||
</Descriptor>
|
||||
}
|
||||
onClick={onClick}
|
||||
/>
|
||||
</PopupContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export function TransactionPopupContent({
|
||||
chainId,
|
||||
hash,
|
||||
onClose,
|
||||
}: {
|
||||
chainId: ChainId
|
||||
hash: string
|
||||
onClose: () => void
|
||||
}) {
|
||||
const transaction = useTransaction(hash)
|
||||
const tokens = useAllTokensMultichain()
|
||||
if (!transaction) return null
|
||||
|
||||
const activity = transactionToActivity(transaction, chainId, tokens)
|
||||
|
||||
if (!activity) return null
|
||||
|
||||
const onClick = () =>
|
||||
window.open(getExplorerLink(activity.chainId, activity.hash, ExplorerDataType.TRANSACTION), '_blank')
|
||||
|
||||
return <ActivityPopupContent activity={activity} onClose={onClose} onClick={onClick} />
|
||||
}
|
||||
|
||||
export function UniswapXOrderPopupContent({ orderHash, onClose }: { orderHash: string; onClose: () => void }) {
|
||||
const order = useOrder(orderHash)
|
||||
const tokens = useAllTokensMultichain()
|
||||
const openOffchainActivityModal = useOpenOffchainActivityModal()
|
||||
if (!order) return null
|
||||
|
||||
const activity = signatureToActivity(order, tokens)
|
||||
|
||||
if (!activity) return null
|
||||
|
||||
const onClick = () => openOffchainActivityModal({ orderHash, status: order.status })
|
||||
|
||||
return <ActivityPopupContent activity={activity} onClose={onClose} onClick={onClick} />
|
||||
}
|
||||
@@ -1,50 +1,9 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useEffect } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import styled, { css, useTheme } from 'styled-components/macro'
|
||||
|
||||
import { useRemovePopup } from '../../state/application/hooks'
|
||||
import { PopupContent } from '../../state/application/reducer'
|
||||
import FailedNetworkSwitchPopup from './FailedNetworkSwitchPopup'
|
||||
import TransactionPopup from './TransactionPopup'
|
||||
|
||||
const StyledClose = styled(X)<{ $padding: number }>`
|
||||
position: absolute;
|
||||
right: ${({ $padding }) => `${$padding}px`};
|
||||
top: ${({ $padding }) => `${$padding}px`};
|
||||
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
const PopupCss = css<{ show: boolean }>`
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
visibility: ${({ show }) => (show ? 'visible' : 'hidden')};
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
position: relative;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
box-shadow: ${({ theme }) => theme.deepShadow};
|
||||
transition: ${({ theme }) => `visibility ${theme.transition.duration.fast} ease-in-out`};
|
||||
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
min-width: 290px;
|
||||
&:not(:last-of-type) {
|
||||
margin-right: 20px;
|
||||
}
|
||||
`}
|
||||
`
|
||||
|
||||
const TransactionPopupContainer = styled.div`
|
||||
${PopupCss}
|
||||
padding: 2px 0px;
|
||||
`
|
||||
|
||||
const FailedSwitchNetworkPopupContainer = styled.div<{ show: boolean }>`
|
||||
${PopupCss}
|
||||
padding: 20px 35px 20px 20px;
|
||||
`
|
||||
import { PopupContent, PopupType } from '../../state/application/reducer'
|
||||
import { FailedNetworkSwitchPopup, TransactionPopupContent, UniswapXOrderPopupContent } from './PopupContent'
|
||||
|
||||
export default function PopupItem({
|
||||
removeAfterMs,
|
||||
@@ -56,7 +15,7 @@ export default function PopupItem({
|
||||
popKey: string
|
||||
}) {
|
||||
const removePopup = useRemovePopup()
|
||||
const theme = useTheme()
|
||||
const onClose = () => removePopup(popKey)
|
||||
|
||||
useEffect(() => {
|
||||
if (removeAfterMs === null) return undefined
|
||||
@@ -70,20 +29,17 @@ export default function PopupItem({
|
||||
}
|
||||
}, [popKey, removeAfterMs, removePopup])
|
||||
|
||||
if ('txn' in content) {
|
||||
return (
|
||||
<TransactionPopupContainer show={true}>
|
||||
<StyledClose $padding={16} color={theme.textSecondary} onClick={() => removePopup(popKey)} />
|
||||
<TransactionPopup hash={content.txn.hash} />
|
||||
</TransactionPopupContainer>
|
||||
)
|
||||
} else if ('failedSwitchNetwork' in content) {
|
||||
return (
|
||||
<FailedSwitchNetworkPopupContainer show={true}>
|
||||
<StyledClose $padding={20} color={theme.textSecondary} onClick={() => removePopup(popKey)} />
|
||||
<FailedNetworkSwitchPopup chainId={content.failedSwitchNetwork} />
|
||||
</FailedSwitchNetworkPopupContainer>
|
||||
)
|
||||
const { chainId } = useWeb3React()
|
||||
|
||||
switch (content.type) {
|
||||
case PopupType.Transaction: {
|
||||
return chainId ? <TransactionPopupContent hash={content.hash} chainId={chainId} onClose={onClose} /> : null
|
||||
}
|
||||
case PopupType.Order: {
|
||||
return <UniswapXOrderPopupContent orderHash={content.orderHash} onClose={onClose} />
|
||||
}
|
||||
case PopupType.FailedSwitchNetwork: {
|
||||
return <FailedNetworkSwitchPopup chainId={content.failedSwitchNetwork} onClose={onClose} />
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { parseLocalActivity } from 'components/AccountDrawer/MiniPortfolio/Activity/parseLocal'
|
||||
import { PortfolioLogo } from 'components/AccountDrawer/MiniPortfolio/PortfolioLogo'
|
||||
import PortfolioRow from 'components/AccountDrawer/MiniPortfolio/PortfolioRow'
|
||||
import Column from 'components/Column'
|
||||
import { useAllTokensMultichain } from 'hooks/Tokens'
|
||||
import useENSName from 'hooks/useENSName'
|
||||
import { useTransaction } from 'state/transactions/hooks'
|
||||
import { TransactionDetails } from 'state/transactions/types'
|
||||
import styled from 'styled-components/macro'
|
||||
import { EllipsisStyle, ThemedText } from 'theme'
|
||||
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
|
||||
|
||||
import { PopupAlertTriangle } from './FailedNetworkSwitchPopup'
|
||||
|
||||
const Descriptor = styled(ThemedText.BodySmall)`
|
||||
${EllipsisStyle}
|
||||
`
|
||||
|
||||
function TransactionPopupContent({ tx, chainId }: { tx: TransactionDetails; chainId: number }) {
|
||||
const success = tx.receipt?.status === 1
|
||||
const tokens = useAllTokensMultichain()
|
||||
const activity = parseLocalActivity(tx, chainId, tokens)
|
||||
const { ENSName } = useENSName(activity?.otherAccount)
|
||||
|
||||
if (!activity) return null
|
||||
|
||||
const explorerUrl = getExplorerLink(chainId, tx.hash, ExplorerDataType.TRANSACTION)
|
||||
|
||||
return (
|
||||
<PortfolioRow
|
||||
left={
|
||||
success ? (
|
||||
<Column>
|
||||
<PortfolioLogo
|
||||
chainId={chainId}
|
||||
currencies={activity.currencies}
|
||||
images={activity.logos}
|
||||
accountAddress={activity.otherAccount}
|
||||
/>
|
||||
</Column>
|
||||
) : (
|
||||
<PopupAlertTriangle />
|
||||
)
|
||||
}
|
||||
title={<ThemedText.SubHeader>{activity.title}</ThemedText.SubHeader>}
|
||||
descriptor={
|
||||
<Descriptor color="textSecondary">
|
||||
{activity.descriptor}
|
||||
{ENSName ?? activity.otherAccount}
|
||||
</Descriptor>
|
||||
}
|
||||
onClick={() => window.open(explorerUrl, '_blank')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default function TransactionPopup({ hash }: { hash: string }) {
|
||||
const { chainId } = useWeb3React()
|
||||
|
||||
const tx = useTransaction(hash)
|
||||
|
||||
if (!chainId || !tx) return null
|
||||
|
||||
return <TransactionPopupContent tx={tx} chainId={chainId} />
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import styled from 'styled-components/macro'
|
||||
import { MEDIA_WIDTHS } from 'theme'
|
||||
|
||||
@@ -62,7 +62,7 @@ export default function Popups() {
|
||||
|
||||
// need extra padding if network is not L1 Ethereum
|
||||
const { chainId } = useWeb3React()
|
||||
const isNotOnMainnet = Boolean(chainId && chainId !== SupportedChainId.MAINNET)
|
||||
const isNotOnMainnet = Boolean(chainId && chainId !== ChainId.MAINNET)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { SupportedChainId, Token, WETH9 } from '@uniswap/sdk-core'
|
||||
import { ChainId, Token, WETH9 } from '@uniswap/sdk-core'
|
||||
import { FeeAmount, Pool } from '@uniswap/v3-sdk'
|
||||
import { USDC_MAINNET } from 'constants/tokens'
|
||||
import { useToken } from 'hooks/Tokens'
|
||||
@@ -25,7 +25,7 @@ beforeEach(() => {
|
||||
// tokenA: Token, tokenB: Token, fee: FeeAmount, sqrtRatioX96: BigintIsh, liquidity: BigintIsh, tickCurrent: number
|
||||
new Pool(
|
||||
USDC_MAINNET,
|
||||
WETH9[SupportedChainId.MAINNET],
|
||||
WETH9[ChainId.MAINNET],
|
||||
FeeAmount.MEDIUM,
|
||||
'1745948049099224684665158875285708',
|
||||
'4203610460178577802',
|
||||
@@ -37,7 +37,7 @@ beforeEach(() => {
|
||||
test('PositionListItem should render a position', () => {
|
||||
const positionDetails = {
|
||||
token0: USDC_MAINNET.address,
|
||||
token1: WETH9[SupportedChainId.MAINNET].address,
|
||||
token1: WETH9[ChainId.MAINNET].address,
|
||||
tokenId: BigNumber.from(479689),
|
||||
fee: FeeAmount.MEDIUM,
|
||||
liquidity: BigNumber.from('1341008833950736'),
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import { RowBetween } from 'components/Row'
|
||||
import { darken } from 'polished'
|
||||
import { PropsWithChildren } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const Button = styled.button<{ isActive?: boolean; activeElement?: boolean }>`
|
||||
align-items: center;
|
||||
background: transparent;
|
||||
border: 2px solid ${({ theme, isActive }) => (isActive ? theme.accentAction : theme.backgroundOutline)};
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
outline: none;
|
||||
padding: 5px;
|
||||
width: fit-content;
|
||||
`
|
||||
|
||||
const ButtonFill = styled.span<{ isActive?: boolean }>`
|
||||
background: ${({ theme, isActive }) => (isActive ? theme.accentAction : theme.textTertiary)};
|
||||
border-radius: 50%;
|
||||
:hover {
|
||||
background: ${({ isActive, theme }) =>
|
||||
isActive ? darken(0.05, theme.accentAction) : darken(0.05, theme.deprecated_bg4)};
|
||||
color: ${({ isActive, theme }) => (isActive ? theme.white : theme.textTertiary)};
|
||||
}
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
opacity: ${({ isActive }) => (isActive ? 1 : 0)};
|
||||
`
|
||||
|
||||
const Container = styled(RowBetween)`
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
interface RadioProps {
|
||||
className?: string
|
||||
isActive: boolean
|
||||
toggle: () => void
|
||||
}
|
||||
|
||||
export default function Radio({ className, isActive, children, toggle }: PropsWithChildren<RadioProps>) {
|
||||
return (
|
||||
<Container className={className} onClick={toggle}>
|
||||
{children}
|
||||
<Button isActive={isActive}>
|
||||
<ButtonFill isActive={isActive} />
|
||||
</Button>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
62
src/components/RouterLabel/UniswapXRouterLabel.tsx
Normal file
62
src/components/RouterLabel/UniswapXRouterLabel.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import Row from 'components/Row'
|
||||
import { useRef } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
import { BoxProps } from '../../nft/components/Box'
|
||||
|
||||
// Gradient with a fallback to solid color.
|
||||
const Gradient = styled.div`
|
||||
color: #4673fa;
|
||||
|
||||
@supports (-webkit-background-clip: text) and (-webkit-text-fill-color: transparent) {
|
||||
background-image: linear-gradient(91.39deg, #4673fa -101.76%, #9646fa 101.76%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
`
|
||||
|
||||
export { Gradient as UniswapXGradient }
|
||||
|
||||
// Uniswap X SVG icon with gradient, copied from Figma.
|
||||
// In order for gradient to work, we must give its definition a unique ID that does not collide
|
||||
// with other occurences of this component on the page.
|
||||
export const UniswapXRouterIcon = () => {
|
||||
const componentIdRef = useRef(uuid())
|
||||
const componentId = `AutoRouterIconGradient${componentIdRef.current}`
|
||||
|
||||
return (
|
||||
<svg width="10" height="14" viewBox="0 0 10 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient
|
||||
id={componentId}
|
||||
x1="-10.1807"
|
||||
y1="-12.0006"
|
||||
x2="10.6573"
|
||||
y2="-11.6017"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#4673FA" />
|
||||
<stop offset="1" stopColor="#9646FA" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
d="M9.97131 6.19803C9.91798 6.07737 9.79866 6.00003 9.66666 6.00003H6.66666V1.00003C6.66666 0.862034 6.58201 0.738037 6.45267 0.688704C6.32267 0.638704 6.17799 0.674696 6.08532 0.776696L0.0853237 7.44336C-0.00267631 7.54136 -0.0253169 7.68137 0.0286831 7.80204C0.0820164 7.9227 0.20133 8.00003 0.33333 8.00003H3.33333V13C3.33333 13.138 3.41799 13.262 3.54732 13.3114C3.58665 13.326 3.62666 13.3334 3.66666 13.3334C3.75933 13.3334 3.85 13.2947 3.91467 13.2227L9.91467 6.55603C10.0027 6.4587 10.0246 6.31803 9.97131 6.19803Z"
|
||||
fill={`url(#${componentId})`}
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export type UnswapXRouterLabelProps = BoxProps & {
|
||||
disableTextGradient?: boolean
|
||||
}
|
||||
|
||||
export default function UniswapXRouterLabel({ children, disableTextGradient, ...rest }: UnswapXRouterLabelProps) {
|
||||
return (
|
||||
<Row gap="xs" width="auto" {...rest} style={{ display: 'inline-flex', ...rest.style }}>
|
||||
<UniswapXRouterIcon />
|
||||
{disableTextGradient ? children : <Gradient>{children}</Gradient>}
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
19
src/components/RouterLabel/index.tsx
Normal file
19
src/components/RouterLabel/index.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
import { isUniswapXTrade } from 'state/routing/utils'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
import UniswapXRouterLabel from './UniswapXRouterLabel'
|
||||
|
||||
export default function RouterLabel({ trade }: { trade: InterfaceTrade }) {
|
||||
if (isUniswapXTrade(trade)) {
|
||||
return (
|
||||
<UniswapXRouterLabel>
|
||||
<ThemedText.BodySmall>Uniswap X</ThemedText.BodySmall>
|
||||
</UniswapXRouterLabel>
|
||||
)
|
||||
}
|
||||
if (trade.fromClientRouter) {
|
||||
return <ThemedText.BodySmall>Uniswap Client</ThemedText.BodySmall>
|
||||
}
|
||||
return <ThemedText.BodySmall>Uniswap API</ThemedText.BodySmall>
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import store from 'state'
|
||||
import { RouterPreference } from 'state/routing/slice'
|
||||
import { updateUserRouterPreference } from 'state/user/reducer'
|
||||
import { fireEvent, render, screen } from 'test-utils/render'
|
||||
|
||||
import RouterPreferenceSettings from '.'
|
||||
|
||||
jest.mock('featureFlags/flags/uniswapx', () => ({
|
||||
useUniswapXEnabled: () => true,
|
||||
}))
|
||||
|
||||
describe('RouterPreferenceSettings', () => {
|
||||
// Restore to default router preference before each unit test
|
||||
beforeEach(() => {
|
||||
store.dispatch(updateUserRouterPreference({ userRouterPreference: RouterPreference.API }))
|
||||
})
|
||||
it('toggles `Uniswap X` router preference', () => {
|
||||
render(<RouterPreferenceSettings />)
|
||||
|
||||
const uniswapXToggle = screen.getByTestId('toggle-uniswap-x-button')
|
||||
|
||||
fireEvent.click(uniswapXToggle)
|
||||
expect(uniswapXToggle).toHaveAttribute('aria-selected', 'true')
|
||||
expect(store.getState().user.userRouterPreference).toEqual(RouterPreference.X)
|
||||
|
||||
fireEvent.click(uniswapXToggle)
|
||||
|
||||
expect(uniswapXToggle).toHaveAttribute('aria-selected', 'false')
|
||||
expect(store.getState().user.userRouterPreference).toEqual(RouterPreference.API)
|
||||
})
|
||||
it('toggles `Local Routing` router preference', () => {
|
||||
render(<RouterPreferenceSettings />)
|
||||
|
||||
const localRoutingToggle = screen.getByTestId('toggle-local-routing-button')
|
||||
|
||||
fireEvent.click(localRoutingToggle)
|
||||
expect(localRoutingToggle).toHaveAttribute('aria-selected', 'true')
|
||||
expect(store.getState().user.userRouterPreference).toEqual(RouterPreference.CLIENT)
|
||||
|
||||
fireEvent.click(localRoutingToggle)
|
||||
|
||||
expect(localRoutingToggle).toHaveAttribute('aria-selected', 'false')
|
||||
expect(store.getState().user.userRouterPreference).toEqual(RouterPreference.API)
|
||||
})
|
||||
})
|
||||
@@ -1,81 +1,84 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import Column from 'components/Column'
|
||||
import Radio from 'components/Radio'
|
||||
import UniswapXBrandMark from 'components/Logo/UniswapXBrandMark'
|
||||
import { RowBetween, RowFixed } from 'components/Row'
|
||||
import Toggle from 'components/Toggle'
|
||||
import { isUniswapXSupportedChain } from 'constants/chains'
|
||||
import { useUniswapXEnabled } from 'featureFlags/flags/uniswapx'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
import { RouterPreference } from 'state/routing/slice'
|
||||
import { useRouterPreference } from 'state/user/hooks'
|
||||
import { updateDisabledUniswapX } from 'state/user/reducer'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { Divider, ExternalLink, ThemedText } from 'theme'
|
||||
|
||||
const Preference = styled(Radio)`
|
||||
background-color: ${({ theme }) => theme.backgroundModule};
|
||||
padding: 12px 16px;
|
||||
`
|
||||
|
||||
const PreferencesContainer = styled(Column)`
|
||||
gap: 1.5px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
const InlineLink = styled(ThemedText.Caption)`
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
display: inline;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
`
|
||||
|
||||
export default function RouterPreferenceSettings() {
|
||||
const { chainId } = useWeb3React()
|
||||
const [routerPreference, setRouterPreference] = useRouterPreference()
|
||||
|
||||
const isAutoRoutingActive = routerPreference === RouterPreference.AUTO
|
||||
const uniswapXEnabled = useUniswapXEnabled() && chainId && isUniswapXSupportedChain(chainId)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
return (
|
||||
<Column gap="md">
|
||||
<>
|
||||
{uniswapXEnabled && (
|
||||
<>
|
||||
<RowBetween gap="sm">
|
||||
<RowFixed>
|
||||
<Column gap="xs">
|
||||
<ThemedText.BodySecondary>
|
||||
<UniswapXBrandMark />
|
||||
</ThemedText.BodySecondary>
|
||||
<ThemedText.Caption color="textSecondary">
|
||||
<Trans>When available, aggregates liquidity sources for better prices and gas free swaps.</Trans>{' '}
|
||||
<ExternalLink href="https://support.uniswap.org/hc/en-us/articles/17515415311501">
|
||||
<InlineLink>Learn more</InlineLink>
|
||||
</ExternalLink>
|
||||
</ThemedText.Caption>
|
||||
</Column>
|
||||
</RowFixed>
|
||||
<Toggle
|
||||
id="toggle-uniswap-x-button"
|
||||
isActive={routerPreference === RouterPreference.X}
|
||||
toggle={() => {
|
||||
if (routerPreference === RouterPreference.X) {
|
||||
// We need to remember if a user disables Uniswap X, so we don't show the opt-in flow again.
|
||||
dispatch(updateDisabledUniswapX({ disabledUniswapX: true }))
|
||||
}
|
||||
setRouterPreference(routerPreference === RouterPreference.X ? RouterPreference.API : RouterPreference.X)
|
||||
}}
|
||||
/>
|
||||
</RowBetween>
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
<RowBetween gap="sm">
|
||||
<RowFixed>
|
||||
<Column gap="xs">
|
||||
<ThemedText.BodySecondary>
|
||||
<Trans>Auto Router API</Trans>
|
||||
<Trans>Local routing</Trans>
|
||||
</ThemedText.BodySecondary>
|
||||
<ThemedText.Caption color="textSecondary">
|
||||
<Trans>Use the Uniswap Labs API to get faster quotes.</Trans>
|
||||
</ThemedText.Caption>
|
||||
</Column>
|
||||
</RowFixed>
|
||||
<Toggle
|
||||
id="toggle-optimized-router-button"
|
||||
isActive={isAutoRoutingActive}
|
||||
toggle={() => setRouterPreference(isAutoRoutingActive ? RouterPreference.API : RouterPreference.AUTO)}
|
||||
id="toggle-local-routing-button"
|
||||
isActive={routerPreference === RouterPreference.CLIENT}
|
||||
toggle={() =>
|
||||
setRouterPreference(
|
||||
routerPreference === RouterPreference.CLIENT ? RouterPreference.API : RouterPreference.CLIENT
|
||||
)
|
||||
}
|
||||
/>
|
||||
</RowBetween>
|
||||
{!isAutoRoutingActive && (
|
||||
<PreferencesContainer>
|
||||
<Preference
|
||||
isActive={routerPreference === RouterPreference.API}
|
||||
toggle={() => setRouterPreference(RouterPreference.API)}
|
||||
>
|
||||
<Column gap="xs">
|
||||
<ThemedText.BodyPrimary>
|
||||
<Trans>Uniswap API</Trans>
|
||||
</ThemedText.BodyPrimary>
|
||||
<ThemedText.Caption color="textSecondary">
|
||||
<Trans>Finds the best route on the Uniswap Protocol using the Uniswap Labs Routing API.</Trans>
|
||||
</ThemedText.Caption>
|
||||
</Column>
|
||||
</Preference>
|
||||
<Preference
|
||||
isActive={routerPreference === RouterPreference.CLIENT}
|
||||
toggle={() => setRouterPreference(RouterPreference.CLIENT)}
|
||||
>
|
||||
<Column gap="xs">
|
||||
<ThemedText.BodyPrimary>
|
||||
<Trans>Uniswap client</Trans>
|
||||
</ThemedText.BodyPrimary>
|
||||
<ThemedText.Caption color="textSecondary">
|
||||
<Trans>
|
||||
Finds the best route on the Uniswap Protocol through your browser. May result in high latency and
|
||||
prices.
|
||||
</Trans>
|
||||
</ThemedText.Caption>
|
||||
</Column>
|
||||
</Preference>
|
||||
</PreferencesContainer>
|
||||
)}
|
||||
</Column>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import AnimatedDropdown from 'components/AnimatedDropdown'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { L2_CHAIN_IDS } from 'constants/chains'
|
||||
import { isSupportedChain, L2_CHAIN_IDS } from 'constants/chains'
|
||||
import useDisableScrolling from 'hooks/useDisableScrolling'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import { isSupportedChainId } from 'lib/hooks/routing/clientSideSmartOrderRouter'
|
||||
import { useRef } from 'react'
|
||||
import { useModalIsOpen, useToggleSettingsMenu } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
import { isUniswapXTrade } from 'state/routing/utils'
|
||||
import styled from 'styled-components/macro'
|
||||
import { Divider } from 'theme'
|
||||
|
||||
@@ -38,11 +39,23 @@ const MenuFlyout = styled(AutoColumn)`
|
||||
min-width: 18.125rem;
|
||||
`};
|
||||
user-select: none;
|
||||
gap: 16px;
|
||||
padding: 1rem;
|
||||
padding: 16px;
|
||||
`
|
||||
|
||||
export default function SettingsTab({ autoSlippage, chainId }: { autoSlippage: Percent; chainId?: number }) {
|
||||
const ExpandColumn = styled(AutoColumn)`
|
||||
gap: 16px;
|
||||
padding-top: 16px;
|
||||
`
|
||||
|
||||
export default function SettingsTab({
|
||||
autoSlippage,
|
||||
chainId,
|
||||
trade,
|
||||
}: {
|
||||
autoSlippage: Percent
|
||||
chainId?: number
|
||||
trade?: InterfaceTrade
|
||||
}) {
|
||||
const { chainId: connectedChainId } = useWeb3React()
|
||||
const showDeadlineSettings = Boolean(chainId && !L2_CHAIN_IDS.includes(chainId))
|
||||
|
||||
@@ -54,22 +67,28 @@ export default function SettingsTab({ autoSlippage, chainId }: { autoSlippage: P
|
||||
|
||||
useDisableScrolling(isOpen)
|
||||
|
||||
const isSupportedChain = isSupportedChainId(chainId)
|
||||
const isChainSupported = isSupportedChain(chainId)
|
||||
|
||||
return (
|
||||
<Menu ref={node}>
|
||||
<MenuButton disabled={!isSupportedChain || chainId !== connectedChainId} isActive={isOpen} onClick={toggleMenu} />
|
||||
<MenuButton disabled={!isChainSupported || chainId !== connectedChainId} isActive={isOpen} onClick={toggleMenu} />
|
||||
{isOpen && (
|
||||
<MenuFlyout>
|
||||
<RouterPreferenceSettings />
|
||||
<Divider />
|
||||
<MaxSlippageSettings autoSlippage={autoSlippage} />
|
||||
{showDeadlineSettings && (
|
||||
<>
|
||||
<AutoColumn gap="16px">
|
||||
<RouterPreferenceSettings />
|
||||
</AutoColumn>
|
||||
<AnimatedDropdown open={!isUniswapXTrade(trade)}>
|
||||
<ExpandColumn>
|
||||
<Divider />
|
||||
<TransactionDeadlineSettings />
|
||||
</>
|
||||
)}
|
||||
<MaxSlippageSettings autoSlippage={autoSlippage} />
|
||||
{showDeadlineSettings && (
|
||||
<>
|
||||
<Divider />
|
||||
<TransactionDeadlineSettings />
|
||||
</>
|
||||
)}
|
||||
</ExpandColumn>
|
||||
</AnimatedDropdown>
|
||||
</MenuFlyout>
|
||||
)}
|
||||
</Menu>
|
||||
|
||||
@@ -78,7 +78,7 @@ export default function Toggle({ id, bgColor, isActive, toggle }: ToggleProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Wrapper id={id} isActive={isActive} onClick={switchToggle}>
|
||||
<Wrapper id={id} data-testid={id} role="option" aria-selected={isActive} isActive={isActive} onClick={switchToggle}>
|
||||
<ToggleElement isActive={isActive} bgColor={bgColor} isInitialToggleLoad={isInitialToggleLoad} />
|
||||
</Wrapper>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { darken } from 'polished'
|
||||
import { useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
@@ -67,7 +67,7 @@ const ResourcesContainer = styled.div`
|
||||
|
||||
type AboutSectionProps = {
|
||||
address: string
|
||||
chainId: SupportedChainId
|
||||
chainId: ChainId
|
||||
description?: string | null
|
||||
homepageUrl?: string | null
|
||||
twitterName?: string | null
|
||||
@@ -105,7 +105,7 @@ export function AboutSection({ address, chainId, description, homepageUrl, twitt
|
||||
</ThemedText.SubHeaderSmall>
|
||||
<ResourcesContainer data-cy="resources-container">
|
||||
<Resource
|
||||
name={chainId === SupportedChainId.MAINNET ? 'Etherscan' : 'Block Explorer'}
|
||||
name={chainId === ChainId.MAINNET ? 'Etherscan' : 'Block Explorer'}
|
||||
link={`${explorer}${address === 'NATIVE' ? '' : 'address/' + address}`}
|
||||
/>
|
||||
<Resource name="More analytics" link={`${infoLink}tokens/${address}`} />
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { ChainId, Currency } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { isSupportedChain } from 'constants/chains'
|
||||
import { asSupportedChain } from 'constants/chains'
|
||||
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
|
||||
import useCurrencyBalance from 'lib/hooks/useCurrencyBalance'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
@@ -67,7 +66,7 @@ const StyledNetworkLabel = styled.div`
|
||||
export default function BalanceSummary({ token }: { token: Currency }) {
|
||||
const { account, chainId } = useWeb3React()
|
||||
const theme = useTheme()
|
||||
const { label, color } = getChainInfo(isSupportedChain(chainId) ? chainId : SupportedChainId.MAINNET)
|
||||
const { label, color } = getChainInfo(asSupportedChain(chainId) ?? ChainId.MAINNET)
|
||||
const balance = useCurrencyBalance(account, token)
|
||||
const formattedBalance = formatCurrencyAmount(balance, NumberType.TokenNonTx)
|
||||
const formattedUsdValue = formatCurrencyAmount(useStablecoinValue(balance), NumberType.FiatTokenStats)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user