Compare commits

...

115 Commits

Author SHA1 Message Date
Jesse
4ba6275b71 fix: add celo gas override (#4147)
fix: add celo gas override to circumvent 'out of gas' error from multicall
2022-07-20 14:48:53 -04:00
Rachel-Eichenberger
17c7a9ee9d fix: increase Polygon gas limit (#3882)
* Update graph link

* Add Gas over ride temp for Polygon

* removal of personal tweaks

* Update index.tsx

* reset to original file

* missing EOL

* Update useClientSideV3Trade.ts

* remove space
2022-07-20 11:59:44 -04:00
Sam Chen
0e36944b23 refactor: clean floating Route (#4144) 2022-07-20 09:47:42 -04:00
Vignesh Mohankumar
5050fe7b06 fix: unused onClickOutside reference (#4140) 2022-07-19 17:54:06 -04:00
Vignesh Mohankumar
80b965f2ca fix: sync chain query parameter (#4019)
* replaceURLChain

* reorder stuff

* don't use usePrevious for previousChainId

* remove the replace param call in promise

* variable names

* comment

* confirm isActive

* wrong place for isActive

* change ret type

* add comments

* check if not previous chain id
2022-07-19 21:42:52 +00:00
Vignesh Mohankumar
417e940c0a test: fix swap test flake (#4135)
* remove all the funky logic

* clear stuff

* uncomment some tests

* remove expert mode tests

* skip these tests again, smh
2022-07-19 16:42:45 -04:00
Sam Chen
c42a5ccf26 chore: access router data with hooks (#4121)
* chore: access router data with hooks

* chore: clean RouteComponentProps

* chore: use children instead of render

* add import
2022-07-19 16:14:16 -04:00
Vignesh Mohankumar
7ba9b1faf6 fix: don't toggle desktop NetworkSelector on click (#4134)
fix: don't NetworkSelector onClick on desktop
2022-07-19 14:35:24 -04:00
Vignesh Mohankumar
5d43d08ff3 build: lock jest-styled-components@7.0.7 (#4132) 2022-07-18 21:42:07 -04:00
lynn
85625d09f0 fix: increase celo blocksPerFetch to 5 to improve interface performance (#4130)
* init commit

* revert yarn.lock changes

* update test snapshots
2022-07-18 18:25:14 -04:00
Vignesh Mohankumar
d85f1f44b9 build: change project name to @uniswap/interface (#4125) 2022-07-18 17:41:32 -04:00
lynn
f41580e43c feat: implement connect wallet category events (#4111)
* init commit

* wallet connected event init commit

* add received_swap_quote event property

* add page context, connect wallet event log

* add received_swap_quote property

* fix typo

* respond to cmcewen comments

* respond to vm comments

* move trace to app.tsx from header

* respond to vm comments
2022-07-18 17:26:29 -04:00
Vignesh Mohankumar
47fe9911fa chore: move prettier, jest-styled-components to devDependencies (#4128)
* change package

* yarn.lock
2022-07-18 16:32:03 -04:00
Noah Zinsmeister
e0f6d82d6c feat: enable 1bp optimism fee tier (#4124)
enable new optimism fee tier
2022-07-18 10:48:28 -04:00
Sam Chen
fb691cf17b fix: catch vibrant failure (#4123)
fix: catch CORS error
2022-07-18 10:05:56 -04:00
Vignesh Mohankumar
f596293b6c build: don't fail cypress on unhandled exception (#4122) 2022-07-18 09:46:31 -04:00
Sam Chen
8012789f69 fix: fixes Popover arrow positioning (#4119)
fix: fix arrow position
2022-07-17 11:22:50 -04:00
Sam Chen
4013743473 chore: upgrades react-router-dom, fixes dev-mode linking (#4115)
* fix: stale route

* fix: add e2e test

* fix: update e2e test
2022-07-17 11:15:04 -04:00
Jesse
e9ef3193ab refactor: remaining changes from the large celo merge (#4088)
* refactor: useUSDCValue -> useStablecoinValue

* refactor: use the isCelo() helper

* refactor: remove unneeded white space
2022-07-15 10:36:19 -04:00
Vignesh Mohankumar
6a1506ade6 build: Revert "build: pause deploy" (#4107)
* Revert "build: pause deploy (#4102)"

This reverts commit 3a1ea3df85.

* prettier
2022-07-14 12:19:23 -04:00
cartcrom
839d4ac8e2 refactor: adding safe getter for ChainInfo (#4110)
* replaced CHAIN_INFO access with a function call
* updated CTACard tests to work with getChainInfo
* updated typechecking, removed console.log
2022-07-14 11:52:37 -04:00
Vignesh Mohankumar
29fdcb80f6 build: upgrade prettier to v2.7.1 (#4109)
* style: prettier based on v2.2

* 2.7.1 instead?

* npx

* ^
2022-07-14 11:28:51 -04:00
lynn
817ea44e8d fix: update styled-components in package.json to latest to remove react invalid hook call warnings (#4103)
* fix warning vig found by updating styled-components

* revert unnecessary yarn.lock changes

* reduce unnecessary changes

* dedup

* manual fix and dedup of yarn.lock

* manually dedup @emotion/is-prop-valid

* update snapshot tests
2022-07-14 10:15:46 -04:00
Vignesh Mohankumar
3a1ea3df85 build: pause deploy (#4102) 2022-07-13 21:24:58 -04:00
Vignesh Mohankumar
2667a897a1 chore(web3-react): fix connectEagerly for MetaMask mobile (#4101)
* chore(web3-react): fix connectEagerly for MetaMask mobile

* fix
2022-07-13 18:20:50 -04:00
lynn
65129604bd chore: upgrade to react 18 (#3992)
* chore: upgrade to react 18

* fix: update tests

* fix: fix lint issues and remove unnecessary react hooks testing library

* fix: add types for stricter typescript checks

* fix: fix additional typescript check issues

* fix: revert to prev commmit

* rebase

* rebase

* fix: fix lint issues and remove unnecessary react hooks testing library

* fix: add types for stricter typescript checks

* fix: fix additional typescript check issues

* rebase

* fix: rebase

* fix

* eslint fix

* fix: package.json changes

* fix: package.json changes

* fix yarn lock

* fix version package.json

* fix: downgrade react-router-dom to original

* fix: undo modification of .github/workflows/release.yaml

* fix: revert cypress testing version update

* rebase

* rebase

* fix: fix lint issues and remove unnecessary react hooks testing library

* fix: add types for stricter typescript checks

* fix: fix additional typescript check issues

* rebase

* chore: upgrade to react 18

* fix: update tests

* fix: fix lint issues and remove unnecessary react hooks testing library

* fix: add types for stricter typescript checks

* fix: fix additional typescript check issues

* fix

* eslint fix

* fix: package.json changes

* fix: package.json changes

* fix yarn lock

* fix version package.json

* fix: downgrade react-router-dom to original

* fix: undo modification of .github/workflows/release.yaml

* fix: revert cypress testing version update

* fix

* fix: error boundary change

* yarn.lock change

* fix: cypress tests finally passing due to zzmp redux multicall fix HOORAY

* undo service worker changes

* build: dedup lockfile

* yarn.lock + lint

* update snapshot tests

* checkpoint

* yarn.lock

* fix: fix type errors during build

* fixes

* fix yarn.lock

* dedup yarn

* fix: import react components explicitly instead of all of react

* dedup

* yarn.lock

* yarn.lock

* dedup

* yarn

* dedup

* dedupe use-sync-external-store

* fix build issues

* dedup use-sync-external-store

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2022-07-13 16:56:09 -04:00
lynn
4e0c9b36a0 feat: implement-page-viewed-event-for-all-main-pages-of-app (#4089)
* init commit: initial constants for pages, implement vote page viewed

* implement swap

* implement pool

* remove charts

* simplify shouldLogImpression
2022-07-13 16:16:42 -04:00
Vignesh Mohankumar
64cb9f3ff2 chore: updates web3-react, adds key for changing connector order (#4085)
* fix connectors changing

* update package

* add connection name

* rename file

* de-dupe

* cb wallet fix

* fix

* yarn change

* log the key

* re-order connections

* memoize the key

* some updates

* rm console

* prevent memory leak

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2022-07-13 15:44:42 -04:00
lynn
cb094a1f4b feat: implement token selector events (#4067)
* init commit

* add amplitude ts sdk to package.json

* add more comments and documentation

* respond to vm comments

* respond to cmcewen comments

* fix: remove unused constants

* init commit

* adapt to web

* add optional event properties to trace

* correct telemetry to analytics

* change telemetry to analytics in doc

* fix: respond to cmcewen comments + initialize analytics in app.tsx + add missing return statement

* init commit

* respond to zzmp comments

* add token selected event

* fixes

* eliminate unnecessary state

* respond to part of zzmp comments

* respond to zzmp comments round 2

* fixes

* respond to zzmp comments

* add imported token event and other fixes

* also log onKeyPress for suggested tokens

* respond to cmcewen comments
2022-07-13 15:43:51 -04:00
Zach Pomerantz
d05fefc231 test: enforce deps deduplication (#4097)
* build: use fewer babel versions

* build: dedup

* test: test deps dedups

* fix: test.yml

* fix: typo

* test: failing

* fix: dedup

* fix: dedup

* test: comment dedup tests

* chore: whitespace
2022-07-13 11:31:19 -04:00
Zach Pomerantz
3e1805a20f build: update caniuse-lite (#4093) 2022-07-13 10:16:21 -04:00
Zach Pomerantz
f58dfe1284 build: upgrade @typescript-eslint (#4095)
build: update @typescript-eslint
2022-07-13 10:14:53 -04:00
github-actions[bot]
f67b7f8b66 chore(i18n): new Crowdin translations (#4090) 2022-07-12 13:09:59 -10:00
Vignesh Mohankumar
869691d43f refactor: wallet specific Option components (#4065)
* refactor: wallet specific Option components

* fix

* fix

* fix coinbase wallet logic

* injected logic

* remove wallet.ts

* install metamask

* move all into InjectedOption

* fix mobile metamask

* wip

* more mocking

* more test fixes

* refactor

* more special casing

* isMetaMask

* simplify components

* fix imports

* fix coinbase wallet

* test fix

* fix connectors changing

* Revert "fix connectors changing"

This reverts commit 2acfe645ca.

* more to typescript logic instead of jsx
2022-07-12 18:33:24 -04:00
lynn
817d808ec5 feat: implement trace framework for analytics (#4060)
* init commit

* add amplitude ts sdk to package.json

* add more comments and documentation

* respond to vm comments

* respond to cmcewen comments

* fix: remove unused constants

* init commit

* adapt to web

* add optional event properties to trace

* correct telemetry to analytics

* change telemetry to analytics in doc

* fix: respond to cmcewen comments + initialize analytics in app.tsx + add missing return statement

* respond to zzmp comments

* fixes

* eliminate unnecessary state

* respond to part of zzmp comments

* respond to zzmp comments round 2

* fixes

* respond to zzmp comments
2022-07-12 16:43:37 -04:00
github-actions[bot]
aee1bce612 chore(i18n): new Crowdin translations (#4084)
chore(i18n): synchronize translations from crowdin [skip ci]

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-07-12 13:52:06 -04:00
Bruno Crosier
8b38a9c4e0 fix: add crossplatform prei18n-extract script (#3728)
* fix: 🐛 add crossplatform `prei18n-extract` script

* fix: 🚨 add newline

* Revert "fix: 🐛 add crossplatform `prei18n-extract` script"

This reverts commit 201bd2308a.

* build: 📦 add `shx` as dev dep, use it in `prei18n:extract` script

* fix: 🐛 use platform-specific commands for prei18n-extract
2022-07-12 18:51:24 +02:00
cartcrom
0a115fab17 fix: unsupported chain displays message instead of crash (#4054)
* made initial changes for pools page displaying w/ unsupported chains
* condensed styling
* added chain validation to CTACards and wrote tests for both CTAcards and Pools page
* linted changes
* switched from snapshot to text matching tests
* switched test to use check for text instead of testid
2022-07-12 12:02:02 -04:00
Kaylee George
882147b533 fix: revert "fix button jump on currency panel" (#4083)
fix padding
2022-07-12 11:43:04 -04:00
Jesse
eb06aef199 feat: add support for Celo (#3915)
* feat: Support for Celo

* fix: wrong condition

* combine celo and alfajores lists

* use celo erc20 representation

* fix: refactor infura.ts to networks.ts & add celo to rpc urls

* feature: add celo contract addresses
fix: remove celo from supported gas estimate chains until feature is available

* refactor: useUSDCPrice to useStablecoinPrice
fix: add celo to supported gas estimate chains

* fix: use unique factory address for getting pool address

* fix: darkmode background graident

* fix: removing a comment left behind

* fix: remove bad import

* fix: remove dead link until the Celo is live on info.uniswap.org

* fix: add asset to common bases & minor refactoring

* fix: celo info links point to root info.uniswap.org

* fix: change celo token bridge to portal

* fix: update redux-multicall to latest version

* refactor: for code readability

* fix: celo banner colors & remove unused alternative logo

* fix: change celo token list to hosted version

* fix: update celo banner colors

* fix: move celo to the bottom of the network selector list

* fix: dedup dependencies @uniswap/router-sdk @uniswap/v3-sdk

* fix: refactoring + move Celo above L2s

* fix: update celo contract addresses

* fix: update celo subgraph

* fix: update v3-sdk and smart-order-router versions

* fix: move Celo to the bottom of the network selector list

* fix: downgrade smart-order-router and add casting fix

* fix: downgrade smart-order-router and add casting fix

* fix: resolve Pool dependency

* fix: bridge chain id types

* fix: explorer link test

* fix: use quoter v2 ABI in useClientSideV3Trade fro Celo

* fix: update connection "infura_rpc" to networks

* fix: revert yarn.lock and force install

* fix: dedup router and v3 sdk

* refactor: mv quoter v2 to client side v3 trade

* build: dedup lockfile

* feature: add portal ether to common bases

* fix: add comment for chains that use QuoterV2

* fix: use token as native asset

* fix: supply correct factory address to getPoolAddress call & refactor nativeOnChain method

* feature: adjust celo tokens presetned

* fix: update celo explorer to celoscan

* fix: celo token casting

* fix: celo celo explorer it

* fix: celo chain info should be consistent with block explorer used.

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
2022-07-12 16:48:03 +02:00
Vignesh Mohankumar
1b91e7ce30 fix: Revert "refactor: remove coinbase wallet resetState" (#4081)
Revert "refactor: remove coinbase wallet resetState (#4024)"

This reverts commit e36722ccb4.
2022-07-12 00:37:45 -04:00
Anas Yousef
535e670c63 refactor: remove hideRouteDiagram prop (#3763) 2022-07-11 20:30:16 -04:00
Bruno Crosier
0b4c77155e fix: NetworkSelect mobile toggle bug (#3698)
* fix windows dev environment bugs

* fix NetworkSelector toggle bug

* revert:  add `prei18n:extract` script back

this change to make the script Windows-compatible will be dealt with in a separate PR

* revert:  revert prettier endOfLine fix

Will be dealt with in a separate PR

* updates

Co-authored-by: Vignesh Mohankumar <vignesh@vigneshmohankumar.com>
Co-authored-by: Vignesh Mohankumar <me@vig.xyz>
2022-07-11 15:55:59 -04:00
Vignesh Mohankumar
e36722ccb4 refactor: remove coinbase wallet resetState (#4024)
* refactor: remove coinbase wallet resetState

* unused import

* bump web3-react

* rm unused

* bump everything
2022-07-11 13:49:49 -04:00
Ian Lapham
ec0b94a920 fix: Fix spacing type (#4073)
* fix: Fix spacing type

* prettier

Co-authored-by: Vignesh Mohankumar <me@vig.xyz>
2022-07-11 13:37:40 -04:00
Zach Pomerantz
d5eed8b15f build: wait 1m between release tests (#4077) 2022-07-11 13:24:01 -04:00
Zach Pomerantz
2447afc43e build: wait on passing cypress (#4076)
* build: wait on passing cypress

* build: video upload
2022-07-11 18:55:03 +02:00
Zach Pomerantz
8eef757f7f refactor: analytics initialization (#4070)
* refactor: analytics initialization

* fix: typings
2022-07-11 12:46:58 -04:00
Zach Pomerantz
b1c29b3bf1 build: resolve cypress matrix to single job (#4075)
* build: resolve cypress matrix to single job

* build: output on cypress-tests

* build: explicitly name matrix steps

* build: quote cypress string

* build: omit protocol

* build: test

* build: fix indentation

* build: test

* build: test

* build: test

* build: cleanup

* build: cleanup
2022-07-11 17:36:48 +02:00
Zach Pomerantz
b211c9f150 build: wait on tests (#4074) 2022-07-11 16:42:52 +02:00
lynn
66cae715f4 feat: implement initial setup of amplitude SDK (#4044)
* init commit

* add amplitude ts sdk to package.json

* add more comments and documentation

* respond to vm comments

* respond to cmcewen comments

* fix: remove unused constants

* fix: respond to cmcewen comments + initialize analytics in app.tsx + add missing return statement
2022-07-08 11:57:47 -04:00
github-actions[bot]
5a4a2782e8 chore(i18n): new Crowdin translations (#4045)
chore(i18n): synchronize translations from crowdin [skip ci]

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-07-07 13:03:44 -07:00
Vignesh Mohankumar
4e462ddbef refactor: extract Web3Provider hooks, create internal Connection representation (#4040)
* refactor: separate hooks file for Web3Provider

* move utils

* rename + comments

* rename Wallet enum to ConnectionType

* more wallet -> connectiontype

* more wallet -> connectiontype

* move hooks

* use Connection everywhere

* connector -> connection

* generic getConnection

* rename injected -> injectedConnection

* check connectionType

* rm unused
2022-07-07 15:17:49 -04:00
Zach Pomerantz
48a962a750 build: enable auto-release (#4058) 2022-07-07 11:56:03 -07:00
Zach Pomerantz
9a55402bdf build: add sleep in release (#4057) 2022-07-07 11:09:47 -07:00
lynn
663644553e feat: update privacy policy to include amplitude (#4046)
init commit
2022-07-06 18:15:12 -04:00
Vignesh Mohankumar
ceec3f0e65 feat: remove Tally Ho specific rendering (#4023)
feat: remove tally
2022-07-06 16:39:24 -04:00
Vignesh Mohankumar
904d1835d2 style: rename library to provider (#4038) 2022-07-06 12:36:54 -04:00
lynn
77366bf81b feat: configure blocks per fetch for l2s (#4028)
* init commit

* update redux multicall version to 1.1.5 in package.json

* fix respond to zzmp comments

* update optimism blocks per fetch to 15

* simplify

Co-authored-by: Lynn Yu <lynn.yu@uniswap.org>
2022-07-05 12:13:20 -04:00
Zach Pomerantz
cd8b048829 fix: deploy after pinning 2022-07-05 09:04:13 -07:00
Zach Pomerantz
d62596ecfc fix: release file 2022-07-05 08:52:54 -07:00
Zach Pomerantz
2b496c62f1 build: deploy without needing check suite 2022-07-05 08:50:28 -07:00
Zach Pomerantz
a3c77708e2 build: deploy without waiting on check suite 2022-07-05 08:49:11 -07:00
Zach Pomerantz
b9e8139699 fix: alter headers on cached response (#4032)
* fix: alter headers on cached response

* test: deflake
2022-07-05 08:31:06 -07:00
Zach Pomerantz
c82dcabd19 build: disable scheduled release (#4022) 2022-07-01 15:26:42 -07:00
Zach Pomerantz
26b37d5274 build: fix await-check-suite with (#4021)
* build: fix await-check-suites

* build: fix await-check-suite with
2022-07-01 15:12:51 -07:00
Zach Pomerantz
d5c464b26b build: fix await-check-suites (#4020) 2022-07-01 15:10:08 -07:00
Zach Pomerantz
c48d4c5425 build: simplify actions (#4014)
* build: simplify actions

* build: add checkout

* build: fix path

* build: fix all paths

* build: missing steps

* build: update build name

* build: rename action

* build: add shell

* build: formalize lint

* build: cleanup

* build: update release

* build: comment workflows
2022-07-01 15:05:37 -07:00
Kaylee George
b28cd9c8b0 fix: token button jump (#4017)
* fix token button jump

* Update index.tsx

* move inline style to CSS
2022-07-01 16:18:46 -04:00
Vignesh Mohankumar
071017879c feat: don't allow disconnect/change on injected mobile wallets (#4015)
* feat: don't allow disconnect/change on injected mobile wallets

* new variable
2022-07-01 13:57:15 -04:00
github-actions[bot]
5535c83db5 chore(i18n): new Crowdin translations (#4013)
chore(i18n): synchronize translations from crowdin [skip ci]

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-07-01 09:03:06 -07:00
Andrii Bodnar
8f9eccabaf fix: Crowdin translations download (#3927) 2022-07-01 09:00:20 -07:00
Vignesh Mohankumar
322cdaf888 refactor: rm useActiveWeb3React (#4004)
* rm activeweb3react

* wrap in web3provider?
2022-06-30 16:38:02 -07:00
Vignesh Mohankumar
edcdbfd8f5 feat: disconnect for coinbase wallet (#3993)
* feat: disconnect for coinbase wallet

* change isActive logic

* remove logs

* reset state for coinbase wallet

* active -> isActive

* rm data-cy
2022-06-30 18:04:42 -04:00
Vignesh Mohankumar
d36f13d7e2 fix: resolves network switch race condition (#4005)
* don't need undo logic

* maybe need to return connector

* make sure the urlChainId doesn't equal existing chainId

* fix network switching

* add back revert logic

* undo changes to switchChain

* move revert logic into the hook
2022-06-30 11:51:23 -05:00
Vignesh Mohankumar
8548b33bd9 feat: show injected options in wallet browsers (#3995)
* feat: show injected options in wallet browsers

* initial testing

* more mocking

* mock more

* mobile tests

* updates

* add data test

* finally got the mock to work

* WORKING

* uncomment

* rm console.log

* fix

* check length

* fix tests to use useWeb3React

* rm

* rename tests
2022-06-30 12:26:22 -04:00
Vignesh Mohankumar
0e148bb1b3 style: use data-testid in cypress test (#4001)
* use data-testid

* findByTestId

test -> it

add types

temp

rm exclude

get -> find

* Revert "findByTestId"

This reverts commit 1e1c483ef9.

* rm some test ids

* fix
2022-06-29 16:57:21 -04:00
Vignesh Mohankumar
9ddb37a982 chore: move cypress to devDependencies (#4002) 2022-06-29 13:41:20 -07:00
Zach Pomerantz
63227cd2c5 build: dedup lockfile (#3985)
* build: dedup ethers

* build: dedup token-lists

* build: dedup deps

* build: clean deps
2022-06-29 09:43:01 -07:00
Zach Pomerantz
dc38dc1c10 build: disable release (#3994) 2022-06-28 18:03:40 -07:00
Zach Pomerantz
2c6757ff62 fix: set content-type on cached document (#3990)
* fix: set content-type on cached document

* fix: delete old content-types

* fix: avoid immutable headers

* test: content-type

* fix: do not destructure response

* test: serve from cache with vercel

* fix: inject cache marker into body
2022-06-28 16:31:05 -07:00
Zach Pomerantz
0d03b09ae9 build: sleep for 10m after pinning (#3991) 2022-06-28 15:40:33 -07:00
Jordan Frankfurt
566da07448 feat(risk): cache risk check with ttl (#3965) 2022-06-24 11:11:32 -05:00
Vignesh Mohankumar
31a3840b1f feat: fix metamask mobile browser connection (#3964)
* fix metamask

* forceActivate

* remove forceActivate

* unused change
2022-06-23 16:50:04 -04:00
Vignesh Mohankumar
f89d7ccd5e feat: empty to deploy 628417f696 (#3962)
feat: empty to deploy
2022-06-23 12:29:46 -04:00
matteenm
628417f696 chore(deps): bump token-lists (#3929) 2022-06-21 15:52:09 -04:00
Kaylee George
ea8c7326d6 fix: crash on HOP token search (#3904) (#3928)
fix: ensure token address is checksummed on construction
2022-06-17 11:54:07 -04:00
Zach Pomerantz
dd5feaacb2 fix: serviceworker request path (#3926)
* fix: serviceworker request path

Always requests the app-shell from the same path as the cache key, in
order to guarantee that the etags will match should the cache be valid.

* fix: avoid returning redirects
2022-06-16 17:51:46 -04:00
Zach Pomerantz
cc919ab3df build: optimize github actions (#3922)
* build: parallelize cypress

- Parallelizes cypress CI runs
- Cleans up CI workflow files

* build: fix typo

* build: cache node_modules

* build: cache node_modules everywhere

* fix: action/cache usage

* fix: do not cache dynamically built files

* build: use standard container for cypress

* fix: cache cypress

* fix: cache cypress
2022-06-16 17:42:18 -04:00
Kaylee George
53d6eb0922 fix: show no price impact eth to weth (#3923)
* fix: show no price impact eth to weth

Fixed the price estimate values to reflect the correct price estimates depending on whether it is a wrapped trade

* fix: show no price impact eth to weth

Fixed to display the correct price estimates depending on whether it is a wrapped trade (eth -> weth should show no price impact)

* Added ETH->wETH testing

Added a Cypress test to check wrapped value swap has no price impact

* make Cypress test cleaner
2022-06-16 15:09:57 -04:00
Vignesh Mohankumar
db0d3cf3fa feat: upgrade to web3-react v8 (#3759)
* initial

* comment more stuff out for now

* more changes

* more temp

* remove walletconnect bug logic

* switch to provider not connector

* remove fortmatic

* remove some usage of network connector

* fix initialize connector

* more changes

* remove switch to network

* connect eagerly

* active -> isActive

* add initial option cards

* upgrade web3-react

* delete tryActivation

* delete pending view, reset option code

* fix hooks

* library -> provider

* rm getLibrary

* eagerly connect

* comment all this code for now

* add back app

* dont connect eagerly here

* deactivate

* switchToNetwork

* switch to useWeb3React

* rm Web3ReactManager

* add back og wallet modal code

* switch back to old option logic

* add account logic back

* add back more network switch logic

* Revert "switch to useWeb3React"

This reverts commit 08ac6319d4.

* add back skip disconnect logic

* check for network connector

* use promise.then again

* remove unnecessary pending error logic

* reset useAddTokenToMetamask

* upgrade packages

* use watchAsset

* add gnosis

* rm fortmatic

* close on disconnection

* add Wallet enum

* remove fortmatic imports

* add wallet state

* set/clear override wallet

* resolve empty

* remove some wallet modal view logic

* useWeb3ReactListener

* move to use effect

* add setwalletoverride in deactivate for now

* start to fix the wallet modal bug

* back button should open options

* connect eagerly to all

* Revert "add setwalletoverride in deactivate for now"

This reverts commit fbc990a924.

* useSelectedIsActive

* switch the enum to not be a bug

* actually dispatch the wallet override

* remove connection useEffect for now

* Revert "remove connection useEffect for now"

This reverts commit 0b92eee689.

* add back the activation useeffect

* handle resetting eagerly connecting

* dont disconnect from coinbase wallet

* disconnect except for coinbase wallet, bc their reload breaks things

* handle eager activation edge case

* backfill wallet override

* rename wrapper components

* update test

* network if override undefined

* npx deduplicate

* comment for why coinbase wallet special cased

* connectorPrevious -> previousConnector

* Array.find instead of forEach

* useState instead of useReducer

* add comments and simplify

* Web3Wrapper component

* add type guard

* check for watchAsset

* revert Option.tsx changes

* set -> updateWalletOverride

* generalize connector type usage

* rm comment

* eagerlyConnect comment

* null -> undefined

* add comment for wallet override

* add back pendingError logic

* merge conflicts

* remove provider dep

* add back connect a wallet

* move active prop out of base props

* add back account details test

* add type of isActiveMap

* add back eslint

* add TODO

* Web3Provider

* return null from Updater

* update comment

* integration tests initial

* try updating test

* check for gnosis safe

* fix gnosis safe check

* pr comments

* pr comments

* don't eagerly connect to any wallets other than gnosis or walletOverride

* remove unused branch

* pendingError from hook

* eslint-disable-line

* try connecting to wallets if not backfilled

* move eager connection logic

* remove connect eagerly set logic

* disconnect on change

* simplify ConnectorState

* better solution for changing wallet priority

* merge fixes

* fix tests

* try fixing test again

* add comment

* add fortmatic back

* set walletOverride for fortmatic

* hide other chains

* handle eager connection

* connect everything eagerly if not backfilled

* fix chain switching

* async

* rm error console

* fortmatic update

* log errors

* don't eagerly connect to fortmatic

* onSelectChain + switchChain

* typo

* don't disconnect from coinbase wallet for now

* upgrade web3-react

* close on disconnection/connection again

* simplify account change check

* comment fix

* comment

* fortmatic icon

* comment for fortmatic in network selector

* consolidate useEffect hooks in walletmodal for connection/disconnection

* switchToChain

* comment

* isEagerlyConnecting instead of eagerlyConnectingWallets

* update web3-react

* close modal fortmatic

* remove error log

* chainIdNotAllowed

* handle useToken

* update SupportedChainId

* move if statements around

* move to wallet reducer

* close as error

* export fix

* add back history change

* add back popular

* fortmatic key

* persist wallet

* remove eagerly connect

* call connect eagerly

* handle modal errors

* handle fortmatic close properly

* connector error changes

* go back to options

* change redux wallets

* simplify reducer

* fix eagerly connect / disconnect

* remove account change hook

* simplify connect eagerly

* remove unused var

* revert chain

* walletOverride reducer

* update web3-react

* fix compile errors for now

* show disconnect button

* clear pending connector

* clear error state

* add back skip toggle check

* MAINNET provider for ENS

* add coinbase wallet sdk

* fix test

* add back style but fix syntax highlighting

* dont create separate json rpc provider

* don't use selected hooks

* dont export

* dispatch first

* useConnectors

* comment

* simplify activeMap

* useIsActiveMap

* prettier

* prop change

* move comment

* useCallback

* coinbase wallet link fix

* rm ModalWallet type

* reportError

* isChainAllowed

* NETWORK_SELECTOR_CHAINS

* mainnet provider

* remove unused wallet views

* add back default case

* selected wallet

* comment change

* !chainAllowed

* rm ensResolver

* rm forEach

* re-define reportError

* move effects arounds

* change error message for switching chain

* simplify Web3Provider

* delete use isActive map

* fix test?

* rm disconnect test for now

* error message updates

* const -> function

* move fn

* undo changes for showing connect wallet state

* clear error before activating

* remove special case for fortmatic error

* backfillable/selectable wallets

* log wallet

* Revert "rm disconnect test for now"

This reverts commit 225bc7dc56.

* check if account exists

* unused dep

* remove reload piece of test

* update connect a wallet default state

* headerRow
2022-06-16 14:39:23 -04:00
Zach Pomerantz
ace4276bcb build: revert cypress projectId (#3921) 2022-06-16 10:08:25 -04:00
Zach Pomerantz
50a2dc9560 build: update cypress projectId (#3918)
* build: update cypress projectId

* build: rm unused cypress key

* test: save cypress videos

* build: rm start-server-and-test

* build: update github action cache usage

* build: use cypress action
2022-06-16 09:17:48 -04:00
Jordan Frankfurt
8cdec6188c chore(vscode): add workspace settings.json (#3914)
* chore(vscode): add workspace settings.json

* add newline

* no bad things allowed

* drop hiding . dirs
2022-06-14 17:39:12 -04:00
Kaylee George
5325d0cbe5 fix: adds messaging for unsupported V2 pool networks (#3762 #3777) (#3913)
Fix: Unsupported V2 Pool network messaging

#3762: Added error messaging for unsupported V2 pool networks (Polygon, Optimism, Arbitrum)
2022-06-14 16:20:31 -04:00
Zach Pomerantz
c16e49e774 feat: service worker with etag cache (#3897)
* fix: always-fresh service worker cache

* chore: clarify service-worker

* fix: cache in CacheStorage

* feat: set __isDocumentCached

* add back in manifest precaching

* add unit tests (incomplete)

* test: simplify test env

* test: add service-worker cypress test

* test: service-worker document handler

* fix: CachedDocument ctor

* fix: Readable for ReadableStream in jest

* build: clean up module loading

* fix: rename commands->ethereum

* build: simplify package.json deps

* build: clean up cypress usage

* build: clean up yarn.lock

* build: record cypress runs

* build: disable chromeWebSecurity in cypress tests

* build: rm babel

* build: disable sw in ci cypress

* build: nits

* build: update workbox version

* chore: fix merge

* test: cache

* test: cypress-ify the before hook

* test: clear sw before each test

* fix: cy then

* test: cypress shenanigans

* style: lint

* chore: rm todo

* test: fail fast for service worker with dev builds

* docs: update contributing to tests

* fix: clean up tests after merge

- Add fast fail in case of dev server, which lacks ServiceWorker

* fix: inject ethereum

* test: service worker

* test: increase sw timeout

* test: sw state

* test: run cypress in chrome

* feat: add on-demand caching to improve sw startup time

* test: test dynamically

* fix: simplify cached doc

* fix: optional sw

* fix: expose response on cached doc

* fix: stub out sw req

* fix: intercept

Co-authored-by: Christine Legge <christine.legge@uniswap.org>
2022-06-14 15:40:52 -04:00
Jordan Frankfurt
7e709e10db fix(L2): removes network-specific polling (#3912) 2022-06-14 14:51:24 -04:00
gzeon
7389b178fd perf: remove Arbitrum polling override (#3907) 2022-06-14 12:45:35 -04:00
Zach Pomerantz
48f8c6a141 test: update cypress (#3908)
* test: update cypress

* chore: comment on infura origin

* test: split build and serve

* chore: rm setupNodeEvents
2022-06-13 17:43:58 -04:00
Noah Zinsmeister
091876a374 feat: add Queue and Execute buttons (#3905)
* add queue and execute buttons

* eta is timestamp not block number

* address comments

* add execute text

* address comments
2022-06-13 13:23:15 -04:00
Lynn
d0e4aa832a fix: add jest coverage config to package.json (#3896)
* fix: add jest coverage config to package.json

* fix: add running test instructions to CONTRIBUTING.md

* fix: respond to zzmp comments

* fix: lower test thresholds

Co-authored-by: Lynn Yu <lynn.yu@uniswap.org>
2022-06-10 16:39:38 -04:00
Lynn
b17a38d94b feat: abstract analytics logging (#3892)
* fix: init commit

* fix: replace ReactGA.event with GoogleAnalyticsProvider.sentEvent

* fix: use GoogleAnalyticsProvider for all other ReactGA usages

* fix: add missing GoogleAnalyticsProvider import

* fix: incorporate zzmp's suggestions

* fix: add import I forgot

* fix: add another import I forgot

* fix: respond to zzmp comments

Co-authored-by: Lynn Yu <lynn.yu@uniswap.org>
2022-06-10 16:36:44 -04:00
Zach Pomerantz
22136b2708 build: clean up module loading (#3898)
* build: clean up module loading

* fix: rename commands->ethereum

* build: simplify package.json deps

* build: clean up cypress usage

* build: clean up yarn.lock

* build: record cypress runs

* build: disable chromeWebSecurity in cypress tests

* build: rm babel

* build: disable sw in ci cypress

* build: use dev env for e2e

* build: reenable web security
2022-06-09 14:39:38 -04:00
Zach Pomerantz
1897330ffc fix: omit native from DOM props (#3902)
fix: omit native from dom
2022-06-09 12:42:44 -04:00
Lynn
6131d0079f fix: fix scroll on page navigation (#3893)
* fix: fix scroll on page navigation

* fix: scroll to top when navigating in whole app

* fix: refactor to use useHistory hook

* fix: change scrollToTop component to hook

Co-authored-by: Lynn Yu <lynn.yu@uniswap.org>
2022-06-08 11:25:46 -04:00
Lynn
e6814994f6 fix: add keyboard accessibility to token selector (#3887)
* fix: add keyboard accessibility to token selector

* fix: update snapshot test

* fix: enable selected of suggested tokens by enter key

Co-authored-by: Lynn Yu <lynn.yu@uniswap.org>
2022-06-07 10:50:08 -04:00
Lynn
fea7d3a867 fix: remove animation from toggles on initial page load (#3886)
* fix: use simple toggle instead of toggle with text init commit

* fix: also change toggle in voting page and list toggle used in manage token list

* fix: simplify all toggle components into one configurable toggle

* fix: add ease-in animations for toggle

* fix: remove animation from toggle on initial page load to reduce thrash

Co-authored-by: Lynn Yu <lynn.yu@UNISWAP-MAC-015.local>
Co-authored-by: Lynn Yu <lynn.yu@uniswap.org>
2022-06-06 11:27:08 -04:00
Crowdin Bot
f4f0f29409 chore(i18n): synchronize translations from crowdin [skip ci] 2022-06-06 00:15:16 +00:00
Lynn
fa25e3c3e5 fix: use simple toggle instead of toggle with text init commit (#3884)
* fix: use simple toggle instead of toggle with text init commit

* fix: also change toggle in voting page and list toggle used in manage token list

* fix: simplify all toggle components into one configurable toggle

* fix: add ease-in animations for toggle

Co-authored-by: Lynn Yu <lynn.yu@UNISWAP-MAC-015.local>
Co-authored-by: Lynn Yu <lynn.yu@uniswap.org>
2022-06-05 19:13:05 -04:00
Crowdin Bot
51d2b3792f chore(i18n): synchronize translations from crowdin [skip ci] 2022-06-03 22:06:41 +00:00
Lynn
04ded04e74 fix: add animated loading bars and ensure sorted tokens upon load (#3874)
* fix: add animated loading bars and ensure sorted tokens upon load

* refactor: undo refactor of token selector component in CurrencySearch

* fix: fix styling as per design, still need to respond to other comments in review

* fix: add timeout to token loader of 2 seconds

* fix: add snapshot test and styling changes as per fred rec

* refactor: simplify function in currency list test

* fix: increase loading bars time from 2 seconds to 3 sec

* fix: respond to zach's comments

* fix: fix import errors

Co-authored-by: Lynn Yu <lynn.yu@UNISWAP-MAC-015.local>
2022-06-03 17:09:16 -04:00
Crowdin Bot
cf188a86de chore(i18n): synchronize translations from crowdin [skip ci] 2022-06-03 15:09:34 +00:00
hunter
f7e2435868 fix: Mobile layout: hide closed positions disappears #3344 (#3858)
* Add 'hide closed positions' to mobile pool view

* Update 'Hide closed positions' mobile font size

* add new toggle from design spec

* change off toggle button color

* Update SimpleToggle.tsx

* update wrapping position for toggle, improve component naming
2022-06-03 10:53:09 -04:00
Vignesh Mohankumar
5817d3bbdb style: simplify otherAmount calculation (#3880)
* simplify otherAmount calculation

* possibly not needed, but adding to keep logic the same
2022-06-01 14:22:38 -04:00
Vignesh Mohankumar
bdeb62ad64 fix: center NetworkSelector circles (#3867)
center NetworkSelector circles
2022-06-01 11:46:09 -04:00
301 changed files with 10463 additions and 7766 deletions

3
.env
View File

@@ -1,2 +1,3 @@
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
REACT_APP_LOCALES="locales"
REACT_APP_FORTMATIC_KEY="pk_live_357F77728B8EB880"
REACT_APP_LOCALES="locales"

View File

@@ -1,3 +1,4 @@
REACT_APP_AMPLITUDE_KEY="1c694b28cd089acc2c386d518f93a775"
REACT_APP_INFURA_KEY="099fc58e0de9451d80b18d7c74caa7c1"
REACT_APP_FORTMATIC_KEY="pk_live_F937DF033A1666BF"
REACT_APP_GOOGLE_ANALYTICS_ID="G-KDP9B6W4H8"

21
.github/actions/setup/action.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Setup
runs:
using: composite
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 14
registry-url: https://registry.npmjs.org
cache: yarn
- uses: actions/cache@v3
id: install-cache
with:
path: node_modules/
key: ${{ runner.os }}-install-${{ hashFiles('**/yarn.lock') }}
- if: steps.install-cache.outputs.cache-hit != 'true'
run: yarn install --frozen-lockfile --ignore-scripts
shell: bash

View File

@@ -2,9 +2,9 @@ version: 2
updates:
- package-ecosystem: npm
# Files stored in repository root
directory: "/"
directory: '/'
schedule:
interval: "daily"
interval: 'daily'
allow:
- dependency-name: "@uniswap/token-lists"
- dependency-name: "@uniswap/default-token-list"
- dependency-name: '@uniswap/token-lists'
- dependency-name: '@uniswap/default-token-list'

View File

@@ -1,4 +1,4 @@
name: "Check PR Title"
name: Check PR Title
on:
pull_request_target:
@@ -8,8 +8,8 @@ on:
- synchronize
jobs:
check-pr-title:
name: Check PR Title
# Ensures that the PR title adheres to [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/).
conventional-commit:
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v3.4.0

View File

@@ -1,47 +1,22 @@
name: Crowdin Download
# hourly we sync translations from Crowdin
on:
schedule:
- cron: '0 * * * *' # every hour we download translations and update the pr from crowdin
# 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:
name: Download translations
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: yarn i18n:extract
- name: Set up node
uses: actions/setup-node@v2
with:
node-version: 14
registry-url: https://registry.npmjs.org
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Extract translations
run: "yarn i18n:extract"
- name: Synchronize
- name: Download Crowdin translations
uses: crowdin/github-action@1.4.9
with:
upload_sources: false
@@ -50,8 +25,9 @@ jobs:
token: ${{ secrets.CROWDIN_PERSONAL_TOKEN_SECRET }}
source: 'src/locales/en-US.po'
translation: 'src/locales/%locale%.po'
create_pull_request: false
localization_branch_name: main
commit_message: "chore(i18n): synchronize translations from crowdin [skip ci]"
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,45 +1,19 @@
name: Crowdin Upload
# on any push to main, we upload the translations to be translated
on:
push:
branches:
- main
jobs:
synchronize-with-crowdin:
name: Upload sources to Crowdin
upload-sources:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: yarn i18n:extract
- name: Set up node
uses: actions/setup-node@v2
with:
node-version: 14
registry-url: https://registry.npmjs.org
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Extract translations
run: "yarn i18n:extract"
- name: Synchronize
- name: Upload Crowdin sources
uses: crowdin/github-action@1.1.0
with:
upload_sources: true

View File

@@ -1,50 +0,0 @@
name: Integration Tests
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
integration-tests:
name: Cypress
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up node
uses: actions/setup-node@v2
with:
node-version: 14
registry-url: https://registry.npmjs.org
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn install --frozen-lockfile
- run: yarn cypress install
- run: yarn build
env:
CI: false
REACT_APP_SERVICE_WORKER: false
- run: yarn test:e2e
env:
CYPRESS_INTEGRATION_TEST_PRIVATE_KEY: ${{ secrets.CYPRESS_INTEGRATION_TEST_PRIVATE_KEY }}
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}

View File

@@ -1,52 +0,0 @@
name: Lint
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
run-linters:
name: Run linters
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up node
uses: actions/setup-node@v2
with:
node-version: 14
registry-url: https://registry.npmjs.org
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Run eslint w/ autofix
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner.login == github.repository_owner }}
uses: wearerequired/lint-action@36c7e6689e80d785d27a22f71d970f3a3b4fcb70
with:
github_token: ${{ secrets.github_token }}
eslint: true
eslint_args: "-c .eslintrc.json"
auto_fix: true
- name: Run eslint
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.owner.login != github.repository_owner }}
run: yarn eslint .

View File

@@ -1,56 +1,58 @@
name: Release
on:
schedule:
- cron: '0 12 * * 1-4' # every day 12:00 UTC Monday-Thursday
- cron: '0 12 * * 1-4' # every day 12:00 UTC Monday-Thursday
# manual trigger
workflow_dispatch:
jobs:
bump_version:
name: Bump Version
wait-on-tests:
runs-on: ubuntu-latest
steps:
- id: unit-tests
uses: fountainhead/action-wait-for-check@v1.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
checkName: unit-tests
- id: cypress-tests
uses: fountainhead/action-wait-for-check@v1.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
checkName: cypress-tests
- if: steps.unit-tests.outputs.conclusion != 'success' || steps.cypress-tests.outputs.conclusion != 'success'
run: exit 1
tag:
needs: wait-on-tests
runs-on: ubuntu-latest
outputs:
new_tag: ${{ steps.github_tag_action.outputs.new_tag }}
changelog: ${{ steps.github_tag_action.outputs.changelog }}
new_tag: ${{ steps.github-tag-action.outputs.new_tag }}
changelog: ${{ steps.github-tag-action.outputs.changelog }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Bump version and push tag
id: github_tag_action
uses: mathieudutour/github-tag-action@331898d5052eedac9b15fec867b5ba66ebf9b692
- uses: actions/checkout@v3
- name: Bump and tag
id: github-tag-action
uses: mathieudutour/github-tag-action@v6.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
release_branches: .*
default_bump: false
default_bump: patch
create_release:
name: Create Release
release:
needs: tag
if: ${{ needs.tag.outputs.new_tag != null }}
runs-on: ubuntu-latest
needs: bump_version
if: ${{ needs.bump_version.outputs.new_tag != null }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up node
uses: actions/setup-node@v2
with:
node-version: 14
registry-url: https://registry.npmjs.org
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Build the IPFS bundle
run: yarn build
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: yarn prepare
- run: yarn build
- name: Pin to IPFS
id: upload
id: pinata
uses: anantaramdas/ipfs-pinata-deploy-action@39bbda1ce1fe24c69c6f57861b8038278d53688d
with:
pin-name: Uniswap ${{ needs.bump_version.outputs.new_tag }}
pin-name: Uniswap ${{ needs.tag.outputs.new_tag }}
path: './build'
pinata-api-key: ${{ secrets.PINATA_API_KEY }}
pinata-secret-api-key: ${{ secrets.PINATA_API_SECRET_KEY }}
@@ -64,10 +66,24 @@ jobs:
seeds: ${{ secrets.CRUST_SEEDS }}
- name: Convert CIDv0 to CIDv1
id: convert_cidv0
id: convert-cidv0
uses: uniswap/convert-cidv0-cidv1@v1.0.0
with:
cidv0: ${{ steps.upload.outputs.hash }}
cidv0: ${{ steps.pinata.outputs.hash }}
- uses: actions/cache@v3
id: cypress-cache
with:
path: /home/runner/.cache/Cypress
key: ${{ runner.os }}-cypress-${{ hashFiles('node_modules/cypress') }}
- if: steps.cypress-cache.outputs.cache-hit != 'true'
run: yarn cypress install
- uses: cypress-io/github-action@v4
with:
install: false
browser: chrome
config-file: cypress.release.config.ts
config: baseUrl=https://cloudflare-ipfs.com/ipfs/${{ steps.pinata.outputs.hash }}
- name: Update DNS with new IPFS hash
env:
@@ -77,20 +93,19 @@ jobs:
CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }}
uses: textileio/cloudflare-update-dnslink@0fe7b7a1ffc865db3a4da9773f0f987447ad5848
with:
cid: ${{ steps.upload.outputs.hash }}
cid: ${{ steps.pinata.outputs.hash }}
- name: Create GitHub Release
id: create_release
- name: Release
uses: actions/create-release@v1.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ needs.bump_version.outputs.new_tag }}
release_name: Release ${{ needs.bump_version.outputs.new_tag }}
tag_name: ${{ needs.tag.outputs.new_tag }}
release_name: Release ${{ needs.tag.outputs.new_tag }}
body: |
IPFS hash of the deployment:
- CIDv0: `${{ steps.upload.outputs.hash }}`
- CIDv1: `${{ steps.convert_cidv0.outputs.cidv1 }}`
- CIDv0: `${{ steps.pinata.outputs.hash }}`
- CIDv1: `${{ steps.convert-cidv0.outputs.cidv1 }}`
The latest release is always accessible via our alias to the Cloudflare IPFS gateway at [app.uniswap.org](https://app.uniswap.org).
@@ -100,8 +115,8 @@ jobs:
Your Uniswap settings are never remembered across different URLs.
IPFS gateways:
- https://${{ steps.convert_cidv0.outputs.cidv1 }}.ipfs.dweb.link/
- https://${{ steps.convert_cidv0.outputs.cidv1 }}.ipfs.cf-ipfs.com/
- [ipfs://${{ steps.upload.outputs.hash }}/](ipfs://${{ steps.upload.outputs.hash }}/)
- https://${{ steps.convert-cidv0.outputs.cidv1 }}.ipfs.dweb.link/
- https://${{ steps.convert-cidv0.outputs.cidv1 }}.ipfs.cf-ipfs.com/
- [ipfs://${{ steps.upload.outputs.hash }}/](ipfs://${{ steps.pinata.outputs.hash }}/)
${{ needs.bump_version.outputs.changelog }}
${{ needs.tag.outputs.changelog }}

103
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,103 @@
name: Test
on:
push:
branches:
- main
pull_request:
branches:
- main
# manual trigger
workflow_dispatch:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: yarn lint
deps-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
# Test that deps satisfy both "fewer" and "highest" strategies. This is to ensure we are
# up-to-date ("highest") while avoiding increasing package size ("fewer").
# These are readonly (--list) and explicitly exclude packages which only satisfy one strategy.
- run: npx yarn-deduplicate --strategy=fewer --list --fail --exclude commander safe-buffer
- run: npx yarn-deduplicate --strategy=highest --list --fail --exclude commander safe-buffer
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: yarn prepare
- run: yarn test
cypress-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: yarn prepare
- run: yarn build
- uses: actions/upload-artifact@v2
with:
name: build
path: build
if-no-files-found: error
- uses: actions/cache@v3
id: cypress-cache
with:
path: /home/runner/.cache/Cypress
key: ${{ runner.os }}-cypress-${{ hashFiles('node_modules/cypress') }}
- if: steps.cypress-cache.outputs.cache-hit != 'true'
run: yarn cypress install
cypress-test-matrix:
needs: cypress-build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
containers: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: actions/download-artifact@v2
with:
name: build
path: build
- uses: actions/cache@v3
id: cypress-cache
with:
path: /home/runner/.cache/Cypress
key: ${{ runner.os }}-cypress-${{ hashFiles('node_modules/cypress') }}
- if: steps.cypress-cache.outputs.cache-hit != 'true'
run: yarn cypress install
- uses: cypress-io/github-action@v4
with:
install: false
start: yarn serve
wait-on: 'http://localhost:3000'
browser: chrome
record: true
parallel: true
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Included as a single job to check against for cypress test success, as cypress runs in a matrix.
cypress-tests:
needs: cypress-test-matrix
runs-on: ubuntu-latest
steps:
- run: echo 'Finished cypress tests https\://dashboard.cypress.io/projects/yp82ef'

View File

@@ -1,40 +0,0 @@
name: Unit Tests
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
unit-tests:
name: Unit tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up node
uses: actions/setup-node@v2
with:
node-version: 14
registry-url: https://registry.npmjs.org
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Run unit tests
run: yarn test

7
.gitignore vendored
View File

@@ -3,9 +3,6 @@
# generated contract types
/src/types/v3
/src/abis/types
/src/lib/locales/**/*.js
/src/lib/locales/**/en-US.po
/src/lib/locales/**/pseudo.po
/src/locales/**/*.js
/src/locales/**/en-US.po
/src/locales/**/pseudo.po
@@ -37,10 +34,8 @@ yarn-error.log*
notes.txt
.idea/
.vscode/
package-lock.json
cypress/videos
cypress/screenshots
cypress/fixtures/example.json

19
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,19 @@
{
"npm.packageManager": "yarn",
"typescript.updateImportsOnFileMove.enabled": "always",
"javascript.updateImportsOnFileMove.enabled": "always",
"editor.formatOnSaveMode": "file",
"editor.tabCompletion": "on",
"editor.tabSize": 2,
"editor.formatOnSave": true,
"editor.inlineSuggest.enabled": true,
"editor.codeActionsOnSave": {
"source.fixAll": true
},
"files.eol": "\n",
"eslint.enable": true,
"eslint.debug": true,
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}

View File

@@ -1,24 +1,73 @@
# Contributing
Thank you for your interest in contributing to the Uniswap interface! 🦄
# Development
Before running anything, you'll need to install the dependencies:
```
yarn install
```
## Running the interface locally
1. `yarn install`
1. `yarn start`
```
yarn start
```
The interface should automatically open. If it does not, navigate to [http://localhost:3000].
## Creating a production build
1. `yarn install`
1. `yarn build`
```
yarn build
```
To serve the production build:
```
yarn serve
```
Then, navigate to [http://localhost:3000] to see it.
## Running unit tests
```
yarn test
```
By default, this runs only unit tests that have been affected since the last commit. To run _all_ unit tests:
```
yarn test --watchAll
```
## Running integration tests (cypress)
Integration tests require a server to be running. In order to see your changes quickly, run `start` in its own tab/window:
```
yarn start
```
Integration tests are run using `cypress`. When developing locally, use `cypress:open` for an interactive UI, and to inspect the rendered page:
```
yarn cypress:open
```
To run _all_ cypress integration tests _from the command line_:
```
yarn cypress:run
```
## Engineering standards
Code merged into the `main` branch of this repository should adhere to high standards of correctness and maintainability.
Use your best judgment when applying these standards. If code is in the critical path, will be frequently visited, or
Code merged into the `main` branch of this repository should adhere to high standards of correctness and maintainability.
Use your best judgment when applying these standards. If code is in the critical path, will be frequently visited, or
makes large architectural changes, consider following all the standards.
- Have at least one engineer approve of large code refactorings
@@ -39,7 +88,7 @@ The following points should help guide your development:
- Avoid adding steps to the development/build processes
- The build must be deterministic, i.e. a particular commit hash always produces the same build
- Decentralization: anyone can run the interface
- An Ethereum node should be the only critical dependency
- An Ethereum node should be the only critical dependency
- All other external dependencies should only enhance the UX ([graceful degradation](https://developer.mozilla.org/en-US/docs/Glossary/Graceful_degradation))
- Accessibility: anyone can use the interface
- The interface should be responsive, small and also run well on low performance devices (majority of swaps on mobile!)
@@ -48,14 +97,14 @@ The following points should help guide your development:
Releases are cut automatically from the `main` branch Monday-Thursday in the morning according to the [release workflow](./.github/workflows/release.yaml).
Fix pull requests should be merged whenever ready and tested.
Fix pull requests should be merged whenever ready and tested.
If a fix is urgently needed in production, releases can be manually triggered on [GitHub](https://github.com/Uniswap/uniswap-interface/actions/workflows/release.yaml)
after the fix is merged into `main`.
Features should not be merged into `main` until they are ready for users.
When building larger features or collaborating with other developers, create a new branch from `main` to track its development.
Use the automatic Vercel preview for sharing the feature to collect feedback.
When the feature is ready for review, create a new pull request from the feature branch into `main` and request reviews from
When the feature is ready for review, create a new pull request from the feature branch into `main` and request reviews from
the appropriate UX reviewers (PMs or designers).
## Finding a first issue
@@ -65,7 +114,7 @@ Start with issues with the label
# Translations
Uniswap uses [Crowdin](https://crowdin.com/project/uniswap-interface) for managing translations.
Uniswap uses [Crowdin](https://crowdin.com/project/uniswap-interface) for managing translations.
[This workflow](./.github/workflows/crowdin.yaml) uploads new strings for translation to the Crowdin project whenever code using the [lingui translation macros](https://lingui.js.org/ref/macro.html) is merged into `main`.
Every hour, translations are synced back down from Crowdin to the repository in [this other workflow](./.github/workflows/crowdin-sync.yaml).

View File

@@ -48,4 +48,4 @@ The Uniswap Interface supports swapping, adding liquidity, removing liquidity an
## Accessing Uniswap V1
The Uniswap V1 interface for mainnet and testnets is accessible via IPFS gateways
linked from the [v1.0.0 release](https://github.com/Uniswap/uniswap-interface/releases/tag/v1.0.0).
linked from the [v1.0.0 release](https://github.com/Uniswap/uniswap-interface/releases/tag/v1.0.0).

View File

@@ -1,16 +0,0 @@
// Custom test environment to provide `TextEncoder`/`TextDecoder`
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Environment = require('jest-environment-jsdom')
module.exports = class CustomTestEnvironment extends Environment {
async setup() {
await super.setup()
if (typeof this.global.TextEncoder === 'undefined') {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { TextEncoder, TextDecoder } = require('util')
this.global.TextEncoder = TextEncoder
this.global.TextDecoder = TextDecoder
}
}
}

20
cypress.config.ts Normal file
View File

@@ -0,0 +1,20 @@
import { defineConfig } from 'cypress'
export default defineConfig({
projectId: 'yp82ef',
videoUploadOnPasses: false,
defaultCommandTimeout: 10000,
chromeWebSecurity: false,
e2e: {
setupNodeEvents(on, config) {
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'),
}
},
baseUrl: 'http://localhost:3000',
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
},
})

View File

@@ -1,8 +0,0 @@
{
"projectId": "yp82ef",
"baseUrl": "http://localhost:3000",
"pluginsFile": false,
"supportFile": "cypress/support/index.js",
"video": false,
"defaultCommandTimeout": 10000
}

View File

@@ -0,0 +1,8 @@
import { defineConfig } from 'cypress'
export default defineConfig({
projectId: 'yp82ef',
e2e: {
specPattern: 'cypress/release.ts',
},
})

View File

@@ -1,5 +1,3 @@
import { TEST_ADDRESS_NEVER_USE_SHORTENED } from '../support/commands'
describe('Landing Page', () => {
beforeEach(() => cy.visit('/'))
it('loads swap page', () => {
@@ -15,9 +13,4 @@ describe('Landing Page', () => {
cy.get('#pool-nav-link').click()
cy.url().should('include', '/pool')
})
it('is connected', () => {
cy.get('#web3-status-connected').click()
cy.get('#web3-account-identifier-row').contains(TEST_ADDRESS_NEVER_USE_SHORTENED)
})
})

8
cypress/e2e/link.test.ts Normal file
View File

@@ -0,0 +1,8 @@
// see https://github.com/Uniswap/interface/pull/4115
describe('Link', () => {
it('should update route', () => {
cy.visit('/')
cy.get('[data-cy="pool-nav-link"]').click()
cy.get('[data-cy="join-pool-button"]').should('exist')
})
})

View File

@@ -1,5 +1,6 @@
describe('Pool', () => {
beforeEach(() => cy.visit('/pool'))
it('add liquidity links to /add/ETH', () => {
cy.get('#join-pool-button').click()
cy.url().should('contain', '/add/ETH')

View File

@@ -0,0 +1,84 @@
import assert = require('assert')
describe('Service Worker', () => {
before(() => {
// Fail fast if there is no Service Worker on this build.
cy.request({ url: '/service-worker.js', headers: { 'Service-Worker': 'script' } }).then((response) => {
const isValid = isValidServiceWorker(response)
if (!isValid) {
throw new Error(
'\n' +
'Service Worker tests must be run on a production-like build\n' +
'To test, build with `yarn build:e2e` and serve with `yarn serve`'
)
}
})
function isValidServiceWorker(response: Cypress.Response<any>) {
const contentType = response.headers['content-type']
return !(response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1))
}
})
function unregister() {
return cy.log('unregister service worker').then(async () => {
const cacheKeys = await window.caches.keys()
const cacheKey = cacheKeys.find((key) => key.match(/precache/))
if (cacheKey) {
await window.caches.delete(cacheKey)
}
const sw = await window.navigator.serviceWorker.getRegistration(Cypress.config().baseUrl ?? undefined)
await sw?.unregister()
})
}
before(unregister)
after(unregister)
beforeEach(() => {
cy.intercept({ hostname: 'www.google-analytics.com' }, (req) => {
const body = req.body.toString()
if (req.query['ep.event_category'] === 'Service Worker' || body.includes('Service%20Worker')) {
if (req.query['en'] === 'Not Installed' || body.includes('Not%20Installed')) {
req.alias = 'NotInstalled'
} else if (req.query['en'] === 'Cache Hit' || body.includes('Cache%20Hit')) {
req.alias = 'CacheHit'
} else if (req.query['en'] === 'Cache Miss' || body.includes('Cache%20Miss')) {
req.alias = 'CacheMiss'
}
}
})
})
it('installs a ServiceWorker', () => {
cy.visit('/', { serviceWorker: true })
.get('#swap-page')
.wait('@NotInstalled', { timeout: 20000 })
.window({ timeout: 20000 })
.and((win) => {
expect(win.navigator.serviceWorker.controller?.state).to.equal('activated')
})
})
it('records a cache hit', () => {
cy.visit('/', { serviceWorker: true }).get('#swap-page').wait('@CacheHit', { timeout: 20000 })
})
it('records a cache miss', () => {
cy.then(async () => {
const cacheKeys = await window.caches.keys()
const cacheKey = cacheKeys.find((key) => key.match(/precache/))
assert(cacheKey)
const cache = await window.caches.open(cacheKey)
const keys = await cache.keys()
const key = keys.find((key) => key.url.match(/index/))
assert(key)
await cache.put(key, new Response())
})
.visit('/', { serviceWorker: true })
.get('#swap-page')
.wait('@CacheMiss', { timeout: 20000 })
})
})

53
cypress/e2e/swap.test.ts Normal file
View File

@@ -0,0 +1,53 @@
describe('Swap', () => {
before(() => {
cy.visit('/swap')
})
it('starts with ETH selected by default', () => {
cy.get('#swap-currency-input .token-amount-input').should('have.value', '')
cy.get('#swap-currency-input .token-symbol-container').should('contain.text', 'ETH')
cy.get('#swap-currency-output .token-amount-input').should('not.have.value')
cy.get('#swap-currency-output .token-symbol-container').should('contain.text', 'Select a token')
})
it('can enter an amount into input', () => {
cy.get('#swap-currency-input .token-amount-input').clear().type('0.001').should('have.value', '0.001')
})
it('zero swap amount', () => {
cy.get('#swap-currency-input .token-amount-input').clear().type('0.0').should('have.value', '0.0')
})
it('invalid swap amount', () => {
cy.get('#swap-currency-input .token-amount-input').clear().type('\\').should('have.value', '')
})
it('can enter an amount into output', () => {
cy.get('#swap-currency-output .token-amount-input').clear().type('0.001').should('have.value', '0.001')
})
it('zero output amount', () => {
cy.get('#swap-currency-output .token-amount-input').clear().type('0.0').should('have.value', '0.0')
})
it.skip('can swap ETH for DAI', () => {
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').click()
cy.get('#swap-currency-input .token-amount-input').clear().type('0.0000001')
cy.get('#swap-currency-output .token-amount-input').should('not.equal', '')
cy.get('#swap-button').click()
cy.get('#confirm-swap-or-send').should('contain', 'Confirm Swap')
cy.get('[data-cy="confirmation-close-icon"]').click()
})
it('add a recipient does not exist unless in expert mode', () => {
cy.get('#add-recipient-button').should('not.exist')
})
it.skip('ETH to wETH is same value (wrapped swaps have no price impact)', () => {
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get('.token-item-0xc778417E063141139Fce010982780140Aa0cD5Ab').click()
cy.get('#swap-currency-input .token-amount-input').clear().type('0.01')
cy.get('#swap-currency-output .token-amount-input').should('have.value', '0.01')
})
})

View File

@@ -0,0 +1,30 @@
import { TEST_ADDRESS_NEVER_USE_SHORTENED } from '../support/ethereum'
describe('Wallet', () => {
before(() => {
cy.visit('/')
})
it('displays account details', () => {
cy.get('[data-testid=web3-status-connected]').contains(TEST_ADDRESS_NEVER_USE_SHORTENED).click()
})
it('displays account view in wallet modal', () => {
cy.get('[data-testid=web3-account-identifier-row]').contains(TEST_ADDRESS_NEVER_USE_SHORTENED)
})
it('changes back to the options grid', () => {
cy.contains('Change').click()
cy.get('[data-testid=option-grid]').should('exist')
})
it('selects injected wallet option', () => {
cy.contains('Injected').click()
cy.get('[data-testid=web3-account-identifier-row]').contains(TEST_ADDRESS_NEVER_USE_SHORTENED)
})
it('shows connect buttons after disconnect', () => {
cy.contains('Disconnect').click()
cy.get('[data-testid=option-grid]').should('exist')
})
})

View File

@@ -1,76 +0,0 @@
describe('Swap', () => {
beforeEach(() => {
cy.visit('/swap')
})
it('starts with ETH selected by default', () => {
cy.get('#swap-currency-input .token-amount-input').should('have.value', '')
cy.get('#swap-currency-input .token-symbol-container').should('contain.text', 'ETH')
cy.get('#swap-currency-output .token-amount-input').should('not.have.value')
cy.get('#swap-currency-output .token-symbol-container').should('contain.text', 'Select a token')
})
it('can enter an amount into input', () => {
cy.get('#swap-currency-input .token-amount-input')
.clear()
.type('0.001', { delay: 200 })
.should('have.value', '0.001')
})
it('zero swap amount', () => {
cy.get('#swap-currency-input .token-amount-input').clear().type('0.0', { delay: 200 }).should('have.value', '0.0')
})
it('invalid swap amount', () => {
cy.get('#swap-currency-input .token-amount-input').clear().type('\\', { delay: 200 }).should('have.value', '')
})
it('can enter an amount into output', () => {
cy.get('#swap-currency-output .token-amount-input').type('0.001', { delay: 200 }).should('have.value', '0.001')
})
it('zero output amount', () => {
cy.get('#swap-currency-output .token-amount-input').type('0.0', { delay: 200 }).should('have.value', '0.0')
})
it.skip('can swap ETH for DAI', () => {
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').should('be.visible')
cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').click({ force: true })
cy.get('#swap-currency-input .token-amount-input').should('be.visible')
cy.get('#swap-currency-input .token-amount-input').type('0.001', { force: true, delay: 200 })
cy.get('#swap-currency-output .token-amount-input').should('not.equal', '')
cy.get('#swap-button').click()
cy.get('#confirm-swap-or-send').should('contain', 'Confirm Swap')
})
it.skip('add a recipient does not exist unless in expert mode', () => {
cy.get('#add-recipient-button').should('not.exist')
})
describe('expert mode', () => {
beforeEach(() => {
cy.window().then((win) => {
cy.stub(win, 'prompt').returns('confirm')
})
cy.get('#open-settings-dialog-button').click()
cy.get('#toggle-expert-mode-button').click()
cy.get('#confirm-expert-mode').click()
})
it.skip('add a recipient is visible', () => {
cy.get('#add-recipient-button').should('be.visible')
})
it.skip('add a recipient', () => {
cy.get('#add-recipient-button').click()
cy.get('#recipient').should('exist')
})
it.skip('remove recipient', () => {
cy.get('#add-recipient-button').click()
cy.get('#remove-recipient-button').click()
cy.get('#recipient').should('not.exist')
})
})
})

20
cypress/release.ts Normal file
View File

@@ -0,0 +1,20 @@
const ONE_MINUTE = 60_000
describe(
'Release',
{
pageLoadTimeout: ONE_MINUTE,
retries: 30,
},
() => {
it('loads swap page', () => {
// We *must* wait in order to space out the retry attempts.
cy.wait(ONE_MINUTE)
.visit('/', {
retryOnStatusCodeFailure: true,
retryOnNetworkFailure: true,
})
.get('#swap-page')
})
}
)

View File

@@ -1,10 +0,0 @@
export const TEST_ADDRESS_NEVER_USE: string
export const TEST_ADDRESS_NEVER_USE_SHORTENED: string
// declare namespace Cypress {
// // eslint-disable-next-line @typescript-eslint/class-name-casing
// interface cy {
// additionalCommands(): void
// }
// }

58
cypress/support/e2e.ts Normal file
View File

@@ -0,0 +1,58 @@
// ***********************************************************
// This file is processed and loaded automatically before your test files.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.ts using ES2015 syntax:
import { injected } from './ethereum'
import assert = require('assert')
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface ApplicationWindow {
ethereum: typeof injected
}
interface VisitOptions {
serviceWorker?: true
}
}
}
// sets up the injected provider to be a mock ethereum provider with the given mnemonic/index
// eslint-disable-next-line no-undef
Cypress.Commands.overwrite(
'visit',
(original, url: string | Partial<Cypress.VisitOptions>, options?: Partial<Cypress.VisitOptions>) => {
assert(typeof url === 'string')
cy.intercept('/service-worker.js', options?.serviceWorker ? undefined : { statusCode: 404 }).then(() => {
original({
...options,
url: (url.startsWith('/') && url.length > 2 && !url.startsWith('/#') ? `/#${url}` : url) + '?chain=rinkeby',
onBeforeLoad(win) {
options?.onBeforeLoad?.(win)
win.localStorage.clear()
win.ethereum = injected
},
})
})
}
)
beforeEach(() => {
// Infura security policies are based on Origin headers.
// These are stripped by cypress because chromeWebSecurity === false; this adds them back in.
cy.intercept(/infura.io/, (res) => {
res.headers['origin'] = 'http://localhost:3000'
res.continue()
})
})
Cypress.on('uncaught:exception', (_err, _runnable) => {
// returning false here prevents Cypress from
// failing the test
return false
})

View File

@@ -1,8 +1,6 @@
// ***********************************************
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
/**
* Updates cy.visit() to include an injected window.ethereum provider.
*/
import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
import { JsonRpcProvider } from '@ethersproject/providers'
@@ -20,14 +18,16 @@ export const TEST_ADDRESS_NEVER_USE_SHORTENED = `${TEST_ADDRESS_NEVER_USE.substr
6
)}...${TEST_ADDRESS_NEVER_USE.substr(-4, 4)}`
class CustomizedBridge extends Eip1193Bridge {
const provider = new JsonRpcProvider('https://rinkeby.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 4)
const signer = new Wallet(TEST_PRIVATE_KEY, provider)
export const injected = new (class extends Eip1193Bridge {
chainId = 4
async sendAsync(...args) {
async sendAsync(...args: any[]) {
console.debug('sendAsync called', ...args)
return this.send(...args)
}
async send(...args) {
async send(...args: any[]) {
console.debug('send called', ...args)
const isCallbackForm = typeof args[0] === 'object' && typeof args[1] === 'function'
let callback
@@ -71,19 +71,4 @@ class CustomizedBridge extends Eip1193Bridge {
}
}
}
}
// sets up the injected provider to be a mock ethereum provider with the given mnemonic/index
// eslint-disable-next-line no-undef
Cypress.Commands.overwrite('visit', (original, url, options) => {
return original(url.startsWith('/') && url.length > 2 && !url.startsWith('/#') ? `/#${url}` : url, {
...options,
onBeforeLoad(win) {
options && options.onBeforeLoad && options.onBeforeLoad(win)
win.localStorage.clear()
const provider = new JsonRpcProvider('https://rinkeby.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 4)
const signer = new Wallet(TEST_PRIVATE_KEY, provider)
win.ethereum = new CustomizedBridge(signer, provider)
},
})
})
})(signer, provider)

View File

@@ -1,9 +0,0 @@
// ***********************************************************
// This file is processed and loaded automatically before your test files.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.ts using ES2015 syntax:
import './commands'

View File

@@ -1,7 +1,6 @@
{
"compilerOptions": {
"strict": true,
"baseUrl": "../node_modules",
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress"]

View File

@@ -1,160 +1,46 @@
{
"name": "@uniswap/widgets",
"name": "@uniswap/interface",
"version": "1.0.7",
"description": "Uniswap Interface",
"homepage": ".",
"files": [
"dist"
],
"type": "module",
"types": "dist/index.d.ts",
"main": "dist/cjs/index.cjs",
"module": "dist/index.js",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/cjs/index.cjs"
},
"./locales/*": {
"import": "./dist/locales/*.js",
"require": "./dist/cjs/locales/*.cjs"
},
"./fonts.css": {
"import": "./dist/fonts.css",
"require": "./dist/fonts.css"
}
},
"devDependencies": {
"@babel/plugin-transform-runtime": "^7.17.0",
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
"@babel/preset-typescript": "^7.16.7",
"@ethersproject/experimental": "^5.4.0",
"@gnosis.pm/safe-apps-web3-react": "^0.6.0",
"@graphql-codegen/cli": "1.21.5",
"@graphql-codegen/typescript": "1.22.3",
"@graphql-codegen/typescript-operations": "^1.18.2",
"@graphql-codegen/typescript-rtk-query": "^1.1.1",
"@lingui/cli": "^3.9.0",
"@lingui/core": "^3.9.0",
"@lingui/macro": "^3.9.0",
"@lingui/react": "^3.9.0",
"@metamask/jazzicon": "^2.0.0",
"@reach/dialog": "^0.10.3",
"@reach/portal": "^0.10.3",
"@react-hook/window-scroll": "^1.3.0",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.0.0",
"@testing-library/react-hooks": "^7.0.2",
"@typechain/ethers-v5": "^7.0.0",
"@types/array.prototype.flat": "^1.2.1",
"@types/array.prototype.flatmap": "^1.2.2",
"@types/d3": "^6.7.1",
"@types/jest": "^25.2.1",
"@types/lingui__core": "^2.7.1",
"@types/lingui__macro": "^2.7.4",
"@types/lingui__react": "^2.8.3",
"@types/ms.macro": "^2.0.0",
"@types/multicodec": "^1.0.0",
"@types/node": "^13.13.5",
"@types/qs": "^6.9.2",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.1",
"@types/react-redux": "^7.1.16",
"@types/react-router-dom": "^5.0.0",
"@types/react-virtualized-auto-sizer": "^1.0.0",
"@types/react-window": "^1.8.2",
"@types/rebass": "^4.0.7",
"@types/styled-components": "^5.1.0",
"@types/testing-library__cypress": "^5.0.5",
"@types/ua-parser-js": "^0.7.35",
"@types/wcag-contrast": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^4.1.0",
"@typescript-eslint/parser": "^4.1.0",
"@uniswap/default-token-list": "^3.0.0",
"@uniswap/governance": "^1.0.2",
"@uniswap/liquidity-staker": "^1.0.2",
"@uniswap/merkle-distributor": "1.0.1",
"@uniswap/v2-core": "1.0.0",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"@uniswap/v3-core": "1.0.0",
"@uniswap/v3-periphery": "^1.1.1",
"@web3-react/metamask": "^8.0.19-beta.0",
"@web3-react/walletconnect": "^8.0.26-beta.0",
"array.prototype.flat": "^1.2.4",
"array.prototype.flatmap": "^1.2.4",
"babel-plugin-macros": "^3.1.0",
"copy-to-clipboard": "^3.2.0",
"cross-env": "^7.0.3",
"cypress": "^7.7.0",
"d3": "^7.0.0",
"eslint": "^7.11.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-better-styled-components": "^1.1.2",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-react": "^7.19.0",
"eslint-plugin-react-hooks": "^4.0.0",
"eslint-plugin-simple-import-sort": "^7.0.0",
"eslint-plugin-unused-imports": "^2.0.0",
"firebase": "^9.1.3",
"graphql": "^15.5.0",
"graphql-request": "^3.4.0",
"inter-ui": "^3.13.1",
"jest-styled-components": "^7.0.5",
"polyfill-object.fromentries": "^1.0.1",
"prettier": "^2.2.1",
"qs": "^6.9.4",
"react": "^17.0.1",
"react-confetti": "^6.0.0",
"react-dom": "^17.0.1",
"react-ga4": "^1.4.1",
"react-is": "^17.0.2",
"react-markdown": "^4.3.1",
"react-redux": "^7.2.2",
"react-router-dom": "^5.0.0",
"react-scripts": "^4.0.3",
"react-spring": "^8.0.27",
"react-use-gesture": "^6.0.14",
"redux": "^4.1.2",
"redux-localstorage-simple": "^2.3.1",
"sass": "^1.45.1",
"serve": "^11.3.2",
"start-server-and-test": "^1.11.0",
"typechain": "^5.0.0",
"typescript": "^4.4.3",
"ua-parser-js": "^0.7.28",
"use-count-up": "^2.2.5",
"use-resize-observer": "^8.0.0",
"wcag-contrast": "^3.0.0",
"web-vitals": "^2.1.0",
"web3-react-abstract-connector": "npm:@web3-react/abstract-connector@^6.0.7",
"web3-react-fortmatic-connector": "npm:@web3-react/fortmatic-connector@^6.0.9",
"web3-react-injected-connector": "npm:@web3-react/injected-connector@^6.0.7",
"web3-react-types": "npm:@web3-react/types@^6.0.7",
"web3-react-walletconnect-connector": "npm:@web3-react/walletconnect-connector@^7.0.2-alpha.0",
"web3-react-walletlink-connector": "npm:@web3-react/walletlink-connector@^6.2.13",
"workbox-core": "^6.1.0",
"workbox-precaching": "^6.1.0",
"workbox-routing": "^6.1.0"
},
"resolutions": {
"@walletconnect/ethereum-provider": "1.7.1"
},
"license": "GPL-3.0-or-later",
"scripts": {
"contracts:compile:abi": "typechain --target ethers-v5 --out-dir src/abis/types \"./src/abis/**/*.json\"",
"contracts:compile:v3": "typechain --target ethers-v5 --out-dir src/types/v3 \"./node_modules/@uniswap/**/artifacts/contracts/**/*[!dbg].json\"",
"contracts:compile": "yarn contracts:compile:abi && yarn contracts:compile:v3",
"graphql:generate": "graphql-codegen --config codegen.yml",
"prei18n:extract": "touch src/locales/en-US.po",
"prei18n:extract": "node prei18n-extract.js",
"i18n:extract": "lingui extract --locale en-US",
"i18n:compile": "yarn i18n:extract && lingui compile",
"i18n:pseudo": "lingui extract --locale pseudo && lingui compile",
"prepare": "yarn contracts:compile && yarn graphql:generate && yarn i18n:compile",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=./custom-test-env.cjs",
"test:e2e": "start-server-and-test 'serve build -l 3000' http://localhost:3000 'cypress run --record'"
"serve": "serve build -l 3000",
"lint": "yarn eslint .",
"test": "react-scripts test --coverage",
"cypress:open": "cypress open --browser chrome --e2e",
"cypress:run": "cypress run --browser chrome --e2e"
},
"jest": {
"collectCoverageFrom": [
"src/components/**/*.ts*",
"src/hooks/**/*.ts*",
"src/lib/hooks/**/*.ts*",
"src/lib/state/**/*.ts*",
"src/lib/utils/**/*.ts*",
"src/pages/**/*.ts*",
"src/state/**/*.ts*",
"src/utils/**/*.ts*"
],
"coverageThreshold": {
"global": {
"branches": 4,
"functions": 6,
"lines": 9,
"statements": 9
}
}
},
"browserslist": {
"production": [
@@ -168,60 +54,148 @@
"last 1 safari version"
]
},
"license": "GPL-3.0-or-later",
"devDependencies": {
"@ethersproject/experimental": "^5.4.0",
"@graphql-codegen/cli": "1.21.5",
"@graphql-codegen/typescript": "1.22.3",
"@graphql-codegen/typescript-operations": "^1.18.2",
"@graphql-codegen/typescript-rtk-query": "^1.1.1",
"@lingui/cli": "^3.9.0",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.1",
"@typechain/ethers-v5": "^7.0.0",
"@types/array.prototype.flat": "^1.2.1",
"@types/array.prototype.flatmap": "^1.2.2",
"@types/d3": "^6.7.1",
"@types/jest": "^25.2.1",
"@types/lingui__core": "^2.7.1",
"@types/lingui__macro": "^2.7.4",
"@types/lingui__react": "^2.8.3",
"@types/ms.macro": "^2.0.0",
"@types/multicodec": "^1.0.0",
"@types/node": "^13.13.5",
"@types/qs": "^6.9.2",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@types/react-redux": "^7.1.24",
"@types/react-router-dom": "^5.3.3",
"@types/react-virtualized-auto-sizer": "^1.0.0",
"@types/react-window": "^1.8.2",
"@types/rebass": "^4.0.7",
"@types/styled-components": "^5.1.25",
"@types/testing-library__cypress": "^5.0.5",
"@types/ua-parser-js": "^0.7.35",
"@types/wcag-contrast": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^4",
"@typescript-eslint/parser": "^4",
"cypress": "^10.1.0",
"env-cmd": "^10.1.0",
"eslint": "^7.11.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-better-styled-components": "^1.1.2",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-react": "^7.19.0",
"eslint-plugin-react-hooks": "^4.0.0",
"eslint-plugin-simple-import-sort": "^7.0.0",
"eslint-plugin-unused-imports": "^2.0.0",
"jest-styled-components": "7.0.7",
"ms.macro": "^2.0.0",
"prettier": "^2.7.1",
"react-scripts": "^4.0.3",
"serve": "^11.3.2",
"typechain": "^5.0.0",
"typescript": "^4.4.3"
},
"dependencies": {
"@babel/runtime": "^7.17.0",
"@amplitude/analytics-browser": "^0.5.1",
"@coinbase/wallet-sdk": "^3.3.0",
"@fontsource/ibm-plex-mono": "^4.5.1",
"@fontsource/inter": "^4.5.1",
"@lingui/core": "^3.14.0",
"@lingui/macro": "^3.14.0",
"@lingui/react": "^3.14.0",
"@metamask/jazzicon": "^2.0.0",
"@popperjs/core": "^2.4.4",
"@reach/dialog": "^0.10.3",
"@reach/portal": "^0.10.3",
"@react-hook/window-scroll": "^1.3.0",
"@reduxjs/toolkit": "^1.6.1",
"@uniswap/redux-multicall": "^1.1.1",
"@uniswap/router-sdk": "^1.0.3",
"@uniswap/governance": "^1.0.2",
"@uniswap/liquidity-staker": "^1.0.2",
"@uniswap/merkle-distributor": "1.0.1",
"@uniswap/redux-multicall": "^1.1.5",
"@uniswap/router-sdk": "^1.0.6",
"@uniswap/sdk-core": "^3.0.1",
"@uniswap/smart-order-router": "^2.5.26",
"@uniswap/token-lists": "^1.0.0-beta.27",
"@uniswap/token-lists": "^1.0.0-beta.30",
"@uniswap/v2-core": "1.0.0",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"@uniswap/v2-sdk": "^3.0.1",
"@uniswap/v3-sdk": "^3.8.2",
"@web3-react/core": "^8.0.23-beta.0",
"@web3-react/eip1193": "^8.0.18-beta.0",
"@web3-react/empty": "^8.0.12-beta.0",
"@web3-react/types": "^8.0.12-beta.0",
"@web3-react/url": "^8.0.17-beta.0",
"@uniswap/v3-core": "1.0.0",
"@uniswap/v3-periphery": "^1.1.1",
"@uniswap/v3-sdk": "^3.9.0",
"@walletconnect/ethereum-provider": "1.7.1",
"@web3-react/coinbase-wallet": "^8.0.34-beta.0",
"@web3-react/core": "^8.0.35-beta.0",
"@web3-react/eip1193": "^8.0.26-beta.0",
"@web3-react/empty": "^8.0.20-beta.0",
"@web3-react/gnosis-safe": "^8.0.6-beta.0",
"@web3-react/metamask": "^8.0.28-beta.0",
"@web3-react/network": "^8.0.27-beta.0",
"@web3-react/types": "^8.0.20-beta.0",
"@web3-react/url": "^8.0.25-beta.0",
"@web3-react/walletconnect": "^8.0.35-beta.0",
"ajv": "^6.12.3",
"array.prototype.flat": "^1.2.4",
"array.prototype.flatmap": "^1.2.4",
"cids": "^1.0.0",
"copy-to-clipboard": "^3.2.0",
"d3": "^7.0.0",
"ethers": "^5.1.4",
"firebase": "^9.1.3",
"fortmatic": "^2.4.0",
"graphql": "^15.5.0",
"graphql-request": "^3.4.0",
"immer": "^9.0.6",
"inter-ui": "^3.13.1",
"jotai": "^1.3.7",
"jsbi": "^3.1.4",
"make-plural": "^7.0.0",
"ms.macro": "^2.0.0",
"multicodec": "^3.0.1",
"multihashes": "^4.0.2",
"node-vibrant": "^3.2.1-alpha.1",
"polished": "^3.3.2",
"polyfill-object.fromentries": "^1.0.1",
"popper-max-size-modifier": "^0.2.0",
"qs": "^6.9.4",
"react": "^18.2.0",
"react-confetti": "^6.0.0",
"react-dom": "^18.2.0",
"react-feather": "^2.0.8",
"react-ga4": "^1.4.1",
"react-is": "^17.0.2",
"react-markdown": "^4.3.1",
"react-popper": "^2.2.3",
"react-redux": "^8.0.2",
"react-router-dom": "^5.3.3",
"react-spring": "^8.0.27",
"react-use-gesture": "^6.0.14",
"react-virtualized-auto-sizer": "^1.0.2",
"react-window": "^1.8.5",
"rebass": "^4.0.7",
"redux": "^4.1.2",
"redux-localstorage-simple": "^2.3.1",
"setimmediate": "^1.0.5",
"styled-components": "^5.3.0",
"styled-components": "^5.3.5",
"tiny-invariant": "^1.2.0",
"ua-parser-js": "^0.7.28",
"use-count-up": "^2.2.5",
"use-resize-observer": "^8.0.0",
"wcag-contrast": "^3.0.0",
"web3-react-core": "npm:@web3-react/core@^6.0.9",
"wicg-inert": "^3.1.1"
},
"peerDependencies": {
"@babel/runtime": "^7.17.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-redux": "^7.2.2",
"redux": "^4.1.2"
},
"optionalDependencies": {
"bufferutil": "^4.0.6",
"encoding": "^0.1.13",
"utf-8-validate": "^5.0.8"
"web-vitals": "^2.1.0",
"workbox-core": "^6.1.0",
"workbox-navigation-preload": "^6.1.0",
"workbox-precaching": "^6.1.0",
"workbox-routing": "^6.1.0"
}
}

9
prei18n-extract.js Normal file
View File

@@ -0,0 +1,9 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const exec = require('child_process').exec
const isWindows = process.platform === 'win32' || /^(msys|cygwin)$/.test(process.env.OSTYPE)
if (isWindows) {
exec(`type nul > src/locales/en-US.po`)
} else {
exec(`touch src/locales/en-US.po`)
}

View File

@@ -24,83 +24,83 @@
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="preconnect" href="https://www.google-analytics.com/">
<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>
<link rel="preload" href="%PUBLIC_URL%/fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format("woff2 supports variations(gvar)"),
url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format("woff2-variations"),
url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format("woff2");
}
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
font-feature-settings: 'ss01' on, 'ss02' on, 'cv01' on, 'cv03' on;
}
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
background: radial-gradient(50% 50% at 50% 50%, #fc077d10 0%, rgba(255, 255, 255, 0) 100%);
transform: translate(-50vw, -100vh);
z-index: -1;
}
html {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
font-feature-settings: 'ss01' on, 'ss02' on, 'cv01' on, 'cv03' on;
background-color: #212429;
}
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
background: radial-gradient(50% 50% at 50% 50%, #fc077d10 0%, rgba(255, 255, 255, 0) 100%);
transform: translate(-50vw, -100vh);
z-index: -1;
}
}
@media (prefers-color-scheme: light) {
html {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
background-color: #212429;
}
}
@media (prefers-color-scheme: light) {
html {
background-color: #F7F8FA;
}
background-color: #f7f8fa;
}
}
</style>
</head>
<body>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1 @@
<svg id="Celo_Rings" data-name="Celo Rings" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 950 950"><defs><style>.cls-1{fill:#fbcc5c;}.cls-2{fill:#35d07f;}.cls-3{fill:#5ea33b;}</style></defs><title>Artboard 1</title><path id="Bottom_Ring" data-name="Bottom Ring" class="cls-1" d="M375,850c151.88,0,275-123.12,275-275S526.88,300,375,300,100,423.12,100,575,223.12,850,375,850Zm0,100C167.9,950,0,782.1,0,575S167.9,200,375,200,750,367.9,750,575,582.1,950,375,950Z"/><path id="Top_Ring" data-name="Top Ring" class="cls-2" d="M575,650c151.88,0,275-123.12,275-275S726.88,100,575,100,300,223.12,300,375,423.12,650,575,650Zm0,100c-207.1,0-375-167.9-375-375S367.9,0,575,0,950,167.9,950,375,782.1,750,575,750Z"/><path id="Rings_Overlap" data-name="Rings Overlap" class="cls-3" d="M587.39,750a274.38,274.38,0,0,0,54.55-108.06A274.36,274.36,0,0,0,750,587.4a373.63,373.63,0,0,1-29.16,133.45A373.62,373.62,0,0,1,587.39,750ZM308.06,308.06A274.36,274.36,0,0,0,200,362.6a373.63,373.63,0,0,1,29.16-133.45A373.62,373.62,0,0,1,362.61,200,274.38,274.38,0,0,0,308.06,308.06Z"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1,4 +1,4 @@
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useWeb3React } from '@web3-react/core'
import { CheckCircle, Triangle } from 'react-feather'
import styled from 'styled-components/macro'
@@ -35,7 +35,7 @@ const IconWrapper = styled.div<{ pending: boolean; success?: boolean }>`
`
export default function Transaction({ hash }: { hash: string }) {
const { chainId } = useActiveWeb3React()
const { chainId } = useWeb3React()
const allTransactions = useAllTransactions()
const tx = allTransactions?.[hash]

View File

@@ -17,7 +17,9 @@ import {
DepositLiquidityStakingTransactionInfo,
ExactInputSwapTransactionInfo,
ExactOutputSwapTransactionInfo,
ExecuteTransactionInfo,
MigrateV2LiquidityToV3TransactionInfo,
QueueTransactionInfo,
RemoveLiquidityV3TransactionInfo,
SubmitProposalTransactionInfo,
TransactionInfo,
@@ -126,6 +128,16 @@ function VoteSummary({ info }: { info: VoteTransactionInfo }) {
}
}
function QueueSummary({ info }: { info: QueueTransactionInfo }) {
const proposalKey = `${info.governorAddress}/${info.proposalId}`
return <Trans>Queue proposal {proposalKey}.</Trans>
}
function ExecuteSummary({ info }: { info: ExecuteTransactionInfo }) {
const proposalKey = `${info.governorAddress}/${info.proposalId}`
return <Trans>Execute proposal {proposalKey}.</Trans>
}
function DelegateSummary({ info: { delegatee } }: { info: DelegateTransactionInfo }) {
const { ENSName } = useENSName(delegatee)
return <Trans>Delegate voting power to {ENSName ?? delegatee}</Trans>
@@ -339,6 +351,12 @@ export function TransactionSummary({ info }: { info: TransactionInfo }) {
case TransactionType.REMOVE_LIQUIDITY_V3:
return <RemoveLiquidityV3Summary info={info} />
case TransactionType.QUEUE:
return <QueueSummary info={info} />
case TransactionType.EXECUTE:
return <ExecuteSummary info={info} />
case TransactionType.SUBMIT_PROPOSAL:
return <SubmitProposalTransactionSummary info={info} />
}

View File

@@ -1,16 +1,16 @@
import { Trans } from '@lingui/macro'
import { Connector } from '@web3-react/types'
import { useWeb3React } from '@web3-react/core'
import CopyHelper from 'components/AccountDetails/Copy'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useCallback, useContext } from 'react'
import { getConnection, getConnectionName, getIsCoinbaseWallet, getIsMetaMask } from 'connection/utils'
import { Context, useCallback, useContext } from 'react'
import { ExternalLink as LinkIcon } from 'react-feather'
import { useAppDispatch } from 'state/hooks'
import { updateSelectedWallet } from 'state/user/reducer'
import { DefaultTheme } from 'styled-components/macro'
import styled, { ThemeContext } from 'styled-components/macro'
import { AbstractConnector } from 'web3-react-abstract-connector'
import { isMobile } from 'utils/userAgent'
import { ReactComponent as Close } from '../../assets/images/x.svg'
import { injected, walletlink } from '../../connectors'
import { SUPPORTED_WALLETS } from '../../constants/wallet'
import { clearAllTransactions } from '../../state/transactions/reducer'
import { ExternalLink, LinkStyledButton, ThemedText } from '../../theme'
import { shortenAddress } from '../../utils'
@@ -162,29 +162,6 @@ const WalletName = styled.div`
color: ${({ theme }) => theme.text3};
`
const IconWrapper = styled.div<{ size?: number }>`
${({ theme }) => theme.flexColumnNoWrap};
align-items: center;
justify-content: center;
margin-right: 8px;
& > img,
span {
height: ${({ size }) => (size ? size + 'px' : '32px')};
width: ${({ size }) => (size ? size + 'px' : '32px')};
}
${({ theme }) => theme.mediaWidth.upToMedium`
align-items: flex-end;
`};
`
function WrappedStatusIcon({ connector }: { connector: AbstractConnector | Connector }) {
return (
<IconWrapper size={16}>
<StatusIcon connector={connector} />
</IconWrapper>
)
}
const TransactionListWrapper = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
`
@@ -226,22 +203,20 @@ export default function AccountDetails({
ENSName,
openOptions,
}: AccountDetailsProps) {
const { chainId, account, connector } = useActiveWeb3React()
const theme = useContext(ThemeContext)
const { chainId, account, connector } = useWeb3React()
const connectionType = getConnection(connector).type
const theme = useContext(ThemeContext as Context<DefaultTheme>)
const dispatch = useAppDispatch()
const isMetaMask = getIsMetaMask()
const isCoinbaseWallet = getIsCoinbaseWallet()
const isInjectedMobileBrowser = (isMetaMask || isCoinbaseWallet) && isMobile
function formatConnectorName() {
const { ethereum } = window
const isMetaMask = !!(ethereum && ethereum.isMetaMask)
const name = Object.keys(SUPPORTED_WALLETS)
.filter(
(k) =>
SUPPORTED_WALLETS[k].connector === connector && (connector !== injected || isMetaMask === (k === 'METAMASK'))
)
.map((k) => SUPPORTED_WALLETS[k].name)[0]
return (
<WalletName>
<Trans>Connected with {name}</Trans>
<Trans>Connected with</Trans> {getConnectionName(connectionType, isMetaMask)}
</WalletName>
)
}
@@ -265,43 +240,41 @@ export default function AccountDetails({
<AccountGroupingRow>
{formatConnectorName()}
<div>
{connector !== injected && connector !== walletlink && (
<WalletAction
style={{ fontSize: '.825rem', fontWeight: 400, marginRight: '8px' }}
onClick={() => {
;(connector as any).close()
}}
>
<Trans>Disconnect</Trans>
</WalletAction>
{!isInjectedMobileBrowser && (
<>
<WalletAction
style={{ fontSize: '.825rem', fontWeight: 400, marginRight: '8px' }}
onClick={() => {
if (connector.deactivate) {
connector.deactivate()
} else {
connector.resetState()
}
dispatch(updateSelectedWallet({ wallet: undefined }))
openOptions()
}}
>
<Trans>Disconnect</Trans>
</WalletAction>
<WalletAction
style={{ fontSize: '.825rem', fontWeight: 400 }}
onClick={() => {
openOptions()
}}
>
<Trans>Change</Trans>
</WalletAction>
</>
)}
<WalletAction
style={{ fontSize: '.825rem', fontWeight: 400 }}
onClick={() => {
openOptions()
}}
>
<Trans>Change</Trans>
</WalletAction>
</div>
</AccountGroupingRow>
<AccountGroupingRow id="web3-account-identifier-row">
<AccountGroupingRow data-testid="web3-account-identifier-row">
<AccountControl>
{ENSName ? (
<>
<div>
{connector && <WrappedStatusIcon connector={connector} />}
<p> {ENSName}</p>
</div>
</>
) : (
<>
<div>
{connector && <WrappedStatusIcon connector={connector} />}
<p> {account && shortenAddress(account)}</p>
</div>
</>
)}
<div>
<StatusIcon connectionType={connectionType} />
<p>{ENSName ? ENSName : account && shortenAddress(account)}</p>
</div>
</AccountControl>
</AccountGroupingRow>
<AccountGroupingRow>

View File

@@ -1,9 +1,9 @@
import { Trans } from '@lingui/macro'
// eslint-disable-next-line no-restricted-imports
import { t } from '@lingui/macro'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { ReactNode, useCallback, useContext } from 'react'
import styled, { ThemeContext } from 'styled-components/macro'
import { useWeb3React } from '@web3-react/core'
import { ChangeEvent, Context, ReactNode, useCallback, useContext } from 'react'
import styled, { DefaultTheme, ThemeContext } from 'styled-components/macro'
import useENS from '../../hooks/useENS'
import { ExternalLink, ThemedText } from '../../theme'
@@ -86,13 +86,13 @@ export default function AddressInputPanel({
// triggers whenever the typed value changes
onChange: (value: string) => void
}) {
const { chainId } = useActiveWeb3React()
const theme = useContext(ThemeContext)
const { chainId } = useWeb3React()
const theme = useContext(ThemeContext as Context<DefaultTheme>)
const { address, loading, name } = useENS(value)
const handleInput = useCallback(
(event) => {
(event: ChangeEvent<HTMLInputElement>) => {
const input = event.target.value
const withoutSpaces = input.replace(/\s+/g, '')
onChange(withoutSpaces)

View File

@@ -0,0 +1,57 @@
import { createContext, memo, PropsWithChildren, useContext, useEffect, useMemo } from 'react'
import { sendAnalyticsEvent } from '.'
import { ElementName, EventName, ModalName, PageName, SectionName } from './constants'
export interface ITraceContext {
// Highest order context: eg Swap or Explore.
page?: PageName
// Enclosed section name. For contexts with modals, refers to the
// section of the page from which the user triggered the modal.
section?: SectionName
modal?: ModalName
// Element name mostly used to identify events sources
// Does not need to be unique given the higher order page and section.
element?: ElementName
}
export const TraceContext = createContext<ITraceContext>({})
type TraceProps = {
shouldLogImpression?: boolean // whether to log impression on mount
name?: EventName
properties?: Record<string, unknown>
} & ITraceContext
/**
* Sends an analytics event on mount (if shouldLogImpression is set),
* and propagates the context to child traces.
*/
export const Trace = memo(
({ shouldLogImpression, name, children, page, section, element, properties }: PropsWithChildren<TraceProps>) => {
const parentTrace = useContext(TraceContext)
const combinedProps = useMemo(
() => ({
...parentTrace,
...Object.fromEntries(Object.entries({ page, section, element }).filter(([_, v]) => v !== undefined)),
}),
[element, parentTrace, page, section]
)
useEffect(() => {
if (shouldLogImpression) {
sendAnalyticsEvent(name ?? EventName.PAGE_VIEWED, { ...combinedProps, ...properties })
}
// Impressions should only be logged on mount.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return <TraceContext.Provider value={combinedProps}>{children}</TraceContext.Provider>
}
)
Trace.displayName = 'Trace'

View File

@@ -0,0 +1,70 @@
import { Children, cloneElement, isValidElement, memo, PropsWithChildren, SyntheticEvent } from 'react'
import { sendAnalyticsEvent } from '.'
import { Event, EventName } from './constants'
import { ITraceContext, Trace, TraceContext } from './Trace'
type TraceEventProps = {
events: Event[]
name: EventName
properties?: Record<string, unknown>
} & ITraceContext
/**
* Analytics instrumentation component that wraps event callbacks with logging logic.
*
* @example
* <TraceEvent events={[Event.onClick]} element={ElementName.SWAP_BUTTON}>
* <Button onClick={() => console.log('clicked')}>Click me</Button>
* </TraceEvent>
*/
export const TraceEvent = memo((props: PropsWithChildren<TraceEventProps>) => {
const { name, properties, events, children, ...traceProps } = props
return (
<Trace {...traceProps}>
<TraceContext.Consumer>
{(traceContext) =>
Children.map(children, (child) => {
if (!isValidElement(child)) {
return child
}
// For each child, augment event handlers defined in `events` with event tracing.
return cloneElement(child, getEventHandlers(child, traceContext, events, name, properties))
})
}
</TraceContext.Consumer>
</Trace>
)
})
TraceEvent.displayName = 'TraceEvent'
/**
* Given a set of child element and event props, returns a spreadable
* object of the event handlers augmented with analytics logging.
*/
function getEventHandlers(
child: React.ReactElement,
traceContext: ITraceContext,
events: Event[],
name: EventName,
properties?: Record<string, unknown>
) {
const eventHandlers: Partial<Record<Event, (e: SyntheticEvent<Element, Event>) => void>> = {}
for (const event of events) {
eventHandlers[event] = (eventHandlerArgs: unknown) => {
// call child event handler with original arguments, must be in array
const args = Array.isArray(eventHandlerArgs) ? eventHandlerArgs : [eventHandlerArgs]
child.props[event]?.apply(child, args)
// augment handler with analytics logging
sendAnalyticsEvent(name, { ...traceContext, ...properties, action: event })
}
}
// return a spreadable event handler object
return eventHandlers
}

View File

@@ -0,0 +1,78 @@
/**
* Event names that can occur in this application.
*
* Subject to change as new features are added and new events are defined
* and logged.
*/
export enum EventName {
CONNECT_WALLET_BUTTON_CLICKED = 'Connect Wallet Button Clicked',
PAGE_VIEWED = 'Page Viewed',
SWAP_SUBMITTED = 'Swap Submitted',
TOKEN_IMPORTED = 'Token Imported',
TOKEN_SELECTED = 'Token Selected',
TOKEN_SELECTOR_OPENED = 'Token Selector Opened',
WALLET_CONNECT_TXN_COMPLETED = 'Wallet Connect Transaction Completed',
WALLET_SELECTED = 'Wallet Selected',
// alphabetize additional event names.
}
export enum WALLET_CONNECTION_RESULT {
SUCCEEDED = 'Succeeded',
FAILED = 'Failed',
}
/**
* Known pages in the app. Highest order context.
*/
export const enum PageName {
EXPLORE_PAGE = 'explore-page',
POOL_PAGE = 'pool-page',
SWAP_PAGE = 'swap-page',
VOTE_PAGE = 'vote-page',
// alphabetize additional page names.
}
/**
* Sections. Disambiguates low-level elements that may share a name.
* eg a `back` button in a modal will have the same `element`,
* but a different `section`.
*/
export const enum SectionName {
CURRENCY_INPUT_PANEL = 'swap-currency-input',
CURRENCY_OUTPUT_PANEL = 'swap-currency-output',
// alphabetize additional section names.
}
/** Known modals for analytics purposes. */
export const enum ModalName {
SWAP = 'swap-modal',
TOKEN_SELECTOR = 'token-selector-modal',
// alphabetize additional modal names.
}
/**
* Known element names for analytics purposes.
* Use to identify low-level components given a TraceContext
*/
export const enum ElementName {
COMMON_BASES_CURRENCY_BUTTON = 'common-bases-currency-button',
CONFIRM_SWAP_BUTTON = 'confirm-swap-or-send',
CONNECT_WALLET_BUTTON = 'connect-wallet-button',
IMPORT_TOKEN_BUTTON = 'import-token-button',
SWAP_BUTTON = 'swap-button',
TOKEN_SELECTOR_ROW = 'token-selector-row',
WALLET_TYPE_OPTION = 'wallet-type-option',
// alphabetize additional element names.
}
/**
* Known events that trigger callbacks.
* @example
* <TraceEvent events={[Event.onClick]} element={name}>
*/
export enum Event {
onClick = 'onClick',
onKeyPress = 'onKeyPress',
onSelect = 'onSelect',
// alphabetize additional events.
}

View File

@@ -0,0 +1,88 @@
import { Identify, identify, init, track } from '@amplitude/analytics-browser'
/**
* Initializes Amplitude with API key for project.
*
* Uniswap has two Amplitude projects: test and production. You must be a
* member of the organization on Amplitude to view details.
*/
export function initializeAnalytics(isDevEnvironment = process.env.NODE_ENV === 'development') {
if (isDevEnvironment) return
const API_KEY = process.env.REACT_APP_AMPLITUDE_KEY
if (typeof API_KEY === 'undefined') {
throw new Error(`REACT_APP_AMPLITUDE_KEY must be a defined environment variable`)
}
init(
API_KEY,
/* userId= */ undefined, // User ID should be undefined to let Amplitude default to Device ID
/* options= */ {
// Disable tracking of private user information by Amplitude
trackingOptions: {
ipAddress: false,
carrier: false,
city: false,
region: false,
country: false,
dma: false, // designated market area
},
}
)
}
/** Sends an event to Amplitude. */
export function sendAnalyticsEvent(eventName: string, eventProperties?: Record<string, unknown>) {
if (process.env.NODE_ENV === 'development') {
console.log(`[amplitude(${eventName})]: ${JSON.stringify(eventProperties)}`)
return
}
track(eventName, eventProperties)
}
/**
* Class that exposes methods to mutate the User Model's properties in
* Amplitude that represents the current session's user.
*
* See https://help.amplitude.com/hc/en-us/articles/115002380567-User-properties-and-event-properties
* for details.
*/
class UserModel {
constructor(private isDevEnvironment = process.env.NODE_ENV === 'development') {}
private log(method: string, ...parameters: unknown[]) {
console.debug(`[amplitude(Identify)]: ${method}(${parameters})`)
}
private call(mutate: (event: Identify) => Identify) {
if (this.isDevEnvironment) {
const log = (_: Identify, method: string) => this.log.bind(this, method)
mutate(new Proxy(new Identify(), { get: log }))
return
}
identify(mutate(new Identify()))
}
set(key: string, value: string | number) {
this.call((event) => event.set(key, value))
}
setOnce(key: string, value: string | number) {
this.call((event) => event.setOnce(key, value))
}
add(key: string, value: string | number) {
this.call((event) => event.add(key, typeof value === 'number' ? value : 0))
}
postInsert(key: string, value: string | number) {
this.call((event) => event.postInsert(key, value))
}
remove(key: string, value: string | number) {
this.call((event) => event.remove(key, value))
}
}
export const user = new UserModel()

View File

@@ -1,5 +1,5 @@
import { Trans } from '@lingui/macro'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useWeb3React } from '@web3-react/core'
import { ReactNode, useMemo } from 'react'
const BLOCKED_ADDRESSES: string[] = [
@@ -38,7 +38,7 @@ const BLOCKED_ADDRESSES: string[] = [
]
export default function Blocklist({ children }: { children: ReactNode }) {
const { account } = useActiveWeb3React()
const { account } = useWeb3React()
const blocked: boolean = useMemo(() => Boolean(account && BLOCKED_ADDRESSES.indexOf(account) !== -1), [account])
if (blocked) {
return (

View File

@@ -1,9 +1,9 @@
import { Trans } from '@lingui/macro'
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import { useWeb3React } from '@web3-react/core'
import { AutoColumn } from 'components/Column'
import { LoadingOpacityContainer, loadingOpacityMixin } from 'components/Loader/styled'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { darken } from 'polished'
import { ReactNode, useCallback, useState } from 'react'
import { Lock } from 'react-feather'
@@ -12,7 +12,7 @@ import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
import useTheme from '../../hooks/useTheme'
import { useCurrencyBalance } from '../../state/wallet/hooks'
import { useCurrencyBalance } from '../../state/connection/hooks'
import { ThemedText } from '../../theme'
import { ButtonGray } from '../Button'
import CurrencyLogo from '../CurrencyLogo'
@@ -103,6 +103,7 @@ const LabelRow = styled.div`
const FiatRow = styled(LabelRow)`
justify-content: flex-end;
height: 16px;
`
const Aligner = styled.span`
@@ -201,7 +202,7 @@ export default function CurrencyInputPanel({
...rest
}: CurrencyInputPanelProps) {
const [modalOpen, setModalOpen] = useState(false)
const { account } = useActiveWeb3React()
const { account } = useWeb3React()
const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined)
const theme = useTheme()

View File

@@ -5,17 +5,23 @@ import styled from 'styled-components/macro'
import Logo from '../Logo'
const StyledLogo = styled(Logo)<{ size: string; native: boolean }>`
const StyledLogo = styled(Logo)<{ size: string }>`
width: ${({ size }) => size};
height: ${({ size }) => size};
background: radial-gradient(white 50%, #ffffff00 calc(75% + 1px), #ffffff00 100%);
border-radius: 50%;
-mox-box-shadow: 0 0 1px ${({ native }) => (native ? 'white' : 'black')};
-webkit-box-shadow: 0 0 1px ${({ native }) => (native ? 'white' : 'black')};
box-shadow: 0 0 1px ${({ native }) => (native ? 'white' : 'black')};
-mox-box-shadow: 0 0 1px black;
-webkit-box-shadow: 0 0 1px black;
box-shadow: 0 0 1px black;
border: 0px solid rgba(255, 255, 255, 0);
`
const StyledNativeLogo = styled(StyledLogo)`
-mox-box-shadow: 0 0 1px white;
-webkit-box-shadow: 0 0 1px white;
box-shadow: 0 0 1px white;
`
export default function CurrencyLogo({
currency,
size = '24px',
@@ -26,16 +32,13 @@ export default function CurrencyLogo({
size?: string
style?: React.CSSProperties
}) {
const logoURIs = useCurrencyLogoURIs(currency)
const props = {
alt: `${currency?.symbol ?? 'token'} logo`,
size,
srcs: useCurrencyLogoURIs(currency),
style,
...rest,
}
return (
<StyledLogo
size={size}
native={currency?.isNative ?? false}
srcs={logoURIs}
alt={`${currency?.symbol ?? 'token'} logo`}
style={style}
{...rest}
/>
)
return currency?.isNative ? <StyledNativeLogo {...props} /> : <StyledLogo {...props} />
}

View File

@@ -1,6 +1,6 @@
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { SupportedChainId } from 'constants/chains'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { AlertOctagon } from 'react-feather'
import styled from 'styled-components/macro'
import { ExternalLink } from 'theme'
@@ -42,7 +42,7 @@ function Wrapper({ children }: { children: React.ReactNode }) {
* Shows a downtime warning for the network if it's relevant
*/
export default function DowntimeWarning() {
const { chainId } = useActiveWeb3React()
const { chainId } = useWeb3React()
if (!isL2ChainId(chainId)) {
return null
}

View File

@@ -1,6 +1,6 @@
import { Trans } from '@lingui/macro'
import React, { ErrorInfo } from 'react'
import ReactGA from 'react-ga4'
import { sendEvent } from 'components/analytics'
import React, { ErrorInfo, PropsWithChildren } from 'react'
import styled from 'styled-components/macro'
import store, { AppState } from '../../state'
@@ -56,8 +56,8 @@ async function updateServiceWorker(): Promise<ServiceWorkerRegistration> {
return ready.update() as unknown as Promise<ServiceWorkerRegistration>
}
export default class ErrorBoundary extends React.Component<unknown, ErrorBoundaryState> {
constructor(props: unknown) {
export default class ErrorBoundary extends React.Component<PropsWithChildren<unknown>, ErrorBoundaryState> {
constructor(props: PropsWithChildren<unknown>) {
super(props)
this.state = { error: null }
}
@@ -85,7 +85,10 @@ export default class ErrorBoundary extends React.Component<unknown, ErrorBoundar
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
ReactGA.event('exception', { description: error.toString() + errorInfo.toString(), fatal: true })
sendEvent('exception', {
description: error.toString() + errorInfo.toString(),
fatal: true,
})
}
render() {

View File

@@ -1,17 +1,17 @@
import { Trans } from '@lingui/macro'
import { Currency } from '@uniswap/sdk-core'
import { FeeAmount } from '@uniswap/v3-sdk'
import { useWeb3React } from '@web3-react/core'
import { sendEvent } from 'components/analytics'
import { ButtonGray } from 'components/Button'
import Card from 'components/Card'
import { AutoColumn } from 'components/Column'
import { RowBetween } from 'components/Row'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useFeeTierDistribution } from 'hooks/useFeeTierDistribution'
import { PoolState, usePools } from 'hooks/usePools'
import usePrevious from 'hooks/usePrevious'
import { DynamicSection } from 'pages/AddLiquidity/styled'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import ReactGA from 'react-ga4'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Box } from 'rebass'
import styled, { keyframes } from 'styled-components/macro'
import { ThemedText } from 'theme'
@@ -59,7 +59,7 @@ export default function FeeSelector({
currencyA?: Currency | undefined
currencyB?: Currency | undefined
}) {
const { chainId } = useActiveWeb3React()
const { chainId } = useWeb3React()
const { isLoading, isError, largestUsageFeeTier, distributions } = useFeeTierDistribution(currencyA, currencyB)
@@ -101,7 +101,7 @@ export default function FeeSelector({
const handleFeePoolSelectWithEvent = useCallback(
(fee: FeeAmount) => {
ReactGA.event({
sendEvent({
category: 'FeePoolSelect',
action: 'Manual',
})
@@ -122,7 +122,7 @@ export default function FeeSelector({
setShowOptions(false)
recommended.current = true
ReactGA.event({
sendEvent({
category: 'FeePoolSelect',
action: ' Recommended',
})

View File

@@ -1,7 +1,7 @@
import { Trans } from '@lingui/macro'
import { FeeAmount } from '@uniswap/v3-sdk'
import { ALL_SUPPORTED_CHAIN_IDS, SupportedChainId } from 'constants/chains'
import { ReactNode } from 'react'
import type { ReactNode } from 'react'
export const FEE_AMOUNT_DETAIL: Record<
FeeAmount,
@@ -10,7 +10,14 @@ export const FEE_AMOUNT_DETAIL: Record<
[FeeAmount.LOWEST]: {
label: '0.01',
description: <Trans>Best for very stable pairs.</Trans>,
supportedChains: [SupportedChainId.MAINNET, SupportedChainId.POLYGON, SupportedChainId.POLYGON_MUMBAI],
supportedChains: [
SupportedChainId.MAINNET,
SupportedChainId.POLYGON,
SupportedChainId.POLYGON_MUMBAI,
SupportedChainId.CELO,
SupportedChainId.CELO_ALFAJORES,
SupportedChainId.OPTIMISM,
],
},
[FeeAmount.LOW]: {
label: '0.05',

View File

@@ -1,7 +1,7 @@
import { Trans } from '@lingui/macro'
import { CHAIN_INFO, L2ChainInfo } from 'constants/chainInfo'
import { useWeb3React } from '@web3-react/core'
import { getChainInfoOrDefault, L2ChainInfo } from 'constants/chainInfo'
import { SupportedChainId } from 'constants/chains'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { AlertOctagon } from 'react-feather'
import styled from 'styled-components/macro'
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
@@ -45,8 +45,8 @@ const Wrapper = styled.div`
`
export function ChainConnectivityWarning() {
const { chainId } = useActiveWeb3React()
const info = CHAIN_INFO[chainId ?? SupportedChainId.MAINNET]
const { chainId } = useWeb3React()
const info = getChainInfoOrDefault(chainId)
const label = info?.label
return (

View File

@@ -1,22 +1,23 @@
import { Trans } from '@lingui/macro'
import { CHAIN_INFO } from 'constants/chainInfo'
import { useWeb3React } from '@web3-react/core'
import { getConnection } from 'connection/utils'
import { getChainInfo } from 'constants/chainInfo'
import { CHAIN_IDS_TO_NAMES, SupportedChainId } from 'constants/chains'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import useParsedQueryString from 'hooks/useParsedQueryString'
import usePrevious from 'hooks/usePrevious'
import { ParsedQs } from 'qs'
import { useCallback, useEffect, useRef } from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
import { ArrowDownCircle, ChevronDown } from 'react-feather'
import { useHistory } from 'react-router-dom'
import { useModalOpen, useToggleModal } from 'state/application/hooks'
import { useCloseModal, useModalIsOpen, useOpenModal, useToggleModal } from 'state/application/hooks'
import { addPopup, ApplicationModal } from 'state/application/reducer'
import { updateConnectionError } from 'state/connection/reducer'
import { useAppDispatch } from 'state/hooks'
import styled from 'styled-components/macro'
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
import { replaceURLParam } from 'utils/routes'
import { useAppDispatch } from '../../state/hooks'
import { switchToNetwork } from '../../utils/switchToNetwork'
import { isChainAllowed, switchChain } from 'utils/switchChain'
import { isMobile } from 'utils/userAgent'
const ActiveRowLinkList = styled.div`
display: flex;
@@ -93,6 +94,13 @@ const FlyoutRowActiveIndicator = styled.div`
height: 9px;
width: 9px;
`
const CircleContainer = styled.div`
width: 20px;
display: flex;
justify-content: center;
`
const LinkOutCircle = styled(ArrowDownCircle)`
transform: rotate(230deg);
width: 16px;
@@ -113,20 +121,18 @@ const SelectorLabel = styled(NetworkLabel)`
margin-right: 8px;
}
`
const SelectorControls = styled.div<{ interactive: boolean }>`
const SelectorControls = styled.div`
align-items: center;
background-color: ${({ theme }) => theme.bg0};
border: 2px solid ${({ theme }) => theme.bg0};
border-radius: 16px;
color: ${({ theme }) => theme.text1};
cursor: ${({ interactive }) => (interactive ? 'pointer' : 'auto')};
display: flex;
font-weight: 500;
justify-content: space-between;
padding: 6px 8px;
`
const SelectorLogo = styled(Logo)<{ interactive?: boolean }>`
margin-right: ${({ interactive }) => (interactive ? 8 : 0)}px;
const SelectorLogo = styled(Logo)`
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
margin-right: 8px;
}
@@ -150,6 +156,9 @@ const BridgeLabel = ({ chainId }: { chainId: SupportedChainId }) => {
case SupportedChainId.POLYGON:
case SupportedChainId.POLYGON_MUMBAI:
return <Trans>Polygon Bridge</Trans>
case SupportedChainId.CELO:
case SupportedChainId.CELO_ALFAJORES:
return <Trans>Portal Bridge</Trans>
default:
return <Trans>Bridge</Trans>
}
@@ -165,6 +174,9 @@ const ExplorerLabel = ({ chainId }: { chainId: SupportedChainId }) => {
case SupportedChainId.POLYGON:
case SupportedChainId.POLYGON_MUMBAI:
return <Trans>Polygonscan</Trans>
case SupportedChainId.CELO:
case SupportedChainId.CELO_ALFAJORES:
return <Trans>Blockscout</Trans>
default:
return <Trans>Etherscan</Trans>
}
@@ -177,18 +189,22 @@ function Row({
targetChain: SupportedChainId
onSelectChain: (targetChain: number) => void
}) {
const { library, chainId } = useActiveWeb3React()
if (!library || !chainId) {
const { provider, chainId } = useWeb3React()
if (!provider || !chainId) {
return null
}
const active = chainId === targetChain
const { helpCenterUrl, explorer, bridge, label, logoUrl } = CHAIN_INFO[targetChain]
const { helpCenterUrl, explorer, bridge, label, logoUrl } = getChainInfo(targetChain)
const rowContent = (
<FlyoutRow onClick={() => onSelectChain(targetChain)} active={active}>
<Logo src={logoUrl} />
<NetworkLabel>{label}</NetworkLabel>
{chainId === targetChain && <FlyoutRowActiveIndicator />}
{chainId === targetChain && (
<CircleContainer>
<FlyoutRowActiveIndicator />
</CircleContainer>
)}
</FlyoutRow>
)
@@ -197,21 +213,30 @@ function Row({
<ActiveRowWrapper>
{rowContent}
<ActiveRowLinkList>
{bridge ? (
{bridge && (
<ExternalLink href={bridge}>
<BridgeLabel chainId={chainId} /> <LinkOutCircle />
<BridgeLabel chainId={chainId} />
<CircleContainer>
<LinkOutCircle />
</CircleContainer>
</ExternalLink>
) : null}
{explorer ? (
)}
{explorer && (
<ExternalLink href={explorer}>
<ExplorerLabel chainId={chainId} /> <LinkOutCircle />
<ExplorerLabel chainId={chainId} />
<CircleContainer>
<LinkOutCircle />
</CircleContainer>
</ExternalLink>
) : null}
{helpCenterUrl ? (
)}
{helpCenterUrl && (
<ExternalLink href={helpCenterUrl}>
<Trans>Help Center</Trans> <LinkOutCircle />
<Trans>Help Center</Trans>
<CircleContainer>
<LinkOutCircle />
</CircleContainer>
</ExternalLink>
) : null}
)}
</ActiveRowLinkList>
</ActiveRowWrapper>
)
@@ -221,9 +246,9 @@ function Row({
const getParsedChainId = (parsedQs?: ParsedQs) => {
const chain = parsedQs?.chain
if (!chain || typeof chain !== 'string') return { urlChain: undefined, urlChainId: undefined }
if (!chain || typeof chain !== 'string') return
return { urlChain: chain.toLowerCase(), urlChainId: getChainIdFromName(chain) }
return getChainIdFromName(chain)
}
const getChainIdFromName = (name: string) => {
@@ -237,93 +262,125 @@ const getChainNameFromId = (id: string | number) => {
return CHAIN_IDS_TO_NAMES[id as SupportedChainId] || ''
}
const NETWORK_SELECTOR_CHAINS = [
SupportedChainId.MAINNET,
SupportedChainId.POLYGON,
SupportedChainId.OPTIMISM,
SupportedChainId.ARBITRUM_ONE,
SupportedChainId.CELO,
]
export default function NetworkSelector() {
const { chainId, library } = useActiveWeb3React()
const dispatch = useAppDispatch()
const { chainId, provider, connector, isActive } = useWeb3React()
const [previousChainId, setPreviousChainId] = useState<number | undefined>(undefined)
// Can't use `usePrevious` because `chainId` can be undefined while activating.
useEffect(() => {
if (chainId && chainId !== previousChainId) {
setPreviousChainId(chainId)
}
}, [chainId, previousChainId])
const parsedQs = useParsedQueryString()
const { urlChain, urlChainId } = getParsedChainId(parsedQs)
const prevChainId = usePrevious(chainId)
const node = useRef<HTMLDivElement>()
const open = useModalOpen(ApplicationModal.NETWORK_SELECTOR)
const toggle = useToggleModal(ApplicationModal.NETWORK_SELECTOR)
useOnClickOutside(node, open ? toggle : undefined)
const urlChainId = getParsedChainId(parsedQs)
const previousUrlChainId = usePrevious(urlChainId)
const history = useHistory()
const info = chainId ? CHAIN_INFO[chainId] : undefined
const node = useRef<HTMLDivElement>(null)
const isOpen = useModalIsOpen(ApplicationModal.NETWORK_SELECTOR)
const openModal = useOpenModal(ApplicationModal.NETWORK_SELECTOR)
const closeModal = useCloseModal(ApplicationModal.NETWORK_SELECTOR)
const toggleModal = useToggleModal(ApplicationModal.NETWORK_SELECTOR)
const dispatch = useAppDispatch()
const info = getChainInfo(chainId)
const handleChainSwitch = useCallback(
(targetChain: number, skipToggle?: boolean) => {
if (!library?.provider) return
switchToNetwork({ provider: library.provider, chainId: targetChain })
.then(() => {
if (!skipToggle) {
toggle()
}
history.replace({
search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(targetChain)),
})
})
.catch((error) => {
console.error('Failed to switch networks', error)
const replaceURLChainParam = useCallback(() => {
if (chainId) {
history.replace({ search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(chainId)) })
}
}, [chainId, history])
// we want app network <-> chainId param to be in sync, so if user changes the network by changing the URL
// but the request fails, revert the URL back to current chainId
if (chainId) {
history.replace({ search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(chainId)) })
}
const onSelectChain = useCallback(
async (targetChain: SupportedChainId, skipClose?: boolean) => {
if (!connector) return
if (!skipToggle) {
toggle()
}
const connectionType = getConnection(connector).type
dispatch(addPopup({ content: { failedSwitchNetwork: targetChain }, key: `failed-network-switch` }))
})
try {
dispatch(updateConnectionError({ connectionType, error: undefined }))
await switchChain(connector, targetChain)
} catch (error) {
console.error('Failed to switch networks', error)
dispatch(updateConnectionError({ connectionType, error: error.message }))
dispatch(addPopup({ content: { failedSwitchNetwork: targetChain }, key: `failed-network-switch` }))
// If we activate a chain and it fails, reset the query param to the current chainId
replaceURLChainParam()
}
if (!skipClose) {
closeModal()
}
},
[dispatch, library, toggle, history, chainId]
[connector, closeModal, dispatch, replaceURLChainParam]
)
// If there is no chain query param, set it to the current chain
useEffect(() => {
if (!chainId || !prevChainId) return
// when network change originates from wallet or dropdown selector, just update URL
if (chainId !== prevChainId) {
history.replace({ search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(chainId)) })
// otherwise assume network change originates from URL
} else if (urlChainId && urlChainId !== chainId) {
handleChainSwitch(urlChainId, true)
const chainQueryUnpopulated = !urlChainId
if (chainQueryUnpopulated && chainId) {
replaceURLChainParam()
}
}, [chainId, urlChainId, prevChainId, handleChainSwitch, history])
}, [chainId, urlChainId, replaceURLChainParam])
// set chain parameter on initial load if not there
// If the chain changed but the query param is stale, update to the current chain
useEffect(() => {
if (chainId && !urlChainId) {
history.replace({ search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(chainId)) })
const chainChanged = chainId !== previousChainId
const chainQueryStale = urlChainId !== chainId
if (chainChanged && chainQueryStale) {
replaceURLChainParam()
}
}, [chainId, history, urlChainId, urlChain])
}, [chainId, previousChainId, replaceURLChainParam, urlChainId])
if (!chainId || !info || !library) {
// If the query param changed, and the chain didn't change, then activate the new chain
useEffect(() => {
const chainQueryManuallyUpdated = urlChainId && urlChainId !== previousUrlChainId
if (chainQueryManuallyUpdated && isActive) {
onSelectChain(urlChainId, true)
}
}, [onSelectChain, urlChainId, previousUrlChainId, isActive])
if (!chainId || !info || !provider) {
return null
}
return (
<SelectorWrapper ref={node as any} onMouseEnter={toggle} onMouseLeave={toggle}>
<SelectorControls interactive>
<SelectorLogo interactive src={info.logoUrl} />
<SelectorWrapper
ref={node}
onMouseEnter={openModal}
onMouseLeave={closeModal}
onClick={isMobile ? toggleModal : undefined}
>
<SelectorControls>
<SelectorLogo src={info.logoUrl} />
<SelectorLabel>{info.label}</SelectorLabel>
<StyledChevronDown />
</SelectorControls>
{open && (
{isOpen && (
<FlyoutMenu>
<FlyoutMenuContents>
<FlyoutHeader>
<Trans>Select a network</Trans>
</FlyoutHeader>
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.MAINNET} />
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.POLYGON} />
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.OPTIMISM} />
<Row onSelectChain={handleChainSwitch} targetChain={SupportedChainId.ARBITRUM_ONE} />
{NETWORK_SELECTOR_CHAINS.map((chainId: SupportedChainId) =>
isChainAllowed(connector, chainId) ? (
<Row onSelectChain={onSelectChain} targetChain={chainId} key={chainId} />
) : null
)}
</FlyoutMenuContents>
</FlyoutMenu>
)}

View File

@@ -1,7 +1,7 @@
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { RowFixed } from 'components/Row'
import { CHAIN_INFO } from 'constants/chainInfo'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { getChainInfo } from 'constants/chainInfo'
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
import useGasPrice from 'hooks/useGasPrice'
import useMachineTimeMs from 'hooks/useMachineTime'
@@ -100,7 +100,7 @@ const DEFAULT_MS_BEFORE_WARNING = ms`10m`
const NETWORK_HEALTH_CHECK_MS = ms`10s`
export default function Polling() {
const { chainId } = useActiveWeb3React()
const { chainId } = useWeb3React()
const blockNumber = useBlockNumber()
const [isMounting, setIsMounting] = useState(false)
const [isHover, setIsHover] = useState(false)
@@ -112,7 +112,7 @@ export default function Polling() {
const priceGwei = ethGasPrice ? JSBI.divide(ethGasPrice, JSBI.BigInt(1000000000)) : undefined
const waitMsBeforeWarning =
(chainId ? CHAIN_INFO[chainId]?.blockWaitMsBeforeWarning : DEFAULT_MS_BEFORE_WARNING) ?? DEFAULT_MS_BEFORE_WARNING
(chainId ? getChainInfo(chainId)?.blockWaitMsBeforeWarning : DEFAULT_MS_BEFORE_WARNING) ?? DEFAULT_MS_BEFORE_WARNING
const warning = Boolean(!!blockTime && machineTime - blockTime.mul(1000).toNumber() > waitMsBeforeWarning)

View File

@@ -1,17 +1,17 @@
import { Trans } from '@lingui/macro'
import useScrollPosition from '@react-hook/window-scroll'
import { CHAIN_INFO } from 'constants/chainInfo'
import { useWeb3React } from '@web3-react/core'
import { getChainInfoOrDefault } from 'constants/chainInfo'
import { SupportedChainId } from 'constants/chains'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useTheme from 'hooks/useTheme'
import { darken } from 'polished'
import { NavLink } from 'react-router-dom'
import { Text } from 'rebass'
import { useShowClaimPopup, useToggleSelfClaimModal } from 'state/application/hooks'
import { useUserHasAvailableClaim } from 'state/claim/hooks'
import { useNativeCurrencyBalances } from 'state/connection/hooks'
import { useUserHasSubmittedClaim } from 'state/transactions/hooks'
import { useDarkModeManager } from 'state/user/hooks'
import { useNativeCurrencyBalances } from 'state/wallet/hooks'
import styled from 'styled-components/macro'
import { ReactComponent as Logo } from '../../assets/svg/logo.svg'
@@ -76,7 +76,7 @@ const HeaderElement = styled.div`
margin-left: 0.5em;
}
/* addresses safari's lack of support for "gap" */
/* addresses safaris lack of support for "gap" */
& > *:not(:first-child) {
margin-left: 8px;
}
@@ -98,7 +98,7 @@ const HeaderLinks = styled(Row)`
overflow: auto;
align-items: center;
${({ theme }) => theme.mediaWidth.upToLarge`
justify-self: start;
justify-self: start;
`};
${({ theme }) => theme.mediaWidth.upToMedium`
justify-self: center;
@@ -246,7 +246,7 @@ const StyledExternalLink = styled(ExternalLink).attrs({
`
export default function Header() {
const { account, chainId } = useActiveWeb3React()
const { account, chainId } = useWeb3React()
const userEthBalance = useNativeCurrencyBalances(account ? [account] : [])?.[account ?? '']
const [darkMode] = useDarkModeManager()
@@ -265,7 +265,7 @@ export default function Header() {
const {
infoLink,
nativeCurrency: { symbol: nativeCurrencySymbol },
} = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET]
} = getChainInfoOrDefault(chainId)
return (
<HeaderFrame showBackground={scrollY > 45}>
@@ -281,6 +281,7 @@ export default function Header() {
<Trans>Swap</Trans>
</StyledNavLink>
<StyledNavLink
data-cy="pool-nav-link"
id={`pool-nav-link`}
to={'/pool'}
isActive={(match, { pathname }) =>

View File

@@ -1,23 +1,42 @@
import { Connector } from '@web3-react/types'
import { AbstractConnector } from 'web3-react-abstract-connector'
import { ConnectionType } from 'connection'
import styled from 'styled-components/macro'
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
import { fortmatic, injected, walletconnect, walletlink } from '../../connectors'
import Identicon from '../Identicon'
export default function StatusIcon({ connector }: { connector: AbstractConnector | Connector }) {
switch (connector) {
case injected:
return <Identicon />
case walletconnect:
return <img src={WalletConnectIcon} alt={'WalletConnect'} />
case walletlink:
return <img src={CoinbaseWalletIcon} alt={'Coinbase Wallet'} />
case fortmatic:
return <img src={FortmaticIcon} alt={'Fortmatic'} />
default:
return null
const IconWrapper = styled.div<{ size?: number }>`
${({ theme }) => theme.flexColumnNoWrap};
align-items: center;
justify-content: center;
margin-right: 8px;
& > img,
span {
height: ${({ size }) => (size ? size + 'px' : '32px')};
width: ${({ size }) => (size ? size + 'px' : '32px')};
}
${({ theme }) => theme.mediaWidth.upToMedium`
align-items: flex-end;
`};
`
export default function StatusIcon({ connectionType }: { connectionType: ConnectionType }) {
let image
switch (connectionType) {
case ConnectionType.INJECTED:
image = <Identicon />
break
case ConnectionType.WALLET_CONNECT:
image = <img src={WalletConnectIcon} alt="WalletConnect" />
break
case ConnectionType.COINBASE_WALLET:
image = <img src={CoinbaseWalletIcon} alt="Coinbase Wallet" />
break
case ConnectionType.FORTMATIC:
image = <img src={FortmaticIcon} alt="Fortmatic" />
break
}
return <IconWrapper size={16}>{image}</IconWrapper>
}

View File

@@ -1,5 +1,5 @@
import jazzicon from '@metamask/jazzicon'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useWeb3React } from '@web3-react/core'
import useENSAvatar from 'hooks/useENSAvatar'
import { useLayoutEffect, useMemo, useRef, useState } from 'react'
import styled from 'styled-components/macro'
@@ -19,7 +19,7 @@ const StyledAvatar = styled.img`
`
export default function Identicon() {
const { account } = useActiveWeb3React()
const { account } = useWeb3React()
const { avatar } = useENSAvatar(account ?? undefined)
const [fetchable, setFetchable] = useState(true)

View File

@@ -1,6 +1,7 @@
import { Trans } from '@lingui/macro'
import { Currency, Price, Token } from '@uniswap/sdk-core'
import { FeeAmount } from '@uniswap/v3-sdk'
import { sendEvent } from 'components/analytics'
import { AutoColumn, ColumnCenter } from 'components/Column'
import Loader from 'components/Loader'
import { format } from 'd3'
@@ -9,7 +10,6 @@ import useTheme from 'hooks/useTheme'
import { saturate } from 'polished'
import React, { ReactNode, useCallback, useMemo } from 'react'
import { BarChart2, CloudOff, Inbox } from 'react-feather'
import ReactGA from 'react-ga4'
import { batch } from 'react-redux'
import { Bound } from 'state/mint/v3/actions'
import styled from 'styled-components/macro'
@@ -103,7 +103,7 @@ export default function LiquidityChartRangeInput({
})
const onBrushDomainChangeEnded = useCallback(
(domain, mode) => {
(domain: [number, number], mode: string | undefined) => {
let leftRangeValue = Number(domain[0])
const rightRangeValue = Number(domain[1])
@@ -158,7 +158,7 @@ export default function LiquidityChartRangeInput({
)
if (isError) {
ReactGA.event('exception', { description: error.toString(), fatal: false })
sendEvent('exception', { description: error.toString(), fatal: false })
}
return (

View File

@@ -1,12 +1,12 @@
// eslint-disable-next-line no-restricted-imports
import { t, Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { PrivacyPolicyModal } from 'components/PrivacyPolicy'
import { L2_CHAIN_IDS } from 'constants/chains'
import { LOCALE_LABEL, SUPPORTED_LOCALES, SupportedLocale } from 'constants/locales'
import { useActiveLocale } from 'hooks/useActiveLocale'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useLocationLinkProps } from 'hooks/useLocationLinkProps'
import React, { useEffect, useRef, useState } from 'react'
import { FunctionComponent, PropsWithChildren, useEffect, useRef, useState } from 'react'
import {
BookOpen,
Check,
@@ -26,7 +26,7 @@ import styled, { css } from 'styled-components/macro'
import { ReactComponent as MenuIcon } from '../../assets/images/menu.svg'
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
import { useModalOpen, useToggleModal } from '../../state/application/hooks'
import { useModalIsOpen, useToggleModal } from '../../state/application/hooks'
import { ApplicationModal } from '../../state/application/reducer'
import { ExternalLink } from '../../theme'
import { ButtonPrimary } from '../Button'
@@ -207,10 +207,10 @@ function LanguageMenu({ close }: { close: () => void }) {
}
export default function Menu() {
const { account, chainId } = useActiveWeb3React()
const { account, chainId } = useWeb3React()
const node = useRef<HTMLDivElement>()
const open = useModalOpen(ApplicationModal.MENU)
const open = useModalIsOpen(ApplicationModal.MENU)
const toggleMenu = useToggleModal(ApplicationModal.MENU)
useOnClickOutside(node, open ? toggleMenu : undefined)
const togglePrivacyPolicy = useToggleModal(ApplicationModal.PRIVACY_POLICY)
@@ -311,7 +311,7 @@ export default function Menu() {
interface NewMenuProps {
flyoutAlignment?: FlyoutAlignment
ToggleUI?: React.FunctionComponent
ToggleUI?: FunctionComponent<PropsWithChildren<unknown>>
menuItems: {
content: any
link: string
@@ -334,7 +334,7 @@ const ExternalMenuItem = styled(MenuItem)`
export const NewMenu = ({ flyoutAlignment = FlyoutAlignment.RIGHT, ToggleUI, menuItems, ...rest }: NewMenuProps) => {
const node = useRef<HTMLDivElement>()
const open = useModalOpen(ApplicationModal.POOL_OVERVIEW_OPTIONS)
const open = useModalIsOpen(ApplicationModal.POOL_OVERVIEW_OPTIONS)
const toggle = useToggleModal(ApplicationModal.POOL_OVERVIEW_OPTIONS)
useOnClickOutside(node, open ? toggle : undefined)
const ToggleElement = ToggleUI || StyledMenuIcon

View File

@@ -1,5 +1,5 @@
import { Trans } from '@lingui/macro'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useWeb3React } from '@web3-react/core'
import { useContext } from 'react'
import { ArrowUpCircle } from 'react-feather'
import styled, { ThemeContext } from 'styled-components/macro'
@@ -50,7 +50,7 @@ export function SubmittedView({
hash: string | undefined
}) {
const theme = useContext(ThemeContext)
const { chainId } = useActiveWeb3React()
const { chainId } = useWeb3React()
return (
<ConfirmOrLoadingWrapper>

View File

@@ -1,7 +1,7 @@
import { Trans } from '@lingui/macro'
import { CHAIN_INFO } from 'constants/chainInfo'
import { useWeb3React } from '@web3-react/core'
import { getChainInfo } from 'constants/chainInfo'
import { SupportedChainId } from 'constants/chains'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { ArrowUpRight } from 'react-feather'
import { useDarkModeManager } from 'state/user/hooks'
import styled from 'styled-components/macro'
@@ -42,6 +42,8 @@ const SHOULD_SHOW_ALERT = {
[SupportedChainId.ARBITRUM_RINKEBY]: true,
[SupportedChainId.POLYGON]: true,
[SupportedChainId.POLYGON_MUMBAI]: true,
[SupportedChainId.CELO]: true,
[SupportedChainId.CELO_ALFAJORES]: true,
}
type NetworkAlertChains = keyof typeof SHOULD_SHOW_ALERT
@@ -54,6 +56,10 @@ const BG_COLORS_BY_DARK_MODE_AND_CHAIN_ID: {
'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]:
'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]:
'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]:
'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.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.OPTIMISTIC_KOVAN]:
@@ -68,6 +74,10 @@ const BG_COLORS_BY_DARK_MODE_AND_CHAIN_ID: {
'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]:
'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]:
'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]:
'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.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.OPTIMISTIC_KOVAN]:
@@ -129,6 +139,8 @@ 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.OPTIMISTIC_KOVAN]: '#ff3856',
[SupportedChainId.ARBITRUM_ONE]: '#0490ed',
@@ -140,14 +152,14 @@ function shouldShowAlert(chainId: number | undefined): chainId is NetworkAlertCh
}
export function NetworkAlert() {
const { chainId } = useActiveWeb3React()
const { chainId } = useWeb3React()
const [darkMode] = useDarkModeManager()
if (!shouldShowAlert(chainId)) {
return null
}
const { label, logoUrl, bridge } = CHAIN_INFO[chainId]
const { label, logoUrl, bridge } = getChainInfo(chainId)
const textColor = TEXT_COLORS[chainId]
return bridge ? (

View File

@@ -26,6 +26,7 @@ const Arrow = styled.div`
position: absolute;
width: 8px;
height: 8px;
box-sizing: border-box;
z-index: 9998;
content: '';
@@ -35,7 +36,7 @@ const Arrow = styled.div`
}
&.arrow-top {
bottom: -5px;
bottom: -4px;
::before {
border-top: none;
border-left: none;
@@ -43,7 +44,7 @@ const Arrow = styled.div`
}
&.arrow-bottom {
top: -5px;
top: -4px;
::before {
border-bottom: none;
border-right: none;
@@ -51,7 +52,7 @@ const Arrow = styled.div`
}
&.arrow-left {
right: -5px;
right: -4px;
::before {
border-bottom: none;
@@ -60,7 +61,7 @@ const Arrow = styled.div`
}
&.arrow-right {
left: -5px;
left: -4px;
::before {
border-right: none;
border-top: none;

View File

@@ -1,14 +1,14 @@
import { Trans } from '@lingui/macro'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useWeb3React } from '@web3-react/core'
import { sendEvent } from 'components/analytics'
import { useCallback, useEffect } from 'react'
import { Heart, X } from 'react-feather'
import ReactGA from 'react-ga4'
import styled, { keyframes } from 'styled-components/macro'
import tokenLogo from '../../assets/images/token-logo.png'
import {
useModalOpen,
useModalIsOpen,
useShowClaimPopup,
useToggleSelfClaimModal,
useToggleShowClaimPopup,
@@ -55,17 +55,17 @@ const UniToken = styled.img`
`
export default function ClaimPopup() {
const { account } = useActiveWeb3React()
const { account } = useWeb3React()
// dont store these in persisted state yet
const showClaimPopup: boolean = useShowClaimPopup()
const toggleShowClaimPopup = useToggleShowClaimPopup()
// toggle for showing this modal
const showClaimModal = useModalOpen(ApplicationModal.SELF_CLAIM)
const showClaimModal = useModalIsOpen(ApplicationModal.SELF_CLAIM)
const toggleSelfClaimModal = useToggleSelfClaimModal()
const handleToggleSelfClaimModal = useCallback(() => {
ReactGA.event({
sendEvent({
category: 'MerkleDrop',
action: 'Toggle self claim modal',
})
@@ -79,7 +79,7 @@ export default function ClaimPopup() {
// listen for available claim and show popup if needed
useEffect(() => {
if (userHasAvailableclaim) {
ReactGA.event({
sendEvent({
category: 'MerkleDrop',
action: 'Show claim popup',
})

View File

@@ -1,5 +1,5 @@
import { Trans } from '@lingui/macro'
import { CHAIN_INFO } from 'constants/chainInfo'
import { getChainInfo } from 'constants/chainInfo'
import { SupportedChainId } from 'constants/chains'
import { useContext } from 'react'
import { AlertCircle } from 'react-feather'
@@ -14,7 +14,7 @@ const RowNoFlex = styled(AutoRow)`
`
export default function FailedNetworkSwitchPopup({ chainId }: { chainId: SupportedChainId }) {
const chainInfo = CHAIN_INFO[chainId]
const chainInfo = getChainInfo(chainId)
const theme = useContext(ThemeContext)
return (

View File

@@ -1,10 +1,10 @@
import { Trans } from '@lingui/macro'
import { sendEvent } from 'components/analytics'
import { AutoColumn } from 'components/Column'
import { RowFixed } from 'components/Row'
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
import { useEffect } from 'react'
import { MessageCircle, X } from 'react-feather'
import ReactGA from 'react-ga4'
import { useShowSurveyPopup } from 'state/user/hooks'
import styled from 'styled-components/macro'
import { ExternalLink, ThemedText, Z_INDEX } from 'theme'
@@ -62,7 +62,7 @@ export default function SurveyPopup() {
if (Math.random() < 0.01) {
setShowSurveyPopup(true)
// log a case of succesful view
ReactGA.event({
sendEvent({
category: 'Survey',
action: 'Saw Survey',
})
@@ -80,7 +80,7 @@ export default function SurveyPopup() {
<Wrapper gap="10px">
<WrappedCloseIcon
onClick={() => {
ReactGA.event({
sendEvent({
category: 'Survey',
action: 'Clicked Survey Link',
})

View File

@@ -1,4 +1,4 @@
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useWeb3React } from '@web3-react/core'
import { useContext } from 'react'
import { AlertCircle, CheckCircle } from 'react-feather'
import styled, { ThemeContext } from 'styled-components/macro'
@@ -16,7 +16,7 @@ const RowNoFlex = styled(AutoRow)`
`
export default function TransactionPopup({ hash }: { hash: string }) {
const { chainId } = useActiveWeb3React()
const { chainId } = useWeb3React()
const tx = useTransaction(hash)
const theme = useContext(ThemeContext)

View File

@@ -1,5 +1,5 @@
import { useWeb3React } from '@web3-react/core'
import { SupportedChainId } from 'constants/chains'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import styled from 'styled-components/macro'
import { MEDIA_WIDTHS } from 'theme'
@@ -63,7 +63,7 @@ export default function Popups() {
const urlWarningActive = useURLWarningVisible()
// need extra padding if network is not L1 Ethereum
const { chainId } = useActiveWeb3React()
const { chainId } = useWeb3React()
const isNotOnMainnet = Boolean(chainId && chainId !== SupportedChainId.MAINNET)
return (

View File

@@ -1,7 +1,7 @@
import { Trans } from '@lingui/macro'
import { CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useWeb3React } from '@web3-react/core'
import JSBI from 'jsbi'
import { transparentize } from 'polished'
import { useState } from 'react'
@@ -13,7 +13,7 @@ import styled from 'styled-components/macro'
import { BIG_INT_ZERO } from '../../constants/misc'
import { useColor } from '../../hooks/useColor'
import { useTotalSupply } from '../../hooks/useTotalSupply'
import { useTokenBalance } from '../../state/wallet/hooks'
import { useTokenBalance } from '../../state/connection/hooks'
import { currencyId } from '../../utils/currencyId'
import { unwrappedToken } from '../../utils/unwrappedToken'
import { ButtonEmpty, ButtonPrimary, ButtonSecondary } from '../Button'
@@ -42,7 +42,7 @@ interface PositionCardProps {
}
export default function V2PositionCard({ pair, border, stakedBalance }: PositionCardProps) {
const { account } = useActiveWeb3React()
const { account } = useWeb3React()
const currency0 = unwrappedToken(pair.token0)
const currency1 = unwrappedToken(pair.token1)

View File

@@ -1,7 +1,7 @@
import { Trans } from '@lingui/macro'
import { CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useWeb3React } from '@web3-react/core'
import JSBI from 'jsbi'
import { transparentize } from 'polished'
import { useState } from 'react'
@@ -13,7 +13,7 @@ import styled from 'styled-components/macro'
import { BIG_INT_ZERO } from '../../constants/misc'
import { useColor } from '../../hooks/useColor'
import { useTotalSupply } from '../../hooks/useTotalSupply'
import { useTokenBalance } from '../../state/wallet/hooks'
import { useTokenBalance } from '../../state/connection/hooks'
import { ExternalLink, ThemedText } from '../../theme'
import { currencyId } from '../../utils/currencyId'
import { unwrappedToken } from '../../utils/unwrappedToken'
@@ -46,7 +46,7 @@ interface PositionCardProps {
}
export function MinimalPositionCard({ pair, showUnwrapped = false, border }: PositionCardProps) {
const { account } = useActiveWeb3React()
const { account } = useWeb3React()
const currency0 = showUnwrapped ? pair.token0 : unwrappedToken(pair.token0)
const currency1 = showUnwrapped ? pair.token1 : unwrappedToken(pair.token1)
@@ -158,7 +158,7 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
}
export default function FullPositionCard({ pair, border, stakedBalance }: PositionCardProps) {
const { account } = useActiveWeb3React()
const { account } = useWeb3React()
const currency0 = unwrappedToken(pair.token0)
const currency1 = unwrappedToken(pair.token1)

View File

@@ -1,6 +1,6 @@
import { Trans } from '@lingui/macro'
import { ButtonText } from 'components/Button'
import PositionListItem from 'components/PositionListItem'
import Toggle from 'components/Toggle'
import React from 'react'
import styled from 'styled-components/macro'
import { MEDIA_WIDTHS } from 'theme'
@@ -28,9 +28,37 @@ const MobileHeader = styled.div`
font-size: 16px;
font-weight: 500;
padding: 8px;
display: flex;
justify-content: space-between;
align-items: center;
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
display: none;
}
@media screen and (max-width: ${MEDIA_WIDTHS.upToExtraSmall}px) {
display: flex;
flex-direction: column;
align-items: start;
}
`
const ToggleWrap = styled.div`
display: flex;
flex-direction: row;
align-items: center;
`
const ToggleLabel = styled.div`
opacity: 0.6;
margin-right: 10px;
`
const MobileTogglePosition = styled.div`
@media screen and (max-width: ${MEDIA_WIDTHS.upToExtraSmall}px) {
position: absolute;
right: 20px;
}
`
type PositionListProps = React.PropsWithChildren<{
@@ -51,12 +79,35 @@ export default function PositionList({
<Trans>Your positions</Trans>
{positions && ' (' + positions.length + ')'}
</div>
<ButtonText style={{ opacity: 0.6 }} onClick={() => setUserHideClosedPositions(!userHideClosedPositions)}>
<Trans>Hide closed positions</Trans>
</ButtonText>
<ToggleWrap>
<ToggleLabel>
<Trans>Show closed positions</Trans>
</ToggleLabel>
<Toggle
id="desktop-hide-closed-positions"
isActive={!userHideClosedPositions}
toggle={() => {
setUserHideClosedPositions(!userHideClosedPositions)
}}
/>
</ToggleWrap>
</DesktopHeader>
<MobileHeader>
<Trans>Your positions</Trans>
<ToggleWrap>
<ToggleLabel>
<Trans>Show closed positions</Trans>
</ToggleLabel>
<MobileTogglePosition>
<Toggle
id="mobile-hide-closed-positions"
isActive={!userHideClosedPositions}
toggle={() => {
setUserHideClosedPositions(!userHideClosedPositions)
}}
/>
</MobileTogglePosition>
</ToggleWrap>
</MobileHeader>
{positions.map((p) => {
return <PositionListItem key={p.tokenId.toString()} positionDetails={p} />

View File

@@ -1,14 +1,14 @@
import { Trans } from '@lingui/macro'
import { sendEvent } from 'components/analytics'
import Card, { DarkGreyCard } from 'components/Card'
import Row, { AutoRow, RowBetween } from 'components/Row'
import { useEffect, useRef } from 'react'
import { ArrowDown, Info, X } from 'react-feather'
import ReactGA from 'react-ga4'
import styled from 'styled-components/macro'
import { ExternalLink, ThemedText } from 'theme'
import { isMobile } from 'utils/userAgent'
import { useModalOpen, useTogglePrivacyPolicy } from '../../state/application/hooks'
import { useModalIsOpen, useTogglePrivacyPolicy } from '../../state/application/hooks'
import { ApplicationModal } from '../../state/application/reducer'
import { AutoColumn } from '../Column'
import Modal from '../Modal'
@@ -70,7 +70,7 @@ const EXTERNAL_APIS = [
),
},
{
name: 'Google Analytics',
name: 'Google Analytics & Amplitude',
description: <Trans>The app logs anonymized usage statistics in order to improve over time.</Trans>,
},
{
@@ -81,13 +81,13 @@ const EXTERNAL_APIS = [
export function PrivacyPolicyModal() {
const node = useRef<HTMLDivElement>()
const open = useModalOpen(ApplicationModal.PRIVACY_POLICY)
const open = useModalIsOpen(ApplicationModal.PRIVACY_POLICY)
const toggle = useTogglePrivacyPolicy()
useEffect(() => {
if (!open) return
ReactGA.event({
sendEvent({
category: 'Modal',
action: 'Show Legal',
})

View File

@@ -1,8 +1,8 @@
import { Trans } from '@lingui/macro'
import { sendEvent } from 'components/analytics'
import { ButtonOutlined } from 'components/Button'
import { AutoRow } from 'components/Row'
import React from 'react'
import ReactGA from 'react-ga4'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
@@ -20,7 +20,7 @@ export default function PresetsButtons({ setFullRange }: { setFullRange: () => v
<Button
onClick={() => {
setFullRange()
ReactGA.event({
sendEvent({
category: 'Liquidity',
action: 'Full Range Clicked',
})

View File

@@ -1,10 +1,11 @@
import { Protocol } from '@uniswap/router-sdk'
import { Currency, Percent } from '@uniswap/sdk-core'
import { FeeAmount } from '@uniswap/v3-sdk'
import { RoutingDiagramEntry } from 'components/swap/SwapRoute'
import { DAI, USDC_MAINNET, WBTC } from 'constants/tokens'
import { render } from 'test-utils'
import RoutingDiagram, { RoutingDiagramEntry } from './RoutingDiagram'
import RoutingDiagram from './RoutingDiagram'
const percent = (strings: TemplateStringsArray) => new Percent(parseInt(strings[0]), 100)

View File

@@ -3,45 +3,45 @@
exports[`renders multi route 1`] = `
<DocumentFragment>
<div
class="RoutingDiagram__Wrapper-sc-i2tbb-0 ivndgC css-vurnku"
class="RoutingDiagram__Wrapper-sc-o1ook0-0 kfWRgd css-vurnku"
>
<div
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteContainerRow-sc-i2tbb-1 lmTMKd hLLNig hDkZVB"
class="sc-bczRLJ Row-sc-nrd8cx-0 RoutingDiagram__RouteContainerRow-sc-o1ook0-1 jITePI cSETNY dmzxCy"
>
CurrencyLogo currency=USDC
<div
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteRow-sc-i2tbb-2 lmTMKd hLLNig hUDqOH"
class="sc-bczRLJ Row-sc-nrd8cx-0 RoutingDiagram__RouteRow-sc-o1ook0-2 jITePI cSETNY fclIfk"
>
<div
class="RoutingDiagram__DottedLine-sc-i2tbb-4 cKqYfU"
class="RoutingDiagram__DottedLine-sc-o1ook0-4 iRYKBb"
>
<svg
class="RoutingDiagram__DotColor-sc-i2tbb-5 fhSaBA"
class="RoutingDiagram__DotColor-sc-o1ook0-5 fEbpBT"
>
dot_line.svg
</svg>
</div>
<div
class="Badge-sc-3epor3-0 RoutingDiagram__OpaqueBadge-sc-i2tbb-6 knpfHF gGARxH"
class="Badge-sc-1mhw5si-0 RoutingDiagram__OpaqueBadge-sc-o1ook0-6 gbzyaI iSegVg"
>
<div
class="Badge-sc-3epor3-0 RoutingDiagram__ProtocolBadge-sc-i2tbb-7 knpfHF lbdUti"
class="Badge-sc-1mhw5si-0 RoutingDiagram__ProtocolBadge-sc-o1ook0-7 gbzyaI lidWLN"
>
<div
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-15li2d9"
class="theme__TextWrapper-sc-18nh1jk-0 chnOFO RoutingDiagram__BadgeText-sc-o1ook0-8 iwzpuz css-15li2d9"
>
V2
</div>
</div>
<div
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-1aekuku"
class="theme__TextWrapper-sc-18nh1jk-0 chnOFO RoutingDiagram__BadgeText-sc-o1ook0-8 iwzpuz css-1aekuku"
style="min-width: auto;"
>
75%
</div>
</div>
<div
class="sc-bdnxRM Row-sc-u7azg8-0 Row__AutoRow-sc-u7azg8-3 iqvZFe hLLNig cUhARX"
class="sc-bczRLJ Row-sc-nrd8cx-0 Row__AutoRow-sc-nrd8cx-3 cYXLjH cSETNY dllMrH"
style="justify-content: space-evenly; z-index: 2;"
width="100%"
>
@@ -51,42 +51,42 @@ exports[`renders multi route 1`] = `
CurrencyLogo currency=DAI
</div>
<div
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteContainerRow-sc-i2tbb-1 lmTMKd hLLNig hDkZVB"
class="sc-bczRLJ Row-sc-nrd8cx-0 RoutingDiagram__RouteContainerRow-sc-o1ook0-1 jITePI cSETNY dmzxCy"
>
CurrencyLogo currency=USDC
<div
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteRow-sc-i2tbb-2 lmTMKd hLLNig hUDqOH"
class="sc-bczRLJ Row-sc-nrd8cx-0 RoutingDiagram__RouteRow-sc-o1ook0-2 jITePI cSETNY fclIfk"
>
<div
class="RoutingDiagram__DottedLine-sc-i2tbb-4 cKqYfU"
class="RoutingDiagram__DottedLine-sc-o1ook0-4 iRYKBb"
>
<svg
class="RoutingDiagram__DotColor-sc-i2tbb-5 fhSaBA"
class="RoutingDiagram__DotColor-sc-o1ook0-5 fEbpBT"
>
dot_line.svg
</svg>
</div>
<div
class="Badge-sc-3epor3-0 RoutingDiagram__OpaqueBadge-sc-i2tbb-6 knpfHF gGARxH"
class="Badge-sc-1mhw5si-0 RoutingDiagram__OpaqueBadge-sc-o1ook0-6 gbzyaI iSegVg"
>
<div
class="Badge-sc-3epor3-0 RoutingDiagram__ProtocolBadge-sc-i2tbb-7 knpfHF lbdUti"
class="Badge-sc-1mhw5si-0 RoutingDiagram__ProtocolBadge-sc-o1ook0-7 gbzyaI lidWLN"
>
<div
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-15li2d9"
class="theme__TextWrapper-sc-18nh1jk-0 chnOFO RoutingDiagram__BadgeText-sc-o1ook0-8 iwzpuz css-15li2d9"
>
V3
</div>
</div>
<div
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-1aekuku"
class="theme__TextWrapper-sc-18nh1jk-0 chnOFO RoutingDiagram__BadgeText-sc-o1ook0-8 iwzpuz css-1aekuku"
style="min-width: auto;"
>
25%
</div>
</div>
<div
class="sc-bdnxRM Row-sc-u7azg8-0 Row__AutoRow-sc-u7azg8-3 iqvZFe hLLNig cUhARX"
class="sc-bczRLJ Row-sc-nrd8cx-0 Row__AutoRow-sc-nrd8cx-3 cYXLjH cSETNY dllMrH"
style="justify-content: space-evenly; z-index: 2;"
width="100%"
>
@@ -102,45 +102,45 @@ exports[`renders multi route 1`] = `
exports[`renders single route 1`] = `
<DocumentFragment>
<div
class="RoutingDiagram__Wrapper-sc-i2tbb-0 ivndgC css-vurnku"
class="RoutingDiagram__Wrapper-sc-o1ook0-0 kfWRgd css-vurnku"
>
<div
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteContainerRow-sc-i2tbb-1 lmTMKd hLLNig hDkZVB"
class="sc-bczRLJ Row-sc-nrd8cx-0 RoutingDiagram__RouteContainerRow-sc-o1ook0-1 jITePI cSETNY dmzxCy"
>
CurrencyLogo currency=USDC
<div
class="sc-bdnxRM Row-sc-u7azg8-0 RoutingDiagram__RouteRow-sc-i2tbb-2 lmTMKd hLLNig hUDqOH"
class="sc-bczRLJ Row-sc-nrd8cx-0 RoutingDiagram__RouteRow-sc-o1ook0-2 jITePI cSETNY fclIfk"
>
<div
class="RoutingDiagram__DottedLine-sc-i2tbb-4 cKqYfU"
class="RoutingDiagram__DottedLine-sc-o1ook0-4 iRYKBb"
>
<svg
class="RoutingDiagram__DotColor-sc-i2tbb-5 fhSaBA"
class="RoutingDiagram__DotColor-sc-o1ook0-5 fEbpBT"
>
dot_line.svg
</svg>
</div>
<div
class="Badge-sc-3epor3-0 RoutingDiagram__OpaqueBadge-sc-i2tbb-6 knpfHF gGARxH"
class="Badge-sc-1mhw5si-0 RoutingDiagram__OpaqueBadge-sc-o1ook0-6 gbzyaI iSegVg"
>
<div
class="Badge-sc-3epor3-0 RoutingDiagram__ProtocolBadge-sc-i2tbb-7 knpfHF lbdUti"
class="Badge-sc-1mhw5si-0 RoutingDiagram__ProtocolBadge-sc-o1ook0-7 gbzyaI lidWLN"
>
<div
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-15li2d9"
class="theme__TextWrapper-sc-18nh1jk-0 chnOFO RoutingDiagram__BadgeText-sc-o1ook0-8 iwzpuz css-15li2d9"
>
V3
</div>
</div>
<div
class="theme__TextWrapper-sc-5lu8um-0 chxxqs RoutingDiagram__BadgeText-sc-i2tbb-8 ijjHig css-1aekuku"
class="theme__TextWrapper-sc-18nh1jk-0 chnOFO RoutingDiagram__BadgeText-sc-o1ook0-8 iwzpuz css-1aekuku"
style="min-width: auto;"
>
100%
</div>
</div>
<div
class="sc-bdnxRM Row-sc-u7azg8-0 Row__AutoRow-sc-u7azg8-3 iqvZFe hLLNig cUhARX"
class="sc-bczRLJ Row-sc-nrd8cx-0 Row__AutoRow-sc-nrd8cx-3 cYXLjH cSETNY dllMrH"
style="justify-content: space-evenly; z-index: 2;"
width="100%"
>
@@ -156,7 +156,7 @@ exports[`renders single route 1`] = `
exports[`renders when no routes are provided 1`] = `
<DocumentFragment>
<div
class="RoutingDiagram__Wrapper-sc-i2tbb-0 ivndgC css-vurnku"
class="RoutingDiagram__Wrapper-sc-o1ook0-0 kfWRgd css-vurnku"
/>
</DocumentFragment>
`;

View File

@@ -1,4 +1,6 @@
import { Currency } from '@uniswap/sdk-core'
import { Currency, Token } from '@uniswap/sdk-core'
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
import { AutoColumn } from 'components/Column'
import CurrencyLogo from 'components/CurrencyLogo'
import { AutoRow } from 'components/Row'
@@ -31,14 +33,35 @@ const BaseWrapper = styled.div<{ disable?: boolean }>`
filter: ${({ disable }) => disable && 'grayscale(1)'};
`
const formatAnalyticsEventProperties = (
currency: Currency,
tokenAddress: string | undefined,
searchQuery: string,
isAddressSearch: string | false
) => ({
token_symbol: currency?.symbol,
token_chain_id: currency?.chainId,
...(tokenAddress ? { token_address: tokenAddress } : {}),
is_suggested_token: true,
is_selected_from_list: false,
is_imported_by_user: false,
...(isAddressSearch === false
? { search_token_symbol_input: searchQuery }
: { search_token_address_input: isAddressSearch }),
})
export default function CommonBases({
chainId,
onSelect,
selectedCurrency,
searchQuery,
isAddressSearch,
}: {
chainId?: number
selectedCurrency?: Currency | null
onSelect: (currency: Currency) => void
searchQuery: string
isAddressSearch: string | false
}) {
const bases = typeof chainId !== 'undefined' ? COMMON_BASES[chainId] ?? [] : []
@@ -47,17 +70,29 @@ export default function CommonBases({
<AutoRow gap="4px">
{bases.map((currency: Currency) => {
const isSelected = selectedCurrency?.equals(currency)
const tokenAddress = currency instanceof Token ? currency?.address : undefined
return (
<BaseWrapper
onClick={() => !isSelected && onSelect(currency)}
disable={isSelected}
<TraceEvent
events={[Event.onClick, Event.onKeyPress]}
name={EventName.TOKEN_SELECTED}
properties={formatAnalyticsEventProperties(currency, tokenAddress, searchQuery, isAddressSearch)}
element={ElementName.COMMON_BASES_CURRENCY_BUTTON}
key={currencyId(currency)}
>
<CurrencyLogoFromList currency={currency} />
<Text fontWeight={500} fontSize={16}>
{currency.symbol}
</Text>
</BaseWrapper>
<BaseWrapper
tabIndex={0}
onKeyPress={(e) => !isSelected && e.key === 'Enter' && onSelect(currency)}
onClick={() => !isSelected && onSelect(currency)}
disable={isSelected}
key={currencyId(currency)}
>
<CurrencyLogoFromList currency={currency} />
<Text fontWeight={500} fontSize={16}>
{currency.symbol}
</Text>
</BaseWrapper>
</TraceEvent>
)
})}
</AutoRow>

View File

@@ -0,0 +1,95 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders currency rows correctly when currencies list is non-empty 1`] = `
<DocumentFragment>
<div
style="position: relative; height: 10px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
>
<div
style="height: 168px; width: 100%;"
>
<div
class="sc-bczRLJ Row-sc-nrd8cx-0 Row__RowBetween-sc-nrd8cx-1 styleds__MenuItem-sc-1xp9ndq-3 jITePI cSETNY ekbhWd dxhejL token-item-0x6B175474E89094C44Da98b954EedeAC495271d0F"
style="position: absolute; left: 0px; top: 0px; height: 56px; width: 100%;"
tabindex="0"
>
CurrencyLogo currency=DAI
<div
class="Column-sc-1kykgp9-0 gdySCE"
>
<div
class="css-8mokm4"
title="Dai Stablecoin"
>
DAI
</div>
<div
class="theme__TextWrapper-sc-18nh1jk-0 gykQyY css-165qfk5"
>
Dai Stablecoin
</div>
</div>
<span />
</div>
<div
class="sc-bczRLJ Row-sc-nrd8cx-0 Row__RowBetween-sc-nrd8cx-1 styleds__MenuItem-sc-1xp9ndq-3 jITePI cSETNY ekbhWd dxhejL token-item-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
style="position: absolute; left: 0px; top: 56px; height: 56px; width: 100%;"
tabindex="0"
>
CurrencyLogo currency=USDC
<div
class="Column-sc-1kykgp9-0 gdySCE"
>
<div
class="css-8mokm4"
title="USD//C"
>
USDC
</div>
<div
class="theme__TextWrapper-sc-18nh1jk-0 gykQyY css-165qfk5"
>
USD//C
</div>
</div>
<span />
</div>
<div
class="sc-bczRLJ Row-sc-nrd8cx-0 Row__RowBetween-sc-nrd8cx-1 styleds__MenuItem-sc-1xp9ndq-3 jITePI cSETNY ekbhWd dxhejL token-item-0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
style="position: absolute; left: 0px; top: 112px; height: 56px; width: 100%;"
tabindex="0"
>
CurrencyLogo currency=WBTC
<div
class="Column-sc-1kykgp9-0 gdySCE"
>
<div
class="css-8mokm4"
title="Wrapped BTC"
>
WBTC
</div>
<div
class="theme__TextWrapper-sc-18nh1jk-0 gykQyY css-165qfk5"
>
Wrapped BTC
</div>
</div>
<span />
</div>
</div>
</div>
</DocumentFragment>
`;
exports[`renders loading rows when isLoading is true 1`] = `
<DocumentFragment>
<div
style="position: relative; height: 10px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
>
<div
style="height: 0px; width: 100%;"
/>
</div>
</DocumentFragment>
`;

View File

@@ -0,0 +1,74 @@
import { Currency, CurrencyAmount as mockCurrencyAmount, Token as mockToken } from '@uniswap/sdk-core'
import { DAI, USDC_MAINNET, WBTC } from 'constants/tokens'
import * as mockJSBI from 'jsbi'
import { render } from 'test-utils'
import CurrencyList from '.'
const noOp = function () {
// do nothing
}
const mockCurrencyAmt = {
[DAI.address]: mockCurrencyAmount.fromRawAmount(DAI, mockJSBI.default.BigInt(100)),
[USDC_MAINNET.address]: mockCurrencyAmount.fromRawAmount(USDC_MAINNET, mockJSBI.default.BigInt(10)),
[WBTC.address]: mockCurrencyAmount.fromRawAmount(WBTC, mockJSBI.default.BigInt(1)),
}
jest.mock(
'components/CurrencyLogo',
() =>
({ currency }: { currency: Currency }) =>
`CurrencyLogo currency=${currency.symbol}`
)
jest.mock('@web3-react/core', () => {
const web3React = jest.requireActual('@web3-react/core')
return {
useWeb3React: () => ({
account: '123',
isActive: true,
}),
...web3React,
}
})
jest.mock('../../../state/connection/hooks', () => {
return {
useCurrencyBalance: (currency: Currency) => {
return mockCurrencyAmt[(currency as mockToken)?.address]
},
}
})
it('renders loading rows when isLoading is true', () => {
const { asFragment } = render(
<CurrencyList
height={10}
currencies={[]}
otherListTokens={[]}
selectedCurrency={null}
onCurrencySelect={noOp}
showImportView={noOp}
setImportToken={noOp}
isLoading={true}
/>
)
expect(asFragment()).toMatchSnapshot()
})
it('renders currency rows correctly when currencies list is non-empty', () => {
const { asFragment } = render(
<CurrencyList
height={10}
currencies={[DAI, USDC_MAINNET, WBTC]}
otherListTokens={[]}
selectedCurrency={null}
onCurrencySelect={noOp}
showImportView={noOp}
setImportToken={noOp}
isLoading={false}
/>
)
expect(asFragment()).toMatchSnapshot()
})

View File

@@ -1,28 +1,30 @@
import { Trans } from '@lingui/macro'
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
import { LightGreyCard } from 'components/Card'
import QuestionHelper from 'components/QuestionHelper'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useTheme from 'hooks/useTheme'
import { CSSProperties, MutableRefObject, useCallback, useMemo } from 'react'
import { FixedSizeList } from 'react-window'
import { Text } from 'rebass'
import styled from 'styled-components/macro'
import TokenListLogo from '../../assets/svg/tokenlist.svg'
import { useIsUserAddedToken } from '../../hooks/Tokens'
import { useCombinedActiveList } from '../../state/lists/hooks'
import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo'
import { useCurrencyBalance } from '../../state/wallet/hooks'
import { ThemedText } from '../../theme'
import { isTokenOnList } from '../../utils'
import Column from '../Column'
import CurrencyLogo from '../CurrencyLogo'
import Loader from '../Loader'
import { RowBetween, RowFixed } from '../Row'
import { MouseoverTooltip } from '../Tooltip'
import ImportRow from './ImportRow'
import { MenuItem } from './styleds'
import TokenListLogo from '../../../assets/svg/tokenlist.svg'
import { useIsUserAddedToken } from '../../../hooks/Tokens'
import { useCurrencyBalance } from '../../../state/connection/hooks'
import { useCombinedActiveList } from '../../../state/lists/hooks'
import { WrappedTokenInfo } from '../../../state/lists/wrappedTokenInfo'
import { ThemedText } from '../../../theme'
import { isTokenOnList } from '../../../utils'
import Column from '../../Column'
import CurrencyLogo from '../../CurrencyLogo'
import Loader from '../../Loader'
import { RowBetween, RowFixed } from '../../Row'
import { MouseoverTooltip } from '../../Tooltip'
import ImportRow from '../ImportRow'
import { LoadingRows, MenuItem } from '../styleds'
function currencyKey(currency: Currency): string {
return currency.isToken ? currency.address : 'ETHER'
@@ -106,6 +108,7 @@ function CurrencyRow({
otherSelected,
style,
showCurrencyAmount,
eventProperties,
}: {
currency: Currency
onSelect: () => void
@@ -113,8 +116,9 @@ function CurrencyRow({
otherSelected: boolean
style: CSSProperties
showCurrencyAmount?: boolean
eventProperties: Record<string, unknown>
}) {
const { account } = useActiveWeb3React()
const { account } = useWeb3React()
const key = currencyKey(currency)
const selectedTokenList = useCombinedActiveList()
const isOnSelectedList = isTokenOnList(selectedTokenList, currency.isToken ? currency : undefined)
@@ -123,33 +127,42 @@ function CurrencyRow({
// only show add or remove buttons if not on selected list
return (
<MenuItem
style={style}
className={`token-item-${key}`}
onClick={() => (isSelected ? null : onSelect())}
disabled={isSelected}
selected={otherSelected}
<TraceEvent
events={[Event.onClick, Event.onKeyPress]}
name={EventName.TOKEN_SELECTED}
properties={{ is_imported_by_user: customAdded, ...eventProperties }}
element={ElementName.TOKEN_SELECTOR_ROW}
>
<CurrencyLogo currency={currency} size={'24px'} />
<Column>
<Text title={currency.name} fontWeight={500}>
{currency.symbol}
</Text>
<ThemedText.DarkGray ml="0px" fontSize={'12px'} fontWeight={300}>
{!currency.isNative && !isOnSelectedList && customAdded ? (
<Trans>{currency.name} Added by user</Trans>
) : (
currency.name
)}
</ThemedText.DarkGray>
</Column>
<TokenTags currency={currency} />
{showCurrencyAmount && (
<RowFixed style={{ justifySelf: 'flex-end' }}>
{balance ? <Balance balance={balance} /> : account ? <Loader /> : null}
</RowFixed>
)}
</MenuItem>
<MenuItem
tabIndex={0}
style={style}
className={`token-item-${key}`}
onKeyPress={(e) => (!isSelected && e.key === 'Enter' ? onSelect() : null)}
onClick={() => (isSelected ? null : onSelect())}
disabled={isSelected}
selected={otherSelected}
>
<CurrencyLogo currency={currency} size={'24px'} />
<Column>
<Text title={currency.name} fontWeight={500}>
{currency.symbol}
</Text>
<ThemedText.DarkGray ml="0px" fontSize={'12px'} fontWeight={300}>
{!currency.isNative && !isOnSelectedList && customAdded ? (
<Trans>{currency.name} Added by user</Trans>
) : (
currency.name
)}
</ThemedText.DarkGray>
</Column>
<TokenTags currency={currency} />
{showCurrencyAmount && (
<RowFixed style={{ justifySelf: 'flex-end' }}>
{balance ? <Balance balance={balance} /> : account ? <Loader /> : null}
</RowFixed>
)}
</MenuItem>
</TraceEvent>
)
}
@@ -184,6 +197,31 @@ function BreakLineComponent({ style }: { style: CSSProperties }) {
)
}
interface TokenRowProps {
data: Array<Currency | BreakLine>
index: number
style: CSSProperties
}
const formatAnalyticsEventProperties = (
token: Token,
index: number,
data: any[],
searchQuery: string,
isAddressSearch: string | false
) => ({
token_symbol: token?.symbol,
token_address: token?.address,
is_suggested_token: false,
is_selected_from_list: true,
scroll_position: '',
token_list_index: index,
token_list_length: data.length,
...(isAddressSearch === false
? { search_token_symbol_input: searchQuery }
: { search_token_address_input: isAddressSearch }),
})
export default function CurrencyList({
height,
currencies,
@@ -195,6 +233,9 @@ export default function CurrencyList({
showImportView,
setImportToken,
showCurrencyAmount,
isLoading,
searchQuery,
isAddressSearch,
}: {
height: number
currencies: Currency[]
@@ -206,6 +247,9 @@ export default function CurrencyList({
showImportView: () => void
setImportToken: (token: Token) => void
showCurrencyAmount?: boolean
isLoading: boolean
searchQuery: string
isAddressSearch: string | false
}) {
const itemData: (Currency | BreakLine)[] = useMemo(() => {
if (otherListTokens && otherListTokens?.length > 0) {
@@ -215,7 +259,7 @@ export default function CurrencyList({
}, [currencies, otherListTokens])
const Row = useCallback(
function TokenRow({ data, index, style }) {
function TokenRow({ data, index, style }: TokenRowProps) {
const row: Currency | BreakLine = data[index]
if (isBreakLine(row)) {
@@ -232,7 +276,15 @@ export default function CurrencyList({
const showImport = index > currencies.length
if (showImport && token) {
if (isLoading) {
return (
<LoadingRows>
<div />
<div />
<div />
</LoadingRows>
)
} else if (showImport && token) {
return (
<ImportRow style={style} token={token} showImportView={showImportView} setImportToken={setImportToken} dim />
)
@@ -245,6 +297,7 @@ export default function CurrencyList({
onSelect={handleSelect}
otherSelected={otherSelected}
showCurrencyAmount={showCurrencyAmount}
eventProperties={formatAnalyticsEventProperties(token, index, data, searchQuery, isAddressSearch)}
/>
)
} else {
@@ -259,6 +312,9 @@ export default function CurrencyList({
setImportToken,
showImportView,
showCurrencyAmount,
isLoading,
isAddressSearch,
searchQuery,
]
)

View File

@@ -1,7 +1,10 @@
// eslint-disable-next-line no-restricted-imports
import { t, Trans } from '@lingui/macro'
import { Currency, Token } from '@uniswap/sdk-core'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useWeb3React } from '@web3-react/core'
import { EventName, ModalName } from 'components/AmplitudeAnalytics/constants'
import { Trace } from 'components/AmplitudeAnalytics/Trace'
import { sendEvent } from 'components/analytics'
import useDebounce from 'hooks/useDebounce'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import useTheme from 'hooks/useTheme'
@@ -9,13 +12,12 @@ import useToggle from 'hooks/useToggle'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { getTokenFilter } from 'lib/hooks/useTokenList/filtering'
import { tokenComparator, useSortTokensByQuery } from 'lib/hooks/useTokenList/sorting'
import { KeyboardEvent, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { ChangeEvent, KeyboardEvent, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Edit } from 'react-feather'
import ReactGA from 'react-ga4'
import AutoSizer from 'react-virtualized-auto-sizer'
import { FixedSizeList } from 'react-window'
import { Text } from 'rebass'
import { useAllTokenBalances } from 'state/wallet/hooks'
import { useAllTokenBalances } from 'state/connection/hooks'
import styled from 'styled-components/macro'
import { useAllTokens, useIsUserAddedToken, useSearchInactiveTokenLists, useToken } from '../../hooks/Tokens'
@@ -71,9 +73,11 @@ export function CurrencySearch({
showImportView,
setImportToken,
}: CurrencySearchProps) {
const { chainId } = useActiveWeb3React()
const { chainId } = useWeb3React()
const theme = useTheme()
const [tokenLoaderTimerElapsed, setTokenLoaderTimerElapsed] = useState(false)
// refs for fixed size lists
const fixedList = useRef<FixedSizeList>()
@@ -91,7 +95,7 @@ export function CurrencySearch({
useEffect(() => {
if (isAddressSearch) {
ReactGA.event({
sendEvent({
category: 'Currency Select',
action: 'Search by address',
label: isAddressSearch,
@@ -103,21 +107,26 @@ export function CurrencySearch({
return Object.values(allTokens).filter(getTokenFilter(debouncedQuery))
}, [allTokens, debouncedQuery])
const balances = useAllTokenBalances()
const [balances, balancesIsLoading] = useAllTokenBalances()
const sortedTokens: Token[] = useMemo(() => {
return filteredTokens.sort(tokenComparator.bind(null, balances))
}, [balances, filteredTokens])
void balancesIsLoading // creates a new array once balances load to update hooks
return [...filteredTokens].sort(tokenComparator.bind(null, balances))
}, [balances, filteredTokens, balancesIsLoading])
const filteredSortedTokens = useSortTokensByQuery(debouncedQuery, sortedTokens)
const native = useNativeCurrency()
const filteredSortedTokensWithETH: Currency[] = useMemo(() => {
if (!native) return filteredSortedTokens
// Use Celo ERC20 Implementation and exclude the native asset
if (!native) {
return filteredSortedTokens
}
const s = debouncedQuery.toLowerCase().trim()
if (native.symbol?.toLowerCase()?.indexOf(s) !== -1) {
return native ? [native, ...filteredSortedTokens] : filteredSortedTokens
// Always bump the native token to the top of the list.
return native ? [native, ...filteredSortedTokens.filter((t) => !t.equals(native))] : filteredSortedTokens
}
return filteredSortedTokens
}, [debouncedQuery, native, filteredSortedTokens])
@@ -137,7 +146,7 @@ export function CurrencySearch({
// manage focus on modal show
const inputRef = useRef<HTMLInputElement>()
const handleInput = useCallback((event) => {
const handleInput = useCallback((event: ChangeEvent<HTMLInputElement>) => {
const input = event.target.value
const checksummedInput = isAddress(input)
setSearchQuery(checksummedInput || input)
@@ -173,76 +182,95 @@ export function CurrencySearch({
filteredTokens.length === 0 || (debouncedQuery.length > 2 && !isAddressSearch) ? debouncedQuery : undefined
)
// Timeout token loader after 3 seconds to avoid hanging in a loading state.
useEffect(() => {
const tokenLoaderTimer = setTimeout(() => {
setTokenLoaderTimerElapsed(true)
}, 3000)
return () => clearTimeout(tokenLoaderTimer)
}, [])
return (
<ContentWrapper>
<PaddedColumn gap="16px">
<RowBetween>
<Text fontWeight={500} fontSize={16}>
<Trans>Select a token</Trans>
</Text>
<CloseIcon onClick={onDismiss} />
</RowBetween>
<Row>
<SearchInput
type="text"
id="token-search-input"
placeholder={t`Search name or paste address`}
autoComplete="off"
value={searchQuery}
ref={inputRef as RefObject<HTMLInputElement>}
onChange={handleInput}
onKeyDown={handleEnter}
/>
</Row>
{showCommonBases && (
<CommonBases chainId={chainId} onSelect={handleCurrencySelect} selectedCurrency={selectedCurrency} />
<Trace name={EventName.TOKEN_SELECTOR_OPENED} modal={ModalName.TOKEN_SELECTOR} shouldLogImpression={true}>
<ContentWrapper>
<PaddedColumn gap="16px">
<RowBetween>
<Text fontWeight={500} fontSize={16}>
<Trans>Select a token</Trans>
</Text>
<CloseIcon onClick={onDismiss} />
</RowBetween>
<Row>
<SearchInput
type="text"
id="token-search-input"
placeholder={t`Search name or paste address`}
autoComplete="off"
value={searchQuery}
ref={inputRef as RefObject<HTMLInputElement>}
onChange={handleInput}
onKeyDown={handleEnter}
/>
</Row>
{showCommonBases && (
<CommonBases
chainId={chainId}
onSelect={handleCurrencySelect}
selectedCurrency={selectedCurrency}
searchQuery={searchQuery}
isAddressSearch={isAddressSearch}
/>
)}
</PaddedColumn>
<Separator />
{searchToken && !searchTokenIsAdded ? (
<Column style={{ padding: '20px 0', height: '100%' }}>
<ImportRow token={searchToken} showImportView={showImportView} setImportToken={setImportToken} />
</Column>
) : filteredSortedTokens?.length > 0 || filteredInactiveTokens?.length > 0 ? (
<div style={{ flex: '1' }}>
<AutoSizer disableWidth>
{({ height }) => (
<CurrencyList
height={height}
currencies={disableNonToken ? filteredSortedTokens : filteredSortedTokensWithETH}
otherListTokens={filteredInactiveTokens}
onCurrencySelect={handleCurrencySelect}
otherCurrency={otherSelectedCurrency}
selectedCurrency={selectedCurrency}
fixedListRef={fixedList}
showImportView={showImportView}
setImportToken={setImportToken}
showCurrencyAmount={showCurrencyAmount}
isLoading={balancesIsLoading && !tokenLoaderTimerElapsed}
searchQuery={searchQuery}
isAddressSearch={isAddressSearch}
/>
)}
</AutoSizer>
</div>
) : (
<Column style={{ padding: '20px', height: '100%' }}>
<ThemedText.Main color={theme.text3} textAlign="center" mb="20px">
<Trans>No results found.</Trans>
</ThemedText.Main>
</Column>
)}
</PaddedColumn>
<Separator />
{searchToken && !searchTokenIsAdded ? (
<Column style={{ padding: '20px 0', height: '100%' }}>
<ImportRow token={searchToken} showImportView={showImportView} setImportToken={setImportToken} />
</Column>
) : filteredSortedTokens?.length > 0 || filteredInactiveTokens?.length > 0 ? (
<div style={{ flex: '1' }}>
<AutoSizer disableWidth>
{({ height }) => (
<CurrencyList
height={height}
currencies={disableNonToken ? filteredSortedTokens : filteredSortedTokensWithETH}
otherListTokens={filteredInactiveTokens}
onCurrencySelect={handleCurrencySelect}
otherCurrency={otherSelectedCurrency}
selectedCurrency={selectedCurrency}
fixedListRef={fixedList}
showImportView={showImportView}
setImportToken={setImportToken}
showCurrencyAmount={showCurrencyAmount}
/>
)}
</AutoSizer>
</div>
) : (
<Column style={{ padding: '20px', height: '100%' }}>
<ThemedText.Main color={theme.text3} textAlign="center" mb="20px">
<Trans>No results found.</Trans>
</ThemedText.Main>
</Column>
)}
<Footer>
<Row justify="center">
<ButtonText onClick={showManageView} color={theme.primary1} className="list-token-manage-button">
<RowFixed>
<IconWrapper size="16px" marginRight="6px" stroke={theme.primaryText1}>
<Edit />
</IconWrapper>
<ThemedText.Main color={theme.primaryText1}>
<Trans>Manage Token Lists</Trans>
</ThemedText.Main>
</RowFixed>
</ButtonText>
</Row>
</Footer>
</ContentWrapper>
<Footer>
<Row justify="center">
<ButtonText onClick={showManageView} color={theme.primary1} className="list-token-manage-button">
<RowFixed>
<IconWrapper size="16px" marginRight="6px" stroke={theme.primaryText1}>
<Edit />
</IconWrapper>
<ThemedText.Main color={theme.primaryText1}>
<Trans>Manage Token Lists</Trans>
</ThemedText.Main>
</RowFixed>
</ButtonText>
</Row>
</Footer>
</ContentWrapper>
</Trace>
)
}

View File

@@ -1,5 +1,6 @@
import { Trans } from '@lingui/macro'
import { TokenList } from '@uniswap/token-lists'
import { sendEvent } from 'components/analytics'
import { ButtonPrimary } from 'components/Button'
import Card from 'components/Card'
import { AutoColumn } from 'components/Column'
@@ -11,7 +12,6 @@ import useTheme from 'hooks/useTheme'
import { transparentize } from 'polished'
import { useCallback, useState } from 'react'
import { AlertTriangle, ArrowLeft } from 'react-feather'
import ReactGA from 'react-ga4'
import { useAppDispatch } from 'state/hooks'
import { enableList, removeList } from 'state/lists/actions'
import { useAllLists } from 'state/lists/hooks'
@@ -54,7 +54,7 @@ export function ImportList({ listURL, list, setModalView, onDismiss }: ImportPro
setAddError(null)
fetchList(listURL)
.then(() => {
ReactGA.event({
sendEvent({
category: 'Lists',
action: 'Add List',
label: listURL,
@@ -66,7 +66,7 @@ export function ImportList({ listURL, list, setModalView, onDismiss }: ImportPro
setModalView(CurrencyModalView.manage)
})
.catch((error) => {
ReactGA.event({
sendEvent({
category: 'Lists',
action: 'Add List Failed',
label: listURL,

View File

@@ -63,7 +63,7 @@ export default function ImportRow({
const list = token instanceof WrappedTokenInfo ? token.list : undefined
return (
<TokenSection style={style}>
<TokenSection tabIndex={0} style={style}>
<CurrencyLogo currency={token} size={'24px'} style={{ opacity: dim ? '0.6' : '1' }} />
<AutoColumn gap="4px" style={{ opacity: dim ? '0.6' : '1' }}>
<AutoRow>

View File

@@ -1,6 +1,8 @@
import { Plural, Trans } from '@lingui/macro'
import { Currency, Token } from '@uniswap/sdk-core'
import { TokenList } from '@uniswap/token-lists'
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
import { ButtonPrimary } from 'components/Button'
import { AutoColumn } from 'components/Column'
import { RowBetween } from 'components/Row'
@@ -30,6 +32,12 @@ interface ImportProps {
handleCurrencySelect?: (currency: Currency) => void
}
const formatAnalyticsEventProperties = (tokens: Token[]) => ({
token_symbols: tokens.map((token) => token?.symbol),
token_addresses: tokens.map((token) => token?.address),
token_chain_ids: tokens.map((token) => token?.chainId),
})
export function ImportToken(props: ImportProps) {
const { tokens, list, onBack, onDismiss, handleCurrencySelect } = props
const theme = useTheme()
@@ -42,6 +50,7 @@ export function ImportToken(props: ImportProps) {
if (intersection.size > 0) {
return <BlockedToken onBack={onBack} onDismiss={onDismiss} blockedTokens={Array.from(intersection)} />
}
return (
<Wrapper>
<PaddedColumn gap="14px" style={{ width: '100%', flex: '1 1' }}>
@@ -67,18 +76,25 @@ export function ImportToken(props: ImportProps) {
{tokens.map((token) => (
<TokenImportCard token={token} list={list} key={'import' + token.address} />
))}
<ButtonPrimary
altDisabledStyle={true}
$borderRadius="20px"
padding="10px 1rem"
onClick={() => {
tokens.map((token) => addToken(token))
handleCurrencySelect && handleCurrencySelect(tokens[0])
}}
className=".token-dismiss-button"
<TraceEvent
events={[Event.onClick]}
name={EventName.TOKEN_IMPORTED}
properties={formatAnalyticsEventProperties(tokens)}
element={ElementName.IMPORT_TOKEN_BUTTON}
>
<Trans>Import</Trans>
</ButtonPrimary>
<ButtonPrimary
altDisabledStyle={true}
$borderRadius="20px"
padding="10px 1rem"
onClick={() => {
tokens.map((token) => addToken(token))
handleCurrencySelect && handleCurrencySelect(tokens[0])
}}
className=".token-dismiss-button"
>
<Trans>Import</Trans>
</ButtonPrimary>
</TraceEvent>
</AutoColumn>
</Wrapper>
)

View File

@@ -1,15 +1,15 @@
// eslint-disable-next-line no-restricted-imports
import { t, Trans } from '@lingui/macro'
import { TokenList } from '@uniswap/token-lists'
import { useWeb3React } from '@web3-react/core'
import { sendEvent } from 'components/analytics'
import Card from 'components/Card'
import { UNSUPPORTED_LIST_URLS } from 'constants/lists'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useListColor } from 'hooks/useColor'
import parseENSAddress from 'lib/utils/parseENSAddress'
import uriToHttp from 'lib/utils/uriToHttp'
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { ChangeEvent, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { CheckCircle, Settings } from 'react-feather'
import ReactGA from 'react-ga4'
import { usePopper } from 'react-popper'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import styled from 'styled-components/macro'
@@ -26,7 +26,7 @@ import { ButtonEmpty, ButtonPrimary } from '../Button'
import Column, { AutoColumn } from '../Column'
import ListLogo from '../ListLogo'
import Row, { RowBetween, RowFixed } from '../Row'
import ListToggle from '../Toggle/ListToggle'
import Toggle from '../Toggle'
import { CurrencyModalView } from './CurrencySearchModal'
import { PaddedColumn, SearchInput, Separator, SeparatorDark } from './styleds'
@@ -95,7 +95,7 @@ function listUrlRowHTMLId(listUrl: string) {
}
const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
const { chainId } = useActiveWeb3React()
const { chainId } = useWeb3React()
const listsByUrl = useAppSelector((state) => state.lists.byUrl)
const dispatch = useAppDispatch()
const { current: list, pendingUpdate: pending } = listsByUrl[listUrl]
@@ -126,7 +126,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
const handleAcceptListUpdate = useCallback(() => {
if (!pending) return
ReactGA.event({
sendEvent({
category: 'Lists',
action: 'Update List from List Select',
label: listUrl,
@@ -135,13 +135,13 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
}, [dispatch, listUrl, pending])
const handleRemoveList = useCallback(() => {
ReactGA.event({
sendEvent({
category: 'Lists',
action: 'Start Remove List',
label: listUrl,
})
if (window.prompt(t`Please confirm you would like to remove this list by typing REMOVE`) === `REMOVE`) {
ReactGA.event({
sendEvent({
category: 'Lists',
action: 'Confirm Remove List',
label: listUrl,
@@ -151,7 +151,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
}, [dispatch, listUrl])
const handleEnableList = useCallback(() => {
ReactGA.event({
sendEvent({
category: 'Lists',
action: 'Enable List',
label: listUrl,
@@ -160,7 +160,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
}, [dispatch, listUrl])
const handleDisableList = useCallback(() => {
ReactGA.event({
sendEvent({
category: 'Lists',
action: 'Disable List',
label: listUrl,
@@ -215,7 +215,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
</StyledMenu>
</RowFixed>
</Column>
<ListToggle
<Toggle
isActive={isActive}
bgColor={listColor}
toggle={() => {
@@ -242,7 +242,7 @@ export function ManageLists({
setImportList: (list: TokenList) => void
setListUrl: (url: string) => void
}) {
const { chainId } = useActiveWeb3React()
const { chainId } = useWeb3React()
const theme = useTheme()
const [listUrlInput, setListUrlInput] = useState<string>('')
@@ -266,7 +266,7 @@ export function ManageLists({
// sort by active but only if not visible
const activeListUrls = useActiveListUrls()
const handleInput = useCallback((e) => {
const handleInput = useCallback((e: ChangeEvent<HTMLInputElement>) => {
setListUrlInput(e.target.value)
}, [])

View File

@@ -1,12 +1,12 @@
import { Trans } from '@lingui/macro'
import { Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import Card from 'components/Card'
import Column from 'components/Column'
import CurrencyLogo from 'components/CurrencyLogo'
import Row, { RowBetween, RowFixed } from 'components/Row'
import { useToken } from 'hooks/Tokens'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { RefObject, useCallback, useMemo, useRef, useState } from 'react'
import { ChangeEvent, RefObject, useCallback, useMemo, useRef, useState } from 'react'
import { useRemoveUserAddedToken, useUserAddedTokens } from 'state/user/hooks'
import styled from 'styled-components/macro'
import { ButtonText, ExternalLink, ExternalLinkIcon, ThemedText, TrashIcon } from 'theme'
@@ -44,14 +44,14 @@ export default function ManageTokens({
setModalView: (view: CurrencyModalView) => void
setImportToken: (token: Token) => void
}) {
const { chainId } = useActiveWeb3React()
const { chainId } = useWeb3React()
const [searchQuery, setSearchQuery] = useState<string>('')
const theme = useTheme()
// manage focus on modal show
const inputRef = useRef<HTMLInputElement>()
const handleInput = useCallback((event) => {
const handleInput = useCallback((event: ChangeEvent<HTMLInputElement>) => {
const input = event.target.value
const checksummedInput = isAddress(input)
setSearchQuery(checksummedInput || input)

View File

@@ -1,12 +1,12 @@
import { Trans } from '@lingui/macro'
import { Token } from '@uniswap/sdk-core'
import { TokenList } from '@uniswap/token-lists'
import { useWeb3React } from '@web3-react/core'
import Card from 'components/Card'
import { AutoColumn } from 'components/Column'
import CurrencyLogo from 'components/CurrencyLogo'
import ListLogo from 'components/ListLogo'
import { RowFixed } from 'components/Row'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { transparentize } from 'polished'
import { AlertCircle } from 'react-feather'
import styled, { useTheme } from 'styled-components/macro'
@@ -33,7 +33,7 @@ interface TokenImportCardProps {
}
const TokenImportCard = ({ list, token }: TokenImportCardProps) => {
const theme = useTheme()
const { chainId } = useActiveWeb3React()
const { chainId } = useWeb3React()
return (
<Card backgroundColor={theme.bg2} padding="2rem">
<AutoColumn gap="10px" justify="center">

View File

@@ -1,3 +1,4 @@
import { LoadingRows as BaseLoadingRows } from 'components/Loader/styled'
import styled from 'styled-components/macro'
import { AutoColumn } from '../Column'
@@ -72,3 +73,25 @@ export const SeparatorDark = styled.div`
height: 1px;
background-color: ${({ theme }) => theme.bg3};
`
export const LoadingRows = styled(BaseLoadingRows)`
grid-column-gap: 0.5em;
grid-template-columns: repeat(12, 1fr);
max-width: 960px;
padding: 12px 20px;
& > div:nth-child(4n + 1) {
grid-column: 1 / 8;
height: 1em;
margin-bottom: 0.25em;
}
& > div:nth-child(4n + 2) {
grid-column: 12;
height: 1em;
margin-top: 0.25em;
}
& > div:nth-child(4n + 3) {
grid-column: 1 / 4;
height: 0.75em;
}
`

View File

@@ -1,16 +1,16 @@
// eslint-disable-next-line no-restricted-imports
import { t, Trans } from '@lingui/macro'
import { Percent } from '@uniswap/sdk-core'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { AUTO_ROUTER_SUPPORTED_CHAINS } from 'lib/hooks/routing/clientSideSmartOrderRouter'
import { useWeb3React } from '@web3-react/core'
import { sendEvent } from 'components/analytics'
import { isSupportedChainId } from 'lib/hooks/routing/clientSideSmartOrderRouter'
import { useContext, useRef, useState } from 'react'
import { Settings, X } from 'react-feather'
import ReactGA from 'react-ga4'
import { Text } from 'rebass'
import styled, { ThemeContext } from 'styled-components/macro'
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
import { useModalOpen, useToggleSettingsMenu } from '../../state/application/hooks'
import { useModalIsOpen, useToggleSettingsMenu } from '../../state/application/hooks'
import { ApplicationModal } from '../../state/application/reducer'
import { useClientSideRouter, useExpertModeManager } from '../../state/user/hooks'
import { ThemedText } from '../../theme'
@@ -119,10 +119,10 @@ const ModalContentWrapper = styled.div`
`
export default function SettingsTab({ placeholderSlippage }: { placeholderSlippage: Percent }) {
const { chainId } = useActiveWeb3React()
const { chainId } = useWeb3React()
const node = useRef<HTMLDivElement>()
const open = useModalOpen(ApplicationModal.SETTINGS)
const open = useModalIsOpen(ApplicationModal.SETTINGS)
const toggle = useToggleSettingsMenu()
const theme = useContext(ThemeContext)
@@ -199,7 +199,7 @@ export default function SettingsTab({ placeholderSlippage }: { placeholderSlippa
<Text fontWeight={600} fontSize={14}>
<Trans>Interface Settings</Trans>
</Text>
{chainId && AUTO_ROUTER_SUPPORTED_CHAINS.includes(chainId) && (
{isSupportedChainId(chainId) && (
<RowBetween>
<RowFixed>
<ThemedText.Black fontWeight={400} fontSize={14} color={theme.text2}>
@@ -211,7 +211,7 @@ export default function SettingsTab({ placeholderSlippage }: { placeholderSlippa
id="toggle-optimized-router-button"
isActive={!clientSideRouter}
toggle={() => {
ReactGA.event({
sendEvent({
category: 'Routing',
action: clientSideRouter ? 'enable routing API' : 'disable routing API',
})

View File

@@ -1,4 +1,4 @@
import { useCallback } from 'react'
import { ChangeEvent, useCallback } from 'react'
import styled from 'styled-components/macro'
const StyledRangeInput = styled.input<{ size: number }>`
@@ -106,7 +106,7 @@ export default function Slider({
...rest
}: InputSliderProps) {
const changeCallback = useCallback(
(e) => {
(e: ChangeEvent<HTMLInputElement>) => {
onChange(parseInt(e.target.value))
},
[onChange]

View File

@@ -1,4 +1,4 @@
import React, { memo, useCallback, useRef } from 'react'
import React, { ChangeEvent, memo, useCallback, useRef } from 'react'
import styled from 'styled-components/macro'
const Input = styled.input<{ error?: boolean; fontSize?: string }>`
@@ -77,7 +77,7 @@ export const TextInput = ({
fontSize: string
}) => {
const handleInput = useCallback(
(event) => {
(event: ChangeEvent<HTMLInputElement>) => {
onUserInput(event.target.value)
},
[onUserInput]
@@ -117,7 +117,7 @@ export const ResizingTextArea = memo(
const inputRef = useRef<HTMLTextAreaElement>(document.createElement('textarea'))
const handleInput = useCallback(
(event) => {
(event: ChangeEvent<HTMLTextAreaElement>) => {
inputRef.current.style.height = 'auto'
inputRef.current.style.height = inputRef.current.scrollHeight + 'px'
onUserInput(event.target.value)

View File

@@ -1,57 +0,0 @@
import { Trans } from '@lingui/macro'
import styled from 'styled-components/macro'
import { ThemedText } from '../../theme'
const Wrapper = styled.button<{ isActive?: boolean; activeElement?: boolean }>`
border-radius: 20px;
border: none;
background: ${({ theme }) => theme.bg1};
display: flex;
width: fit-content;
cursor: pointer;
outline: none;
padding: 0.4rem 0.4rem;
align-items: center;
`
const ToggleElement = styled.span<{ isActive?: boolean; bgColor?: string }>`
border-radius: 50%;
height: 24px;
width: 24px;
background-color: ${({ isActive, bgColor, theme }) => (isActive ? bgColor : theme.bg4)};
:hover {
opacity: 0.8;
}
`
const StatusText = styled(ThemedText.Main)<{ isActive?: boolean }>`
margin: 0 10px;
width: 24px;
color: ${({ theme, isActive }) => (isActive ? theme.text1 : theme.text3)};
`
interface ToggleProps {
id?: string
isActive: boolean
bgColor: string
toggle: () => void
}
export default function ListToggle({ id, isActive, bgColor, toggle }: ToggleProps) {
return (
<Wrapper id={id} isActive={isActive} onClick={toggle}>
{isActive && (
<StatusText fontWeight="600" margin="0 6px" isActive={true}>
<Trans>ON</Trans>
</StatusText>
)}
<ToggleElement isActive={isActive} bgColor={bgColor} />
{!isActive && (
<StatusText fontWeight="600" margin="0 6px" isActive={false}>
<Trans>OFF</Trans>
</StatusText>
)}
</Wrapper>
)
}

View File

@@ -1,57 +1,85 @@
import { Trans } from '@lingui/macro'
import { darken } from 'polished'
import { ReactNode } from 'react'
import styled from 'styled-components/macro'
import { useState } from 'react'
import styled, { keyframes } from 'styled-components/macro'
const ToggleElement = styled.span<{ isActive?: boolean; isOnSwitch?: boolean }>`
padding: 0.25rem 0.6rem;
border-radius: 9px;
background: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.primary1 : theme.bg4) : 'none')};
color: ${({ theme, isActive }) => (isActive ? theme.white : theme.text2)};
font-size: 14px;
font-weight: ${({ isOnSwitch }) => (isOnSwitch ? '500' : '400')};
:hover {
user-select: ${({ isOnSwitch }) => (isOnSwitch ? 'none' : 'initial')};
background: ${({ theme, isActive, isOnSwitch }) =>
isActive ? (isOnSwitch ? darken(0.05, theme.primary1) : darken(0.05, theme.bg4)) : 'none'};
color: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.white : theme.white) : theme.text3)};
const Wrapper = styled.button<{ isActive?: boolean; activeElement?: boolean }>`
align-items: center;
background: ${({ theme }) => theme.bg1};
border: none;
border-radius: 20px;
cursor: pointer;
display: flex;
outline: none;
padding: 0.4rem 0.4rem;
width: fit-content;
`
const turnOnToggle = keyframes`
from {
margin-left: 0em;
margin-right: 2.2em;
}
to {
margin-left: 2.2em;
margin-right: 0em;
}
`
const StyledToggle = styled.button<{ isActive?: boolean; activeElement?: boolean }>`
border-radius: 12px;
border: none;
background: ${({ theme }) => theme.bg0};
display: flex;
width: fit-content;
cursor: pointer;
outline: none;
padding: 2px;
const turnOffToggle = keyframes`
from {
margin-left: 2.2em;
margin-right: 0em;
}
to {
margin-left: 0em;
margin-right: 2.2em;
}
`
const ToggleElementHoverStyle = (hasBgColor: boolean, theme: any, isActive?: boolean) =>
hasBgColor
? {
opacity: '0.8',
}
: {
background: isActive ? darken(0.05, theme.primary1) : darken(0.05, theme.bg4),
color: isActive ? theme.white : theme.text3,
}
const ToggleElement = styled.span<{ isActive?: boolean; bgColor?: string; isInitialToggleLoad?: boolean }>`
animation: 0.1s
${({ isActive, isInitialToggleLoad }) => (isInitialToggleLoad ? 'none' : isActive ? turnOnToggle : turnOffToggle)}
ease-in;
background: ${({ theme, bgColor, isActive }) =>
isActive ? bgColor ?? theme.primary1 : !!bgColor ? theme.bg4 : theme.text3};
border-radius: 50%;
height: 24px;
:hover {
${({ bgColor, theme, isActive }) => ToggleElementHoverStyle(!!bgColor, theme, isActive)}
}
margin-left: ${({ isActive }) => (isActive ? '2.2em' : '0em')};
margin-right: ${({ isActive }) => (!isActive ? '2.2em' : '0em')};
width: 24px;
`
interface ToggleProps {
id?: string
bgColor?: string
isActive: boolean
toggle: () => void
checked?: ReactNode
unchecked?: ReactNode
}
export default function Toggle({
id,
isActive,
toggle,
checked = <Trans>On</Trans>,
unchecked = <Trans>Off</Trans>,
}: ToggleProps) {
export default function Toggle({ id, bgColor, isActive, toggle }: ToggleProps) {
const [isInitialToggleLoad, setIsInitialToggleLoad] = useState(true)
const switchToggle = () => {
toggle()
if (isInitialToggleLoad) setIsInitialToggleLoad(false)
}
return (
<StyledToggle id={id} isActive={isActive} onClick={toggle}>
<ToggleElement isActive={isActive} isOnSwitch={true}>
{checked}
</ToggleElement>
<ToggleElement isActive={!isActive} isOnSwitch={false}>
{unchecked}
</ToggleElement>
</StyledToggle>
<Wrapper id={id} isActive={isActive} onClick={switchToggle}>
<ToggleElement isActive={isActive} bgColor={bgColor} isInitialToggleLoad={isInitialToggleLoad} />
</Wrapper>
)
}

View File

@@ -1,16 +1,16 @@
import { useWeb3React } from '@web3-react/core'
import AddressClaimModal from 'components/claim/AddressClaimModal'
import ConnectedAccountBlocked from 'components/ConnectedAccountBlocked'
import useAccountRiskCheck from 'hooks/useAccountRiskCheck'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useModalOpen, useToggleModal } from 'state/application/hooks'
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
export default function TopLevelModals() {
const addressClaimOpen = useModalOpen(ApplicationModal.ADDRESS_CLAIM)
const addressClaimOpen = useModalIsOpen(ApplicationModal.ADDRESS_CLAIM)
const addressClaimToggle = useToggleModal(ApplicationModal.ADDRESS_CLAIM)
const blockedAccountModalOpen = useModalOpen(ApplicationModal.BLOCKED_ACCOUNT)
const { account } = useActiveWeb3React()
const blockedAccountModalOpen = useModalIsOpen(ApplicationModal.BLOCKED_ACCOUNT)
const { account } = useWeb3React()
useAccountRiskCheck(account)
const open = Boolean(blockedAccountModalOpen && account)

View File

@@ -1,18 +1,18 @@
import { Trans } from '@lingui/macro'
import { Currency } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import Badge from 'components/Badge'
import { CHAIN_INFO } from 'constants/chainInfo'
import { L2_CHAIN_IDS, SupportedL2ChainId } from 'constants/chains'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useAddTokenToMetamask from 'hooks/useAddTokenToMetamask'
import { ReactNode, useContext } from 'react'
import { getChainInfo } from 'constants/chainInfo'
import { SupportedL2ChainId } from 'constants/chains'
import useCurrencyLogoURIs from 'lib/hooks/useCurrencyLogoURIs'
import { ReactNode, useCallback, useContext, useState } from 'react'
import { AlertCircle, AlertTriangle, ArrowUpCircle, CheckCircle } from 'react-feather'
import { Text } from 'rebass'
import { useIsTransactionConfirmed, useTransaction } from 'state/transactions/hooks'
import styled, { ThemeContext } from 'styled-components/macro'
import { isL2ChainId } from 'utils/chains'
import Circle from '../../assets/images/blue-loader.svg'
import MetaMaskLogo from '../../assets/images/metamask.png'
import { ExternalLink } from '../../theme'
import { CloseIcon, CustomLightSpinner } from '../../theme'
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
@@ -97,9 +97,25 @@ function TransactionSubmittedContent({
}) {
const theme = useContext(ThemeContext)
const { library } = useActiveWeb3React()
const { connector } = useWeb3React()
const { addToken, success } = useAddTokenToMetamask(currencyToAdd)
const token = currencyToAdd?.wrapped
const logoURL = useCurrencyLogoURIs(token)[0]
const [success, setSuccess] = useState<boolean | undefined>()
const addToken = useCallback(() => {
if (!token?.symbol || !connector.watchAsset) return
connector
.watchAsset({
address: token.address,
symbol: token.symbol,
decimals: token.decimals,
image: logoURL,
})
.then(() => setSuccess(true))
.catch(() => setSuccess(false))
}, [connector, logoURL, token])
return (
<Wrapper>
@@ -124,13 +140,11 @@ function TransactionSubmittedContent({
</Text>
</ExternalLink>
)}
{currencyToAdd && library?.provider?.isMetaMask && (
{currencyToAdd && connector.watchAsset && (
<ButtonLight mt="12px" padding="6px 12px" width="fit-content" onClick={addToken}>
{!success ? (
<RowFixed>
<Trans>
Add {currencyToAdd.symbol} to Metamask <StyledLogo src={MetaMaskLogo} />
</Trans>
<Trans>Add {currencyToAdd.symbol}</Trans>
</RowFixed>
) : (
<RowFixed>
@@ -169,7 +183,7 @@ export function ConfirmationModalContent({
<Text fontWeight={500} fontSize={16}>
{title}
</Text>
<CloseIcon onClick={onDismiss} />
<CloseIcon onClick={onDismiss} data-cy="confirmation-close-icon" />
</RowBetween>
{topContent()}
</Section>
@@ -219,7 +233,7 @@ function L2Content({
}: {
onDismiss: () => void
hash: string | undefined
chainId: number
chainId: SupportedL2ChainId
currencyToAdd?: Currency | undefined
pendingText: ReactNode
inline?: boolean // not in modal
@@ -235,7 +249,7 @@ function L2Content({
? (transaction.confirmedTime - transaction.addedTime) / 1000
: undefined
const info = CHAIN_INFO[chainId as SupportedL2ChainId]
const info = getChainInfo(chainId)
return (
<Wrapper>
@@ -329,16 +343,14 @@ export default function TransactionConfirmationModal({
content,
currencyToAdd,
}: ConfirmationModalProps) {
const { chainId } = useActiveWeb3React()
const isL2 = Boolean(chainId && L2_CHAIN_IDS.includes(chainId))
const { chainId } = useWeb3React()
if (!chainId) return null
// confirmation screen
return (
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={90}>
{isL2 && (hash || attemptingTxn) ? (
{isL2ChainId(chainId) && (hash || attemptingTxn) ? (
<L2Content chainId={chainId} hash={hash} onDismiss={onDismiss} pendingText={pendingText} />
) : attemptingTxn ? (
<ConfirmationPendingContent onDismiss={onDismiss} pendingText={pendingText} />

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