Compare commits

...

68 Commits

Author SHA1 Message Date
Charles Bachmeier
d6865b033a feat: add feature flag for avax (#6905)
* feat: add feature flag for avax

* add new files

* block swapping when user manually changes chain

* rule of hooks

* remove comment
2023-07-10 17:05:19 -04:00
UL Service Account
e5dff197a9 ci: add global CODEOWNERS 2023-07-07 19:02:47 +00:00
UL Service Account
3b30410284 ci(t9n): download translations from crowdin 2023-07-07 19:02:47 +00:00
Vignesh Mohankumar
e4dbef80eb chore: remove Google Analytics from CSP (#6888)
* disable ga

* comment
2023-07-07 14:15:41 -04:00
Vignesh Mohankumar
e169c9d900 chore: delete WalletConnect v1 (#6857)
* chore: remove walletConnectFallback flag

* chore: remove walletConnectV2 flag

* chore: delete WalletConnect v1

* chore: remove walletConnectV2 flag (#6856)

* rm hidden

* toggle account drawer

* fix test

* Revert "rm hidden"

This reverts commit 9e8dd65b04.

* fix test

* -u

* optional?

* Revert "optional?"

This reverts commit 8456080b78.

* switch mock

* expect

* test

* fix test

* update connection tests
2023-07-07 14:06:07 -04:00
Zach Pomerantz
c8a17f5fe9 build: use context.sha (#6900) 2023-07-07 10:48:00 -07:00
Vignesh Mohankumar
552a38994b chore: remove WalletConnect feature flags (#6855)
* chore: remove walletConnectFallback flag

* chore: remove walletConnectV2 flag (#6856)

* rm hidden

* toggle account drawer

* fix test

* Revert "rm hidden"

This reverts commit 9e8dd65b04.

* fix test

* -u

* optional?

* Revert "optional?"

This reverts commit 8456080b78.

* switch mock

* expect

* test
2023-07-07 13:15:13 -04:00
Zach Pomerantz
b313ac9843 build: upgrade cypress-hardhat (#6897) 2023-07-07 10:03:06 -07:00
Jack Short
88aec2c894 fix: 0 showing as collected fee amount (#6804)
* fix: 0 showing as collected fee amount

* initial test setup

* fire events

* checking for data in tests
2023-07-07 12:51:35 -04:00
Zach Pomerantz
86677ed193 test(staging): add staging-only t9n test (#6899)
* test(staging): add staging-only test

* test(staging): translations mount

* ci(t9n): download translations from crowdin

* test: proving that it works

* Revert "ci(t9n): download translations from crowdin"

This reverts commit b6e0f0c5c2.

* Revert "test: proving that it works"

This reverts commit 1d3a1c2a23.

* fix: spec->test

---------

Co-authored-by: UL Service Account <hello-happy-puppy@users.noreply.github.com>
2023-07-07 09:41:20 -07:00
Zach Pomerantz
accf0905f8 build: block promotion on Test (#6898) 2023-07-07 08:47:32 -07:00
Brendan Wong
ef5065de48 feat: wrangler infra and test environment (#6797)
* feat: add token and nft injection

* feat: basic tests

* fix: get jest configured properly

* fix: change timeout

* fix: uninstall port ready

* fix: readd port ready

* fix: local tests work

* fix: remove other stuff to make this pr smaller

* fix: remove unneeded dependencies

* fix: remove port-ready

* Update yarn.lock

* fix: add lint disable due to module exports for jest

* fix: added waitPort

* fix: deduplicated things?

* Update package.json

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>

* update typing

* change tests to typescript instead of javascript

* changing typing of globalThis servers

* yarn deduplicate

* Update functions/global-teardown.ts

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>

* Update functions/global.d.ts

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>

* Update functions/tsconfig.json

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>

* Update functions/tsconfig.json

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>

* Update package.json

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>

* change dependencies to dev dependencies

* final touches

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-07-07 11:41:09 -04:00
Vignesh Mohankumar
bcb45cded6 feat: add user id to sentry (#6896)
* feat: add user id to sentry

* downgrade

* key

* Update src/tracing/index.ts

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>

* Update src/tracing/index.ts

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>

* fixes

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-07-07 11:14:36 -04:00
cartcrom
dfe50b4bee feat: remove old routing slice and flag (#6895)
feat: remove ura flag
2023-07-07 09:49:42 -04:00
Charles Bachmeier
90f72e05b9 feat: upgrade sdk-core to 3.2.6 and add AVAX (#6757)
* init refactor

* upgrade to 3.2.6 and refactor more uses of chainid

* cleaned up lock file

* remove console log and add placeholder

* use supported chains type for switch fn

* allow passing of all chainIds to switcher

* additional typecast

* better casting solution

* yarn.lock cleanup

* prettier

* better casting for rpc

* prettier

* deprecate no longer needed addresses

* better isSupported checking

* deprecate redundant fn

* cleanup toSupportedChainId

* address initial ocmments

* pretier

* includes testnet

* remove unused export

* merge conflicts

* spread for mutable copy

* explorer text

* check is supported before activating chain

* remove extra uses of mumbai

* remove cast to MockChainId

* retain var name supportedChainId

* updated prettier

* use t for explorer translation

* use mockchainid for test

* feat: Add Avalanche support (#6776)

* init avax

* add most avax props

* add logo

* correct subgraph

* update avax blocksPerFetch

* add square logo

* upgrade ur sdk

* version syntax

* correct tokens

* remove unused token

* remove unused token

* update token list and add coming soon to searchbar

* add coming soon to token explore page

* names to ids

* cleaned up routing

* usdc token

* upgrade default token list

* update SOR

* upgrade SOR

* merge conflicts

* snowtrace

* upgrade SOR

* handle be avax support

* temp hide avax

* show pool positions

* whole

* spotprice update

* not yet supported

* BACKEND_SUPPORTED_CHAINS

* add avax to BE not supported

* update multicall blocks to 5

* add todo

* updated prettier

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>

* be added avax token balances

* validateUrlChainParam should return eth for backend unsupported chain

* readonly

* respond to comments

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-07-06 21:44:06 -07:00
Vignesh Mohankumar
3a0f6920d0 build: use webpack-retry-chunk-load-plugin (#6885)
* build: use webpack retry chunk load plugin

* fix

* dedupe

* lint

* retry backoff

* reduce from 1000 to 500ms

* add cache bust query

* rm cache bust

* 3

* cache bust

* Update craco.config.cjs

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-07-06 17:54:49 -04:00
eddie
07eb9eb9a2 fix: combine userState with default injected wallet in cypress setup (#6849)
* fix: combine userState with default injected wallet in cypress setup

* fix: unconnected state in two tests

* fix: update name
2023-07-06 14:01:13 -07:00
Brendan Wong
a9e8e8b275 fix: web app without NFTs (#6712)
* fix: web app without NFTs

* fix: change card display to memoization

* fix: remove unneeded imports and variables

* fix: readd search bar modification

* fix: update dependencies

* fix: update atom value to use hook

* fix: change hook name

* fix: add tests

* Update src/pages/Landing/index.tsx

Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>

* Update src/components/NavBar/SearchBar.tsx

Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>

* Update src/components/NavBar/SearchBarDropdown.tsx

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>

* Update src/components/NavBar/SearchBarDropdown.test.tsx

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>

* Update SearchBar.tsx

* fix lint

---------

Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-07-06 15:46:41 -04:00
Vignesh Mohankumar
4708d3d3d7 fix: update InterfaceGqlChain syntax (#6892)
lint: update InterfaceGqlChain syntax
2023-07-06 12:31:15 -04:00
Vignesh Mohankumar
27acddc0e7 chore: upgrade prettier (#6887)
* lint

* upgrade prettier

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-07-06 11:54:04 -04:00
Charles Bachmeier
102a935ff8 fix: Handle new and unsupported chains passed in from GQL BE (#6878)
* add subset chain type and checks

* add sentry logging for invalid chain errors

* add test cases

* dynamic token balances

* add test for BE adding a new chain

* rename and use slice

* address comments

* undo yarn.lock changes

* make copy in utils

* chore: Declare GQL variables as readonly (#6889)

* declare gql variables as readonly

* remove console log

* Merge branch 'cab/error_supported_chain' into tina/gql-readonly

---------

Co-authored-by: Charlie B <charles@bachmeier.io>

---------

Co-authored-by: Charlie Bachmeier <charlie.bachmeier@Charlies-MacBook-Pro.local>
Co-authored-by: Tina <59578595+tinaszheng@users.noreply.github.com>
2023-07-05 21:31:30 -07:00
Charles Bachmeier
614c15243e fix: tokens with same name brake cypress test (#6890)
* fix: tokens with same name brake cypress test

* lowercase
2023-07-05 20:23:03 -07:00
Zach Pomerantz
082db21308 build: set test status per commit (#6824)
* build: annotate commit with test status

* build: Test / promotion

* fix: ellipsis

* build: also run on releases/staging

* errant comma

* script context

* runId

* build: post after pre
2023-07-05 14:42:43 -07:00
Jack Short
0f75c6a52b chore: matching swap input number formatting to mobile (#6788)
* initial currency formatting

* updating price formatting on swap

* updating the test
2023-07-05 17:37:57 -04:00
Brendan Wong
1c2ed1d9e4 fix: add margins for empty pool icon (#6884)
* fix: increase height of svg

* update test
2023-07-05 16:36:18 -04:00
Vignesh Mohankumar
0e956fb7c4 chore: remove @walletconnect/modal dependency (#6877) 2023-07-05 13:34:24 -07:00
Zach Pomerantz
b49dd03e25 test(e2e): use electron (#6827)
* test(e2e): use electron

* build: use default container for cypress-test-matrix
2023-07-05 13:30:54 -07:00
Charles Bachmeier
82211a888c fix: show bnb pool positions in mini port (#6880) 2023-07-05 11:16:11 -07:00
cartcrom
5dd8cbbdc9 chore: update tos & privacy policy last updated date (#6882) 2023-07-05 14:12:20 -04:00
eddie
5640c115de fix: remove error popup when switching chains from URL param (#6866) 2023-07-03 17:13:12 -07:00
cartcrom
7b8114b401 fix: remove phantom from injection detection (#6875)
* fix: remove phantom from injection detection

* fix: remove logo image
2023-07-03 16:24:08 -04:00
Brendan Wong
8e36361866 fix: remove blue dot next to buy (#6808)
* remove button

* remove dependencies

* fix: remove useBuyFiatFlowCompleted

* fix: update lint and unit tests

* fix: update user state

* fix: remove migration

* Revert "fix: remove migration"

This reverts commit eef313f9ce.

* test

* fix: add ts ignore
2023-07-03 15:55:49 -04:00
Brendan Wong
0a04cff7eb fix: icons no longer clip (#6812) 2023-06-30 17:41:53 -04:00
Jack Short
d3dd1d4ebd fix: position list row highlight overflowing container (#6805) 2023-06-30 17:25:24 -04:00
Vignesh Mohankumar
50f2e2770a chore: upgrade @web3-react/walletconnect-v2 (#6867)
chore: upgrade @web3-react/walletconnect-v2 (#6863)

* update

* types
2023-06-30 15:27:37 -05:00
Vignesh Mohankumar
830cd07f38 fix: upgrade @web3-react/gnosis-safe (#6868)
fix: upgrade @web3-react/gnosis-safe (#6864)

* fix: upgrade @web3-react/gnosis-safe

* Update yarn.lock
2023-06-30 15:27:20 -05:00
Vignesh Mohankumar
326ae58f04 chore: update @walletconnect/modal (#6851)
* chore: upgrade @walletconnect/modal

* fix
2023-06-30 14:05:10 -04:00
Vignesh Mohankumar
25f5a7178f chore: upgrade @web3-react/walletconnect-v2 (#6847) 2023-06-30 13:34:03 -04:00
Zach Pomerantz
e5591e8f06 fix: allow wallets w/o mainnet to connect to WCv2 (#6854)
* fix: re-initialize wc connector with active chain

* test(e2e): fix universal-search flake
2023-06-30 13:28:48 -04:00
Jordan Frankfurt
1c4a383a49 feat: gate v1 retirement (#6845)
* Revert "feat(wallet-connect): retire v1 (#6820)"

This reverts commit d6759b86e3.

* gate v1/v2 switch with v2 fallback on wc entry

* fix tests

* add a second flag--isolate ... menu and default values from each other

* Update src/components/WalletModal/Option.tsx

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>

* Update src/featureFlags/flags/walletConnectPopover.ts

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>

* pr feedback

* pr feedback

* pr nit

---------

Co-authored-by: Jordan Frankfurt <jordan@CORN-Jordan-949.frankfurt>
Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2023-06-26 15:59:13 -04:00
Vignesh Mohankumar
469a006088 fix: filter more CSP errors (#6839)
* fix: filter more CSP errors

* fix regex

* fix
2023-06-26 14:04:54 -04:00
Vignesh Mohankumar
c673c9e458 fix: filter user reject errors temporarily (#6840) 2023-06-26 13:41:17 -04:00
Vignesh Mohankumar
dd957d07e4 chore: upgrade @web3-react/walletconnect-v2 (#6842)
* chore: upgrade @web3-react/walletconnect-v2

* fix typecheck
2023-06-26 13:15:16 -04:00
Vignesh Mohankumar
3837ce24ac revert: "feat(wallet-connect): retire v1" (#6841)
Revert "feat(wallet-connect): retire v1 (#6820)"

This reverts commit d6759b86e3.
2023-06-26 12:43:16 -04:00
Zach Pomerantz
4b87e3d9b8 test(e2e): skip failing slippage test (#6844) 2023-06-26 12:29:42 -04:00
Jordan Frankfurt
d6759b86e3 feat(wallet-connect): retire v1 (#6820)
* feat(wallet-connect): retire v1

* fix unit test

* Update src/state/user/reducer.ts

Co-authored-by: cartcrom <39385577+cartcrom@users.noreply.github.com>

* useToggleAccountDrawer

* fix test

---------

Co-authored-by: cartcrom <39385577+cartcrom@users.noreply.github.com>
2023-06-23 15:12:21 -05:00
Jordan Frankfurt
df55456409 fix: chrome tie sort resolution (#6833) 2023-06-23 15:05:47 -05:00
Jack Short
f290787b99 fix: divide by zero error on pool page (#6837)
* fix: divide by zero error on pool page

* removing changes here

* adding price in conditional

---------

Co-authored-by: John Short <john.short@CORN-Jack-899.local>
2023-06-23 15:56:41 -04:00
Zach Pomerantz
2f84507a23 chore: add CODEOWNERS to public (#6813) 2023-06-23 14:41:14 -04:00
Zach Pomerantz
96c58361a5 build: rm global yarn cache (#6822)
* build: omit caching global modules

* docs: build omitting cache
2023-06-23 14:41:06 -04:00
Zach Pomerantz
011136d0e9 build: improve workflow caching (#6825)
* build: only cache on main

* build: do not glob yarn.lock

* build: do not cache on yarn.lock
2023-06-23 14:40:58 -04:00
Vignesh Mohankumar
054d1de88a chore: upgrade @web3-react/walletconnect-v2 (#6831) 2023-06-23 14:40:30 -04:00
Jack Short
ebab00d7bd fix: adding wallet connect v2 behind feature flag (#6826)
* fix: adding wallet connect v2 behind feature flag

* passing unit tests
2023-06-22 16:16:41 -04:00
Brendan Wong
01dc10d4f3 fix: scroll behavior on MP on mobile (#6750)
* fix: update overflow

* fix: add margin for mobile

* fix: update css to use built in styling
2023-06-22 16:11:23 -04:00
Brendan Wong
fb3abf275e fix: added op and arb as base tokens (#6810)
* fix: added op and arb as base tokens

* fix: unit testing
2023-06-22 16:10:49 -04:00
Zach Pomerantz
f6ad694200 build: fix slack markdown (#6811)
* build: fix slack markdown

* build: simpler slack messages

* build: simpler message
2023-06-22 14:52:26 -04:00
Zach Pomerantz
a3d72a4bbc build: simplify workflows (#6814)
* build: simplify workflows

* docs: better name
2023-06-22 14:48:42 -04:00
Vignesh Mohankumar
1bb750f136 feat: track quote method in Quote Received event (#6807)
* WIP

* WIP

* more work

* comment

* fix

* v2slice

* add method to data

* add names

* fellback

* rm-log

* only success
2023-06-22 14:19:23 -04:00
Zach Pomerantz
5315272694 chore: rm committed t9n (#6792)
* chore: rm committed t9n

* chore: ignore t9n

* test(e2e): fix locale selection test
2023-06-22 09:30:06 -04:00
Jordan Frankfurt
43b9e398b5 fix: allow more session namespace keys (#6802)
* allow more session namespace keys

* pr feedback

* add test case
2023-06-21 17:02:13 -05:00
eddie
1247989cf4 fix: cut off wallet icon (#6815) 2023-06-21 14:35:08 -07:00
Jordan Frankfurt
45a5ca3b88 feat: sort supported chains to top of the chain selector list (#6800)
* feat: sort supported chains to top of the chain selector list

* pr feedback

---------

Co-authored-by: Jordan Frankfurt <jordan@CORN-Jordan-949.frankfurt>
2023-06-21 16:10:46 -05:00
eddie
54b4567a81 test: add e2e test for usdt special case (#6784)
* feat: revoke USDT approvals in ConfirmSwapModal

* chore: fix bad merge

* test: add e2e test for usdt special case

* fix: refactor test

* fix: make variable static

* fix: comments
2023-06-21 14:02:14 -07:00
Jordan Frankfurt
fc45a504fb fix: log the error message instead of [object Object] (#6773)
* fix: log the error message instead of [object Object]

add tina's rec

pr review

zzmp input

* pr feedback
2023-06-21 15:35:23 -05:00
eddie
052cc69414 feat: log minimum_output_after_slippage to amplitude (#6769)
* feat: log minimum_output_after_slippage to amplitude

* fix: memoize logging props

* fix: dont memoize at all
2023-06-21 10:22:48 -07:00
Zach Pomerantz
5caaaf1b1f test(e2e): switching network (#6662)
* test(e2e): switching network

Co-authored-by: Jordan Frankfurt <<jordan@CORN-Jordan-949.frankfurt>

* fix: reconnect after failed chain switch

* test(e2e): add forks to hardhat.config

* test(e2e): wait on wallet_switchEthereumChain

* build: upgrade cypress-hardhat

* fix: do not disconnect whilst switching

* fix: unit tests

* fix: better chain selector check

* test(e2e): better helper fn naming

* test(e2e): better stub naming

* fix: doc re-activation

* fix: add back click

* build: upgrade cypress-hardhat to include network caching

* unwrap web3status

---------

Co-authored-by: Jordan Frankfurt <<jordan@CORN-Jordan-949.frankfurt>
2023-06-21 13:15:03 -04:00
Zach Pomerantz
c0163767ed build: rm crowdin-sync (#6793) 2023-06-21 10:45:21 -04:00
Zach Pomerantz
342b0c81f6 chore: rm pseudo locale (#6794)
chore: rm pseudo t9n
2023-06-21 10:45:03 -04:00
277 changed files with 16878 additions and 5973 deletions

View File

@@ -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',
},
},

View File

@@ -0,0 +1,32 @@
name: Cache on main
description: caches node_modules/.cache, but only saves from main
inputs:
path:
description: 'A list of files, directories, and wildcard patterns to cache and store'
required: true
key:
description: 'An explicit key for restoring and saving the cache'
required: true
restore-keys:
description: 'An ordered list of keys to use for restoring stale cache if no cache hit occured for key. Note `cache-hit` returns false in this case.'
required: false
# Many build steps have their own caches to improve subsequent build times.
# Build tools are configured to cache to node_modules/.cache, so they are cached independently of node_modules.
# Caches are saved every run *on main* (by keying on github.run_id), and the most recent available cache is loaded.
# Caches are not saved on feature branches because they have limited utility, and extend the runtime of the workflow.
# See https://jongleberry.medium.com/speed-up-your-ci-and-dx-with-node-modules-cache-ac8df82b7bb0.
runs:
using: composite
steps:
- uses: actions/cache/restore@v3
with:
path: ${{ inputs.path }}
key: ${{ inputs.key }}
restore-keys: ${{ inputs.restore-keys }}
- if: github.ref_name == 'main'
uses: actions/cache/save@v3
with:
path: ${{ inputs.path }}
key: ${{ inputs.key }}

View File

@@ -10,7 +10,7 @@ runs:
with:
node-version: 18
registry-url: https://registry.npmjs.org
cache: 'yarn'
# cache is intentionally omitted, as it is faster with yarn v1 to cache node_modules.
- uses: actions/cache@v3
id: install-cache
@@ -19,7 +19,7 @@ runs:
path: |
node_modules
!node_modules/.cache
key: ${{ runner.os }}-install-${{ hashFiles('**/yarn.lock') }}
key: ${{ runner.os }}-install-${{ hashFiles('yarn.lock') }}
- if: steps.install-cache.outputs.cache-hit != 'true'
run: yarn install --frozen-lockfile --ignore-scripts
shell: bash
@@ -55,8 +55,7 @@ runs:
# Messages are extracted from source.
# A record of source file content hashes and catalogs is maintained in node_modules/.cache/lingui.
# Messages are always extracted, but extraction may short-circuit from the custom extractor's cache.
- uses: actions/cache@v3
id: i18n-extract-cache
- uses: ./.github/actions/cache-on-main
with:
path: node_modules/.cache
key: ${{ runner.os }}-i18n-extract-${{ github.run_id }}

View File

@@ -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 }}
@@ -44,7 +59,7 @@ jobs:
- name: Add translations
run: |
rm src/locales/en-US.po
git add src/locales/*.po
git add -f src/locales/*.po
git commit -m 'ci(t9n): download translations from crowdin'
- name: Add CODEOWNERS

View File

@@ -10,23 +10,23 @@ jobs:
environment:
name: deploy/staging
steps:
- name: Send Slack message that deploy is starting
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
continue-on-error: true
with:
payload: |
{
"text": "Staging deploy started for branch: ${{ github.ref_name }}"
"text": "Deploy _started_ for ${{ github.ref_name }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: yarn prepare
- run: yarn build
env:
REACT_APP_STAGING: 1
- name: Update Cloudflare Pages deployment
id: pages-deployment
uses: cloudflare/pages-action@364c7ca09a4b57837c5967871d64a2c31adb8c0d
@@ -38,18 +38,19 @@ jobs:
githubToken: ${{ secrets.GITHUB_TOKEN }}
# Cloudflare uses `main` as the default production branch, so we push using the `main` branch so that it can be aliased by a custom domain.
branch: main
- name: Send Slack message about deployment outcome
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
continue-on-error: true
if: always()
with:
payload: |
{
"text": "Staging deploy **${{ steps.pages-deployment.outcome }}** for: ${{ github.ref_name }}"
"text": "Deploy *${{ steps.pages-deployment.outcome }}* for ${{ github.ref_name }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- name: Upload source maps to Sentry
uses: getsentry/action-release@bd5f874fcda966ba48139b0140fb3ec0cb3aabdd
continue-on-error: true

View File

@@ -10,21 +10,21 @@ jobs:
environment:
name: deploy/prod
steps:
- name: Send Slack message that build is starting
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
continue-on-error: true
with:
payload: |
{
"text": "Production deploy started for branch: ${{ github.ref_name }}"
"text": "Deploy _started_ for ${{ github.ref_name }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: yarn prepare
- run: yarn build
- name: Bump and tag
id: github-tag-action
uses: mathieudutour/github-tag-action@d745f2e74aaf1ee82e747b181f7a0967978abee0
@@ -48,7 +48,7 @@ jobs:
with:
cidv0: ${{ steps.pinata.outputs.hash }}
- name: Release
- name: Publish release
uses: actions/create-release@v1.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -86,18 +86,18 @@ jobs:
# Cloudflare uses `main` as the default production branch, so we push using the `main` branch so that it can be aliased by a custom domain.
branch: main
- name: Send Slack message about deployment outcome
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
continue-on-error: true
if: always()
with:
payload: |
{
"text": "Production deploy **${{ steps.pages-deployment.outcome }}** for: ${{ github.ref_name }}"
"text": "Deploy *${{ steps.pages-deployment.outcome }}* for ${{ github.ref_name }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- name: Upload source maps to Sentry
uses: getsentry/action-release@4744f6a65149f441c5f396d5b0877307c0db52c7
continue-on-error: true

View File

@@ -1,33 +0,0 @@
name: Crowdin Download
on:
schedule:
# Download translations every hour.
# This is not done as part of the build so that builds remain reproducible.
- cron: '0 * * * *'
# manual trigger
workflow_dispatch:
jobs:
download-translations:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: yarn i18n:extract
- name: Download Crowdin translations
uses: crowdin/github-action@3133cc916c35590475cf6705f482fb653d8e36e9
with:
upload_sources: false
download_translations: true
project_id: 458284
token: ${{ secrets.CROWDIN_PERSONAL_TOKEN_SECRET }}
source: 'src/locales/en-US.po'
translation: 'src/locales/%locale%.po'
create_pull_request: true
pull_request_title: 'chore(i18n): new Crowdin translations'
localization_branch_name: l10n_crowdin
commit_message: 'chore(i18n): synchronize translations from crowdin [skip ci]'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,4 +1,4 @@
name: Slack notifications for releases/* merges
name: Slack notification on pushes to releases/*
# This CI job will push notifications to Slack whenever code is merged into any releases/* branch
#
@@ -25,7 +25,6 @@ on:
jobs:
notify-slack:
name: 'Emit Slack notification(s)'
runs-on: ubuntu-latest
environment:
name: notify/releases
@@ -45,9 +44,7 @@ jobs:
| awk '{print substr($0,0,3000);}' \
> /tmp/parsed_github_context
echo "SLACK_COMMITS=$(cat /tmp/parsed_github_context)" >> "$GITHUB_OUTPUT"
- name: Send custom JSON data to Slack workflow
id: slack
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
- uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
with:
payload: |
{

View File

@@ -1,7 +1,7 @@
name: Test
# Many build steps have their own caches, so each job has its own cache to improve subsequent build times.
# Build tools are configured to cache cache to node_modules/.cache, so this is cached independently of node_modules.
# Build tools are configured to cache to node_modules/.cache, so they are cached independently of node_modules.
# Caches are saved every run (by keying on github.run_id), and the most recent available cache is loaded.
# See https://jongleberry.medium.com/speed-up-your-ci-and-dx-with-node-modules-cache-ac8df82b7bb0.
@@ -9,9 +9,8 @@ on:
push:
branches:
- main
- releases/staging
pull_request:
# manual trigger
workflow_dispatch:
jobs:
lint:
@@ -19,12 +18,11 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
id: eslint-cache
- uses: ./.github/actions/cache-on-main
with:
path: node_modules/.cache
key: ${{ runner.os }}-eslint-${{ hashFiles('**/yarn.lock') }}-${{ github.run_id }}
restore-keys: ${{ runner.os }}-eslint-${{ hashFiles('**/yarn.lock') }}-
key: ${{ runner.os }}-eslint-${{ github.run_id }}
restore-keys: ${{ runner.os }}-eslint-
- run: yarn lint
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
@@ -37,12 +35,11 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
id: tsc-cache
- uses: ./.github/actions/cache-on-main
with:
path: node_modules/.cache
key: ${{ runner.os }}-tsc-${{ hashFiles('**/yarn.lock') }}-${{ github.run_id }}
restore-keys: ${{ runner.os }}-tsc-${{ hashFiles('**/yarn.lock') }}-
key: ${{ runner.os }}-tsc-${{ github.run_id }}
restore-keys: ${{ runner.os }}-tsc-
- run: yarn typecheck
- if: failure() && github.ref_name == 'main'
uses: ./.github/actions/report
@@ -67,12 +64,11 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
id: jest-cache
- uses: ./.github/actions/cache-on-main
with:
path: node_modules/.cache
key: ${{ runner.os }}-jest-${{ hashFiles('**/yarn.lock') }}-${{ github.run_id }}
restore-keys: ${{ runner.os }}-jest-${{ hashFiles('**/yarn.lock') }}-
key: ${{ runner.os }}-jest-${{ github.run_id }}
restore-keys: ${{ runner.os }}-jest-
- run: yarn test --coverage --maxWorkers=100%
- uses: codecov/codecov-action@v3
with:
@@ -90,12 +86,11 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
id: build-e2e-cache
- uses: ./.github/actions/cache-on-main
with:
path: node_modules/.cache
key: ${{ runner.os }}-build-e2e-${{ hashFiles('**/yarn.lock') }}-${{ github.run_id }}
restore-keys: ${{ runner.os }}-build-e2e-${{ hashFiles('**/yarn.lock') }}-
key: ${{ runner.os }}-build-e2e-${{ github.run_id }}
restore-keys: ${{ runner.os }}-build-e2e-
- run: yarn build:e2e
env:
NODE_OPTIONS: "--max_old_space_size=4096"
@@ -114,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:
@@ -122,8 +116,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/cache@v3
id: cypress-cache
- uses: ./.github/actions/cache-on-main
with:
path: /root/.cache/Cypress
key: ${{ runner.os }}-cypress-${{ hashFiles('**/node_modules/cypress/package.json') }}
@@ -136,8 +129,7 @@ jobs:
name: build-e2e
path: build
- uses: actions/cache@v3
id: hardhat-cache
- uses: ./.github/actions/cache-on-main
with:
path: cache
key: ${{ runner.os }}-hardhat-${{ hashFiles('hardhat.config.js') }}-${{ github.run_id }}
@@ -150,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 }}
@@ -176,11 +169,44 @@ jobs:
name: Cypress tests
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TEST_REPORTER_WEBHOOK }}
# Included as a single job to check for cypress-test-matrix success, as a matrix cannot be checked.
cypress-tests:
if: always()
needs: [cypress-test-matrix]
pre:
if: ${{ github.ref_name == 'main' || github.ref_name == 'releases/staging' }}
runs-on: ubuntu-latest
steps:
- if: needs.cypress-test-matrix.result != 'success'
run: exit 1
- 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' }}

3
.gitignore vendored
View File

@@ -5,8 +5,7 @@
/src/types/v3
/src/abis/types
/src/locales/**/*.js
/src/locales/**/en-US.po
/src/locales/**/pseudo.po
/src/locales/**/*.po
# generated files
/src/**/__generated__

1
CODEOWNERS Normal file
View File

@@ -0,0 +1 @@
@uniswap/web-admins

View File

@@ -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:

View File

@@ -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',
},
})

View File

@@ -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()
})

View File

@@ -1,6 +1,8 @@
import { BigNumber } from '@ethersproject/bignumber'
import { MaxUint160, MaxUint256 } from '@uniswap/permit2-sdk'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { DAI, USDC_MAINNET } from '../../src/constants/tokens'
import { DAI, USDC_MAINNET, USDT } from '../../src/constants/tokens'
import { getTestSelector } from '../utils'
/** Initiates a swap. */
@@ -13,30 +15,26 @@ function initiateSwap() {
}
describe('Permit2', () => {
// The same tokens are used for all permit2 tests.
const INPUT_TOKEN = DAI
const OUTPUT_TOKEN = USDC_MAINNET
beforeEach(() => {
// Sets up a swap between INPUT_TOKEN and OUTPUT_TOKEN.
cy.visit(`/swap/?inputCurrency=${INPUT_TOKEN.address}&outputCurrency=${OUTPUT_TOKEN.address}`, {
function setupInputs(inputToken: Token, outputToken: Token) {
// Sets up a swap between inputToken and outputToken.
cy.visit(`/swap/?inputCurrency=${inputToken.address}&outputCurrency=${outputToken.address}`, {
ethereum: 'hardhat',
})
cy.get('#swap-currency-input .token-amount-input').type('0.01')
})
}
/** Asserts permit2 has a max approval for spend of the input token on-chain. */
function expectTokenAllowanceForPermit2ToBeMax() {
function expectTokenAllowanceForPermit2ToBeMax(inputToken: Token) {
// check token approval
cy.hardhat()
.then(({ approval, wallet }) => approval.getTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }))
.then(({ approval, wallet }) => approval.getTokenAllowanceForPermit2({ owner: wallet, token: inputToken }))
.should('deep.equal', MaxUint256)
}
/** Asserts the universal router has a max permit2 approval for spend of the input token on-chain. */
function expectPermit2AllowanceForUniversalRouterToBeMax() {
function expectPermit2AllowanceForUniversalRouterToBeMax(inputToken: Token) {
cy.hardhat()
.then((hardhat) => hardhat.approval.getPermit2Allowance({ owner: hardhat.wallet, token: INPUT_TOKEN }))
.then((hardhat) => hardhat.approval.getPermit2Allowance({ owner: hardhat.wallet, token: inputToken }))
.then((allowance) => {
cy.wrap(MaxUint160.eq(allowance.amount)).should('eq', true)
// Asserts that the on-chain expiration is in 30 days, within a tolerance of 40 seconds.
@@ -51,6 +49,7 @@ describe('Permit2', () => {
beforeEach(() => cy.hardhat({ automine: false }))
it('swaps after completing full permit2 approval process', () => {
setupInputs(DAI, USDC_MAINNET)
initiateSwap()
// verify that the modal retains its state when the window loses focus
@@ -61,7 +60,7 @@ describe('Permit2', () => {
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
cy.get(getTestSelector('popups')).contains('Approved')
expectTokenAllowanceForPermit2ToBeMax()
expectTokenAllowanceForPermit2ToBeMax(DAI)
// Verify permit2 approval
cy.contains('Allow DAI to be used for swapping')
@@ -70,12 +69,13 @@ describe('Permit2', () => {
cy.hardhat().then((hardhat) => hardhat.mine())
cy.contains('Success')
cy.get(getTestSelector('popups')).contains('Swapped')
expectPermit2AllowanceForUniversalRouterToBeMax()
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
})
it('swaps with existing permit approval and missing token approval', () => {
setupInputs(DAI, USDC_MAINNET)
cy.hardhat().then(async (hardhat) => {
await hardhat.approval.setPermit2Allowance({ owner: hardhat.wallet, token: INPUT_TOKEN })
await hardhat.approval.setPermit2Allowance({ owner: hardhat.wallet, token: DAI })
await hardhat.mine()
})
initiateSwap()
@@ -85,7 +85,50 @@ describe('Permit2', () => {
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
cy.get(getTestSelector('popups')).contains('Approved')
expectTokenAllowanceForPermit2ToBeMax()
expectTokenAllowanceForPermit2ToBeMax(DAI)
// Verify transaction
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
cy.contains('Success')
cy.get(getTestSelector('popups')).contains('Swapped')
})
/**
* On mainnet, you have to revoke USDT approval before increasing it.
* From the token contract:
* To change the approve amount you first have to reduce the addresses`
* allowance to zero by calling `approve(_spender, 0)` if it is not
* already 0 to mitigate the race condition described here:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*/
it('swaps USDT with existing permit, and existing but insufficient token approval', () => {
cy.hardhat().then(async (hardhat) => {
await hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDT, 2e6))
await hardhat.mine()
await hardhat.approval.setTokenAllowanceForPermit2({ owner: hardhat.wallet, token: USDT }, 1e6)
await hardhat.mine()
await hardhat.approval.setPermit2Allowance({ owner: hardhat.wallet, token: USDT })
await hardhat.mine()
})
setupInputs(USDT, USDC_MAINNET)
cy.get('#swap-currency-input .token-amount-input').clear().type('2')
initiateSwap()
// Verify allowance revocation
cy.contains('Reset USDT')
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
cy.hardhat()
.then(({ approval, wallet }) => approval.getTokenAllowanceForPermit2({ owner: wallet, token: USDT }))
.should('deep.equal', BigNumber.from(0))
// Verify token approval
cy.contains('Enable spending USDT on Uniswap')
cy.wait('@eth_sendRawTransaction')
cy.hardhat().then((hardhat) => hardhat.mine())
cy.get(getTestSelector('popups')).contains('Approved')
expectTokenAllowanceForPermit2ToBeMax(USDT)
// Verify transaction
cy.wait('@eth_sendRawTransaction')
@@ -98,10 +141,11 @@ describe('Permit2', () => {
it('swaps when user has already approved token and permit2', () => {
cy.hardhat().then(({ approval, wallet }) =>
Promise.all([
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }),
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN }),
approval.setTokenAllowanceForPermit2({ owner: wallet, token: DAI }),
approval.setPermit2Allowance({ owner: wallet, token: DAI }),
])
)
setupInputs(DAI, USDC_MAINNET)
initiateSwap()
// Verify transaction
@@ -110,6 +154,7 @@ describe('Permit2', () => {
})
it('swaps after handling user rejection of both approval and signature', () => {
setupInputs(DAI, USDC_MAINNET)
const USER_REJECTION = { code: 4001 }
cy.hardhat().then((hardhat) => {
// Reject token approval
@@ -132,7 +177,7 @@ describe('Permit2', () => {
// Verify token approval
cy.get(getTestSelector('popups')).contains('Approved')
expectTokenAllowanceForPermit2ToBeMax()
expectTokenAllowanceForPermit2ToBeMax(DAI)
// Verify permit2 approval rejection
cy.wrap(permitApprovalStub).should('be.calledWith', 'eth_signTypedData_v4')
@@ -145,30 +190,32 @@ describe('Permit2', () => {
// Verify permit2 approval
cy.contains('Success')
cy.get(getTestSelector('popups')).contains('Swapped')
expectPermit2AllowanceForUniversalRouterToBeMax()
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
})
})
it('prompts token approval when existing approval amount is too low', () => {
setupInputs(DAI, USDC_MAINNET)
cy.hardhat().then(({ approval, wallet }) =>
Promise.all([
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN }),
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }, 1),
approval.setPermit2Allowance({ owner: wallet, token: DAI }),
approval.setTokenAllowanceForPermit2({ owner: wallet, token: DAI }, 1),
])
)
initiateSwap()
// Verify token approval
cy.get(getTestSelector('popups')).contains('Approved')
expectPermit2AllowanceForUniversalRouterToBeMax()
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
})
it('prompts signature when existing permit approval is expired', () => {
setupInputs(DAI, USDC_MAINNET)
const expiredAllowance = { expiration: Math.floor((Date.now() - 1) / 1000) }
cy.hardhat().then(({ approval, wallet }) =>
Promise.all([
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }),
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN }, expiredAllowance),
approval.setTokenAllowanceForPermit2({ owner: wallet, token: DAI }),
approval.setPermit2Allowance({ owner: wallet, token: DAI }, expiredAllowance),
])
)
initiateSwap()
@@ -177,15 +224,16 @@ describe('Permit2', () => {
cy.wait('@eth_signTypedData_v4')
cy.contains('Success')
cy.get(getTestSelector('popups')).contains('Swapped')
expectPermit2AllowanceForUniversalRouterToBeMax()
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
})
it('prompts signature when existing permit approval amount is too low', () => {
setupInputs(DAI, USDC_MAINNET)
const smallAllowance = { amount: 1 }
cy.hardhat().then(({ approval, wallet }) =>
Promise.all([
approval.setTokenAllowanceForPermit2({ owner: wallet, token: INPUT_TOKEN }),
approval.setPermit2Allowance({ owner: wallet, token: INPUT_TOKEN }, smallAllowance),
approval.setTokenAllowanceForPermit2({ owner: wallet, token: DAI }),
approval.setPermit2Allowance({ owner: wallet, token: DAI }, smallAllowance),
])
)
initiateSwap()
@@ -194,6 +242,6 @@ describe('Permit2', () => {
cy.wait('@eth_signTypedData_v4')
cy.contains('Success')
cy.get(getTestSelector('popups')).contains('Swapped')
expectPermit2AllowanceForUniversalRouterToBeMax()
expectPermit2AllowanceForUniversalRouterToBeMax(DAI)
})
})

View File

@@ -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', () => {
@@ -64,7 +64,7 @@ describe('Swap errors', () => {
})
})
it('slippage failure', () => {
it.skip('slippage failure', () => {
cy.visit(`/swap?inputCurrency=ETH&outputCurrency=${UNI_MAINNET.address}`, { ethereum: 'hardhat' })
cy.hardhat({ automine: false })
getBalance(USDC_MAINNET).then((initialBalance) => {

View File

@@ -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', () => {

View File

@@ -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(() => {

View File

@@ -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')

View File

@@ -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', () => {

View File

@@ -1,13 +1,7 @@
import { getTestSelector } from '../utils'
describe('Universal search bar', () => {
before(() => {
beforeEach(() => {
cy.visit('/')
cy.get('[data-cy="magnifying-icon"]')
.parent()
.then(($navIcon) => {
$navIcon.click()
})
cy.get('[data-cy="magnifying-icon"]').parent().eq(1).click()
})
it('should yield clickable result for regular token or nft collection search term', () => {
@@ -19,20 +13,7 @@ describe('Universal search bar', () => {
.and('contain.text', '$')
.and('contain.text', '%')
cy.get('[data-cy="searchbar-token-row-UNI"]').first().click()
cy.get('div').contains('Uniswap').should('exist')
// Stats should have: TVL, 24H Volume, 52W low, 52W high.
cy.get(getTestSelector('token-details-stats')).should('exist')
cy.get(getTestSelector('token-details-stats')).within(() => {
cy.get('[data-cy="tvl"]').should('include.text', '$')
cy.get('[data-cy="volume-24h"]').should('include.text', '$')
cy.get('[data-cy="52w-low"]').should('include.text', '$')
cy.get('[data-cy="52w-high"]').should('include.text', '$')
})
// About section should have description of token.
cy.get(getTestSelector('token-details-about-section')).should('exist')
cy.contains('UNI is the governance token for Uniswap').should('exist')
cy.location('hash').should('equal', '#/tokens/ethereum/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984')
})
it.skip('should show recent tokens and popular tokens with empty search term', () => {

View File

@@ -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()

View File

@@ -0,0 +1,130 @@
import { createDeferredPromise } from '../../../src/test-utils/promise'
import { getTestSelector } from '../../utils'
function waitsForActiveChain(chain: string) {
cy.get(getTestSelector('chain-selector-logo')).invoke('attr', 'alt').should('eq', chain)
}
function switchChain(chain: string) {
cy.get(getTestSelector('chain-selector')).eq(1).click()
cy.contains(chain).click()
}
describe('network switching', () => {
beforeEach(() => {
cy.visit('/swap', { ethereum: 'hardhat' })
cy.get(getTestSelector('web3-status-connected'))
})
function rejectsNetworkSwitchWith(rejection: unknown) {
cy.hardhat().then((hardhat) => {
// Reject network switch
const sendStub = cy.stub(hardhat.provider, 'send').log(false).as('switch')
sendStub.withArgs('wallet_switchEthereumChain').rejects(rejection)
sendStub.callThrough() // allows other calls to return non-stubbed values
})
switchChain('Polygon')
// Verify rejected network switch
cy.get('@switch').should('have.been.calledWith', 'wallet_switchEthereumChain')
waitsForActiveChain('Ethereum')
cy.get(getTestSelector('web3-status-connected'))
}
it('should not display message on user rejection', () => {
const USER_REJECTION = { code: 4001 }
rejectsNetworkSwitchWith(USER_REJECTION)
cy.get(getTestSelector('popups')).should('not.contain', 'Failed to switch networks')
})
it('should display message on unknown error', () => {
rejectsNetworkSwitchWith(new Error('Unknown error'))
cy.get(getTestSelector('popups')).contains('Failed to switch networks')
})
it('should add missing chain', () => {
cy.hardhat().then((hardhat) => {
// https://docs.metamask.io/guide/rpc-api.html#unrestricted-methods
const CHAIN_NOT_ADDED = { code: 4902 } // missing message in useSelectChain
// Reject network switch with CHAIN_NOT_ADDED
const sendStub = cy.stub(hardhat.provider, 'send').log(false).as('switch')
let added = false
sendStub
.withArgs('wallet_switchEthereumChain')
.callsFake(() => (added ? Promise.resolve(null) : Promise.reject(CHAIN_NOT_ADDED)))
sendStub.withArgs('wallet_addEthereumChain').callsFake(() => {
added = true
return Promise.resolve(null)
})
sendStub.callThrough() // allows other calls to return non-stubbed values
})
switchChain('Polygon')
// Verify the network was added
cy.get('@switch').should('have.been.calledWith', 'wallet_switchEthereumChain')
cy.get('@switch').should('have.been.calledWith', 'wallet_addEthereumChain', [
{
blockExplorerUrls: ['https://polygonscan.com/'],
chainId: '0x89',
chainName: 'Polygon',
nativeCurrency: { name: 'Polygon Matic', symbol: 'MATIC', decimals: 18 },
rpcUrls: ['https://polygon-rpc.com/'],
},
])
})
it('should not disconnect while switching', () => {
const promise = createDeferredPromise()
cy.hardhat().then((hardhat) => {
// Reject network switch with CHAIN_NOT_ADDED
const sendStub = cy.stub(hardhat.provider, 'send').log(false).as('switch')
sendStub.withArgs('wallet_switchEthereumChain').returns(promise)
sendStub.callThrough() // allows other calls to return non-stubbed values
})
switchChain('Polygon')
// Verify there is no disconnection
cy.get('@switch').should('have.been.calledWith', 'wallet_switchEthereumChain')
cy.contains('Connecting to Polygon')
cy.get(getTestSelector('web3-status-connected')).should('be.disabled')
promise.resolve()
})
it('should switch networks', () => {
// Select an output currency
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.contains('USDC').click()
// Populate input/output fields
cy.get('#swap-currency-input .token-amount-input').clear().type('1')
cy.get('#swap-currency-output .token-amount-input').should('not.equal', '')
// Switch network
switchChain('Polygon')
// Verify network switch
cy.wait('@wallet_switchEthereumChain')
waitsForActiveChain('Polygon')
cy.get(getTestSelector('web3-status-connected'))
// Verify that the input/output fields were reset
cy.get('#swap-currency-input .token-amount-input').should('have.value', '')
cy.get(`#swap-currency-input .token-symbol-container`).should('contain.text', 'MATIC')
cy.get(`#swap-currency-output .token-amount-input`).should('not.have.value')
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')
})
})

View File

@@ -1,8 +1,8 @@
import { getTestSelector } from '../utils'
describe('Wallet Dropdown', () => {
function itShouldChangeTheTheme() {
it('should change the theme', () => {
function itChangesTheme() {
it('should change theme', () => {
cy.get(getTestSelector('theme-lightmode')).click()
cy.get(getTestSelector('theme-lightmode')).should('not.have.css', 'background-color', 'rgba(0, 0, 0, 0)')
@@ -21,13 +21,17 @@ describe('Wallet Dropdown', () => {
})
}
function itShouldChangeTheLanguage() {
it('should select a language', () => {
cy.get(getTestSelector('wallet-language-item')).contains('Deutsch').click({ force: true })
cy.get(getTestSelector('wallet-header')).should('contain', 'Sprache')
function itChangesLocale() {
it('should change locale', () => {
cy.contains('Uniswap available in: English').should('not.exist')
cy.get(getTestSelector('wallet-language-item')).contains('Afrikaans').click({ force: true })
cy.location('hash').should('match', /\?lng=af-ZA$/)
cy.contains('Uniswap available in: English')
cy.get(getTestSelector('wallet-language-item')).contains('English').click({ force: true })
cy.get(getTestSelector('wallet-header')).should('contain', 'Language')
cy.get(getTestSelector('wallet-back')).click()
cy.location('hash').should('match', /\?lng=en-US$/)
cy.contains('Uniswap available in: English').should('not.exist')
})
}
@@ -37,8 +41,8 @@ describe('Wallet Dropdown', () => {
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-settings')).click()
})
itShouldChangeTheTheme()
itShouldChangeTheLanguage()
itChangesTheme()
itChangesLocale()
})
describe('testnet toggle', () => {
@@ -68,8 +72,8 @@ describe('Wallet Dropdown', () => {
cy.get(getTestSelector('wallet-disconnect')).click()
cy.get(getTestSelector('wallet-settings')).click()
})
itShouldChangeTheTheme()
itShouldChangeTheLanguage()
itChangesTheme()
itChangesLocale()
})
describe('with color theme', () => {

View 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')
})
})

View File

@@ -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.

View File

@@ -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)

View File

@@ -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 }

View File

@@ -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(...))`',
})
}
},
}
},
}

View File

@@ -0,0 +1 @@
export const presets = ['@babel/preset-env']

View 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,
})
}

View 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
View 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>>
}

View 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
View File

@@ -0,0 +1,3 @@
test('example', async () => {
expect(true).toBe(true)
})

19
functions/tsconfig.json Normal file
View 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"]
}
}

View File

@@ -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,
},
},
},

View File

@@ -1,3 +1,5 @@
import { ChainId } from '@uniswap/sdk-core'
/* eslint-env node */
require('dotenv').config()
@@ -5,20 +7,33 @@ require('dotenv').config()
// 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 POLYGON_BLOCK_NUMBER = 43600000
const mainnetFork = {
url: `https://mainnet.infura.io/v3/${process.env.REACT_APP_INFURA_KEY}`,
blockNumber: BLOCK_NUMBER,
const forkingConfig = {
httpHeaders: {
Origin: 'localhost:3000', // infura allowlists requests by origin
},
}
const forks = {
[ChainId.MAINNET]: {
url: `https://mainnet.infura.io/v3/${process.env.REACT_APP_INFURA_KEY}`,
blockNumber: BLOCK_NUMBER,
...forkingConfig,
},
[ChainId.POLYGON]: {
url: `https://polygon-mainnet.infura.io/v3/${process.env.REACT_APP_INFURA_KEY}`,
blockNumber: POLYGON_BLOCK_NUMBER,
...forkingConfig,
},
}
module.exports = {
forks,
networks: {
hardhat: {
chainId: 1,
forking: mainnetFork,
chainId: ChainId.MAINNET,
forking: forks[ChainId.MAINNET],
accounts: {
count: 2,
},

View File

@@ -115,13 +115,11 @@ const linguiConfig = {
'vi-VN',
'zh-CN',
'zh-TW',
'pseudo',
],
orderBy: 'messageId',
rootDir: '.',
runtimeConfigModule: ['@lingui/core', 'i18n'],
sourceLocale: 'en-US',
pseudoLocale: 'pseudo',
extractors: [cachingExtractor],
}

View File

@@ -15,11 +15,11 @@
"graphql:generate": "yarn graphql:generate:data && yarn graphql:generate:thegraph",
"graphql": "yarn graphql:fetch && yarn graphql:generate",
"i18n:extract": "lingui extract --locale en-US",
"i18n:pseudo": "lingui extract --locale pseudo",
"i18n:compile": "lingui compile",
"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",
@@ -27,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"
@@ -67,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",
@@ -98,35 +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.3.0",
"cypress-hardhat": "^2.4.2",
"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",
"wrangler": "https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/4925945367/npm-package-wrangler-3048",
"webpack-retry-chunk-load-plugin": "^3.1.1",
"yarn-deduplicate": "^6.0.0"
},
"dependencies": {
@@ -165,10 +175,10 @@
"@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/sdk-core": "^3.2.6",
"@uniswap/smart-order-router": "3.13.5",
"@uniswap/token-lists": "^1.0.0-beta.31",
"@uniswap/universal-router-sdk": "^1.5.1",
"@uniswap/universal-router-sdk": "^1.5.3",
"@uniswap/v2-core": "1.0.0",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"@uniswap/v2-sdk": "^3.0.1",
@@ -190,13 +200,12 @@
"@web3-react/core": "^8.2.0",
"@web3-react/eip1193": "^8.2.0",
"@web3-react/empty": "^8.2.0",
"@web3-react/gnosis-safe": "^8.2.0",
"@web3-react/gnosis-safe": "^8.2.1",
"@web3-react/metamask": "^8.2.0",
"@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.3",
"@web3-react/walletconnect-v2": "^8.3.7",
"ajv": "^8.11.0",
"ajv-formats": "^2.1.1",
"array.prototype.flat": "^1.2.4",

1
public/CODEOWNERS Normal file
View File

@@ -0,0 +1 @@
@uniswap/web-admins

View File

@@ -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>

View 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

View 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

View File

@@ -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

View File

@@ -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>

View File

@@ -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'
@@ -40,6 +40,7 @@ const AuthenticatedHeaderWrapper = styled.div`
display: flex;
flex-direction: column;
flex: 1;
overflow: auto;
`
const HeaderButton = styled(ThemeButton)`
@@ -104,7 +105,6 @@ const StatusWrapper = styled.div`
display: inline-block;
width: 70%;
max-width: 70%;
overflow: hidden;
padding-right: 14px;
display: inline-flex;
`
@@ -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
@@ -240,7 +241,7 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
<AuthenticatedHeaderWrapper>
<HeaderWrapper>
<StatusWrapper>
<StatusIcon connection={connection} size={40} />
<StatusIcon account={account} connection={connection} size={40} />
{account && (
<AccountNamesWrapper>
<ThemedText.SubHeader>

View File

@@ -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'
@@ -46,7 +46,7 @@ function mockSwapInfo(
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,7 +246,7 @@ describe('parseLocalActivity', () => {
status: 1,
},
} as TransactionDetails
const chainId = SupportedChainId.MAINNET
const chainId = ChainId.MAINNET
expect(parseLocalActivity(details, chainId, mockTokenAddressMap)).toEqual({
chainId: 1,
currencies: [MockUSDC_MAINNET, MockDAI],
@@ -287,7 +287,7 @@ describe('parseLocalActivity', () => {
status: 1,
},
} as TransactionDetails
const chainId = SupportedChainId.MAINNET
const chainId = ChainId.MAINNET
expect(parseLocalActivity(details, chainId, mockTokenAddressMap)).toMatchObject({
chainId: 1,
currencies: [MockUSDC_MAINNET, MockDAI],
@@ -311,7 +311,7 @@ describe('parseLocalActivity', () => {
status: 1,
},
} as TransactionDetails
const chainId = SupportedChainId.MAINNET
const chainId = ChainId.MAINNET
const tokens = {} as ChainTokenMap
expect(parseLocalActivity(details, chainId, tokens)).toMatchObject({
chainId: 1,

View File

@@ -1,9 +1,8 @@
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 { TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
import { ChainTokenMap, useAllTokensMultichain } from 'hooks/Tokens'
import { useMemo } from 'react'
@@ -26,7 +25,7 @@ import {
import { getActivityTitle } 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,7 +45,7 @@ function buildCurrencyDescriptor(
function parseSwap(
swap: ExactInputSwapTransactionInfo | ExactOutputSwapTransactionInfo,
chainId: SupportedChainId,
chainId: ChainId,
tokens: ChainTokenMap
): Partial<Activity> {
const tokenIn = getCurrency(swap.inputCurrencyId, chainId, tokens)
@@ -62,7 +61,7 @@ function parseSwap(
}
}
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 +75,7 @@ function parseWrap(wrap: WrapTransactionInfo, chainId: SupportedChainId, status:
function parseApproval(
approval: ApproveTransactionInfo,
chainId: SupportedChainId,
chainId: ChainId,
tokens: ChainTokenMap,
status: TransactionStatus
): Partial<Activity> {
@@ -97,7 +96,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 +107,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 +122,7 @@ function parseCollectFees(
function parseMigrateCreateV3(
lp: MigrateV2LiquidityToV3TransactionInfo | CreateV3PoolTransactionInfo,
chainId: SupportedChainId,
chainId: ChainId,
tokens: ChainTokenMap
): Partial<Activity> {
const baseCurrency = getCurrency(lp.baseCurrencyId, chainId, tokens)
@@ -137,7 +136,7 @@ function parseMigrateCreateV3(
export function parseLocalActivity(
details: TransactionDetails,
chainId: SupportedChainId,
chainId: ChainId,
tokens: ChainTokenMap
): Activity | undefined {
try {

View File

@@ -1,8 +1,7 @@
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 moonpayLogoSrc from 'assets/svg/moonpay.svg'
import { NONFUNGIBLE_POSITION_MANAGER_ADDRESSES, UNI_ADDRESS } from 'constants/addresses'
import { nativeOnChain } from 'constants/tokens'
import {
ActivityType,
@@ -14,7 +13,7 @@ import {
TokenApprovalPartsFragment,
TokenTransferPartsFragment,
} 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'
@@ -38,7 +37,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],
@@ -76,10 +75,9 @@ function isSameAddress(a?: string, b?: string) {
}
function callsPositionManagerContract(assetActivity: AssetActivityPartsFragment) {
return isSameAddress(
assetActivity.transaction.to,
NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[fromGraphQLChain(assetActivity.chain)]
)
const supportedChain = supportedChainIdFromGQLChain(assetActivity.chain)
if (!supportedChain) return false
return isSameAddress(assetActivity.transaction.to, NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[supportedChain])
}
// Gets counts for number of NFTs in each collection present
@@ -93,15 +91,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 {
@@ -269,9 +276,17 @@ 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),
chainId: supportedChain,
status: assetActivity.transaction.status,
timestamp: assetActivity.timestamp,
logos: getLogoSrcs(changes),
@@ -289,7 +304,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

View File

@@ -1,12 +1,11 @@
import { Currency } from '@uniswap/sdk-core'
import { SupportedChainId } from 'constants/chains'
import { ChainId, Currency } from '@uniswap/sdk-core'
import { AssetActivityPartsFragment, TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
type Receipt = AssetActivityPartsFragment['transaction']
export type Activity = {
hash: string
chainId: SupportedChainId
chainId: ChainId
status: TransactionStatus
timestamp: number
title: string

View File

@@ -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()

View File

@@ -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 {}

View File

@@ -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 }

View File

@@ -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'
@@ -10,11 +10,13 @@ import useMultiChainPositions from './useMultiChainPositions'
jest.mock('./useMultiChainPositions')
jest.spyOn(console, 'warn').mockImplementation()
const owner = '0xf5b6bb25f5beaea03dd014c6ef9fa9f3926bf36c'
const pool = new Pool(
USDC_MAINNET,
WETH9[SupportedChainId.MAINNET],
WETH9[ChainId.MAINNET],
FeeAmount.MEDIUM,
'1851127709498178402383049949138810',
'7076437181775065414',
@@ -32,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,
@@ -46,7 +48,7 @@ const useMultiChainPositionsReturnValue = {
positions: [
{
owner,
chainId: SupportedChainId.MAINNET,
chainId: ChainId.MAINNET,
position,
pool,
details,

View File

@@ -8,12 +8,12 @@ import { useToggleAccountDrawer } from 'components/AccountDrawer'
import Row from 'components/Row'
import { MouseoverTooltip } from 'components/Tooltip'
import { useFilterPossiblyMaliciousPositions } from 'hooks/useFilterPossiblyMaliciousPositions'
import { useSwitchChain } from 'hooks/useSwitchChain'
import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletContent'
import { useCallback, useMemo, useReducer } from 'react'
import { useNavigate } from 'react-router-dom'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import { switchChain } from 'utils/switchChain'
import { ExpandoRow } from '../ExpandoRow'
import { PortfolioLogo } from '../PortfolioLogo'
@@ -126,11 +126,12 @@ function PositionListItem({ positionInfo }: { positionInfo: PositionInfo }) {
const navigate = useNavigate()
const toggleWalletDrawer = useToggleAccountDrawer()
const { chainId: walletChainId, connector } = useWeb3React()
const switchChain = useSwitchChain()
const onClick = useCallback(async () => {
if (walletChainId !== chainId) await switchChain(connector, chainId)
toggleWalletDrawer()
navigate('/pool/' + details.tokenId)
}, [walletChainId, chainId, connector, toggleWalletDrawer, navigate, details.tokenId])
}, [walletChainId, chainId, switchChain, connector, toggleWalletDrawer, navigate, details.tokenId])
const analyticsEventProperties = useMemo(
() => ({
chain_id: chainId,

View File

@@ -1,11 +1,10 @@
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'
import { useFilterChainsForAvalanche } from 'featureFlags/flags/avalanche'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { PositionDetails } from 'types/position'
import { NonfungiblePositionManager, UniswapInterfaceMulticall } from 'types/v3'
@@ -18,7 +17,7 @@ import { useInterfaceMulticallContracts, usePoolPriceMap, useV3ManagerContracts
function createPositionInfo(
owner: string,
chainId: SupportedChainId,
chainId: ChainId,
details: PositionDetails,
slot0: any,
tokenA: Token,
@@ -42,11 +41,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 }
@@ -61,10 +62,11 @@ type UseMultiChainPositionsData = { positions?: PositionInfo[]; loading: boolean
* @returns positions, fees
*/
export default function useMultiChainPositions(account: string, chains = DEFAULT_CHAINS): UseMultiChainPositionsData {
const pms = useV3ManagerContracts(chains)
const multicalls = useInterfaceMulticallContracts(chains)
const gatedChains = useFilterChainsForAvalanche(chains)
const pms = useV3ManagerContracts(gatedChains)
const multicalls = useInterfaceMulticallContracts(gatedChains)
const getTokens = useGetCachedTokens(chains)
const getTokens = useGetCachedTokens(gatedChains)
const poolAddressCache = usePoolAddressCache()
const [cachedPositions, setPositions] = useCachedPositions(account)
@@ -117,7 +119,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 +160,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]

View File

@@ -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()
})

View File

@@ -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" />

View File

@@ -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]}

View File

@@ -4,13 +4,12 @@ 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 { ThemedText } from 'theme'
import { BREAKPOINTS, ThemedText } from 'theme'
import { ActivityTab } from './Activity'
import NFTs from './NFTs'
@@ -25,6 +24,10 @@ const Wrapper = styled(Column)`
height: 100%;
gap: 12px;
@media screen and (max-width: ${BREAKPOINTS.sm}px) {
margin-bottom: 48px;
}
${PortfolioRowWrapper} {
&:hover {
background: ${({ theme }) => theme.hoverDefault};
@@ -95,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]

View File

@@ -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])

View File

@@ -1,14 +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 } 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 { QRCodeSVG } from 'qrcode.react'
import { useEffect, useState } from 'react'
import styled, { useTheme } from 'styled-components/macro'
@@ -44,16 +44,14 @@ export default function UniwalletModal() {
// Displays the modal if a Uniswap Wallet Connection is pending & qrcode URI is available
const open =
activationState.status === ActivationStatus.PENDING &&
activationState.connection.type === ConnectionType.UNISWAP_WALLET &&
activationState.connection.type === ConnectionType.UNISWAP_WALLET_V2 &&
!!uri
useEffect(() => {
;(uniwalletConnectConnection.connector as WalletConnect).events.addListener(
UniwalletConnect.UNI_URI_AVAILABLE,
(uri) => {
uri && setUri(uri)
}
)
const connectorV2 = uniwalletWCV2ConnectConnection.connector as WalletConnectv2
connectorV2.events.addListener(UniwalletConnectV2.UNI_URI_AVAILABLE, (uri: string) => {
uri && setUri(uri)
})
}, [])
useEffect(() => {

View File

@@ -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'

View File

@@ -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>

View File

@@ -1,8 +1,8 @@
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
import { useAvalancheFlag } from 'featureFlags/flags/avalanche'
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 { useUpdateAtom } from 'jotai/utils'
import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react'
import { X } from 'react-feather'
@@ -209,18 +209,18 @@ export default function FeatureFlagModal() {
featureFlag={FeatureFlag.detailsV2}
label="Use the new details page for nfts"
/>
<FeatureFlagOption
variant={UnifiedRouterVariant}
value={useRoutingAPIV2Flag()}
featureFlag={FeatureFlag.uraEnabled}
label="Enable the Unified Routing API"
/>
<FeatureFlagOption
variant={BaseVariant}
value={useRoutingAPIForPriceFlag()}
featureFlag={FeatureFlag.routingAPIPrice}
label="Use the URA or routing-api for price fetches"
/>
<FeatureFlagOption
variant={BaseVariant}
value={useAvalancheFlag()}
featureFlag={FeatureFlag.avalanche}
label="Enable Avalanche chain"
/>
<FeatureFlagGroup name="Debug">
<FeatureFlagOption
variant={TraceJsonRpcVariant}

View File

@@ -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,
},
}

View File

@@ -5,6 +5,8 @@ import { render } from 'test-utils/render'
import StatusIcon from './StatusIcon'
const ACCOUNT = '0x0'
jest.mock('../../hooks/useSocksBalance', () => ({
useHasSocks: () => true,
}))
@@ -13,15 +15,15 @@ describe('StatusIcon', () => {
describe('with no account', () => {
it('renders children in correct order', () => {
const supportedConnections = getConnections()
const injectedConnection = supportedConnections[1]
const component = render(<StatusIcon connection={injectedConnection} />)
const injectedConnection = supportedConnections[2]
const component = render(<StatusIcon account={ACCOUNT} connection={injectedConnection} />)
expect(component.getByTestId('StatusIconRoot')).toMatchSnapshot()
})
it('renders without mini icons', () => {
const supportedConnections = getConnections()
const injectedConnection = supportedConnections[1]
const component = render(<StatusIcon connection={injectedConnection} showMiniIcons={false} />)
const injectedConnection = supportedConnections[2]
const component = render(<StatusIcon account={ACCOUNT} connection={injectedConnection} showMiniIcons={false} />)
expect(component.getByTestId('StatusIconRoot').children.length).toEqual(0)
})
})
@@ -36,16 +38,16 @@ describe('StatusIcon', () => {
it('renders children in correct order', () => {
const supportedConnections = getConnections()
const injectedConnection = supportedConnections[1]
const component = render(<StatusIcon connection={injectedConnection} />)
const injectedConnection = supportedConnections[2]
const component = render(<StatusIcon account={ACCOUNT} connection={injectedConnection} />)
expect(component.getByTestId('StatusIconRoot')).toMatchSnapshot()
})
it('renders without mini icons', () => {
const supportedConnections = getConnections()
const injectedConnection = supportedConnections[1]
const component = render(<StatusIcon connection={injectedConnection} showMiniIcons={false} />)
expect(component.getByTestId('StatusIconRoot').children.length).toEqual(1)
const injectedConnection = supportedConnections[2]
const component = render(<StatusIcon account={ACCOUNT} connection={injectedConnection} showMiniIcons={false} />)
expect(component.getByTestId('StatusIconRoot').children.length).toEqual(0)
})
})
})

View File

@@ -1,4 +1,3 @@
import { useWeb3React } from '@web3-react/core'
import { Unicon } from 'components/Unicon'
import { Connection, ConnectionType } from 'connection/types'
import useENSAvatar from 'hooks/useENSAvatar'
@@ -67,24 +66,25 @@ const MiniWalletIcon = ({ connection, side }: { connection: Connection; side: 'l
)
}
const MainWalletIcon = ({ connection, size }: { connection: Connection; size: number }) => {
const { account } = useWeb3React()
const MainWalletIcon = ({ account, connection, size }: { account: string; connection: Connection; size: number }) => {
const { avatar } = useENSAvatar(account ?? undefined)
if (!account) {
return null
} else if (avatar || (connection.type === ConnectionType.INJECTED && connection.getName() === 'MetaMask')) {
return <Identicon size={size} />
return <Identicon account={account} size={size} />
} else {
return <Unicon address={account} size={size} />
}
}
export default function StatusIcon({
account,
connection,
size = 16,
showMiniIcons = true,
}: {
account: string
connection: Connection
size?: number
showMiniIcons?: boolean
@@ -93,7 +93,7 @@ export default function StatusIcon({
return (
<IconWrapper size={size} data-testid="StatusIconRoot">
<MainWalletIcon connection={connection} size={size} />
<MainWalletIcon account={account} connection={connection} size={size} />
{showMiniIcons && <MiniWalletIcon connection={connection} side="right" />}
{hasSocks && showMiniIcons && <Socks />}
</IconWrapper>

View File

@@ -108,155 +108,13 @@ exports[`StatusIcon with account renders children in correct order 1`] = `
data-testid="StatusIconRoot"
size="16"
>
<div
style="height: 16px; width: 16px; position: relative;"
>
<div
style="height: 16px; width: 16px; overflow: visible; position: absolute;"
>
<svg
viewBox="0 0 16 16"
>
<defs>
<defs>
<mask
id="container-mask0x52270d8234b864dcAC9947f510CE9275A8a116Db16"
>
<rect
fill="white"
height="100%"
width="100%"
x="0"
y="0"
/>
<g
transform="scale(0.4444444444444444)
translate(0, 0)"
>
<path
d="M18.1309 3.25957C9.91898 3.40293 3.14567 10.1762 3.00231 18.3882C2.85896 26.6001 9.39985 33.141 17.6118 32.9977C25.8238 32.8543 32.5971 26.081 32.7404 17.8691L33 3L18.1309 3.25957Z"
fill="black"
/>
</g>
</mask>
<mask
id="shape-mask0x52270d8234b864dcAC9947f510CE9275A8a116Db16"
>
<rect
fill="white"
height="100%"
width="100%"
x="0"
y="0"
/>
<g
transform="scale(0.4444444444444444)
translate(10, 10)"
>
<path
clip-rule="evenodd"
d="M13.6569 13.6568C12.0059 10.0663 12.0059 5.93368 13.6569 2.34314C10.0663 3.99414 5.93368 3.99414 2.34315 2.34314C3.99414 5.93368 3.99414 10.0663 2.34315 13.6568C5.93368 12.0059 10.0663 12.0059 13.6569 13.6568ZM8 11C9.65685 11 11 9.65686 11 8.00001C11 6.34315 9.65685 5.00001 8 5.00001C6.34315 5.00001 5 6.34315 5 8.00001C5 9.65686 6.34315 11 8 11Z"
fill="black"
fill-rule="evenodd"
/>
</g>
</mask>
<mask
id="mask0x52270d8234b864dcAC9947f510CE9275A8a116Db16"
>
<g
fill="white"
>
<g
mask="url(#shape-mask0x52270d8234b864dcAC9947f510CE9275A8a116Db16)"
>
<g
transform="scale(0.4444444444444444)"
>
<path
d="M18.1309 3.25957C9.91898 3.40293 3.14567 10.1762 3.00231 18.3882C2.85896 26.6001 9.39985 33.141 17.6118 32.9977C25.8238 32.8543 32.5971 26.081 32.7404 17.8691L33 3L18.1309 3.25957Z"
/>
</g>
</g>
<g
mask="url(#container-mask0x52270d8234b864dcAC9947f510CE9275A8a116Db16)"
>
<g
transform="scale(0.4444444444444444)
translate(10, 10)"
>
<path
clip-rule="evenodd"
d="M13.6569 13.6568C12.0059 10.0663 12.0059 5.93368 13.6569 2.34314C10.0663 3.99414 5.93368 3.99414 2.34315 2.34314C3.99414 5.93368 3.99414 10.0663 2.34315 13.6568C5.93368 12.0059 10.0663 12.0059 13.6569 13.6568ZM8 11C9.65685 11 11 9.65686 11 8.00001C11 6.34315 9.65685 5.00001 8 5.00001C6.34315 5.00001 5 6.34315 5 8.00001C5 9.65686 6.34315 11 8 11Z"
fill-rule="evenodd"
/>
</g>
</g>
</g>
</mask>
</defs>
<lineargradient
id="gradient0x52270d8234b864dcAC9947f510CE9275A8a116Db16"
>
<stop
offset="0%"
stop-color="#36DBFF"
/>
<stop
offset="100%"
stop-color="#B8C3B7"
/>
</lineargradient>
<filter
height="200%"
id="blur0x52270d8234b864dcAC9947f510CE9275A8a116Db16"
width="200%"
x="-50%"
y="-50%"
>
<fegaussianblur
in="SourceGraphic"
stdDeviation="5.333333333333333"
/>
</filter>
</defs>
<g
mask="url(#mask0x52270d8234b864dcAC9947f510CE9275A8a116Db16)"
>
<rect
fill="url(#gradient0x52270d8234b864dcAC9947f510CE9275A8a116Db16)"
height="100%"
width="100%"
x="0"
y="0"
/>
<rect
fill="black"
height="100%"
opacity="0.08"
width="100%"
x="0"
y="0"
/>
<ellipse
cx="8"
cy="0"
fill="#9D99F5"
filter="url(#blur0x52270d8234b864dcAC9947f510CE9275A8a116Db16)"
rx="8"
ry="8"
/>
</g>
</svg>
</div>
</div>
<div
class="c1"
>
<img
alt="Install MetaMask icon"
alt="WalletConnect icon"
class="c2"
src="metamask-icon.svg"
src="walletconnect-icon.svg"
/>
</div>
<div
@@ -382,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

View File

@@ -1,5 +1,4 @@
import jazzicon from '@metamask/jazzicon'
import { useWeb3React } from '@web3-react/core'
import useENSAvatar from 'hooks/useENSAvatar'
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'
import styled from 'styled-components/macro'
@@ -18,8 +17,7 @@ const StyledAvatar = styled.img`
border-radius: inherit;
`
export default function Identicon({ size }: { size?: number }) {
const { account } = useWeb3React()
export default function Identicon({ account, size }: { account: string; size?: number }) {
const { avatar } = useENSAvatar(account ?? undefined)
const [fetchable, setFetchable] = useState(true)
const iconSize = size ?? 24

View File

@@ -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',

View File

@@ -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

View File

@@ -1,18 +1,14 @@
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 { useIsAvalancheEnabled } from 'featureFlags/flags/avalanche'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import useSelectChain from 'hooks/useSelectChain'
import useSyncChainQuery from 'hooks/useSyncChainQuery'
@@ -21,9 +17,10 @@ import { Box } from 'nft/components/Box'
import { Portal } from 'nft/components/common/Portal'
import { Column, Row } from 'nft/components/Flex'
import { useIsMobile } from 'nft/hooks'
import { useCallback, useRef, useState } from 'react'
import { useCallback, useMemo, useRef, useState } from 'react'
import { AlertTriangle, ChevronDown, ChevronUp } from 'react-feather'
import { useTheme } from 'styled-components/macro'
import { getSupportedChainIdsFromWalletConnectSession } from 'utils/getSupportedChainIdsFromWalletConnectSession'
import * as styles from './ChainSelector.css'
import ChainSelectorRow from './ChainSelectorRow'
@@ -35,26 +32,15 @@ interface ChainSelectorProps {
leftAlign?: boolean
}
// accounts is an array of strings in the format of "eip155:<chainId>:<address>"
function getChainsFromEIP155Accounts(accounts?: string[]): SupportedChainId[] {
if (!accounts) return []
return accounts
.map((account) => {
const splitAccount = account.split(':')
return splitAccount[1] ? parseInt(splitAccount[1]) : undefined
})
.filter((x) => x !== undefined) as SupportedChainId[]
}
function useWalletSupportedChains() {
function useWalletSupportedChains(): ChainId[] {
const { connector } = useWeb3React()
const connectionType = getConnection(connector).type
switch (connectionType) {
case ConnectionType.WALLET_CONNECT_V2:
return getChainsFromEIP155Accounts((connector as WalletConnect).provider?.session?.namespaces.eip155.accounts)
case ConnectionType.UNISWAP_WALLET:
return getSupportedChainIdsFromWalletConnectSession((connector as WalletConnectV2).provider?.session)
case ConnectionType.UNISWAP_WALLET_V2:
return UniWalletSupportedChains
default:
return NETWORK_SELECTOR_CHAINS
@@ -65,13 +51,30 @@ export const ChainSelector = ({ leftAlign }: ChainSelectorProps) => {
const { chainId } = useWeb3React()
const [isOpen, setIsOpen] = useState<boolean>(false)
const isMobile = useIsMobile()
const isAvalancheEnabled = useIsAvalancheEnabled()
const theme = useTheme()
const showTestnets = useAtomValue(showTestnetsAtom)
const chains = showTestnets
? NETWORK_SELECTOR_CHAINS
: NETWORK_SELECTOR_CHAINS.filter((chain) => !TESTNET_CHAIN_IDS.has(chain))
const walletSupportsChain = useWalletSupportedChains()
const [supportedChains, unsupportedChains] = useMemo(() => {
const { supported, unsupported } = NETWORK_SELECTOR_CHAINS.filter(
(chain: number) =>
(showTestnets || !TESTNET_CHAIN_IDS.includes(chain)) && (isAvalancheEnabled || chain !== ChainId.AVALANCHE)
).reduce(
(acc, chain) => {
if (walletSupportsChain.includes(chain)) {
acc.supported.push(chain)
} else {
acc.unsupported.push(chain)
}
return acc
},
{ supported: [], unsupported: [] } as Record<string, ChainId[]>
)
return [supported, unsupported]
}, [isAvalancheEnabled, showTestnets, walletSupportsChain])
const ref = useRef<HTMLDivElement>(null)
const modalRef = useRef<HTMLDivElement>(null)
@@ -82,10 +85,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)
@@ -94,18 +97,16 @@ export const ChainSelector = ({ leftAlign }: ChainSelectorProps) => {
[selectChain, setIsOpen]
)
const walletSupportsChain = useWalletSupportedChains()
if (!chainId) {
return null
}
const isSupported = !!info
const isSupported = !!info && (isAvalancheEnabled || chainId !== ChainId.AVALANCHE)
const dropdown = (
<NavDropdown top="56" left={leftAlign ? '0' : 'auto'} right={leftAlign ? 'auto' : '0'} ref={modalRef}>
<Column paddingX="8" data-testid="chain-selector-options">
{chains.map((selectorChain: SupportedChainId) => (
{supportedChains.map((selectorChain) => (
<ChainSelectorRow
disabled={!walletSupportsChain.includes(selectorChain)}
onSelectChain={onSelectChain}
@@ -114,6 +115,15 @@ export const ChainSelector = ({ leftAlign }: ChainSelectorProps) => {
isPending={selectorChain === pendingChainId}
/>
))}
{unsupportedChains.map((selectorChain) => (
<ChainSelectorRow
disabled
onSelectChain={() => undefined}
targetChain={selectorChain}
key={selectorChain}
isPending={false}
/>
))}
</Column>
</NavDropdown>
)

View File

@@ -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'
@@ -18,7 +18,7 @@ const Container = styled.button<{ disabled: boolean }>`
display: grid;
grid-template-columns: min-content 1fr min-content;
justify-content: space-between;
line-height: 24px;
line-height: 20px;
opacity: ${({ disabled }) => (disabled ? 0.6 : 1)};
padding: 10px 8px;
text-align: left;
@@ -63,7 +63,7 @@ const Logo = styled.img`
`
interface ChainSelectorRowProps {
disabled?: boolean
targetChain: SupportedChainId
targetChain: ChainId
onSelectChain: (targetChain: number) => void
isPending: boolean
}
@@ -80,7 +80,6 @@ export default function ChainSelectorRow({ disabled, targetChain, onSelectChain,
onClick={() => {
if (!disabled) onSelectChain(targetChain)
}}
data-testid={`chain-selector-option-${label.toLowerCase()}`}
>
<Logo src={logoUrl} alt={label} />
<Label>{label}</Label>

View File

@@ -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 })

View 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()
})
})

View File

@@ -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>}

View 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')
})
})

View File

@@ -1,14 +1,18 @@
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 { useFilterChainsForAvalanche } from 'featureFlags/flags/avalanche'
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 +23,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 +106,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 +151,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 +315,7 @@ export const SearchBarDropdown = ({
isLoading={!trendingTokenData}
/>
)}
{!isTokenPage && (
{Boolean(!isTokenPage && !shouldDisableNFTRoutes) && (
<SearchBarDropdownSection
hoveredIndex={hoveredIndex}
startingIndex={shortenedHistory.length + (isNFTPage ? 0 : trendingTokens?.length ?? 0)}
@@ -351,23 +355,36 @@ export const SearchBarDropdown = ({
trace,
searchHistory,
trendingCollectionsAreLoading,
shouldDisableNFTRoutes,
])
const showBNBComingSoonBadge = chainId === SupportedChainId.BNB && !isLoading
const gatedUnsupportedChains = useFilterChainsForAvalanche([...BACKEND_NOT_YET_SUPPORTED_CHAIN_IDS])
const showChainComingSoonBadge = chainId && gatedUnsupportedChains.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
}
}

View 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>
`;

View File

@@ -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>
`;

View File

@@ -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 (
<>

View File

@@ -1,7 +1,8 @@
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 { useIsAvalancheEnabled } from 'featureFlags/flags/avalanche'
import { ArrowUpRight } from 'react-feather'
import styled from 'styled-components/macro'
import { ExternalLink, HideSmall } from 'theme'
@@ -30,15 +31,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 +49,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 +141,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 {
@@ -153,13 +160,14 @@ function shouldShowAlert(chainId: number | undefined): chainId is NetworkAlertCh
export function NetworkAlert() {
const { chainId } = useWeb3React()
const [darkMode] = useDarkModeManager()
const isAvalancheEnabled = useIsAvalancheEnabled()
if (!shouldShowAlert(chainId)) {
return null
}
const chainInfo = getChainInfo(chainId)
if (!chainInfo) return null
if (!chainInfo || (!isAvalancheEnabled && chainId === ChainId.AVALANCHE)) return null
const { label, logoUrl, bridge } = chainInfo
const textColor = TEXT_COLORS[chainId]

View File

@@ -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>

View File

@@ -1,7 +1,7 @@
import { Trans } from '@lingui/macro'
import { ChainId } from '@uniswap/sdk-core'
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'
@@ -22,7 +22,7 @@ export const PopupAlertTriangle = styled(AlertTriangleFilled)`
height: 32px;
`
export default function FailedNetworkSwitchPopup({ chainId }: { chainId: SupportedChainId }) {
export default function FailedNetworkSwitchPopup({ chainId }: { chainId: ChainId }) {
const chainInfo = getChainInfo(chainId)
return (

View File

@@ -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 (
<>

View File

@@ -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'),

View File

@@ -1,11 +1,9 @@
// eslint-disable-next-line no-restricted-imports
import { Percent } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
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'
@@ -54,11 +52,11 @@ 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 />

View File

@@ -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}`} />

View File

@@ -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)

View File

@@ -1,8 +1,8 @@
import { Trans } from '@lingui/macro'
import { ChainId } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { ButtonPrimary } from 'components/Button'
import { getChainInfo } from 'constants/chainInfo'
import { SupportedChainId } from 'constants/chains'
import useSelectChain from 'hooks/useSelectChain'
import { useNavigate } from 'react-router-dom'
import styled from 'styled-components/macro'
@@ -43,7 +43,7 @@ export default function InvalidTokenDetails({
pageChainId,
isInvalidAddress,
}: {
pageChainId: SupportedChainId
pageChainId: ChainId
isInvalidAddress?: boolean
}) {
const { chainId } = useWeb3React()

View File

@@ -1,8 +1,8 @@
import { Trans } from '@lingui/macro'
import { formatNumber, NumberType } from '@uniswap/conedison/format'
import { ChainId } from '@uniswap/sdk-core'
import { MouseoverTooltip } from 'components/Tooltip'
import { getChainInfo } from 'constants/chainInfo'
import { SupportedChainId } from 'constants/chains'
import { ReactNode } from 'react'
import styled from 'styled-components/macro'
import { ExternalLink, ThemedText } from 'theme'
@@ -68,7 +68,7 @@ function Stat({
}
type StatsSectionProps = {
chainId: SupportedChainId
chainId: ChainId
address: string
priceLow52W?: NumericStat
priceHigh52W?: NumericStat

View File

@@ -26,7 +26,7 @@ import { checkWarning } from 'constants/tokenSafety'
import { TokenPriceQuery } from 'graphql/data/__generated__/types-and-hooks'
import { Chain, TokenQuery, TokenQueryData } from 'graphql/data/Token'
import { QueryToken } from 'graphql/data/Token'
import { CHAIN_NAME_TO_CHAIN_ID, getTokenDetailsURL } from 'graphql/data/util'
import { getTokenDetailsURL, InterfaceGqlChain, supportedChainIdFromGQLChain } from 'graphql/data/util'
import { useOnGlobalChainSwitch } from 'hooks/useGlobalChainSwitch'
import { UNKNOWN_TOKEN_SYMBOL, useTokenFromActiveNetwork } from 'lib/hooks/useCurrency'
import { Swap } from 'pages/Swap'
@@ -89,7 +89,7 @@ function useRelevantToken(
type TokenDetailsProps = {
urlAddress?: string
inputTokenAddress?: string
chain: Chain
chain: InterfaceGqlChain
tokenQuery: TokenQuery
tokenPriceQuery?: TokenPriceQuery
onChangeTimePeriod: OnChangeTimePeriod
@@ -111,8 +111,7 @@ export default function TokenDetails({
)
const { chainId: connectedChainId } = useWeb3React()
const pageChainId = CHAIN_NAME_TO_CHAIN_ID[chain]
const pageChainId = supportedChainIdFromGQLChain(chain)
const tokenQueryData = tokenQuery.token
const crossChainMap = useMemo(
() =>
@@ -185,6 +184,7 @@ export default function TokenDetails({
},
[continueSwap, setContinueSwap]
)
// address will never be undefined if token is defined; address is checked here to appease typechecker
if (detailedToken === undefined || !address) {
return <InvalidTokenDetails pageChainId={pageChainId} isInvalidAddress={!address} />

View File

@@ -1,6 +1,12 @@
import Badge from 'components/Badge'
import { getChainInfo } from 'constants/chainInfo'
import { BACKEND_CHAIN_NAMES, CHAIN_NAME_TO_CHAIN_ID, validateUrlChainParam } from 'graphql/data/util'
import { useFilterChainsForAvalanche } from 'featureFlags/flags/avalanche'
import {
BACKEND_NOT_YET_SUPPORTED_CHAIN_IDS,
BACKEND_SUPPORTED_CHAINS,
supportedChainIdFromGQLChain,
validateUrlChainParam,
} from 'graphql/data/util'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { useRef } from 'react'
import { Check, ChevronDown, ChevronUp } from 'react-feather'
@@ -112,12 +118,12 @@ export default function NetworkFilter() {
const toggleMenu = useToggleModal(ApplicationModal.NETWORK_FILTER)
useOnClickOutside(node, open ? toggleMenu : undefined)
const navigate = useNavigate()
const gatedUnsupportedChains = useFilterChainsForAvalanche([...BACKEND_NOT_YET_SUPPORTED_CHAIN_IDS])
const { chainName } = useParams<{ chainName?: string }>()
const currentChainName = validateUrlChainParam(chainName)
const chainInfo = getChainInfo(CHAIN_NAME_TO_CHAIN_ID[currentChainName])
const BNBChainInfo = getChainInfo(CHAIN_NAME_TO_CHAIN_ID.BNB)
const chainInfo = getChainInfo(supportedChainIdFromGQLChain(currentChainName))
return (
<StyledMenu ref={node}>
@@ -129,7 +135,7 @@ export default function NetworkFilter() {
>
<StyledMenuContent>
<NetworkLabel>
<Logo src={chainInfo?.logoUrl} /> {chainInfo?.label}
<Logo src={chainInfo.logoUrl} /> {chainInfo.label}
</NetworkLabel>
<Chevron open={open}>
{open ? (
@@ -142,9 +148,8 @@ export default function NetworkFilter() {
</NetworkFilterOption>
{open && (
<MenuTimeFlyout>
{BACKEND_CHAIN_NAMES.map((network) => {
const chainInfo = getChainInfo(CHAIN_NAME_TO_CHAIN_ID[network])
if (!chainInfo) return null
{BACKEND_SUPPORTED_CHAINS.map((network) => {
const chainInfo = getChainInfo(supportedChainIdFromGQLChain(network))
return (
<InternalLinkMenuItem
key={network}
@@ -166,13 +171,22 @@ export default function NetworkFilter() {
</InternalLinkMenuItem>
)
})}
<InternalLinkMenuItem data-testid="tokens-network-filter-option-bnb-chain" disabled>
<NetworkLabel>
<Logo src={BNBChainInfo.logoUrl} />
{BNBChainInfo.label}
</NetworkLabel>
<Tag>Coming soon</Tag>
</InternalLinkMenuItem>
{gatedUnsupportedChains.map((network) => {
const chainInfo = getChainInfo(network)
return (
<InternalLinkMenuItem
key={network}
data-testid={`tokens-network-filter-option-${network}-chain`}
disabled
>
<NetworkLabel>
<Logo src={chainInfo.logoUrl} />
{chainInfo.label}
</NetworkLabel>
<Tag>Coming soon</Tag>
</InternalLinkMenuItem>
)
})}
</MenuTimeFlyout>
)}
</StyledMenu>

View File

@@ -1,4 +1,4 @@
import { SupportedChainId } from 'constants/chains'
import { ChainId } from '@uniswap/sdk-core'
import { Currency, TokenStandard } from 'graphql/data/__generated__/types-and-hooks'
import { CHAIN_ID_TO_BACKEND_NAME } from 'graphql/data/util'
import { render, screen } from 'test-utils/render'
@@ -72,7 +72,7 @@ describe('LoadedRow.tsx', () => {
__typename: 'Token',
id: 'VG9rZW46RVRIRVJFVU1fMHhBMGI4Njk5MWM2MjE4YjM2YzFkMTlENGEyZTlFYjBjRTM2MDZlQjQ4',
name: 'USD Coin',
chain: CHAIN_ID_TO_BACKEND_NAME[SupportedChainId.MAINNET],
chain: CHAIN_ID_TO_BACKEND_NAME[ChainId.MAINNET],
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
symbol: 'USDC',
standard: TokenStandard.Erc20,
@@ -96,7 +96,7 @@ describe('LoadedRow.tsx', () => {
__typename: 'Token',
id: 'VG9rZW46RVRIRVJFVU1fMHhBMGI4Njk5MWM2MjE4YjM2YzFkMTlENGEyZTlFYjBjRTM2MDZlQjQ4',
name: 'USD Coin',
chain: CHAIN_ID_TO_BACKEND_NAME[SupportedChainId.MAINNET],
chain: CHAIN_ID_TO_BACKEND_NAME[ChainId.MAINNET],
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
symbol: 'USDC',
standard: TokenStandard.Erc20,

View File

@@ -7,7 +7,7 @@ import SparklineChart from 'components/Charts/SparklineChart'
import QueryTokenLogo from 'components/Logo/QueryTokenLogo'
import { MouseoverTooltip } from 'components/Tooltip'
import { SparklineMap, TopToken } from 'graphql/data/TopTokens'
import { CHAIN_NAME_TO_CHAIN_ID, getTokenDetailsURL, validateUrlChainParam } from 'graphql/data/util'
import { getTokenDetailsURL, supportedChainIdFromGQLChain, validateUrlChainParam } from 'graphql/data/util'
import { useAtomValue } from 'jotai/utils'
import { ForwardedRef, forwardRef } from 'react'
import { CSSProperties, ReactNode } from 'react'
@@ -435,7 +435,7 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
const filterString = useAtomValue(filterStringAtom)
const filterNetwork = validateUrlChainParam(useParams<{ chainName?: string }>().chainName?.toUpperCase())
const chainId = CHAIN_NAME_TO_CHAIN_ID[filterNetwork]
const chainId = supportedChainIdFromGQLChain(filterNetwork)
const timePeriod = useAtomValue(filterTimeAtom)
const delta = token.market?.pricePercentChange?.value
const arrow = getDeltaArrow(delta)
@@ -458,7 +458,7 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
// TODO: currency logo sizing mobile (32px) vs. desktop (24px)
return (
<div ref={ref} data-testid={`token-table-row-${token.symbol}`}>
<div ref={ref} data-testid={`token-table-row-${token.address}`}>
<StyledLink
to={getTokenDetailsURL(token)}
onClick={() =>

View File

@@ -327,7 +327,7 @@ exports[`LoadedRow.tsx renders a row 1`] = `
}
<div
data-testid="token-table-row-USDC"
data-testid="token-table-row-0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
>
<a
class="c0"

View File

@@ -1,4 +1,4 @@
import { SupportedChainId } from 'constants/chains'
import { ChainId } from '@uniswap/sdk-core'
export const MAX_WIDTH_MEDIA_BREAKPOINT = '1200px'
export const XLARGE_MEDIA_BREAKPOINT = '960px'
@@ -8,4 +8,4 @@ export const SMALL_MEDIA_BREAKPOINT = '540px'
export const MOBILE_MEDIA_BREAKPOINT = '420px'
// includes chains that the backend does not current source off-chain metadata for
export const UNSUPPORTED_METADATA_CHAINS = [SupportedChainId.BNB]
export const UNSUPPORTED_METADATA_CHAINS = [ChainId.BNB, ChainId.AVALANCHE]

View File

@@ -8,11 +8,10 @@ import useAccountRiskCheck from 'hooks/useAccountRiskCheck'
import { lazy } from 'react'
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
import { retry } from 'utils/retry'
const Bag = lazy(() => retry(() => import('nft/components/bag/Bag')))
const TransactionCompleteModal = lazy(() => retry(() => import('nft/components/collection/TransactionCompleteModal')))
const AirdropModal = lazy(() => retry(() => import('components/AirdropModal')))
const Bag = lazy(() => import('nft/components/bag/Bag'))
const TransactionCompleteModal = lazy(() => import('nft/components/collection/TransactionCompleteModal'))
const AirdropModal = lazy(() => import('components/AirdropModal'))
export default function TopLevelModals() {
const addressClaimOpen = useModalIsOpen(ApplicationModal.ADDRESS_CLAIM)

Some files were not shown because too many files have changed in this diff Show More