Compare commits

..

142 Commits

Author SHA1 Message Date
Jordan Frankfurt
fd964c5b74 feat(google-analytics): add tx hash to swap events (#3854) 2022-05-19 15:30:04 -05:00
Jordan Frankfurt
83b6eec271 fix(copy-icon): makes copy icon configurable for different uses (#3853) 2022-05-19 15:09:29 -05:00
Noah Zinsmeister
cd76fffbbe fix build warning 2022-05-17 10:53:48 -04:00
Noah Zinsmeister
2c0ac56296 Revert "fix build warning"
This reverts commit f836e3ca32.
2022-05-17 10:53:01 -04:00
Noah Zinsmeister
f836e3ca32 fix build warning 2022-05-17 10:48:29 -04:00
Crowdin Bot
1733fbb378 chore(i18n): synchronize translations from crowdin [skip ci] 2022-05-17 10:11:18 +00:00
Crowdin Bot
78142270a8 chore(i18n): synchronize translations from crowdin [skip ci] 2022-05-16 21:07:07 +00:00
dependabot[bot]
db4e2a9bee chore(deps-dev): bump @uniswap/default-token-list from 3.1.0 to 3.2.0 (#3755)
Bumps [@uniswap/default-token-list](https://github.com/Uniswap/default-token-list) from 3.1.0 to 3.2.0.
- [Release notes](https://github.com/Uniswap/default-token-list/releases)
- [Commits](https://github.com/Uniswap/default-token-list/compare/v3.1.0...v3.2.0)

---
updated-dependencies:
- dependency-name: "@uniswap/default-token-list"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-16 17:00:10 -04:00
Michael Wei
9cee94a473 fix: ignore dbg.json files during typechain (#3751) 2022-05-16 16:55:37 -04:00
Noah Zinsmeister
db5a14387f perf: constrain log-fetching block ranges (#3846)
* first pass

* don't re-fetch historical logs

* hide cancelled proposals by default
2022-05-16 16:45:27 -04:00
Crowdin Bot
5dc7d36669 chore(i18n): synchronize translations from crowdin [skip ci] 2022-05-13 23:06:42 +00:00
Tott0
a3cbe672c7 feat: changes wallet connect terms of service layout (#3835)
Reformat ToS text and remove unneeded links
2022-05-13 18:44:39 -04:00
DhruvJain1122
dc368ed7ac feat: Adding Tally Ho wallet with name & logo for Tally Ho Users (#3820)
* changes

* add tally
2022-05-13 18:38:35 -04:00
Crowdin Bot
b109248b4c chore(i18n): synchronize translations from crowdin [skip ci] 2022-05-12 18:11:13 +00:00
Vignesh Mohankumar
64cc6fb88c chore: initial PR template (#3832) 2022-05-12 08:43:56 -04:00
Crowdin Bot
74f6a4ef3f chore(i18n): synchronize translations from crowdin [skip ci] 2022-05-11 16:12:13 +00:00
Clayton Lin
f468001404 style: build new error connect state (#3831)
* style: build new error connect state

* use usecallback for resetAcountView

* remove fontSize props
2022-05-11 11:48:53 -04:00
Crowdin Bot
f26ec2ff1b chore(i18n): synchronize translations from crowdin [skip ci] 2022-05-10 21:06:48 +00:00
Zach Pomerantz
61d1036d28 chore: rm widget code (#3810)
* chore: rm widget tooling

* chore: rm widget components

* chore: rm widget theme

* chore: rm widget assets

* chore: rm widget business logic

* chore: rm widget meta

* chore: update yarn.lock

* chore: mv type to usage
2022-05-10 13:49:04 -07:00
Crowdin Bot
e11d2080a4 chore(i18n): synchronize translations from crowdin [skip ci] 2022-05-10 15:12:51 +00:00
Clayton Lin
da33423719 style: build new connecting pending state (#3825)
* style: build new connecting pending state

* use currentcolor rather than direct theme text1

* remove unnecessary margin usage
2022-05-10 10:18:51 -04:00
Simeon Kerkola
bd4545538d Chore: Use optional chaining (#3795)
Use optional chaining to check `window.ethereum` object chain.
2022-05-09 14:57:55 -05:00
Crowdin Bot
4274db67d5 chore(i18n): synchronize translations from crowdin [skip ci] 2022-05-09 19:06:52 +00:00
Clayton Lin
28498706cb style: add learn more about wallets link (#3821)
* web-91: add learn more about wallets link

* move externallink outside

* fix trans usage
2022-05-09 14:50:11 -04:00
Crowdin Bot
68c71a67dd chore(i18n): synchronize translations from crowdin [skip ci] 2022-05-09 17:18:03 +00:00
Moody Salem
fe195b63f7 chore: fix translations download CI 2022-05-09 12:05:19 -04:00
Noah Zinsmeister
86b85e25a5 fix proposal formatting 2022-05-09 10:34:25 -04:00
Jordan Frankfurt
0ea635ce15 chore: remove hostname check on risk screen (#3805) 2022-05-04 21:35:24 -05:00
Christine Legge
99ab581a87 refactor: migrate state/user to createSlice (#3779)
* use slice in transactions reducer

* update transaction reducer tests

* update user reducer to use slice

* fix merge conflicts
2022-05-02 15:37:44 -04:00
Will Hennessy
fc571d0f63 chore: update compliance email test (#3788) 2022-05-02 14:37:51 -04:00
David Mihal
2de43a8cdb feat: take tick range from URL (#3208)
* Take tick range from URL

* Keep minPrice/maxPrice in the URL
2022-05-02 12:10:27 -05:00
Jordan Frankfurt
5383436c88 feat(widgets): empty token list on network alert (#3627)
* feat(widgets): empty token list on network alert

* make it work

* pr review

* split dialog header out of tokenselect

* correctly filter token list case

* find -> some

* pr feedback

* clean up query hooks
2022-05-02 10:47:27 -05:00
Jordan Frankfurt
521f3aae04 chore(monitoring): trm cleanup (#3783)
* remove old monitoring code

* cleanup

* remove unneeded .then
2022-04-29 15:42:09 -05:00
0xlucius
9318c1204b feat: Add on-hover tooltips for tx details (#3178)
* Add on-hover tooltips for tx details

* Change tooltips to use <Trans> macro instead of t

* fix: remove info tooltip on transaction popup

* fix: update getting the nativeCurrencyVariable

* use getNativeCurrency() instead of chainInfo const

Co-authored-by: Christine Legge <christine.legge@uniswap.org>
2022-04-25 12:43:41 -04:00
Jordan Frankfurt
5055695b9b feat(optimism): update to new bridge app (#3771) 2022-04-25 10:44:00 -05:00
Christine Legge
ae8c0377de refactor: move state/transactions to createSlice (#3758)
* use slice in transactions reducer

* update transaction reducer tests

* chore: move state/transactions types into their own folder

* fix: fix broken transaction/reducer tests
2022-04-25 09:22:31 -04:00
Jordan Frankfurt
8eaf1f4964 feat(analytics): add a GA event on risk block (#3768)
* feat(analytics): add a GA event on risk block

* Update src/hooks/useAccountRiskCheck.ts

Co-authored-by: Will Hennessy <hennessywill@gmail.com>

Co-authored-by: Will Hennessy <hennessywill@gmail.com>
2022-04-21 21:44:34 -05:00
Zach Pomerantz
f717bf4a49 chore: bump to 1.0.7 (#3753) 2022-04-19 13:14:58 -04:00
Zach Pomerantz
dcbd4e475d chore: rm "with no slippage" (#3752) 2022-04-19 13:13:33 -04:00
Jordan Frankfurt
b704bdac94 feat(compliance): risk screening (#3714)
* feat(compliance): risk screening

* add api endpoint

* hosted app only

* add help center link and click-to-copy email address

* only show on app.uniswap.org and fix spacing nits

* 12px for bottom section
2022-04-19 10:12:28 -05:00
Zach Pomerantz
00d3df95c0 fix: rm console logs (#3743) 2022-04-15 15:19:34 -04:00
Ian Lapham
251b8b703a update list (#3737) 2022-04-15 15:19:11 -04:00
Zach Pomerantz
ef8432437d fix: missing token img (#3727) 2022-04-15 14:19:38 -04:00
Christine Legge
71aebf33db fix: remove unused var (#3736) 2022-04-14 16:26:42 -04:00
Christine Legge
5ff428b04b fix: update widgets README 2022-04-14 16:06:57 -04:00
Mark Carbajal
acb0c2056e chore: Remove Portis (#3693)
* Removed portis

* Removed portis

* Removed portis

* Update src/components/WalletModal/index.tsx

Co-authored-by: Bruno Crosier <bruno.crosier@gmail.com>

* regenerate yarn.lock

* revert translation changes

Co-authored-by: Bruno Crosier <bruno.crosier@gmail.com>
Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2022-04-14 15:23:31 -04:00
Christine Legge
0a4bcb62da add 1bp fee tier to polygon (#3724) 2022-04-14 15:11:58 -04:00
Zach Pomerantz
f50bcbdb2d fix: initial transitions (#3719)
* fix: rm action fade

* fix: disallow stale swaps

* fix: fade in buttons

* fix: fade in input text

* fix: standardize border handling

* fix: transition token button width

* fix: cleanup transitions

* fix: use transition for button

* chore: cleanup
2022-04-13 11:45:29 -07:00
Christine Legge
cbe421ee23 fix: Reload the app when there is a javascript error and a new version of the app (#3715)
* reload the app when encountering a javascript error if there is an update

* remove console.logs

* Add more comments
2022-04-13 13:53:54 -04:00
Zach Pomerantz
3439786c38 feat: display connecting state (#3713) 2022-04-13 09:21:28 -07:00
Zach Pomerantz
6294915be6 fix: convert token list to context (#3712)
* fix: convert token list to context

* fix: cosmos
2022-04-12 14:53:50 -07:00
Zach Pomerantz
984c742d0e fix: use context for block number (#3708)
* fix: use context for block number

* fix: check for valid BlockNumberContext
2022-04-12 10:10:57 -07:00
Zach Pomerantz
00b151d7fa fix: activation frames (#3711) 2022-04-12 09:23:19 -07:00
guil-lambert
5967cf5d9d fix: bug where user cannot burn lp position if fetching fee values fails. (#3633)
* fix: can burn position even if fetching fees fails.

* Revert "fix: can burn position even if fetching fees fails."

This reverts commit a96f7178e5.

* recover more gracefully from failed fee fetch

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2022-04-12 11:41:31 -04:00
Zach Pomerantz
e480f0ebe5 chore: simplify swap info (#3710)
* fix: prevent unnecessary TokenImg renders

* fix: prevent unnecessary trade renders

* fix: simplify swap info computation
2022-04-11 17:09:11 -07:00
Zach Pomerantz
f6ceecbc5e fix: update hook deps to improve ref equality checks (#3707)
* fix: prevent unnecessary TokenImg renders

* fix: prevent unnecessary trade renders
2022-04-11 16:57:53 -07:00
Zach Pomerantz
a0348b45be chore: bump to v1.0.6 (#3696) 2022-04-08 13:13:11 -07:00
Zach Pomerantz
e4b37cffcc fix: skewed swap info state (#3695)
* fix: skewed swap info state

* fix: typings
2022-04-08 13:11:19 -07:00
Zach Pomerantz
dd69cccf91 fix: always run global updaters (#3694) 2022-04-08 12:38:39 -07:00
Zach Pomerantz
8b228de88f chore: bump to v1.0.5 (#3691) 2022-04-08 10:52:30 -07:00
Zach Pomerantz
f91fc3c6a6 fix: defer layout effects (#3687)
* fix: use effect for color

* chore: clean up token defaults

* fix: condition updaters on active tokens
2022-04-08 10:27:10 -07:00
Zach Pomerantz
e0e2b40f9f chore: bump to v1.0.4 (#3686) 2022-04-07 15:05:35 -07:00
Zach Pomerantz
bc1c61b63a fix: omit document ref (#3685) 2022-04-07 15:05:11 -07:00
Alex Dorsch
446ad3e0d4 fix: missing token balance (#3661)
* increase gas required to read token balance

* set token balance gas requirement to 185_000
2022-04-07 15:00:03 -07:00
Zach Pomerantz
65e58a08cf fix: show i18n keys while messages load (#3683)
* fix: show i18n keys while messages load

* fix: i18n initialization check
2022-04-07 14:55:09 -07:00
Zach Pomerantz
71b20b432c fix: block number stability (#3684)
* fix: block number stability

* fix: chainBlock logic
2022-04-07 14:26:50 -07:00
Zach Pomerantz
ecfa179b3f chore: bump to v1.0.3 2022-04-07 11:24:33 -07:00
Zach Pomerantz
6c94a0f585 fix: swap validator (#3682) 2022-04-07 11:23:21 -07:00
Zach Pomerantz
600aeaaff1 fix: polling memory leak (#3676)
* chore: clarify stale callback

* fix: polling memory leak
2022-04-06 17:21:44 -07:00
Zach Pomerantz
3bfbc74e47 chore: bump to v1.0.2 (#3675) 2022-04-06 13:05:06 -07:00
Zach Pomerantz
84f76e34b2 fix: do not fetch wrap price (#3673)
* fix: do not fetch wrap price

* fix: abort trade computation for wraps
2022-04-06 13:04:16 -07:00
Zach Pomerantz
b965bed865 fix: token input height (#3672) 2022-04-06 12:14:15 -07:00
Zach Pomerantz
a9039e8d0b chore: bump to v1.0.1 (#3670) 2022-04-06 10:02:37 -07:00
Zach Pomerantz
60d35b46f3 fix: simplify validation (#3665)
* fix: simplify widget validation

* test: update cosmos to trigger edge cases

* fix: simplify swap validation
2022-04-06 09:21:50 -07:00
Ian Lapham
3d422cf707 update address list (#3669) 2022-04-06 11:48:47 -04:00
Zach Pomerantz
84c70ac84d chore: bump to v1.0.0 (#3663) 2022-04-05 10:45:36 -07:00
Zach Pomerantz
de3a33dfcb fix: stale data edge cases (#3657)
* fix: stale chain block

* chore: simplify atom usage

* fix: support single-token chain

* fix: avoid extra rpcs

* chore: rename isDisabled

* fix: simplify useUSDCPrice

* fix: simplify useComputeSwapInfo

* chore: include type

* fix: guard hasAmounts
2022-04-05 10:45:21 -07:00
Zach Pomerantz
99a084f230 fix: JsonRpc url wrapper (#3662)
* fix: JsonRpc url wrapper

* chore: finish renaming
2022-04-05 10:00:17 -07:00
Jordan Frankfurt
e880955743 chore(widgets): bump version (#3645) 2022-04-01 18:06:12 -04:00
Zach Pomerantz
bbf43fcd27 fix: walletconnect numeric chain id (#3643) 2022-04-01 10:57:11 -07:00
Zach Pomerantz
a00ac56389 chore: bump to v0.0.30-beta (#3637) 2022-03-31 14:49:44 -07:00
Noah Zinsmeister
7201944bc2 Revert "fix(error handling): try reloading the app when encountering a javascript error (#3435)"
This reverts commit 5cf9e84db5.
2022-03-31 17:24:47 -04:00
Noah Zinsmeister
b0ff0f83b0 fix crash (#3634) 2022-03-31 16:41:01 -04:00
Moody Salem
5cf9e84db5 fix(error handling): try reloading the app when encountering a javascript error (#3435) 2022-03-31 16:39:31 -04:00
Zach Pomerantz
c0bdb8db12 fix: break unnecessary hierarchical deps (#3629) 2022-03-30 21:23:33 -07:00
Zach Pomerantz
2d8f767d74 feat: upgrade web3-react (#3628)
* chore: upgrade web3-react

* feat: use a JsonRpcConnector

* chore: rm @ethersproject/experimental

* fix: assert Web3Provider in app

* fix: type providers more loosely

* chore: reinstall experimental for testing
2022-03-30 20:45:43 -07:00
Zach Pomerantz
1303416eca feat: lazy load the lib's smart-order-router (#3624)
* feat: lazy-load the smart-order-router

* chore: guard against regression
2022-03-30 14:09:18 -07:00
Zach Pomerantz
124f6420a5 fix: lazy load en-us (#3626) 2022-03-30 13:32:49 -07:00
Zach Pomerantz
91f5fc0881 chore: upgrade redux-multicall and smart-order-router (#3623)
* chore: upgrade redux-multicall and smart-order-router

* chore: provide @ethersproject through ethers
2022-03-29 12:00:56 -07:00
Zach Pomerantz
ec831f8433 chore: destructure json imports (#3622) 2022-03-29 12:00:42 -07:00
Crowdin Bot
56bd9b68d7 chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-29 06:07:44 +00:00
Crowdin Bot
865d21f039 chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-28 23:07:52 +00:00
Jordan Frankfurt
dceadf8472 chore(docs): add widgets info to the repo README (#3600)
* add widgets info to the repo README

* pr feedback from z

* pr feedback from w and z

* separate README.md and INTERFACE_README.md

* Update WIDGETS_README.md

Co-authored-by: Will Hennessy <hennessywill@gmail.com>

Co-authored-by: Will Hennessy <hennessywill@gmail.com>
2022-03-28 11:35:14 -05:00
Crowdin Bot
cd3a91bca8 chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-25 07:06:55 +00:00
Connor McEwen
de1f5d1adc feat: migrate to GA4 (#3599) 2022-03-24 21:44:48 -04:00
Crowdin Bot
b5d403768f chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-24 20:07:03 +00:00
Zach Pomerantz
c4c811aeb3 chore: bump to v0.0.26-beta (#3593) 2022-03-24 16:02:38 -04:00
Zach Pomerantz
33c24a3f05 fix: trade displays (#3594)
* fix: show syncing over insufficient balance

* fix: mv anti-janking up a level

* feat: add error caption for completeness

* chore: clarify naming
2022-03-24 16:02:20 -04:00
Zach Pomerantz
9ef2b3a116 chore: add ethers (#3591) 2022-03-24 12:51:03 -04:00
Zach Pomerantz
afe38a2d10 fix: tick before returning quote (#3598) 2022-03-24 12:33:35 -04:00
dependabot[bot]
d28607a1c8 chore(deps-dev): bump @uniswap/default-token-list from 3.0.0 to 3.1.0 (#3564) 2022-03-24 12:09:44 -04:00
dependabot[bot]
7fb363ac46 chore(deps): bump @uniswap/token-lists (#3596) 2022-03-24 12:09:22 -04:00
Zach Pomerantz
16b0b1530d fix: memoize client side v3 router call data (#3595)
* fix: memoize client side v3 router call data

* fix: rm log
2022-03-24 10:30:28 -04:00
Zach Pomerantz
abb2696f40 fix: upgrade @web3-react/eip1193 to fallback to eth_accounts (#3590) 2022-03-23 20:45:49 -04:00
Zach Pomerantz
772178fc86 fix: add web3 debug log (#3587) 2022-03-23 20:45:22 -04:00
Crowdin Bot
9f1378f635 chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-23 19:09:22 +00:00
Zach Pomerantz
84275dcce1 fix: action button jank (#3582) 2022-03-23 14:32:45 -04:00
Zach Pomerantz
a76ece6ce3 feat: prompt for interaction "in your wallet" (#3585)
* feat: prompt approval in wallet

* feat: prompt wrap in wallet

* feat: prompt confirm in wallet

* fix: animations

* fix: test typing
2022-03-23 14:12:58 -04:00
Zach Pomerantz
334e137fb3 fix: empty toolbar for empty input (#3584) 2022-03-23 14:07:56 -04:00
Zach Pomerantz
eb6c4d464a fix: do not allow zeroes (#3583) 2022-03-23 14:07:41 -04:00
Zach Pomerantz
24734e6a34 Revert "fix: retain permit validity for lesser amounts (#3580)" (#3586)
This reverts commit f1bcee3c08.
2022-03-23 10:33:50 -04:00
Zach Pomerantz
f1bcee3c08 fix: retain permit validity for lesser amounts (#3580) 2022-03-23 09:56:07 -04:00
Crowdin Bot
7a215ccdb4 chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-23 08:07:22 +00:00
Crowdin Bot
c5c4f48d96 chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-22 22:07:21 +00:00
Zach Pomerantz
bdcf761ddd chore: refactor swap button for maintainability (#3579)
* chore: mv SwapButton to dir

* chore: mv approval data to its own hook

* chore: mv approval actions to approvals hook

* chore: simplify SwapButton logic

* fix: pass through approval amount

* fix: mv error handling to consumer
2022-03-22 17:59:05 -04:00
Crowdin Bot
e876267d83 chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-22 20:06:44 +00:00
Zach Pomerantz
76cbd82cb7 fix: bring wrap ui to spec (#3577) 2022-03-22 15:30:12 -04:00
Zach Pomerantz
6c4f7ab9a1 fix: modal border radius (#3576) 2022-03-22 13:44:33 -04:00
Zach Pomerantz
da20315724 fix: retain stale trade (#3578) 2022-03-22 13:03:02 -04:00
Zach Pomerantz
963b910552 fix: trade loading state (#3572)
* fix: invert stale callback

* fix: polling and validation logic

* fix: rm unused conditional
2022-03-22 12:23:38 -04:00
Zach Pomerantz
9e2dc9a435 fix: rm approval invariant (#3569) 2022-03-21 16:40:27 -04:00
Ian Lapham
6567f18bf5 update code formatting for prettier (#3571) 2022-03-21 16:20:59 -04:00
Zach Pomerantz
ee96973212 chore: clean dialog mounting logic (#3559)
* fix: apply scrollbar css on first render

* fix: useUnmount portability

* chore: clean up dialog ordering

* fix: dialog border-radius

* chore: cleanup dialog unmount animation
2022-03-21 15:55:46 -04:00
Zach Pomerantz
ce6c783174 fix: trade UI latency (#3563)
* fix: do not delay trade update

* fix: do not delay initial input
2022-03-21 15:03:09 -04:00
Zach Pomerantz
64e8c3ced9 fix: token img jank (#3562) 2022-03-21 15:00:03 -04:00
Zach Pomerantz
46e6c2295d fix: prevent scroll on token select focus (#3560) 2022-03-21 14:58:49 -04:00
Crowdin Bot
3626dbdeec chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-21 18:12:24 +00:00
Zach Pomerantz
eb75e0dc2e fix: set approval to pending on wallet request (#3570) 2022-03-21 13:37:53 -04:00
Zach Pomerantz
c26ecdfc88 fix: use a min fresh block (#3568)
* chore: mv useFilterFresh to its own hook

* fix: use a minimum fresh block

* fix: re-poll on stale data

* chore: rename to staleCallback

* check for undefined

* chore: rename fresh->valid

Co-authored-by: ianlapham <ianlapham@gmail.com>
2022-03-21 13:18:21 -04:00
Zach Pomerantz
f508788026 fix: impact rendering (#3553)
* fix: price impact rendering

* fix: webkit-compat gradient
2022-03-21 10:26:35 -04:00
Zach Pomerantz
377026bca8 fix: summary expando height (#3556) 2022-03-19 17:05:56 -05:00
Zach Pomerantz
9470c49d11 fix: fix loading delays for cached data (#3549)
* fix: loading transition

* fix: add check to usePoll
2022-03-18 11:03:26 -07:00
Ian Lapham
e1abd81a1d fix: add syncing state to trade loading (#3545)
* add syncing state to trade loading

* remove log statement

* update loading state

* update useLast trade logic

* nit fixes
2022-03-18 10:11:16 -07:00
Zach Pomerantz
7d9657867d fix: polling lag (#3543) 2022-03-18 08:52:55 -07:00
Zach Pomerantz
7cc52abb96 fix: cache computed pool addresses (#3537) 2022-03-18 08:00:35 -07:00
Crowdin Bot
5ac41417b0 chore(i18n): synchronize translations from crowdin [skip ci] 2022-03-18 00:15:22 +00:00
Zach Pomerantz
2c74c5f2df chore: include usdc in swap info (#3539)
* chore: refactor useComputeSwapInfo to include usdc

* chore: use passed usdc if able

* fix: fixture
2022-03-17 16:51:41 -07:00
Ian Lapham
cbc2ff668e disable swap button if trade not populated (#3542) 2022-03-17 16:26:37 -07:00
Zach Pomerantz
a73f59b4ff fix: only debounce input amounts (#3540) 2022-03-17 15:42:32 -07:00
Zach Pomerantz
7a75626c31 chore: bump to v0.0.25-beta (#3538) 2022-03-17 14:17:58 -07:00
Jordan Frankfurt
a0e14bef10 fix(vote): allow 0-vote proposals to render (#3536) 2022-03-17 15:37:36 -05:00
Yadong Zhang
9b5a53b2e8 fix: typeError: Cannot read properties of undefined (reading 'split') (#3518) 2022-03-17 14:30:13 -04:00
261 changed files with 3495 additions and 18735 deletions

View File

@@ -1,5 +1,4 @@
REACT_APP_INFURA_KEY="099fc58e0de9451d80b18d7c74caa7c1"
REACT_APP_PORTIS_ID="c0e2bf01-4b08-4fd5-ac7b-8e26b58cd236"
REACT_APP_FORTMATIC_KEY="pk_live_F937DF033A1666BF"
REACT_APP_GOOGLE_ANALYTICS_ID="UA-128182339-4"
REACT_APP_GOOGLE_ANALYTICS_ID="G-KDP9B6W4H8"
REACT_APP_FIREBASE_KEY="AIzaSyBcZWwTcTJHj_R6ipZcrJkXdq05PuX0Rs0"

24
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,24 @@
Your PR title must follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#summary), and should start with one of the following [types](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#type):
- build: Changes that affect the build system or external dependencies (example scopes: yarn, eslint, typescript)
- ci: Changes to our CI configuration files and scripts (example scopes: vercel, github, cypress)
- docs: Documentation only changes
- feat: A new feature
- fix: A bug fix
- perf: A code change that improves performance
- refactor: A code change that neither fixes a bug nor adds a feature
- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
- test: Adding missing tests or correcting existing tests
Example commit messages:
- feat: adds support for gnosis safe wallet
- fix: removes a polling memory leak
- chore: bumps redux version
Other things to note:
- Please describe the change using verb statements (ex: Removes X from Y)
- PRs with multiple changes should use a list of verb statements
- Add any relevant unit / integration tests
- Changes will be previewable via vercel. Non-obvious changes should include instructions for how to reproduce them

View File

@@ -1,40 +0,0 @@
name: Widgets
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
name: Build
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: Build
run: yarn widgets:build

View File

@@ -42,7 +42,7 @@ jobs:
run: "yarn i18n:extract"
- name: Synchronize
uses: crowdin/github-action@1.1.0
uses: crowdin/github-action@1.4.9
with:
upload_sources: false
download_translations: true

2
.gitignore vendored
View File

@@ -19,8 +19,6 @@
# builds
/build
/cosmos-export
/dist
/dts
# misc

View File

@@ -1,9 +1,9 @@
# Uniswap Interface
# Uniswap Labs Interface
[![Unit Tests](https://github.com/Uniswap/uniswap-interface/actions/workflows/unit-tests.yaml/badge.svg)](https://github.com/Uniswap/uniswap-interface/actions/workflows/unit-tests.yaml)
[![Integration Tests](https://github.com/Uniswap/uniswap-interface/actions/workflows/integration-tests.yaml/badge.svg)](https://github.com/Uniswap/uniswap-interface/actions/workflows/integration-tests.yaml)
[![Lint](https://github.com/Uniswap/uniswap-interface/actions/workflows/lint.yml/badge.svg)](https://github.com/Uniswap/uniswap-interface/actions/workflows/lint.yml)
[![Release](https://github.com/Uniswap/uniswap-interface/actions/workflows/release.yaml/badge.svg)](https://github.com/Uniswap/uniswap-interface/actions/workflows/release.yaml)
[![Unit Tests](https://github.com/Uniswap/interface/actions/workflows/unit-tests.yaml/badge.svg)](https://github.com/Uniswap/interface/actions/workflows/unit-tests.yaml)
[![Integration Tests](https://github.com/Uniswap/interface/actions/workflows/integration-tests.yaml/badge.svg)](https://github.com/Uniswap/interface/actions/workflows/integration-tests.yaml)
[![Lint](https://github.com/Uniswap/interface/actions/workflows/lint.yml/badge.svg)](https://github.com/Uniswap/interface/actions/workflows/lint.yml)
[![Release](https://github.com/Uniswap/interface/actions/workflows/release.yaml/badge.svg)](https://github.com/Uniswap/interface/actions/workflows/release.yaml)
[![Crowdin](https://badges.crowdin.net/uniswap-interface/localized.svg)](https://crowdin.com/project/uniswap-interface)
An open source interface for Uniswap -- a protocol for decentralized exchange of Ethereum tokens.
@@ -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,10 +0,0 @@
{
"watchDirs": [
"src"
],
"webpack": {
"configPath": "react-scripts/config/webpack.config",
"overridePath": "cosmos.override.cjs"
},
"port": 5001
}

View File

@@ -1,26 +0,0 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { DefinePlugin } = require('webpack')
// Renders the cosmos fixtures in isolation, instead of using public/index.html.
module.exports = (webpackConfig) => ({
...webpackConfig,
plugins: webpackConfig.plugins.map((plugin) => {
if (plugin instanceof HtmlWebpackPlugin) {
return new HtmlWebpackPlugin({
templateContent: '<body></body>',
})
}
if (plugin instanceof DefinePlugin) {
return new DefinePlugin({
...plugin.definitions,
'process.env': {
...plugin.definitions['process.env'],
REACT_APP_IS_WIDGET: true,
REACT_APP_LOCALES: '"../locales"',
},
})
}
return plugin
}),
})

View File

@@ -1,6 +1,6 @@
{
"name": "@uniswap/widgets",
"version": "0.0.24-beta",
"version": "1.0.7",
"description": "Uniswap Interface",
"homepage": ".",
"files": [
@@ -30,6 +30,7 @@
"@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",
@@ -43,16 +44,6 @@
"@reach/dialog": "^0.10.3",
"@reach/portal": "^0.10.3",
"@react-hook/window-scroll": "^1.3.0",
"@rollup/plugin-alias": "^3.1.9",
"@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-commonjs": "^21.0.1",
"@rollup/plugin-eslint": "^8.0.1",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.1.3",
"@rollup/plugin-replace": "^3.0.1",
"@rollup/plugin-typescript": "^8.3.0",
"@rollup/plugin-url": "^6.1.0",
"@svgr/rollup": "^6.2.0",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.0.0",
"@testing-library/react-hooks": "^7.0.2",
@@ -89,8 +80,8 @@
"@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.13-beta.0",
"@web3-react/walletconnect": "8.0.18-beta.0",
"@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",
@@ -116,9 +107,8 @@
"qs": "^6.9.4",
"react": "^17.0.1",
"react-confetti": "^6.0.0",
"react-cosmos": "^5.6.6",
"react-dom": "^17.0.1",
"react-ga": "^2.5.7",
"react-ga4": "^1.4.1",
"react-is": "^17.0.2",
"react-markdown": "^4.3.1",
"react-redux": "^7.2.2",
@@ -128,14 +118,6 @@
"react-use-gesture": "^6.0.14",
"redux": "^4.1.2",
"redux-localstorage-simple": "^2.3.1",
"rollup": "^2.63.0",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-delete": "^2.0.0",
"rollup-plugin-dts": "^4.1.0",
"rollup-plugin-multi-input": "^1.3.1",
"rollup-plugin-node-externals": "^3.1.2",
"rollup-plugin-scss": "^3.0.0",
"rollup-plugin-typescript2": "^0.31.1",
"sass": "^1.45.1",
"serve": "^11.3.2",
"start-server-and-test": "^1.11.0",
@@ -149,7 +131,6 @@
"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-portis-connector": "npm:@web3-react/portis-connector@^6.0.9",
"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",
@@ -162,7 +143,7 @@
},
"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/**/*.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",
@@ -170,13 +151,10 @@
"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",
"prepublishOnly": "yarn widgets:build",
"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'",
"widgets:start": "cosmos",
"widgets:build": "rollup --config --failAfterWarnings --configPlugin typescript2"
"test:e2e": "start-server-and-test 'serve build -l 3000' http://localhost:3000 'cypress run --record'"
},
"browserslist": {
"production": [
@@ -193,39 +171,25 @@
"license": "GPL-3.0-or-later",
"dependencies": {
"@babel/runtime": "^7.17.0",
"@ethersproject/abi": "^5.4.1",
"@ethersproject/abstract-provider": "^5.4.1",
"@ethersproject/abstract-signer": "^5.4.1",
"@ethersproject/address": "^5.4.0",
"@ethersproject/bignumber": "^5.4.2",
"@ethersproject/bytes": "^5.4.0",
"@ethersproject/constants": "^5.4.0",
"@ethersproject/contracts": "^5.4.1",
"@ethersproject/experimental": "^5.4.0",
"@ethersproject/hash": "^5.4.0",
"@ethersproject/providers": "^5.4.0",
"@ethersproject/solidity": "^5.4.0",
"@ethersproject/strings": "^5.4.0",
"@ethersproject/units": "^5.4.0",
"@ethersproject/wallet": "^5.4.0",
"@fontsource/ibm-plex-mono": "^4.5.1",
"@fontsource/inter": "^4.5.1",
"@popperjs/core": "^2.4.4",
"@reduxjs/toolkit": "^1.6.1",
"@uniswap/redux-multicall": "^1.0.0",
"@uniswap/redux-multicall": "^1.1.1",
"@uniswap/router-sdk": "^1.0.3",
"@uniswap/sdk-core": "^3.0.1",
"@uniswap/smart-order-router": "^2.5.20",
"@uniswap/smart-order-router": "^2.5.26",
"@uniswap/token-lists": "^1.0.0-beta.27",
"@uniswap/v2-sdk": "^3.0.1",
"@uniswap/v3-sdk": "^3.8.2",
"@web3-react/core": "8.0.17-beta.0",
"@web3-react/eip1193": "8.0.12-beta.0",
"@web3-react/empty": "8.0.10-beta.0",
"@web3-react/types": "8.0.10-beta.0",
"@web3-react/url": "8.0.12-beta.0",
"@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",
"ajv": "^6.12.3",
"cids": "^1.0.0",
"ethers": "^5.1.4",
"immer": "^9.0.6",
"jotai": "^1.3.7",
"jsbi": "^3.1.4",

View File

@@ -1,176 +0,0 @@
/**
* Bundles the widgets library, which is released independently of the interface application.
* This library lives in src/lib, but shares code with the interface application.
*/
import alias from '@rollup/plugin-alias'
import babel from '@rollup/plugin-babel'
import commonjs from '@rollup/plugin-commonjs'
import json from '@rollup/plugin-json'
import resolve from '@rollup/plugin-node-resolve'
import replace from '@rollup/plugin-replace'
import typescript from '@rollup/plugin-typescript'
import url from '@rollup/plugin-url'
import svgr from '@svgr/rollup'
import path from 'path'
import { RollupWarning } from 'rollup'
import copy from 'rollup-plugin-copy'
import del from 'rollup-plugin-delete'
import dts from 'rollup-plugin-dts'
// @ts-ignore // missing types
import multi from 'rollup-plugin-multi-input'
import externals from 'rollup-plugin-node-externals'
import sass from 'rollup-plugin-scss'
import { CompilerOptions } from 'typescript'
const REPLACEMENTS = {
'process.env.REACT_APP_IS_WIDGET': true,
'process.env.REACT_APP_LOCALES': '"./locales"',
// esm requires fully-specified paths:
'react/jsx-runtime': 'react/jsx-runtime.js',
}
const EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx']
const ASSET_EXTENSIONS = ['.png', '.svg']
function isAsset(source: string) {
const extname = path.extname(source)
return extname && [...ASSET_EXTENSIONS, '.css', '.scss'].includes(extname)
}
const TS_CONFIG = './tsconfig.lib.json'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { baseUrl, paths }: CompilerOptions = require(TS_CONFIG).compilerOptions
const aliases = Object.entries({ ...paths }).flatMap(([find, replacements]) => {
return replacements.map((replacement) => ({
find: path.dirname(find),
replacement: path.join(__dirname, baseUrl || '.', path.dirname(replacement)),
}))
})
const plugins = [
// Dependency resolution
resolve({ extensions: EXTENSIONS }), // resolves third-party modules within node_modules/
alias({ entries: aliases }), // resolves paths aliased through the tsconfig (babel does not use tsconfig path resolution)
// Source code transformation
replace({ ...REPLACEMENTS, preventAssignment: true }),
json(), // imports json as ES6; doing so enables type-checking and module resolution
]
const check = {
input: 'src/lib/index.tsx',
output: { file: 'dist/widgets.tsc', inlineDynamicImports: true },
external: isAsset,
plugins: [
externals({ exclude: ['constants'], deps: true, peerDeps: true }), // marks builtins, dependencies, and peerDependencies external
...plugins,
typescript({ tsconfig: TS_CONFIG }),
],
onwarn: squelchTranspilationWarnings, // this pipeline is only for typechecking and generating definitions
}
const type = {
input: 'dist/dts/lib/index.d.ts',
output: { file: 'dist/index.d.ts' },
external: isAsset,
plugins: [
externals({ exclude: ['constants'], deps: true, peerDeps: true }),
dts({ compilerOptions: { baseUrl: 'dist/dts' } }),
process.env.ROLLUP_WATCH ? undefined : del({ hook: 'buildEnd', targets: ['dist/widgets.tsc', 'dist/dts'] }),
],
}
/**
* This exports scheme works for nextjs and for CRA5.
*
* It will also work for CRA4 if you use direct imports:
* instead of `import { SwapWidget } from '@uniswap/widgets'`,
* `import { SwapWidget } from '@uniswap/widgets/dist/index.js'`.
* I do not know why CRA4 does not seem to use exports for resolution.
*
* Note that chunks are enabled. This is so the tokenlist spec can be loaded async,
* to improve first load time (due to ajv). Locales are also in separate chunks.
*
* Lastly, note that JSON and lingui are bundled into the library, as neither are fully
* supported/compatible with ES Modules. Both _could_ be bundled only with esm, but this
* yields a less complex pipeline.
*/
const transpile = {
input: 'src/lib/index.tsx',
output: [
{
dir: 'dist',
format: 'esm',
sourcemap: false,
},
{
dir: 'dist/cjs',
entryFileNames: '[name].cjs',
chunkFileNames: '[name]-[hash].cjs',
format: 'cjs',
sourcemap: false,
},
],
plugins: [
externals({
exclude: [
'constants',
/@lingui\/(core|react)/, // @lingui incorrectly exports esm, so it must be bundled in
/\.json$/, // esm does not support JSON loading, so it must be bundled in
],
deps: true,
peerDeps: true,
}),
...plugins,
// Source code transformation
url({ include: ASSET_EXTENSIONS.map((extname) => '**/*' + extname), limit: Infinity }), // imports assets as data URIs
svgr({ exportType: 'named', svgo: false }), // imports svgs as React components
sass({ output: 'dist/fonts.css' }), // generates fonts.css
commonjs(), // transforms cjs dependencies into tree-shakeable ES modules
babel({
babelHelpers: 'runtime',
presets: ['@babel/preset-env', ['@babel/preset-react', { runtime: 'automatic' }], '@babel/preset-typescript'],
extensions: EXTENSIONS,
plugins: [
'macros', // enables @lingui and styled-components macros
'@babel/plugin-transform-runtime', // embeds the babel runtime for library distribution
],
}),
],
onwarn: squelchTypeWarnings, // this pipeline is only for transpilation
}
const locales = {
input: 'src/locales/*.js',
output: [
{
dir: 'dist',
format: 'esm',
sourcemap: false,
},
],
plugins: [
copy({
copyOnce: true,
targets: [{ src: 'src/locales/*.js', dest: 'dist/cjs/locales', rename: (name) => `${name}.cjs` }],
}),
commonjs(),
multi(),
],
}
const config = [check, type, transpile, locales]
export default config
function squelchTranspilationWarnings(warning: RollupWarning, warn: (warning: RollupWarning) => void) {
if (warning.pluginCode === 'TS5055') return
warn(warning)
}
function squelchTypeWarnings(warning: RollupWarning, warn: (warning: RollupWarning) => void) {
if (warning.code === 'UNUSED_EXTERNAL_IMPORT') return
warn(warning)
}

BIN
src/assets/images/tally.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

View File

@@ -1,49 +1,64 @@
import { Trans } from '@lingui/macro'
import React from 'react'
import useCopyClipboard from 'hooks/useCopyClipboard'
import React, { useCallback } from 'react'
import { CheckCircle, Copy } from 'react-feather'
import styled from 'styled-components/macro'
import useCopyClipboard from '../../hooks/useCopyClipboard'
import { LinkStyledButton } from '../../theme'
import { LinkStyledButton } from 'theme'
const CopyIcon = styled(LinkStyledButton)`
color: ${({ theme }) => theme.text3};
color: ${({ color, theme }) => color || theme.text3};
flex-shrink: 0;
display: flex;
text-decoration: none;
font-size: 0.825rem;
:hover,
:active,
:focus {
text-decoration: none;
color: ${({ theme }) => theme.text2};
color: ${({ color, theme }) => color || theme.text2};
}
`
const TransactionStatusText = styled.span`
const StyledText = styled.span`
margin-left: 0.25rem;
font-size: 0.825rem;
${({ theme }) => theme.flexRowNoWrap};
align-items: center;
`
export default function CopyHelper(props: { toCopy: string; children?: React.ReactNode }) {
const Copied = ({ iconSize }: { iconSize?: number }) => (
<StyledText>
<CheckCircle size={iconSize ?? '16'} />
<StyledText>
<Trans>Copied</Trans>
</StyledText>
</StyledText>
)
const Icon = ({ iconSize }: { iconSize?: number }) => (
<StyledText>
<Copy size={iconSize ?? '16'} />
</StyledText>
)
interface BaseProps {
toCopy: string
color?: string
iconSize?: number
iconPosition?: 'left' | 'right'
}
export type CopyHelperProps = BaseProps & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof BaseProps>
export default function CopyHelper({ color, toCopy, children, iconSize, iconPosition }: CopyHelperProps) {
const [isCopied, setCopied] = useCopyClipboard()
const copy = useCallback(() => {
setCopied(toCopy)
}, [toCopy, setCopied])
return (
<CopyIcon onClick={() => setCopied(props.toCopy)}>
{isCopied ? (
<TransactionStatusText>
<CheckCircle size={'16'} />
<TransactionStatusText>
<Trans>Copied</Trans>
</TransactionStatusText>
</TransactionStatusText>
) : (
<TransactionStatusText>
<Copy size={'16'} />
</TransactionStatusText>
)}
{isCopied ? '' : props.children}
<CopyIcon onClick={copy} color={color}>
{iconPosition === 'left' ? isCopied ? <Copied iconSize={iconSize} /> : <Icon iconSize={iconSize} /> : null}
{iconPosition === 'left' && <>&nbsp;</>}
{isCopied ? '' : children}
{iconPosition === 'right' && <>&nbsp;</>}
{iconPosition === 'right' ? isCopied ? <Copied iconSize={iconSize} /> : <Icon iconSize={iconSize} /> : null}
</CopyIcon>
)
}

View File

@@ -25,7 +25,7 @@ import {
VoteTransactionInfo,
WithdrawLiquidityStakingTransactionInfo,
WrapTransactionInfo,
} from '../../state/transactions/actions'
} from '../../state/transactions/types'
function formatAmount(amountRaw: string, decimals: number, sigFigs: number): string {
return new Fraction(amountRaw, JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(decimals))).toSignificant(sigFigs)

View File

@@ -1,5 +1,6 @@
import { Trans } from '@lingui/macro'
import { Connector } from '@web3-react/types'
import CopyHelper from 'components/AccountDetails/Copy'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useCallback, useContext } from 'react'
import { ExternalLink as LinkIcon } from 'react-feather'
@@ -8,16 +9,15 @@ import styled, { ThemeContext } from 'styled-components/macro'
import { AbstractConnector } from 'web3-react-abstract-connector'
import { ReactComponent as Close } from '../../assets/images/x.svg'
import { injected, portis, walletlink } from '../../connectors'
import { injected, walletlink } from '../../connectors'
import { SUPPORTED_WALLETS } from '../../constants/wallet'
import { clearAllTransactions } from '../../state/transactions/actions'
import { clearAllTransactions } from '../../state/transactions/reducer'
import { ExternalLink, LinkStyledButton, ThemedText } from '../../theme'
import { shortenAddress } from '../../utils'
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
import { ButtonSecondary } from '../Button'
import StatusIcon from '../Identicon/StatusIcon'
import { AutoRow } from '../Row'
import Copy from './Copy'
import Transaction from './Transaction'
const HeaderRow = styled.div`
@@ -181,15 +181,6 @@ function WrappedStatusIcon({ connector }: { connector: AbstractConnector | Conne
return (
<IconWrapper size={16}>
<StatusIcon connector={connector} />
{connector === portis && (
<MainWalletAction
onClick={() => {
portis.portis.showPortis()
}}
>
<Trans>Show Portis</Trans>
</MainWalletAction>
)}
</IconWrapper>
)
}
@@ -210,10 +201,6 @@ const WalletAction = styled(ButtonSecondary)`
}
`
const MainWalletAction = styled(WalletAction)`
color: ${({ theme }) => theme.primary1};
`
function renderTransactions(transactions: string[]) {
return (
<TransactionListWrapper>
@@ -323,11 +310,11 @@ export default function AccountDetails({
<AccountControl>
<div>
{account && (
<Copy toCopy={account}>
<CopyHelper toCopy={account} iconPosition="left">
<span style={{ marginLeft: '4px' }}>
<Trans>Copy Address</Trans>
</span>
</Copy>
</CopyHelper>
)}
{chainId && account && (
<AddressLink
@@ -349,11 +336,11 @@ export default function AccountDetails({
<AccountControl>
<div>
{account && (
<Copy toCopy={account}>
<CopyHelper toCopy={account} iconPosition="left">
<span style={{ marginLeft: '4px' }}>
<Trans>Copy Address</Trans>
</span>
</Copy>
</CopyHelper>
)}
{chainId && account && (
<AddressLink

View File

@@ -2,7 +2,6 @@ import { Trans } from '@lingui/macro'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { ReactNode, useMemo } from 'react'
// SDN OFAC addresses
const BLOCKED_ADDRESSES: string[] = [
'0x7Db418b5D567A4e0E8c59Ad71BE1FcE48f3E6107',
'0x72a5843cc08275C8171E582972Aa4fDa8C397B2A',
@@ -24,15 +23,18 @@ const BLOCKED_ADDRESSES: string[] = [
'0x6acdfba02d390b97ac2b2d42a63e85293bcc1',
'0x48549a34ae37b12f6a30566245176994e17c6',
'0x5512d943ed1f7c8a43f3435c85f7ab68b30121',
'0xc455f7fd3e0e12afd51fba5c106909934d8a0e',
'0x3cbded43efdaf0fc77b9c55f6fc9988fcc9b757d',
'0xC455f7fd3e0e12afd51fba5c106909934D8A0e4a',
'0x3CBdeD43EFdAf0FC77b9C55F6fC9988fCC9b757d',
'0x67d40EE1A85bf4a4Bb7Ffae16De985e8427B6b45',
'0x6f1ca141a28907f78ebaa64fb83a9088b02a8352',
'0x6acdfba02d390b97ac2b2d42a63e85293bcc160e',
'0x6F1cA141A28907F78Ebaa64fb83A9088b02A8352',
'0x6aCDFBA02D390b97Ac2b2d42A63E85293BCc160e',
'0x48549a34ae37b12f6a30566245176994e17c6b4a',
'0x5512d943ed1f7c8a43f3435c85f7ab68b30121b0',
'0xc455f7fd3e0e12afd51fba5c106909934d8a0e4a',
'0xC455f7fd3e0e12afd51fba5c106909934D8A0e4a',
'0x629e7Da20197a5429d30da36E77d06CdF796b71A',
'0x7FF9cFad3877F21d41Da833E2F775dB0569eE3D9',
'0x098B716B8Aaf21512996dC57EB0615e2383E2f96',
'0xfEC8A60023265364D066a1212fDE3930F6Ae8da7',
]
export default function Blocklist({ children }: { children: ReactNode }) {

View File

@@ -0,0 +1,58 @@
import { Trans } from '@lingui/macro'
import CopyHelper from 'components/AccountDetails/Copy'
import Column from 'components/Column'
import useTheme from 'hooks/useTheme'
import { AlertOctagon } from 'react-feather'
import styled from 'styled-components/macro'
import { ExternalLink, ThemedText } from 'theme'
import Modal from '../Modal'
const ContentWrapper = styled(Column)`
align-items: center;
margin: 32px;
text-align: center;
`
const WarningIcon = styled(AlertOctagon)`
min-height: 22px;
min-width: 22px;
color: ${({ theme }) => theme.warning};
`
const Copy = styled(CopyHelper)`
font-size: 12px;
`
interface ConnectedAccountBlockedProps {
account: string | null | undefined
isOpen: boolean
}
export default function ConnectedAccountBlocked(props: ConnectedAccountBlockedProps) {
const theme = useTheme()
return (
<Modal isOpen={props.isOpen} onDismiss={Function.prototype()}>
<ContentWrapper>
<WarningIcon />
<ThemedText.LargeHeader lineHeight={2} marginBottom={1} marginTop={1}>
<Trans>Blocked Address</Trans>
</ThemedText.LargeHeader>
<ThemedText.DarkGray fontSize={12} marginBottom={12}>
{props.account}
</ThemedText.DarkGray>
<ThemedText.Main fontSize={14} marginBottom={12}>
<Trans>This address is blocked on the Uniswap Labs interface because it is associated with one or more</Trans>{' '}
<ExternalLink href="https://help.uniswap.org/en/articles/6149816">
<Trans>blocked activities</Trans>
</ExternalLink>
.
</ThemedText.Main>
<ThemedText.Main fontSize={12}>
<Trans>If you believe this is an error, please send an email including your address to </Trans>{' '}
</ThemedText.Main>
<Copy iconSize={12} toCopy="compliance@uniswap.org" color={theme.primary1} iconPosition="right">
compliance@uniswap.org
</Copy>
</ContentWrapper>
</Modal>
)
}

View File

@@ -1,6 +1,6 @@
import { Trans } from '@lingui/macro'
import React, { ErrorInfo } from 'react'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import styled from 'styled-components/macro'
import store, { AppState } from '../../state'
@@ -49,6 +49,13 @@ type ErrorBoundaryState = {
const IS_UNISWAP = window.location.hostname === 'app.uniswap.org'
async function updateServiceWorker(): Promise<ServiceWorkerRegistration> {
const ready = await navigator.serviceWorker.ready
// the return type of update is incorrectly typed as Promise<void>. See
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/update
return ready.update() as unknown as Promise<ServiceWorkerRegistration>
}
export default class ErrorBoundary extends React.Component<unknown, ErrorBoundaryState> {
constructor(props: unknown) {
super(props)
@@ -56,15 +63,29 @@ export default class ErrorBoundary extends React.Component<unknown, ErrorBoundar
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
updateServiceWorker()
.then(async (registration) => {
// We want to refresh only if we detect a new service worker is waiting to be activated.
// See details about it: https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle
if (registration?.waiting) {
await registration.unregister()
// Makes Workbox call skipWaiting(). For more info on skipWaiting see: https://developer.chrome.com/docs/workbox/handling-service-worker-updates/
registration.waiting.postMessage({ type: 'SKIP_WAITING' })
// Once the service worker is unregistered, we can reload the page to let
// the browser download a fresh copy of our app (invalidating the cache)
window.location.reload()
}
})
.catch((error) => {
console.error('Failed to update service worker', error)
})
return { error }
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
ReactGA.exception({
...error,
...errorInfo,
fatal: true,
})
ReactGA.event('exception', { description: error.toString() + errorInfo.toString(), fatal: true })
}
render() {

View File

@@ -11,7 +11,7 @@ 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-ga'
import ReactGA from 'react-ga4'
import { Box } from 'rebass'
import styled, { keyframes } from 'styled-components/macro'
import { ThemedText } from 'theme'

View File

@@ -10,7 +10,7 @@ export const FEE_AMOUNT_DETAIL: Record<
[FeeAmount.LOWEST]: {
label: '0.01',
description: <Trans>Best for very stable pairs.</Trans>,
supportedChains: [SupportedChainId.MAINNET],
supportedChains: [SupportedChainId.MAINNET, SupportedChainId.POLYGON, SupportedChainId.POLYGON_MUMBAI],
},
[FeeAmount.LOW]: {
label: '0.05',

View File

@@ -146,7 +146,7 @@ const BridgeLabel = ({ chainId }: { chainId: SupportedChainId }) => {
return <Trans>Arbitrum Bridge</Trans>
case SupportedChainId.OPTIMISM:
case SupportedChainId.OPTIMISTIC_KOVAN:
return <Trans>Optimism Gateway</Trans>
return <Trans>Optimism Bridge</Trans>
case SupportedChainId.POLYGON:
case SupportedChainId.POLYGON_MUMBAI:
return <Trans>Polygon Bridge</Trans>
@@ -255,8 +255,8 @@ export default function NetworkSelector() {
const handleChainSwitch = useCallback(
(targetChain: number, skipToggle?: boolean) => {
if (!library) return
switchToNetwork({ library, chainId: targetChain })
if (!library?.provider) return
switchToNetwork({ provider: library.provider, chainId: targetChain })
.then(() => {
if (!skipToggle) {
toggle()

View File

@@ -3,9 +3,8 @@ import { AbstractConnector } from 'web3-react-abstract-connector'
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
import PortisIcon from '../../assets/images/portisIcon.png'
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
import { fortmatic, injected, portis, walletconnect, walletlink } from '../../connectors'
import { fortmatic, injected, walletconnect, walletlink } from '../../connectors'
import Identicon from '../Identicon'
export default function StatusIcon({ connector }: { connector: AbstractConnector | Connector }) {
@@ -18,8 +17,6 @@ export default function StatusIcon({ connector }: { connector: AbstractConnector
return <img src={CoinbaseWalletIcon} alt={'Coinbase Wallet'} />
case fortmatic:
return <img src={FortmaticIcon} alt={'Fortmatic'} />
case portis:
return <img src={PortisIcon} alt={'Portis'} />
default:
return null
}

View File

@@ -9,7 +9,7 @@ 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-ga'
import ReactGA from 'react-ga4'
import { batch } from 'react-redux'
import { Bound } from 'state/mint/v3/actions'
import styled from 'styled-components/macro'
@@ -158,11 +158,7 @@ export default function LiquidityChartRangeInput({
)
if (isError) {
ReactGA.exception({
...error,
category: 'Liquidity',
fatal: false,
})
ReactGA.event('exception', { description: error.toString(), fatal: false })
}
return (

View File

@@ -3,7 +3,7 @@ import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useCallback, useEffect } from 'react'
import { Heart, X } from 'react-feather'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import styled, { keyframes } from 'styled-components/macro'
import tokenLogo from '../../assets/images/token-logo.png'

View File

@@ -4,7 +4,7 @@ import { RowFixed } from 'components/Row'
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
import { useEffect } from 'react'
import { MessageCircle, X } from 'react-feather'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import { useShowSurveyPopup } from 'state/user/hooks'
import styled from 'styled-components/macro'
import { ExternalLink, ThemedText, Z_INDEX } from 'theme'

View File

@@ -3,7 +3,7 @@ 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-ga'
import ReactGA from 'react-ga4'
import styled from 'styled-components/macro'
import { ExternalLink, ThemedText } from 'theme'
import { isMobile } from 'utils/userAgent'

View File

@@ -2,7 +2,7 @@ import { Trans } from '@lingui/macro'
import { ButtonOutlined } from 'components/Button'
import { AutoRow } from 'components/Row'
import React from 'react'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'

View File

@@ -5,8 +5,8 @@ import Badge from 'components/Badge'
import CurrencyLogo from 'components/CurrencyLogo'
import DoubleCurrencyLogo from 'components/DoubleLogo'
import Row, { AutoRow } from 'components/Row'
import { RoutingDiagramEntry } from 'components/swap/SwapRoute'
import { useTokenInfoFromActiveList } from 'hooks/useTokenInfoFromActiveList'
import { RoutingDiagramEntry } from 'lib/components/Swap/RoutingDiagram/utils'
import { Box } from 'rebass'
import styled from 'styled-components/macro'
import { ThemedText, Z_INDEX } from 'theme'

View File

@@ -11,7 +11,7 @@ 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 { Edit } from 'react-feather'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import AutoSizer from 'react-virtualized-auto-sizer'
import { FixedSizeList } from 'react-window'
import { Text } from 'rebass'

View File

@@ -11,7 +11,7 @@ import useTheme from 'hooks/useTheme'
import { transparentize } from 'polished'
import { useCallback, useState } from 'react'
import { AlertTriangle, ArrowLeft } from 'react-feather'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import { useAppDispatch } from 'state/hooks'
import { enableList, removeList } from 'state/lists/actions'
import { useAllLists } from 'state/lists/hooks'

View File

@@ -9,7 +9,7 @@ import parseENSAddress from 'lib/utils/parseENSAddress'
import uriToHttp from 'lib/utils/uriToHttp'
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { CheckCircle, Settings } from 'react-feather'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import { usePopper } from 'react-popper'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import styled from 'styled-components/macro'

View File

@@ -5,7 +5,7 @@ import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { AUTO_ROUTER_SUPPORTED_CHAINS } from 'lib/hooks/routing/clientSideSmartOrderRouter'
import { useContext, useRef, useState } from 'react'
import { Settings, X } from 'react-feather'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import { Text } from 'rebass'
import styled, { ThemeContext } from 'styled-components/macro'

View File

@@ -18,6 +18,7 @@ export const TooltipContainer = styled.div`
interface TooltipProps extends Omit<PopoverProps, 'content'> {
text: ReactNode
disableHover?: boolean // disable the hover and content display
}
interface TooltipContentProps extends Omit<PopoverProps, 'content'> {
@@ -29,19 +30,20 @@ interface TooltipContentProps extends Omit<PopoverProps, 'content'> {
}
export default function Tooltip({ text, ...rest }: TooltipProps) {
return <Popover content={<TooltipContainer>{text}</TooltipContainer>} {...rest} />
return <Popover content={text && <TooltipContainer>{text}</TooltipContainer>} {...rest} />
}
function TooltipContent({ content, wrap = false, ...rest }: TooltipContentProps) {
return <Popover content={wrap ? <TooltipContainer>{content}</TooltipContainer> : content} {...rest} />
}
export function MouseoverTooltip({ children, ...rest }: Omit<TooltipProps, 'show'>) {
/** Standard text tooltip. */
export function MouseoverTooltip({ text, disableHover, children, ...rest }: Omit<TooltipProps, 'show'>) {
const [show, setShow] = useState(false)
const open = useCallback(() => setShow(true), [setShow])
const close = useCallback(() => setShow(false), [setShow])
return (
<Tooltip {...rest} show={show}>
<Tooltip {...rest} show={show} text={disableHover ? null : text}>
<div onMouseEnter={open} onMouseLeave={close}>
{children}
</div>
@@ -49,6 +51,7 @@ export function MouseoverTooltip({ children, ...rest }: Omit<TooltipProps, 'show
)
}
/** Tooltip that displays custom content. */
export function MouseoverTooltipContent({
content,
children,

View File

@@ -0,0 +1,23 @@
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 { ApplicationModal } from 'state/application/reducer'
export default function TopLevelModals() {
const addressClaimOpen = useModalOpen(ApplicationModal.ADDRESS_CLAIM)
const addressClaimToggle = useToggleModal(ApplicationModal.ADDRESS_CLAIM)
const blockedAccountModalOpen = useModalOpen(ApplicationModal.BLOCKED_ACCOUNT)
const { account } = useActiveWeb3React()
useAccountRiskCheck(account)
const open = Boolean(blockedAccountModalOpen && account)
return (
<>
<AddressClaimModal isOpen={addressClaimOpen} onDismiss={addressClaimToggle} />
<ConnectedAccountBlocked account={account} isOpen={open} />
</>
)
}

View File

@@ -1,12 +1,10 @@
import { Trans } from '@lingui/macro'
import { darken } from 'polished'
import { ButtonEmpty, ButtonPrimary } from 'components/Button'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import { AbstractConnector } from 'web3-react-abstract-connector'
import { injected } from '../../connectors'
import { SUPPORTED_WALLETS } from '../../constants/wallet'
import Loader from '../Loader'
import Option from './Option'
const PendingSection = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
@@ -18,18 +16,18 @@ const PendingSection = styled.div`
}
`
const StyledLoader = styled(Loader)`
margin-right: 1rem;
`
const LoadingMessage = styled.div<{ error?: boolean }>`
const LoaderContainer = styled.div`
margin: 16px 0;
${({ theme }) => theme.flexRowNoWrap};
align-items: center;
justify-content: flex-start;
justify-content: center;
`
const LoadingMessage = styled.div`
${({ theme }) => theme.flexRowNoWrap};
align-items: center;
justify-content: center;
border-radius: 12px;
margin-bottom: 20px;
color: ${({ theme, error }) => (error ? theme.red1 : 'inherit')};
border: 1px solid ${({ theme, error }) => (error ? theme.red1 : theme.text4)};
& > * {
padding: 1rem;
@@ -37,29 +35,13 @@ const LoadingMessage = styled.div<{ error?: boolean }>`
`
const ErrorGroup = styled.div`
${({ theme }) => theme.flexRowNoWrap};
${({ theme }) => theme.flexColumnNoWrap};
align-items: center;
justify-content: flex-start;
`
const ErrorButton = styled.div`
border-radius: 8px;
font-size: 12px;
color: ${({ theme }) => theme.text1};
background-color: ${({ theme }) => theme.bg4};
margin-left: 1rem;
padding: 0.5rem;
font-weight: 600;
user-select: none;
&:hover {
cursor: pointer;
background-color: ${({ theme }) => darken(0.1, theme.text4)};
}
`
const LoadingWrapper = styled.div`
${({ theme }) => theme.flexRowNoWrap};
${({ theme }) => theme.flexColumnNoWrap};
align-items: center;
justify-content: center;
`
@@ -69,65 +51,56 @@ export default function PendingView({
error = false,
setPendingError,
tryActivation,
resetAccountView,
}: {
connector?: AbstractConnector
error?: boolean
setPendingError: (error: boolean) => void
tryActivation: (connector: AbstractConnector) => void
resetAccountView: () => void
}) {
const isMetamask = window?.ethereum?.isMetaMask
return (
<PendingSection>
<LoadingMessage error={error}>
<LoadingMessage>
<LoadingWrapper>
{error ? (
<ErrorGroup>
<div>
<ThemedText.MediumHeader marginBottom={12}>
<Trans>Error connecting</Trans>
</div>
<ErrorButton
</ThemedText.MediumHeader>
<ThemedText.Small marginBottom={36} textAlign="center">
<Trans>
The connection attempt failed. Please click try again and follow the steps to connect in your wallet.
</Trans>
</ThemedText.Small>
<ButtonPrimary
$borderRadius="12px"
padding="12px"
onClick={() => {
setPendingError(false)
connector && tryActivation(connector)
}}
>
<Trans>Try Again</Trans>
</ErrorButton>
</ButtonPrimary>
<ButtonEmpty width="fit-content" padding="0" marginTop={20}>
<ThemedText.Link fontSize={12} onClick={resetAccountView}>
<Trans>Back to wallet selection</Trans>
</ThemedText.Link>
</ButtonEmpty>
</ErrorGroup>
) : (
<>
<StyledLoader />
<Trans>Initializing...</Trans>
<ThemedText.Black fontSize={20} marginY={16}>
<LoaderContainer>
<Loader stroke="currentColor" size="32px" />
</LoaderContainer>
<Trans>Connecting...</Trans>
</ThemedText.Black>
</>
)}
</LoadingWrapper>
</LoadingMessage>
{Object.keys(SUPPORTED_WALLETS).map((key) => {
const option = SUPPORTED_WALLETS[key]
if (option.connector === connector) {
if (option.connector === injected) {
if (isMetamask && option.name !== 'MetaMask') {
return null
}
if (!isMetamask && option.name === 'MetaMask') {
return null
}
}
return (
<Option
id={`connect-${key}`}
key={key}
clickable={false}
color={option.color}
header={option.name}
subheader={option.description}
icon={option.iconURL}
/>
)
}
return null
})}
</PendingSection>
)
}

View File

@@ -1,19 +1,19 @@
import { Trans } from '@lingui/macro'
import { AutoColumn } from 'components/Column'
import { PrivacyPolicy } from 'components/PrivacyPolicy'
import Row, { AutoRow, RowBetween } from 'components/Row'
import { useWalletConnectMonitoringEventCallback } from 'hooks/useMonitoringEventCallback'
import { useEffect, useState } from 'react'
import { ArrowLeft, ArrowRight, Info } from 'react-feather'
import ReactGA from 'react-ga'
import Row, { AutoRow } from 'components/Row'
import { useCallback, useEffect, useState } from 'react'
import { ArrowLeft } from 'react-feather'
import ReactGA from 'react-ga4'
import styled from 'styled-components/macro'
import { AbstractConnector } from 'web3-react-abstract-connector'
import { UnsupportedChainIdError, useWeb3React } from 'web3-react-core'
import { WalletConnectConnector } from 'web3-react-walletconnect-connector'
import MetamaskIcon from '../../assets/images/metamask.png'
import TallyIcon from '../../assets/images/tally.png'
import { ReactComponent as Close } from '../../assets/images/x.svg'
import { fortmatic, injected, portis } from '../../connectors'
import { fortmatic, injected } from '../../connectors'
import { OVERLAY_READY } from '../../connectors/Fortmatic'
import { SUPPORTED_WALLETS } from '../../constants/wallet'
import usePrevious from '../../hooks/usePrevious'
@@ -22,7 +22,7 @@ import { ApplicationModal } from '../../state/application/reducer'
import { ExternalLink, ThemedText } from '../../theme'
import { isMobile } from '../../utils/userAgent'
import AccountDetails from '../AccountDetails'
import Card, { LightCard } from '../Card'
import { LightCard } from '../Card'
import Modal from '../Modal'
import Option from './Option'
import PendingView from './PendingView'
@@ -109,16 +109,6 @@ const HoverText = styled.div`
}
`
const LinkCard = styled(Card)`
background-color: ${({ theme }) => theme.bg1};
color: ${({ theme }) => theme.text3};
:hover {
cursor: pointer;
filter: brightness(0.9);
}
`
const WALLET_VIEWS = {
OPTIONS: 'options',
OPTIONS_SECONDARY: 'options_secondary',
@@ -151,7 +141,10 @@ export default function WalletModal({
const previousAccount = usePrevious(account)
const logMonitoringEvent = useWalletConnectMonitoringEventCallback()
const resetAccountView = useCallback(() => {
setPendingError(false)
setWalletView(WALLET_VIEWS.ACCOUNT)
}, [setPendingError, setWalletView])
// close on connection, when logged out before
useEffect(() => {
@@ -163,10 +156,9 @@ export default function WalletModal({
// always reset to account view
useEffect(() => {
if (walletModalOpen) {
setPendingError(false)
setWalletView(WALLET_VIEWS.ACCOUNT)
resetAccountView()
}
}, [walletModalOpen])
}, [walletModalOpen, resetAccountView])
// close modal when a connection is successful
const activePrevious = usePrevious(active)
@@ -200,18 +192,13 @@ export default function WalletModal({
}
connector &&
activate(connector, undefined, true)
.then(async () => {
const walletAddress = await connector.getAccount()
logMonitoringEvent({ walletAddress })
})
.catch((error) => {
if (error instanceof UnsupportedChainIdError) {
activate(connector) // a little janky...can't use setError because the connector isn't set
} else {
setPendingError(true)
}
})
activate(connector, undefined, true).catch((error) => {
if (error instanceof UnsupportedChainIdError) {
activate(connector) // a little janky...can't use setError because the connector isn't set
} else {
setPendingError(true)
}
})
}
// close wallet modal if fortmatic modal is active
@@ -223,16 +210,12 @@ export default function WalletModal({
// get wallets user can switch too, depending on device/browser
function getOptions() {
const isMetamask = window.ethereum && window.ethereum.isMetaMask
const isMetamask = !!window.ethereum?.isMetaMask
const isTally = !!window.ethereum?.isTally
return Object.keys(SUPPORTED_WALLETS).map((key) => {
const option = SUPPORTED_WALLETS[key]
// check for mobile options
if (isMobile) {
//disable portis on mobile for now
if (option.connector === portis) {
return null
}
if (!window.web3 && !window.ethereum && option.mobile) {
return (
<Option
@@ -280,6 +263,24 @@ export default function WalletModal({
// likewise for generic
else if (option.name === 'Injected' && isMetamask) {
return null
} else if (option.name === 'Injected' && isTally) {
return (
<Option
id={`connect-${key}`}
key={key}
onClick={() => {
option.connector === connector
? setWalletView(WALLET_VIEWS.ACCOUNT)
: !option.href && tryActivation(option.connector)
}}
color={'#E8831D'}
header={<Trans>Tally</Trans>}
active={option.connector === connector}
subheader={null}
link={null}
icon={TallyIcon}
/>
)
}
}
@@ -371,12 +372,7 @@ export default function WalletModal({
</CloseIcon>
{walletView !== WALLET_VIEWS.ACCOUNT ? (
<HeaderRow color="blue">
<HoverText
onClick={() => {
setPendingError(false)
setWalletView(WALLET_VIEWS.ACCOUNT)
}}
>
<HoverText onClick={resetAccountView}>
<ArrowLeft />
</HoverText>
</HeaderRow>
@@ -390,39 +386,38 @@ export default function WalletModal({
<ContentWrapper>
<AutoColumn gap="16px">
<LightCard>
<AutoRow style={{ flexWrap: 'nowrap' }}>
<ThemedText.Black fontSize={14}>
<Trans>
By connecting a wallet, you agree to Uniswap Labs{' '}
<ExternalLink href="https://uniswap.org/terms-of-service/">Terms of Service</ExternalLink> and
acknowledge that you have read and understand the Uniswap{' '}
<ExternalLink href="https://uniswap.org/disclaimer/">Protocol Disclaimer</ExternalLink>.
</Trans>
</ThemedText.Black>
</AutoRow>
</LightCard>
{walletView === WALLET_VIEWS.PENDING ? (
{walletView === WALLET_VIEWS.PENDING && (
<PendingView
connector={pendingWallet}
error={pendingError}
setPendingError={setPendingError}
tryActivation={tryActivation}
resetAccountView={resetAccountView}
/>
) : (
<OptionGrid>{getOptions()}</OptionGrid>
)}
<LinkCard padding=".5rem" $borderRadius=".75rem" onClick={() => setWalletView(WALLET_VIEWS.LEGAL)}>
<RowBetween>
<AutoRow gap="4px">
<Info size={20} />
<ThemedText.Label fontSize={14}>
<Trans>How this app uses APIs</Trans>
</ThemedText.Label>
{walletView !== WALLET_VIEWS.PENDING && <OptionGrid>{getOptions()}</OptionGrid>}
{!pendingError && (
<LightCard>
<AutoRow style={{ flexWrap: 'nowrap' }}>
<ThemedText.Body fontSize={12}>
<Trans>
By connecting a wallet, you agree to Uniswap Labs{' '}
<ExternalLink
style={{ textDecoration: 'underline' }}
href="https://uniswap.org/terms-of-service/"
>
Terms of Service
</ExternalLink>{' '}
and acknowledge that you have read and understand the Uniswap{' '}
<ExternalLink style={{ textDecoration: 'underline' }} href="https://uniswap.org/disclaimer/">
Protocol Disclaimer
</ExternalLink>
.
</Trans>
</ThemedText.Body>
</AutoRow>
<ArrowRight size={16} />
</RowBetween>
</LinkCard>
</LightCard>
)}
</AutoColumn>
</ContentWrapper>
</UpperSection>

View File

@@ -13,7 +13,7 @@ import useENSName from '../../hooks/useENSName'
import { useHasSocks } from '../../hooks/useSocksBalance'
import { useWalletModalToggle } from '../../state/application/hooks'
import { isTransactionRecent, useAllTransactions } from '../../state/transactions/hooks'
import { TransactionDetails } from '../../state/transactions/reducer'
import { TransactionDetails } from '../../state/transactions/types'
import { shortenAddress } from '../../utils'
import { ButtonSecondary } from '../Button'
import StatusIcon from '../Identicon/StatusIcon'

View File

@@ -1,18 +1,13 @@
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useEffect } from 'react'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import { RouteComponentProps } from 'react-router-dom'
import { getCLS, getFCP, getFID, getLCP, Metric } from 'web-vitals'
import { GOOGLE_ANALYTICS_CLIENT_ID_STORAGE_KEY } from './index'
function reportWebVitals({ name, delta, id }: Metric) {
ReactGA.timing({
category: 'Web Vitals',
variable: name,
value: Math.round(name === 'CLS' ? delta * 1000 : delta),
label: id,
})
ReactGA._gaCommandSendTiming('Web Vitals', name, Math.round(name === 'CLS' ? delta * 1000 : delta), id)
}
// tracks web vitals and pageviews
@@ -35,7 +30,7 @@ export default function GoogleAnalyticsReporter({ location: { pathname, search }
}, [pathname, search])
useEffect(() => {
// typed as 'any' in react-ga -.-
// typed as 'any' in react-ga4 -.-
ReactGA.ga((tracker: any) => {
if (!tracker) return

View File

@@ -1,4 +1,4 @@
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import { isMobile } from 'utils/userAgent'
export const GOOGLE_ANALYTICS_CLIENT_ID_STORAGE_KEY = 'ga_client_id'
@@ -23,5 +23,5 @@ if (typeof GOOGLE_ANALYTICS_ID === 'string') {
: 'mobileRegular',
})
} else {
ReactGA.initialize('test', { testMode: true, debug: true })
ReactGA.initialize('test', { gtagOptions: { debug_mode: true } })
}

View File

@@ -7,8 +7,8 @@ import styled from 'styled-components/macro'
import { useContract } from '../../hooks/useContract'
import { StakingInfo } from '../../state/stake/hooks'
import { TransactionType } from '../../state/transactions/actions'
import { useTransactionAdder } from '../../state/transactions/hooks'
import { TransactionType } from '../../state/transactions/types'
import { CloseIcon, ThemedText } from '../../theme'
import { ButtonError } from '../Button'
import { AutoColumn } from '../Column'

View File

@@ -12,8 +12,8 @@ import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallbac
import { useContract, usePairContract, useV2RouterContract } from '../../hooks/useContract'
import useTransactionDeadline from '../../hooks/useTransactionDeadline'
import { StakingInfo, useDerivedStakeInfo } from '../../state/stake/hooks'
import { TransactionType } from '../../state/transactions/actions'
import { useTransactionAdder } from '../../state/transactions/hooks'
import { TransactionType } from '../../state/transactions/types'
import { CloseIcon, ThemedText } from '../../theme'
import { formatCurrencyAmount } from '../../utils/formatCurrencyAmount'
import { maxAmountSpend } from '../../utils/maxAmountSpend'

View File

@@ -7,8 +7,8 @@ import styled from 'styled-components/macro'
import { useContract } from '../../hooks/useContract'
import { StakingInfo } from '../../state/stake/hooks'
import { TransactionType } from '../../state/transactions/actions'
import { useTransactionAdder } from '../../state/transactions/hooks'
import { TransactionType } from '../../state/transactions/types'
import { CloseIcon, ThemedText } from '../../theme'
import { ButtonError } from '../Button'
import { AutoColumn } from '../Column'

View File

@@ -4,6 +4,7 @@ import Card from 'components/Card'
import { LoadingRows } from 'components/Loader/styled'
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { useContext, useMemo } from 'react'
import { InterfaceTrade } from 'state/routing/types'
import styled, { ThemeContext } from 'styled-components/macro'
@@ -12,6 +13,7 @@ import { Separator, ThemedText } from '../../theme'
import { computeRealizedLPFeePercent } from '../../utils/prices'
import { AutoColumn } from '../Column'
import { RowBetween, RowFixed } from '../Row'
import { MouseoverTooltip } from '../Tooltip'
import FormattedPriceImpact from './FormattedPriceImpact'
const StyledCard = styled(Card)`
@@ -23,6 +25,7 @@ interface AdvancedSwapDetailsProps {
allowedSlippage: Percent
syncing?: boolean
hideRouteDiagram?: boolean
hideInfoTooltips?: boolean
}
function TextWithLoadingPlaceholder({
@@ -43,9 +46,15 @@ function TextWithLoadingPlaceholder({
)
}
export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }: AdvancedSwapDetailsProps) {
export function AdvancedSwapDetails({
trade,
allowedSlippage,
syncing = false,
hideInfoTooltips = false,
}: AdvancedSwapDetailsProps) {
const theme = useContext(ThemeContext)
const { chainId } = useActiveWeb3React()
const nativeCurrency = useNativeCurrency()
const { expectedOutputAmount, priceImpact } = useMemo(() => {
if (!trade) return { expectedOutputAmount: undefined, priceImpact: undefined }
@@ -60,9 +69,19 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
<AutoColumn gap="8px">
<RowBetween>
<RowFixed>
<ThemedText.SubHeader color={theme.text1}>
<Trans>Expected Output</Trans>
</ThemedText.SubHeader>
<MouseoverTooltip
text={
<Trans>
The amount you expect to receive at the current market price. You may receive less or more if the
market price changes while your transaction is pending.
</Trans>
}
disableHover={hideInfoTooltips}
>
<ThemedText.SubHeader color={theme.text1}>
<Trans>Expected Output</Trans>
</ThemedText.SubHeader>
</MouseoverTooltip>
</RowFixed>
<TextWithLoadingPlaceholder syncing={syncing} width={65}>
<ThemedText.Black textAlign="right" fontSize={14}>
@@ -74,9 +93,14 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
</RowBetween>
<RowBetween>
<RowFixed>
<ThemedText.SubHeader color={theme.text1}>
<Trans>Price Impact</Trans>
</ThemedText.SubHeader>
<MouseoverTooltip
text={<Trans>The impact your trade has on the market price of this pool.</Trans>}
disableHover={hideInfoTooltips}
>
<ThemedText.SubHeader color={theme.text1}>
<Trans>Price Impact</Trans>
</ThemedText.SubHeader>
</MouseoverTooltip>
</RowFixed>
<TextWithLoadingPlaceholder syncing={syncing} width={50}>
<ThemedText.Black textAlign="right" fontSize={14}>
@@ -87,14 +111,24 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
<Separator />
<RowBetween>
<RowFixed style={{ marginRight: '20px' }}>
<ThemedText.SubHeader color={theme.text3}>
{trade.tradeType === TradeType.EXACT_INPUT ? (
<Trans>Minimum received</Trans>
) : (
<Trans>Maximum sent</Trans>
)}{' '}
<Trans>after slippage</Trans> ({allowedSlippage.toFixed(2)}%)
</ThemedText.SubHeader>
<MouseoverTooltip
text={
<Trans>
The minimum amount you are guaranteed to receive. If the price slips any further, your transaction
will revert.
</Trans>
}
disableHover={hideInfoTooltips}
>
<ThemedText.SubHeader color={theme.text3}>
{trade.tradeType === TradeType.EXACT_INPUT ? (
<Trans>Minimum received</Trans>
) : (
<Trans>Maximum sent</Trans>
)}{' '}
<Trans>after slippage</Trans> ({allowedSlippage.toFixed(2)}%)
</ThemedText.SubHeader>
</MouseoverTooltip>
</RowFixed>
<TextWithLoadingPlaceholder syncing={syncing} width={70}>
<ThemedText.Black textAlign="right" fontSize={14} color={theme.text3}>
@@ -106,9 +140,18 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
</RowBetween>
{!trade?.gasUseEstimateUSD || !chainId || !SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId) ? null : (
<RowBetween>
<ThemedText.SubHeader color={theme.text3}>
<Trans>Network Fee</Trans>
</ThemedText.SubHeader>
<MouseoverTooltip
text={
<Trans>
The fee paid to miners who process your transaction. This must be paid in {nativeCurrency.symbol}.
</Trans>
}
disableHover={hideInfoTooltips}
>
<ThemedText.SubHeader color={theme.text3}>
<Trans>Network Fee</Trans>
</ThemedText.SubHeader>
</MouseoverTooltip>
<TextWithLoadingPlaceholder syncing={syncing} width={50}>
<ThemedText.Black textAlign="right" fontSize={14} color={theme.text3}>
~${trade.gasUseEstimateUSD.toFixed(2)}

View File

@@ -4,7 +4,7 @@ import { AutoColumn } from 'components/Column'
import { LoadingOpacityContainer } from 'components/Loader/styled'
import { RowFixed } from 'components/Row'
import { MouseoverTooltipContent } from 'components/Tooltip'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import { InterfaceTrade } from 'state/routing/types'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'

View File

@@ -147,7 +147,12 @@ export default function SwapDetailsDropdown({
content={
<ResponsiveTooltipContainer origin="top right" style={{ padding: '0' }}>
<Card padding="12px">
<AdvancedSwapDetails trade={trade} allowedSlippage={allowedSlippage} syncing={syncing} />
<AdvancedSwapDetails
trade={trade}
allowedSlippage={allowedSlippage}
syncing={syncing}
hideInfoTooltips={true}
/>
</Card>
</ResponsiveTooltipContainer>
}

View File

@@ -1,5 +1,8 @@
import { Trans } from '@lingui/macro'
import { Currency, TradeType } from '@uniswap/sdk-core'
import { Protocol } from '@uniswap/router-sdk'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import { FeeAmount } from '@uniswap/v3-sdk'
import AnimatedDropdown from 'components/AnimatedDropdown'
import { AutoColumn } from 'components/Column'
import { LoadingRows } from 'components/Loader/styled'
@@ -8,7 +11,6 @@ import { AutoRow, RowBetween } from 'components/Row'
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useAutoRouterSupported from 'hooks/useAutoRouterSupported'
import { getTokenPath } from 'lib/components/Swap/RoutingDiagram/utils'
import { memo, useState } from 'react'
import { Plus } from 'react-feather'
import { InterfaceTrade } from 'state/routing/types'
@@ -106,3 +108,41 @@ export default memo(function SwapRoute({ trade, syncing, fixedOpen = false, ...r
</Wrapper>
)
})
export interface RoutingDiagramEntry {
percent: Percent
path: [Currency, Currency, FeeAmount][]
protocol: Protocol
}
const V2_DEFAULT_FEE_TIER = 3000
/**
* Loops through all routes on a trade and returns an array of diagram entries.
*/
export function getTokenPath(trade: InterfaceTrade<Currency, Currency, TradeType>): RoutingDiagramEntry[] {
return trade.swaps.map(({ route: { path: tokenPath, pools, protocol }, inputAmount, outputAmount }) => {
const portion =
trade.tradeType === TradeType.EXACT_INPUT
? inputAmount.divide(trade.inputAmount)
: outputAmount.divide(trade.outputAmount)
const percent = new Percent(portion.numerator, portion.denominator)
const path: RoutingDiagramEntry['path'] = []
for (let i = 0; i < pools.length; i++) {
const nextPool = pools[i]
const tokenIn = tokenPath[i]
const tokenOut = tokenPath[i + 1]
const entry: RoutingDiagramEntry['path'][0] = [
tokenIn,
tokenOut,
nextPool instanceof Pair ? V2_DEFAULT_FEE_TIER : nextPool.fee,
]
path.push(entry)
}
return {
percent,
path,
protocol,
}
})
}

View File

@@ -3,7 +3,6 @@ import { SafeAppConnector } from '@gnosis.pm/safe-apps-web3-react'
import { ALL_SUPPORTED_CHAIN_IDS, SupportedChainId } from 'constants/chains'
import { INFURA_NETWORK_URLS } from 'constants/infura'
import { InjectedConnector } from 'web3-react-injected-connector'
import { PortisConnector } from 'web3-react-portis-connector'
import { WalletConnectConnector } from 'web3-react-walletconnect-connector'
import { WalletLinkConnector } from 'web3-react-walletlink-connector'
@@ -13,7 +12,6 @@ import { FortmaticConnector } from './Fortmatic'
import { NetworkConnector } from './NetworkConnector'
const FORMATIC_KEY = process.env.REACT_APP_FORTMATIC_KEY
const PORTIS_ID = process.env.REACT_APP_PORTIS_ID
export const network = new NetworkConnector({
urls: INFURA_NETWORK_URLS,
@@ -43,12 +41,6 @@ export const fortmatic = new FortmaticConnector({
chainId: 1,
})
// mainnet only
export const portis = new PortisConnector({
dAppId: PORTIS_ID ?? '',
networks: [1],
})
export const walletlink = new WalletLinkConnector({
url: INFURA_NETWORK_URLS[SupportedChainId.MAINNET],
appName: 'Uniswap',

View File

@@ -94,7 +94,7 @@ export const CHAIN_INFO: ChainInfoMap = {
[SupportedChainId.OPTIMISM]: {
networkType: NetworkType.L2,
blockWaitMsBeforeWarning: ms`25m`,
bridge: 'https://gateway.optimism.io/?chainId=1',
bridge: 'https://app.optimism.io/bridge',
defaultListUrl: OPTIMISM_LIST,
docs: 'https://optimism.io/',
explorer: 'https://optimistic.etherscan.io/',
@@ -108,7 +108,7 @@ export const CHAIN_INFO: ChainInfoMap = {
[SupportedChainId.OPTIMISTIC_KOVAN]: {
networkType: NetworkType.L2,
blockWaitMsBeforeWarning: ms`25m`,
bridge: 'https://gateway.optimism.io/',
bridge: 'https://app.optimism.io/bridge',
defaultListUrl: OPTIMISM_LIST,
docs: 'https://optimism.io/',
explorer: 'https://optimistic.etherscan.io/',

View File

@@ -1,6 +1,7 @@
import {
GOVERNANCE_ALPHA_V0_ADDRESSES,
GOVERNANCE_ALPHA_V1_ADDRESSES,
GOVERNANCE_BRAVO_ADDRESSES,
TIMELOCK_ADDRESS,
UNI_ADDRESS,
} from './addresses'
@@ -11,7 +12,8 @@ export const COMMON_CONTRACT_NAMES: Record<number, { [address: string]: string }
[UNI_ADDRESS[SupportedChainId.MAINNET]]: 'UNI',
[TIMELOCK_ADDRESS[SupportedChainId.MAINNET]]: 'Timelock',
[GOVERNANCE_ALPHA_V0_ADDRESSES[SupportedChainId.MAINNET]]: 'Governance (V0)',
[GOVERNANCE_ALPHA_V1_ADDRESSES[SupportedChainId.MAINNET]]: 'Governance',
[GOVERNANCE_ALPHA_V1_ADDRESSES[SupportedChainId.MAINNET]]: 'Governance (V1)',
[GOVERNANCE_BRAVO_ADDRESSES[SupportedChainId.MAINNET]]: 'Governance',
'0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e': 'ENS Registry',
'0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41': 'ENS Public Resolver',
},

View File

@@ -35,10 +35,7 @@ export const SUPPORTED_LOCALES = [
]
export type SupportedLocale = typeof SUPPORTED_LOCALES[number] | 'pseudo'
// eslint-disable-next-line import/first
import * as enUS from 'locales/en-US'
export const DEFAULT_LOCALE: SupportedLocale = 'en-US'
export const DEFAULT_CATALOG = enUS
export const LOCALE_LABEL: { [locale in SupportedLocale]: string } = {
'af-ZA': 'Afrikaans',

View File

@@ -2,3 +2,4 @@ export const UNISWAP_GRANTS_START_BLOCK = 11473815
export const BRAVO_START_BLOCK = 13059344
export const ONE_BIP_START_BLOCK = 13551293
export const POLYGON_START_BLOCK = 13786993
export const MOONBEAN_START_BLOCK = 14732457

View File

@@ -1,23 +1,86 @@
import { Currency, Ether, NativeCurrency, Token, WETH9 } from '@uniswap/sdk-core'
import {
USDC_ARBITRUM,
USDC_ARBITRUM_RINKEBY,
USDC_GÖRLI,
USDC_KOVAN,
USDC_MAINNET,
USDC_OPTIMISM,
USDC_OPTIMISTIC_KOVAN,
USDC_POLYGON,
USDC_POLYGON_MUMBAI,
USDC_RINKEBY,
USDC_ROPSTEN,
} from '@uniswap/smart-order-router'
import invariant from 'tiny-invariant'
import { UNI_ADDRESS } from './addresses'
import { SupportedChainId } from './chains'
export { USDC_ARBITRUM, USDC_MAINNET, USDC_OPTIMISM, USDC_POLYGON }
export const USDC_MAINNET = new Token(
SupportedChainId.MAINNET,
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
6,
'USDC',
'USD//C'
)
export const USDC_ROPSTEN = new Token(
SupportedChainId.ROPSTEN,
'0x07865c6e87b9f70255377e024ace6630c1eaa37f',
6,
'USDC',
'USD//C'
)
export const USDC_RINKEBY = new Token(
SupportedChainId.RINKEBY,
'0x4DBCdF9B62e891a7cec5A2568C3F4FAF9E8Abe2b',
6,
'tUSDC',
'test USD//C'
)
export const USDC_GOERLI = new Token(
SupportedChainId.GOERLI,
'0x07865c6e87b9f70255377e024ace6630c1eaa37f',
6,
'USDC',
'USD//C'
)
export const USDC_KOVAN = new Token(
SupportedChainId.KOVAN,
'0x31eeb2d0f9b6fd8642914ab10f4dd473677d80df',
6,
'USDC',
'USD//C'
)
export const USDC_OPTIMISM = new Token(
SupportedChainId.OPTIMISM,
'0x7F5c764cBc14f9669B88837ca1490cCa17c31607',
6,
'USDC',
'USD//C'
)
export const USDC_OPTIMISTIC_KOVAN = new Token(
SupportedChainId.OPTIMISTIC_KOVAN,
'0x3b8e53b3ab8e01fb57d0c9e893bc4d655aa67d84',
6,
'USDC',
'USD//C'
)
export const USDC_ARBITRUM = new Token(
SupportedChainId.ARBITRUM_ONE,
'0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8',
6,
'USDC',
'USD//C'
)
export const USDC_ARBITRUM_RINKEBY = new Token(
SupportedChainId.ARBITRUM_RINKEBY,
'0x09b98f8b2395d076514037ff7d39a091a536206c',
6,
'USDC',
'USD//C'
)
export const USDC_POLYGON = new Token(
SupportedChainId.POLYGON,
'0x2791bca1f2de4661ed88a30c99a7a9449aa84174',
6,
'USDC',
'USD//C'
)
export const USDC_POLYGON_MUMBAI = new Token(
SupportedChainId.POLYGON_MUMBAI,
'0xe11a86849d99f524cac3e7a0ec1241828e332c62',
6,
'USDC',
'USD//C'
)
export const AMPL = new Token(
SupportedChainId.MAINNET,
@@ -55,7 +118,7 @@ export const USDC: { [chainId in SupportedChainId]: Token } = {
[SupportedChainId.OPTIMISTIC_KOVAN]: USDC_OPTIMISTIC_KOVAN,
[SupportedChainId.POLYGON]: USDC_POLYGON,
[SupportedChainId.POLYGON_MUMBAI]: USDC_POLYGON_MUMBAI,
[SupportedChainId.GOERLI]: USDC_GÖRLI,
[SupportedChainId.GOERLI]: USDC_GOERLI,
[SupportedChainId.RINKEBY]: USDC_RINKEBY,
[SupportedChainId.KOVAN]: USDC_KOVAN,
[SupportedChainId.ROPSTEN]: USDC_ROPSTEN,
@@ -310,7 +373,7 @@ export const TOKEN_SHORTHANDS: { [shorthand: string]: { [chainId in SupportedCha
[SupportedChainId.OPTIMISTIC_KOVAN]: USDC_OPTIMISTIC_KOVAN.address,
[SupportedChainId.POLYGON]: USDC_POLYGON.address,
[SupportedChainId.POLYGON_MUMBAI]: USDC_POLYGON_MUMBAI.address,
[SupportedChainId.GOERLI]: USDC_GÖRLI.address,
[SupportedChainId.GOERLI]: USDC_GOERLI.address,
[SupportedChainId.RINKEBY]: USDC_RINKEBY.address,
[SupportedChainId.KOVAN]: USDC_KOVAN.address,
[SupportedChainId.ROPSTEN]: USDC_ROPSTEN.address,

View File

@@ -4,9 +4,8 @@ import INJECTED_ICON_URL from '../assets/images/arrow-right.svg'
import COINBASE_ICON_URL from '../assets/images/coinbaseWalletIcon.svg'
import FORTMATIC_ICON_URL from '../assets/images/fortmaticIcon.png'
import METAMASK_ICON_URL from '../assets/images/metamask.png'
import PORTIS_ICON_URL from '../assets/images/portisIcon.png'
import WALLETCONNECT_ICON_URL from '../assets/images/walletConnectIcon.svg'
import { fortmatic, injected, portis, walletconnect, walletlink } from '../connectors'
import { fortmatic, injected, walletconnect, walletlink } from '../connectors'
interface WalletInfo {
connector?: AbstractConnector
@@ -73,13 +72,4 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
color: '#6748FF',
mobile: true,
},
Portis: {
connector: portis,
name: 'Portis',
iconURL: PORTIS_ICON_URL,
description: 'Login using Portis hosted wallet',
href: null,
color: '#4A6C9B',
mobile: true,
},
}

View File

@@ -0,0 +1,33 @@
import { useEffect } from 'react'
import ReactGA from 'react-ga4'
import { ApplicationModal, setOpenModal } from 'state/application/reducer'
import { useAppDispatch } from 'state/hooks'
export default function useAccountRiskCheck(account: string | null | undefined) {
const dispatch = useAppDispatch()
useEffect(() => {
if (account) {
const headers = new Headers({ 'Content-Type': 'application/json' })
fetch('https://screening-worker.uniswap.workers.dev', {
method: 'POST',
headers,
body: JSON.stringify({ address: account }),
})
.then((res) => res.json())
.then((data) => {
if (data.block) {
dispatch(setOpenModal(ApplicationModal.BLOCKED_ACCOUNT))
ReactGA.event({
category: 'Address Screening',
action: 'blocked',
label: account,
})
}
})
.catch(() => {
dispatch(setOpenModal(null))
})
}
}, [account, dispatch])
}

View File

@@ -1,15 +1,10 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { Web3Provider } from '@ethersproject/providers'
import { default as useWidgetsWeb3React } from 'lib/hooks/useActiveWeb3React'
import { useWeb3React } from 'web3-react-core'
import { NetworkContextName } from '../constants/misc'
export default function useActiveWeb3React() {
if (process.env.REACT_APP_IS_WIDGET) {
return useWidgetsWeb3React()
}
const interfaceContext = useWeb3React<Web3Provider>()
const interfaceNetworkContext = useWeb3React<Web3Provider>(
process.env.REACT_APP_IS_WIDGET ? undefined : NetworkContextName

View File

@@ -15,7 +15,7 @@ export default function useAddTokenToMetamask(currencyToAdd: Currency | undefine
const logoURL = useCurrencyLogoURIs(token)[0]
const addToken = useCallback(() => {
if (library && library.provider.isMetaMask && library.provider.request && token) {
if (library && library?.provider?.isMetaMask && library.provider.request && token) {
library.provider
.request({
method: 'wallet_watchAsset',

View File

@@ -1,7 +1,7 @@
import { useEffect } from 'react'
import { useAppDispatch } from 'state/hooks'
import { updateUserExpertMode } from '../state/user/actions'
import { updateUserExpertMode } from '../state/user/reducer'
import useParsedQueryString from './useParsedQueryString'
export default function ApeModeQueryParamReader(): null {

View File

@@ -6,8 +6,8 @@ import useSwapApproval, { useSwapApprovalOptimizedTrade } from 'lib/hooks/swap/u
import { ApprovalState, useApproval } from 'lib/hooks/useApproval'
import { useCallback } from 'react'
import { TransactionType } from '../state/transactions/actions'
import { useHasPendingApproval, useTransactionAdder } from '../state/transactions/hooks'
import { TransactionType } from '../state/transactions/types'
export { ApprovalState } from 'lib/hooks/useApproval'
function useGetAndTrackApproval(getApproval: ReturnType<typeof useApproval>[1]) {

View File

@@ -13,6 +13,7 @@ import useUSDCPrice, { useUSDCValue } from './useUSDCPrice'
const V3_SWAP_DEFAULT_SLIPPAGE = new Percent(50, 10_000) // .50%
const ONE_TENTHS_PERCENT = new Percent(10, 10_000) // .10%
export const DEFAULT_AUTO_SLIPPAGE = ONE_TENTHS_PERCENT
/**
* Return a guess of the gas cost used in computing slippage tolerance for a given trade
@@ -41,10 +42,10 @@ export default function useAutoSlippageTolerance(
const gasEstimate = guesstimateGas(trade)
const nativeCurrency = useNativeCurrency()
const nativeCurrencyPrice = useUSDCPrice(nativeCurrency ?? undefined)
const nativeCurrencyPrice = useUSDCPrice((trade && nativeCurrency) ?? undefined)
return useMemo(() => {
if (!trade || onL2) return ONE_TENTHS_PERCENT
if (!trade || onL2) return DEFAULT_AUTO_SLIPPAGE
const nativeGasCost =
nativeGasPrice && typeof gasEstimate === 'number'

View File

@@ -28,26 +28,24 @@ export function useClientSideV3Trade<TTradeType extends TradeType>(
amountSpecified?: CurrencyAmount<Currency>,
otherCurrency?: Currency
): { state: TradeState; trade: InterfaceTrade<Currency, Currency, TTradeType> | undefined } {
const [currencyIn, currencyOut] = useMemo(
() =>
tradeType === TradeType.EXACT_INPUT
? [amountSpecified?.currency, otherCurrency]
: [otherCurrency, amountSpecified?.currency],
[tradeType, amountSpecified, otherCurrency]
)
const [currencyIn, currencyOut] =
tradeType === TradeType.EXACT_INPUT
? [amountSpecified?.currency, otherCurrency]
: [otherCurrency, amountSpecified?.currency]
const { routes, loading: routesLoading } = useAllV3Routes(currencyIn, currencyOut)
const quoter = useV3Quoter()
const { chainId } = useActiveWeb3React()
const quotesResults = useSingleContractWithCallData(
quoter,
amountSpecified
? routes.map((route) => SwapQuoter.quoteCallParameters(route, amountSpecified, tradeType).calldata)
: [],
{
gasRequired: chainId ? QUOTE_GAS_OVERRIDES[chainId] ?? DEFAULT_GAS_QUOTE : undefined,
}
const callData = useMemo(
() =>
amountSpecified
? routes.map((route) => SwapQuoter.quoteCallParameters(route, amountSpecified, tradeType).calldata)
: [],
[amountSpecified, routes, tradeType]
)
const quotesResults = useSingleContractWithCallData(quoter, callData, {
gasRequired: chainId ? QUOTE_GAS_OVERRIDES[chainId] ?? DEFAULT_GAS_QUOTE : undefined,
})
return useMemo(() => {
if (

View File

@@ -3,7 +3,7 @@ import { SupportedChainId } from 'constants/chains'
import uriToHttp from 'lib/utils/uriToHttp'
import Vibrant from 'node-vibrant/lib/bundle.js'
import { shade } from 'polished'
import { useLayoutEffect, useState } from 'react'
import { useEffect, useState } from 'react'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
import { hex } from 'wcag-contrast'
@@ -64,7 +64,7 @@ async function getColorFromUriPath(uri: string): Promise<string | null> {
export function useColor(token?: Token) {
const [color, setColor] = useState('#2172E5')
useLayoutEffect(() => {
useEffect(() => {
let stale = false
if (token) {
@@ -87,7 +87,7 @@ export function useColor(token?: Token) {
export function useListColor(listImageUri?: string) {
const [color, setColor] = useState('#2172E5')
useLayoutEffect(() => {
useEffect(() => {
let stale = false
if (listImageUri) {

View File

@@ -4,7 +4,7 @@ import { FeeAmount } from '@uniswap/v3-sdk'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import ms from 'ms.macro'
import { useMemo } from 'react'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import { useFeeTierDistributionQuery } from 'state/data/enhanced'
import { FeeTierDistributionQuery } from 'state/data/generated'
@@ -112,9 +112,7 @@ function usePoolTVL(token0: Token | undefined, token1: Token | undefined) {
}
if (latestBlock - (_meta?.block?.number ?? 0) > MAX_DATA_BLOCK_AGE) {
ReactGA.exception({
description: `Graph stale (latest block: ${latestBlock})`,
})
ReactGA.event('exception', { description: `Graph stale (latest block: ${latestBlock})` })
return {
isLoading,

View File

@@ -12,13 +12,14 @@ function isWindowVisible() {
* Returns whether the window is currently visible to the user.
*/
export default function useIsWindowVisible(): boolean {
const [focused, setFocused] = useState<boolean>(isWindowVisible())
const [focused, setFocused] = useState<boolean>(false)
const listener = useCallback(() => {
setFocused(isWindowVisible())
}, [setFocused])
useEffect(() => {
if (!isVisibilityStateSupported()) return undefined
setFocused((focused) => isWindowVisible())
document.addEventListener('visibilitychange', listener)
return () => {

View File

@@ -3,7 +3,7 @@ import { LocationDescriptor } from 'history'
import useParsedQueryString from 'hooks/useParsedQueryString'
import { stringify } from 'qs'
import { useMemo } from 'react'
import ReactGA from 'react-ga'
import ReactGA from 'react-ga4'
import { useLocation } from 'react-router-dom'
import { useActiveLocale } from './useActiveLocale'

View File

@@ -1,98 +0,0 @@
import { TransactionResponse } from '@ethersproject/providers'
import { initializeApp } from 'firebase/app'
import { getDatabase, push, ref } from 'firebase/database'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useCallback } from 'react'
import { TransactionInfo, TransactionType } from 'state/transactions/actions'
type PartialTransactionResponse = Pick<TransactionResponse, 'hash' | 'v' | 'r' | 's'>
const SUPPORTED_TRANSACTION_TYPES = [
TransactionType.ADD_LIQUIDITY_V2_POOL,
TransactionType.ADD_LIQUIDITY_V3_POOL,
TransactionType.CREATE_V3_POOL,
TransactionType.REMOVE_LIQUIDITY_V3,
TransactionType.SWAP,
]
const FIREBASE_API_KEY = process.env.REACT_APP_FIREBASE_KEY
const firebaseEnabled = typeof FIREBASE_API_KEY !== 'undefined'
if (firebaseEnabled) initializeFirebase()
function useMonitoringEventCallback() {
const { chainId } = useActiveWeb3React()
return useCallback(
async function log(
type: string,
{
transactionResponse,
walletAddress,
}: { transactionResponse: PartialTransactionResponse; walletAddress: string | undefined }
) {
if (!firebaseEnabled) return
const db = getDatabase()
if (!walletAddress) {
console.debug('Wallet address required to log monitoring events.')
return
}
try {
push(ref(db, 'trm'), {
chainId,
origin: window.location.origin,
timestamp: Date.now(),
tx: transactionResponse,
type,
walletAddress,
})
} catch (e) {
console.debug('Error adding document: ', e)
}
},
[chainId]
)
}
export function useTransactionMonitoringEventCallback() {
const { account } = useActiveWeb3React()
const log = useMonitoringEventCallback()
return useCallback(
(info: TransactionInfo, transactionResponse: TransactionResponse) => {
if (SUPPORTED_TRANSACTION_TYPES.includes(info.type)) {
log(TransactionType[info.type], {
transactionResponse: (({ hash, v, r, s }: PartialTransactionResponse) => ({ hash, v, r, s }))(
transactionResponse
),
walletAddress: account ?? undefined,
})
}
},
[account, log]
)
}
export function useWalletConnectMonitoringEventCallback() {
const log = useMonitoringEventCallback()
return useCallback(
(walletAddress) => {
log('WALLET_CONNECTED', { transactionResponse: { hash: '', r: '', s: '', v: -1 }, walletAddress })
},
[log]
)
}
function initializeFirebase() {
initializeApp({
apiKey: process.env.REACT_APP_FIREBASE_KEY,
authDomain: 'interface-monitoring.firebaseapp.com',
databaseURL: 'https://interface-monitoring-default-rtdb.firebaseio.com',
projectId: 'interface-monitoring',
storageBucket: 'interface-monitoring.appspot.com',
messagingSenderId: '968187720053',
appId: '1:968187720053:web:acedf72dce629d470be33c',
})
}

View File

@@ -1,7 +1,7 @@
import { skipToken } from '@reduxjs/toolkit/query/react'
import { Currency } from '@uniswap/sdk-core'
import { ChainId } from '@uniswap/smart-order-router'
import { FeeAmount, nearestUsableTick, Pool, TICK_SPACINGS, tickToPrice } from '@uniswap/v3-sdk'
import { SupportedChainId } from 'constants/chains'
import { ZERO_ADDRESS } from 'constants/misc'
import JSBI from 'jsbi'
import { useSingleContractMultipleData } from 'lib/hooks/multicall'
@@ -14,7 +14,7 @@ import { useTickLens } from './useContract'
import { PoolState, usePool } from './usePools'
const PRICE_FIXED_DIGITS = 8
const CHAIN_IDS_MISSING_SUBGRAPH_DATA = [ChainId.ARBITRUM_ONE, ChainId.ARBITRUM_RINKEBY]
const CHAIN_IDS_MISSING_SUBGRAPH_DATA = [SupportedChainId.ARBITRUM_ONE, SupportedChainId.ARBITRUM_RINKEBY]
export interface TickData {
tick: number

View File

@@ -1,6 +1,6 @@
import { Interface } from '@ethersproject/abi'
import { BigintIsh, Currency, Token } from '@uniswap/sdk-core'
import IUniswapV3PoolStateJson from '@uniswap/v3-core/artifacts/contracts/interfaces/pool/IUniswapV3PoolState.sol/IUniswapV3PoolState.json'
import { abi as IUniswapV3PoolStateABI } from '@uniswap/v3-core/artifacts/contracts/interfaces/pool/IUniswapV3PoolState.sol/IUniswapV3PoolState.json'
import { computePoolAddress } from '@uniswap/v3-sdk'
import { FeeAmount, Pool } from '@uniswap/v3-sdk'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
@@ -11,15 +11,41 @@ import { useMemo } from 'react'
import { V3_CORE_FACTORY_ADDRESSES } from '../constants/addresses'
import { IUniswapV3PoolStateInterface } from '../types/v3/IUniswapV3PoolState'
const { abi: IUniswapV3PoolStateABI } = IUniswapV3PoolStateJson
const POOL_STATE_INTERFACE = new Interface(IUniswapV3PoolStateABI) as IUniswapV3PoolStateInterface
// Classes are expensive to instantiate, so this caches the recently instantiated pools.
// This avoids re-instantiating pools as the other pools in the same request are loaded.
class PoolCache {
// pools is a FIFO, using unshift/pop. This makes recent entries faster to find.
// Evict after 128 entries. Empirically, a swap uses 64 entries.
private static MAX_ENTRIES = 128
// These are FIFOs, using unshift/pop. This makes recent entries faster to find.
private static pools: Pool[] = []
private static addresses: { key: string; address: string }[] = []
static getPoolAddress(factoryAddress: string, tokenA: Token, tokenB: Token, fee: FeeAmount): string {
if (this.addresses.length > this.MAX_ENTRIES) {
this.addresses = this.addresses.slice(0, this.MAX_ENTRIES / 2)
}
const { address: addressA } = tokenA
const { address: addressB } = tokenB
const key = `${factoryAddress}:${addressA}:${addressB}:${fee.toString()}`
const found = this.addresses.find((address) => address.key === key)
if (found) return found.address
const address = {
key,
address: computePoolAddress({
factoryAddress,
tokenA,
tokenB,
fee,
}),
}
this.addresses.unshift(address)
return address.address
}
static getPool(
tokenA: Token,
@@ -29,9 +55,8 @@ class PoolCache {
liquidity: BigintIsh,
tick: number
): Pool {
// Evict after 128 entries. Empirically, a swap uses 64 entries.
if (this.pools.length > 128) {
this.pools = this.pools.slice(0, 64)
if (this.pools.length > this.MAX_ENTRIES) {
this.pools = this.pools.slice(0, this.MAX_ENTRIES / 2)
}
const found = this.pools.find(
@@ -43,9 +68,7 @@ class PoolCache {
JSBI.EQ(pool.liquidity, liquidity) &&
pool.tickCurrent === tick
)
if (found) {
return found
}
if (found) return found
const pool = new Pool(tokenA, tokenB, fee, sqrtPriceX96, liquidity, tick)
this.pools.unshift(pool)
@@ -84,16 +107,7 @@ export function usePools(
const v3CoreFactoryAddress = chainId && V3_CORE_FACTORY_ADDRESSES[chainId]
if (!v3CoreFactoryAddress) return new Array(poolTokens.length)
return poolTokens.map(
(value) =>
value &&
computePoolAddress({
factoryAddress: v3CoreFactoryAddress,
tokenA: value[0],
tokenB: value[1],
fee: value[2],
})
)
return poolTokens.map((value) => value && PoolCache.getPoolAddress(v3CoreFactoryAddress, ...value))
}, [chainId, poolTokens])
const slot0s = useMultipleContractSingleData(poolAddresses, POOL_STATE_INTERFACE, 'slot0')

View File

@@ -4,8 +4,8 @@ import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { SwapCallbackState, useSwapCallback as useLibSwapCallBack } from 'lib/hooks/swap/useSwapCallback'
import { ReactNode, useMemo } from 'react'
import { TransactionType } from '../state/transactions/actions'
import { useTransactionAdder } from '../state/transactions/hooks'
import { TransactionType } from '../state/transactions/types'
import { currencyId } from '../utils/currencyId'
import useENS from './useENS'
import { SignatureData } from './useERC20Permit'

View File

@@ -1,7 +1,7 @@
import { Currency, CurrencyAmount, Price, Token, TradeType } from '@uniswap/sdk-core'
import useActiveWeb3React from 'hooks/useActiveWeb3React'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { useMemo } from 'react'
import { useMemo, useRef } from 'react'
import { SupportedChainId } from '../constants/chains'
import { DAI_OPTIMISM, USDC_ARBITRUM, USDC_MAINNET, USDC_POLYGON } from '../constants/tokens'
@@ -32,8 +32,7 @@ export default function useUSDCPrice(currency?: Currency): Price<Currency, Token
maxHops: 2,
})
const v3USDCTrade = useClientSideV3Trade(TradeType.EXACT_OUTPUT, amountOut, currency)
return useMemo(() => {
const price = useMemo(() => {
if (!currency || !stablecoin) {
return undefined
}
@@ -54,6 +53,12 @@ export default function useUSDCPrice(currency?: Currency): Price<Currency, Token
return undefined
}, [currency, stablecoin, v2USDCTrade, v3USDCTrade.trade])
const lastPrice = useRef(price)
if (!price || !lastPrice.current || !price.equalTo(lastPrice.current)) {
lastPrice.current = price
}
return lastPrice.current
}
export function useUSDCValue(currencyAmount: CurrencyAmount<Currency> | undefined | null) {

View File

@@ -1,14 +1,12 @@
import { Interface } from '@ethersproject/abi'
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import IUniswapV2PairJson from '@uniswap/v2-core/build/IUniswapV2Pair.json'
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
import { computePairAddress, Pair } from '@uniswap/v2-sdk'
import { useMultipleContractSingleData } from 'lib/hooks/multicall'
import { useMemo } from 'react'
import { V2_FACTORY_ADDRESSES } from '../constants/addresses'
const { abi: IUniswapV2PairABI } = IUniswapV2PairJson
const PAIR_INTERFACE = new Interface(IUniswapV2PairABI)
export enum PairState {

View File

@@ -23,13 +23,11 @@ export function useV3PositionFees(
const tokenIdHexString = tokenId?.toHexString()
const latestBlockNumber = useBlockNumber()
// TODO find a way to get this into multicall
// we can't use multicall for this because we need to simulate the call from a specific address
// latestBlockNumber is included to ensure data stays up-to-date every block
const [amounts, setAmounts] = useState<[BigNumber, BigNumber]>()
const [amounts, setAmounts] = useState<[BigNumber, BigNumber] | undefined>()
useEffect(() => {
let stale = false
if (positionManager && tokenIdHexString && owner && typeof latestBlockNumber === 'number') {
if (positionManager && tokenIdHexString && owner) {
positionManager.callStatic
.collect(
{
@@ -41,19 +39,15 @@ export function useV3PositionFees(
{ from: owner } // need to simulate the call as the owner
)
.then((results) => {
if (!stale) setAmounts([results.amount0, results.amount1])
setAmounts([results.amount0, results.amount1])
})
}
return () => {
stale = true
}
}, [positionManager, tokenIdHexString, owner, latestBlockNumber])
if (pool && amounts) {
return [
CurrencyAmount.fromRawAmount(!asWETH ? unwrappedToken(pool.token0) : pool.token0, amounts[0].toString()),
CurrencyAmount.fromRawAmount(!asWETH ? unwrappedToken(pool.token1) : pool.token1, amounts[1].toString()),
CurrencyAmount.fromRawAmount(asWETH ? pool.token0 : unwrappedToken(pool.token0), amounts[0].toString()),
CurrencyAmount.fromRawAmount(asWETH ? pool.token1 : unwrappedToken(pool.token1), amounts[1].toString()),
]
} else {
return [undefined, undefined]

View File

@@ -6,8 +6,8 @@ import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { useMemo } from 'react'
import { WRAPPED_NATIVE_CURRENCY } from '../constants/tokens'
import { TransactionType } from '../state/transactions/actions'
import { useTransactionAdder } from '../state/transactions/hooks'
import { TransactionType } from '../state/transactions/types'
import { useCurrencyBalance } from '../state/wallet/hooks'
import { useWETHContract } from './useContract'

View File

@@ -3,7 +3,7 @@ import 'inter-ui'
import 'polyfills'
import 'components/analytics'
import { BlockUpdater } from 'lib/hooks/useBlockNumber'
import { BlockNumberProvider } from 'lib/hooks/useBlockNumber'
import { MulticallUpdater } from 'lib/state/multicall'
import { StrictMode } from 'react'
import ReactDOM from 'react-dom'
@@ -40,7 +40,6 @@ function Updaters() {
<UserUpdater />
<ApplicationUpdater />
<TransactionUpdater />
<BlockUpdater />
<MulticallUpdater />
<LogsUpdater />
</>
@@ -55,11 +54,13 @@ ReactDOM.render(
<Web3ReactProvider getLibrary={getLibrary}>
<Web3ProviderNetwork getLibrary={getLibrary}>
<Blocklist>
<Updaters />
<ThemeProvider>
<ThemedGlobalStyle />
<App />
</ThemeProvider>
<BlockNumberProvider>
<Updaters />
<ThemeProvider>
<ThemedGlobalStyle />
<App />
</ThemeProvider>
</BlockNumberProvider>
</Blocklist>
</Web3ProviderNetwork>
</Web3ReactProvider>

View File

@@ -1,24 +0,0 @@
{
"extends": ["../../.eslintrc.json"],
"plugins": ["better-styled-components"],
"rules": {
"better-styled-components/sort-declarations-alphabetically": "error",
"no-restricted-imports": [
"error",
{
"paths": [
{
"name": "react-feather",
"message": "Please import from lib/icons to ensure performant usage."
}
],
"patterns": [
{
"group": ["styled-components"],
"message": "Please import styled from lib/theme to get the correct typings."
}
]
}
]
}
}

View File

@@ -1,22 +0,0 @@
// Use Inter mixin to set font-display: block.
@use "@fontsource/inter/scss/mixins" as Inter;
@include Inter.fontFace(
$fontName: 'Inter',
$weight: 400,
$display: block,
);
@include Inter.fontFace(
$fontName: 'Inter',
$weight: 500,
$display: block,
);
@include Inter.fontFace(
$fontName: 'Inter',
$weight: 600,
$display: block,
);
@include Inter.fontFaceVariable(
$display: block,
);
@import "~@fontsource/ibm-plex-mono/400.css";

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1,10 +0,0 @@
<svg width="23" height="20" viewBox="0 0 23 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="gradient" x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(95)">
<stop id="stop1" offset="0" stop-color="#2274E2"/>
<stop id="stop1" offset="0.5" stop-color="#2274E2"/>
<stop id="stop2" offset="1" stop-color="#3FB672" />
</linearGradient>
</defs>
<path d="M16 16C10 16 9 10 5 10M16 16C16 17.6569 17.3431 19 19 19C20.6569 19 22 17.6569 22 16C22 14.3431 20.6569 13 19 13C17.3431 13 16 14.3431 16 16ZM5 10C9 10 10 4 16 4M5 10H1.5M16 4C16 5.65685 17.3431 7 19 7C20.6569 7 22 5.65685 22 4C22 2.34315 20.6569 1 19 1C17.3431 1 16 2.34315 16 4Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke="url(#gradient)" />
</svg>

Before

Width:  |  Height:  |  Size: 780 B

View File

@@ -1,4 +0,0 @@
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="10" cy="10" r="10" />
<path d="M14 7L8.5 12.5L6 10" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 233 B

View File

@@ -1,4 +0,0 @@
<svg viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" xmlns="http://www.w3.org/2000/svg">
<polyline class="left" points="18 15 12 9"></polyline>
<polyline class="right" points="12 9 6 15"></polyline>
</svg>

Before

Width:  |  Height:  |  Size: 238 B

View File

@@ -1,35 +0,0 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<mask id="mask">
<circle cx="12" cy="12" r="10" fill="black" stroke="black" stroke-width="2" />
<rect width="12" height="12" fill="white" stroke-width="0" />
<circle cx="2" cy="12" r="1" fill="white" stroke-width="0" />
<circle cx="12" cy="2" r="1" fill="white" stroke-width="0" />
</mask>
<circle
cx="12"
cy="12"
r="6"
stroke="none"
/>
<circle
id="track"
cx="12"
cy="12"
r="10"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
fill="none"
/>
<circle
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
fill="none"
mask="url(#mask)"
/>
</svg>

Before

Width:  |  Height:  |  Size: 931 B

View File

@@ -1,13 +0,0 @@
<svg viewBox="0 0 14 15" fill="black" xmlns="http://www.w3.org/2000/svg">
<g>
<path d="M4.15217 1.55141C3.96412 1.52242 3.95619 1.51902 4.04468 1.5055C4.21427 1.47958 4.61472 1.51491 4.89067 1.58012C5.53489 1.73232 6.12109 2.12221 6.74683 2.81466L6.91307 2.99862L7.15088 2.96062C8.15274 2.8006 9.17194 2.92778 10.0244 3.31918C10.2589 3.42686 10.6287 3.64121 10.6749 3.69629C10.6896 3.71384 10.7166 3.82684 10.7349 3.94742C10.7982 4.36458 10.7665 4.68434 10.6382 4.92317C10.5683 5.05313 10.5644 5.09432 10.6114 5.20554C10.6489 5.2943 10.7534 5.35999 10.8569 5.35985C11.0687 5.35956 11.2968 5.0192 11.4024 4.54561L11.4444 4.3575L11.5275 4.45109C11.9835 4.96459 12.3417 5.66488 12.4032 6.16335L12.4192 6.29332L12.3426 6.17517C12.2107 5.97186 12.0781 5.83346 11.9084 5.72183C11.6024 5.52062 11.2789 5.45215 10.4222 5.40727C9.64839 5.36675 9.21045 5.30106 8.77621 5.16032C8.03738 4.9209 7.66493 4.60204 6.78729 3.4576C6.39748 2.94928 6.15654 2.66804 5.91687 2.44155C5.37228 1.92691 4.83716 1.65701 4.15217 1.55141Z"/>
<path d="M10.8494 2.68637C10.8689 2.34575 10.9153 2.12108 11.0088 1.9159C11.0458 1.83469 11.0804 1.76822 11.0858 1.76822C11.0911 1.76822 11.075 1.82816 11.05 1.90142C10.9821 2.10054 10.9709 2.3729 11.0177 2.68978C11.0771 3.09184 11.1109 3.14985 11.5385 3.58416C11.739 3.78788 11.9723 4.0448 12.0568 4.15511L12.2106 4.35568L12.0568 4.21234C11.8688 4.03705 11.4364 3.6952 11.3409 3.64633C11.2768 3.61356 11.2673 3.61413 11.2278 3.65321C11.1914 3.68922 11.1837 3.74333 11.1787 3.99915C11.1708 4.39786 11.1161 4.65377 10.9842 4.90965C10.9128 5.04805 10.9015 5.01851 10.9661 4.8623C11.0143 4.74566 11.0192 4.69439 11.0189 4.30842C11.0181 3.53291 10.9255 3.34647 10.3823 3.02709C10.2447 2.94618 10.0179 2.8295 9.87839 2.76778C9.73887 2.70606 9.62805 2.6523 9.63208 2.64828C9.64746 2.63307 10.1772 2.78675 10.3905 2.86828C10.7077 2.98954 10.76 3.00526 10.7985 2.99063C10.8244 2.98082 10.8369 2.90608 10.8494 2.68637Z"/>
<path d="M4.51745 4.01304C4.13569 3.49066 3.89948 2.68973 3.95062 2.091L3.96643 1.90572L4.05333 1.92148C4.21652 1.95106 4.49789 2.05515 4.62964 2.13469C4.9912 2.35293 5.14773 2.64027 5.30697 3.37811C5.35362 3.59423 5.41482 3.8388 5.44298 3.9216C5.48831 4.05487 5.65962 4.36617 5.7989 4.56834C5.89922 4.71395 5.83258 4.78295 5.61082 4.76305C5.27215 4.73267 4.8134 4.41799 4.51745 4.01304Z"/>
<path d="M10.3863 7.90088C8.60224 7.18693 7.97389 6.56721 7.97389 5.52157C7.97389 5.36769 7.97922 5.24179 7.98571 5.24179C7.99221 5.24179 8.06124 5.29257 8.1391 5.35465C8.50088 5.64305 8.906 5.76623 10.0275 5.92885C10.6875 6.02455 11.0589 6.10185 11.4015 6.21477C12.4904 6.57371 13.1641 7.30212 13.3248 8.29426C13.3715 8.58255 13.3441 9.12317 13.2684 9.4081C13.2087 9.63315 13.0263 10.0388 12.9779 10.0544C12.9645 10.0587 12.9514 10.0076 12.9479 9.93809C12.9296 9.56554 12.7402 9.20285 12.4221 8.93116C12.0604 8.62227 11.5745 8.37633 10.3863 7.90088Z"/>
<path d="M9.13385 8.19748C9.11149 8.06527 9.07272 7.89643 9.04769 7.82228L9.00217 7.68748L9.08672 7.7818C9.20374 7.91233 9.2962 8.07937 9.37457 8.30185C9.43438 8.47165 9.44111 8.52215 9.44066 8.79807C9.4402 9.06896 9.43273 9.12575 9.3775 9.27858C9.29042 9.51959 9.18233 9.69048 9.00097 9.87391C8.67507 10.2036 8.25607 10.3861 7.65143 10.4618C7.54633 10.4749 7.24 10.4971 6.97069 10.511C6.292 10.5461 5.84531 10.6186 5.44393 10.7587C5.38623 10.7788 5.3347 10.7911 5.32947 10.7859C5.31323 10.7698 5.58651 10.6079 5.81223 10.4998C6.1305 10.3474 6.44733 10.2643 7.15719 10.1468C7.50785 10.0887 7.86998 10.0183 7.96194 9.99029C8.83033 9.72566 9.27671 9.04276 9.13385 8.19748Z"/>
<path d="M9.95169 9.64109C9.71465 9.13463 9.66022 8.64564 9.79009 8.18961C9.80399 8.14088 9.82632 8.101 9.83976 8.101C9.85319 8.101 9.90913 8.13105 9.96404 8.16777C10.0733 8.24086 10.2924 8.36395 10.876 8.68023C11.6043 9.0749 12.0196 9.3805 12.302 9.72965C12.5493 10.0354 12.7023 10.3837 12.776 10.8084C12.8177 11.0489 12.7932 11.6277 12.7311 11.8699C12.5353 12.6337 12.0802 13.2336 11.4311 13.5837C11.336 13.635 11.2506 13.6771 11.2414 13.6773C11.2321 13.6775 11.2668 13.5899 11.3184 13.4827C11.5367 13.029 11.5616 12.5877 11.3965 12.0965C11.2954 11.7957 11.0893 11.4287 10.6732 10.8084C10.1893 10.0873 10.0707 9.89539 9.95169 9.64109Z"/>
<path d="M3.25046 12.3737C3.91252 11.8181 4.73629 11.4234 5.48666 11.3022C5.81005 11.25 6.34877 11.2707 6.64823 11.3469C7.12824 11.469 7.55763 11.7425 7.78094 12.0683C7.99918 12.3867 8.09281 12.6642 8.19029 13.2816C8.22875 13.5252 8.27057 13.7697 8.28323 13.8251C8.35644 14.1451 8.4989 14.4008 8.67544 14.5293C8.95583 14.7333 9.43865 14.7459 9.91362 14.5618C9.99423 14.5305 10.0642 14.5089 10.0691 14.5138C10.0864 14.5308 9.84719 14.6899 9.67847 14.7737C9.45143 14.8864 9.2709 14.93 9.03102 14.93C8.59601 14.93 8.23486 14.7101 7.9335 14.2616C7.87419 14.1733 7.7409 13.909 7.63729 13.6741C7.3191 12.9528 7.16199 12.7331 6.79255 12.4926C6.47104 12.2834 6.05641 12.2459 5.74449 12.3979C5.33475 12.5976 5.22043 13.118 5.51389 13.4478C5.63053 13.5789 5.84803 13.6919 6.02588 13.7139C6.35861 13.7551 6.64455 13.5035 6.64455 13.1696C6.64455 12.9528 6.56071 12.8291 6.34966 12.7344C6.0614 12.6051 5.75156 12.7562 5.75304 13.0254C5.75368 13.1402 5.80396 13.2122 5.91971 13.2643C5.99397 13.2977 5.99569 13.3003 5.93514 13.2878C5.67066 13.2333 5.6087 12.9164 5.82135 12.706C6.07667 12.4535 6.60461 12.5649 6.78591 12.9097C6.86208 13.0545 6.87092 13.3429 6.80451 13.517C6.6559 13.9068 6.22256 14.1117 5.78297 14.0002C5.48368 13.9242 5.36181 13.842 5.00097 13.4726C4.37395 12.8306 4.13053 12.7062 3.22657 12.566L3.05335 12.5391L3.25046 12.3737Z"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.308383 0.883984C2.40235 3.40996 3.84457 4.45213 4.00484 4.67231C4.13717 4.85412 4.08737 5.01757 3.86067 5.14567C3.7346 5.21689 3.47541 5.28905 3.34564 5.28905C3.19887 5.28905 3.14847 5.23278 3.14847 5.23278C3.06337 5.15255 3.01544 5.16658 2.5784 4.39555C1.97166 3.45981 1.46389 2.68357 1.45004 2.67057C1.41801 2.64052 1.41856 2.64153 2.51654 4.59413C2.69394 5.0011 2.55182 5.15049 2.55182 5.20845C2.55182 5.32636 2.51946 5.38834 2.37311 5.55059C2.12914 5.8211 2.02008 6.12505 1.94135 6.7541C1.8531 7.45926 1.60492 7.95737 0.917156 8.80989C0.514562 9.30893 0.448686 9.4004 0.3471 9.60153C0.219144 9.85482 0.183961 9.99669 0.169701 10.3165C0.154629 10.6547 0.183983 10.8732 0.287934 11.1965C0.378939 11.4796 0.473932 11.6665 0.716778 12.0403C0.926351 12.3629 1.04702 12.6027 1.04702 12.6965C1.04702 12.7711 1.06136 12.7712 1.38611 12.6983C2.16328 12.5239 2.79434 12.2171 3.14925 11.8411C3.36891 11.6084 3.42048 11.4799 3.42215 11.1611C3.42325 10.9525 3.41587 10.9088 3.35914 10.7888C3.2668 10.5935 3.09869 10.4311 2.72817 10.1794C2.2427 9.84953 2.03534 9.58398 1.97807 9.21878C1.93108 8.91913 1.98559 8.70771 2.25416 8.14825C2.53214 7.56916 2.60103 7.32239 2.64763 6.73869C2.67773 6.36158 2.71941 6.21286 2.82842 6.09348C2.94212 5.969 3.04447 5.92684 3.32584 5.88863C3.78457 5.82635 4.07667 5.70839 4.31677 5.48849C4.52505 5.29772 4.61221 5.11391 4.62558 4.8372L4.63574 4.62747L4.51934 4.49259C4.09783 4.00411 0.0261003 0.5 0.000160437 0.5C-0.00538105 0.5 0.133325 0.672804 0.308383 0.883984ZM1.28364 10.6992C1.37894 10.5314 1.3283 10.3158 1.16889 10.2104C1.01827 10.1109 0.78428 10.1578 0.78428 10.2875C0.78428 10.3271 0.806303 10.3559 0.855937 10.3813C0.939514 10.424 0.945581 10.4721 0.879823 10.5703C0.81323 10.6698 0.818604 10.7573 0.894991 10.8167C1.0181 10.9125 1.19237 10.8598 1.28364 10.6992Z"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.92523 5.99865C4.70988 6.06439 4.50054 6.29124 4.43574 6.5291C4.39621 6.67421 4.41864 6.92875 4.47785 7.00736C4.57351 7.13433 4.66602 7.16778 4.91651 7.16603C5.40693 7.16263 5.83327 6.95358 5.88284 6.69224C5.92347 6.47801 5.73622 6.18112 5.4783 6.05078C5.34521 5.98355 5.06217 5.95688 4.92523 5.99865ZM5.49853 6.44422C5.57416 6.33741 5.54107 6.22198 5.41245 6.14391C5.1675 5.99525 4.79708 6.11827 4.79708 6.34826C4.79708 6.46274 4.99025 6.58765 5.16731 6.58765C5.28516 6.58765 5.44644 6.5178 5.49853 6.44422Z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 7.8 KiB

View File

@@ -1,17 +0,0 @@
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask">
<circle cx="12" cy="12" r="10" stroke="white" stroke-width="2" />
<rect width="12" height="12" fill="black" stroke-width="0" />
<circle cx="2" cy="12" r="1" fill="white" stroke-width="0" />
<circle cx="12" cy="2" r="1" fill="white" stroke-width="0" />
</mask>
<circle
cx="12"
cy="12"
r="10"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
mask="url(#mask)"
/>
</svg>

Before

Width:  |  Height:  |  Size: 592 B

View File

@@ -1,5 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 7C2 5.89543 2.89543 5 4 5H20C21.1046 5 22 5.89543 22 7V18C22 19.1046 21.1046 20 20 20H4C2.89543 20 2 19.1046 2 18V7Z" stroke="currentColor" stroke-width="2"/>
<path d="M4 19H20C21.1046 19 22 18.1046 22 17V14C22 12.8954 21.1046 12 20 12H16C15.4477 12 14.9935 12.4624 14.7645 12.965C14.4438 13.6688 13.789 14.5 12 14.5C10.29 14.5 9.48213 13.7406 9.1936 13.0589C8.96576 12.5206 8.49905 12 7.91447 12H4C2.89543 12 2 12.8954 2 14V17C2 18.1046 2.89543 19 4 19Z" fill="currentColor"/>
<path d="M22 13V11C22 9.89543 21.1034 9 19.9989 9C14.0294 9 9.97062 9 4.00115 9C2.89658 9 2 9.89543 2 11V13" stroke="currentColor" stroke-width="2"/>
</svg>

Before

Width:  |  Height:  |  Size: 743 B

View File

@@ -1,88 +0,0 @@
import { AlertTriangle, Icon, LargeIcon } from 'lib/icons'
import styled, { Color, css, keyframes, ThemedText } from 'lib/theme'
import { ReactNode, useMemo } from 'react'
import Button from './Button'
import Row from './Row'
const StyledButton = styled(Button)`
border-radius: ${({ theme }) => theme.borderRadius}em;
flex-grow: 1;
transition: background-color 0.25s ease-out, flex-grow 0.25s ease-out, padding 0.25s ease-out;
:disabled {
margin: -1px;
}
`
const ActionRow = styled(Row)``
const grow = keyframes`
from {
opacity: 0;
width: 0;
}
to {
opacity: 1;
width: max-content;
}
`
const actionCss = css`
border: 1px solid ${({ theme }) => theme.outline};
padding: calc(0.25em - 1px);
padding-left: calc(0.75em - 1px);
${ActionRow} {
animation: ${grow} 0.25s ease-in;
white-space: nowrap;
}
${StyledButton} {
border-radius: ${({ theme }) => theme.borderRadius * 0.75}em;
flex-grow: 0;
padding: 1em;
}
`
export const Overlay = styled(Row)<{ hasAction: boolean }>`
border-radius: ${({ theme }) => theme.borderRadius}em;
flex-direction: row-reverse;
min-height: 3.5em;
transition: padding 0.25s ease-out;
${({ hasAction }) => hasAction && actionCss}
`
export interface Action {
message: ReactNode
icon?: Icon
onClick: () => void
children: ReactNode
}
export interface BaseProps {
color?: Color
action?: Action
}
export type ActionButtonProps = BaseProps & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof BaseProps>
export default function ActionButton({ color = 'accent', disabled, action, onClick, children }: ActionButtonProps) {
const textColor = useMemo(() => (color === 'accent' && !disabled ? 'onAccent' : 'currentColor'), [color, disabled])
return (
<Overlay hasAction={Boolean(action)} flex align="stretch">
<StyledButton color={color} disabled={disabled} onClick={action ? action.onClick : onClick}>
<ThemedText.TransitionButton buttonSize={action ? 'medium' : 'large'} color={textColor}>
{action ? action.children : children}
</ThemedText.TransitionButton>
</StyledButton>
{action && (
<ActionRow gap={0.5}>
<LargeIcon color="currentColor" icon={action.icon || AlertTriangle} />
<ThemedText.Subhead2>{action?.message}</ThemedText.Subhead2>
</ActionRow>
)}
</Overlay>
)
}

View File

@@ -1,41 +0,0 @@
import { Trans } from '@lingui/macro'
import Row from 'lib/components/Row'
import { Logo } from 'lib/icons'
import styled, { brand, ThemedText } from 'lib/theme'
import { memo } from 'react'
import ExternalLink from './ExternalLink'
const UniswapA = styled(ExternalLink)`
color: ${({ theme }) => theme.secondary};
cursor: pointer;
text-decoration: none;
${Logo} {
fill: ${({ theme }) => theme.secondary};
height: 1em;
transition: transform 0.25s ease, fill 0.25s ease;
width: 1em;
will-change: transform;
}
:hover ${Logo} {
fill: ${brand};
transform: rotate(-5deg);
}
`
export default memo(function BrandedFooter() {
return (
<Row justify="center">
<UniswapA href={`https://uniswap.org/`}>
<Row gap={0.25}>
<Logo />
<ThemedText.Caption>
<Trans>Powered by the Uniswap protocol</Trans>
</ThemedText.Caption>
</Row>
</UniswapA>
</Row>
)
})

View File

@@ -1,67 +0,0 @@
import { Icon } from 'lib/icons'
import styled, { Color } from 'lib/theme'
import { ComponentProps, forwardRef } from 'react'
export const BaseButton = styled.button`
background-color: transparent;
border: none;
border-radius: 0.5em;
color: currentColor;
cursor: pointer;
font-size: inherit;
font-weight: inherit;
height: inherit;
line-height: inherit;
margin: 0;
padding: 0;
:disabled {
cursor: initial;
filter: saturate(0) opacity(0.4);
}
`
export default styled(BaseButton)<{ color?: Color }>`
color: ${({ color = 'interactive', theme }) => color === 'interactive' && theme.onInteractive};
:enabled {
background-color: ${({ color = 'interactive', theme }) => theme[color]};
}
:enabled:hover {
background-color: ${({ color = 'interactive', theme }) => theme.onHover(theme[color])};
}
:disabled {
border: 1px solid ${({ theme }) => theme.outline};
color: ${({ theme }) => theme.secondary};
cursor: initial;
}
`
const transparentButton = (defaultColor: Color) => styled(BaseButton)<{ color?: Color }>`
color: ${({ color = defaultColor, theme }) => theme[color]};
:enabled:hover {
color: ${({ color = defaultColor, theme }) => theme.onHover(theme[color])};
}
`
export const TextButton = transparentButton('accent')
const SecondaryButton = transparentButton('secondary')
interface IconButtonProps {
icon: Icon
iconProps?: ComponentProps<Icon>
}
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps & ComponentProps<typeof BaseButton>>(
function IconButton({ icon: Icon, iconProps, ...props }: IconButtonProps & ComponentProps<typeof BaseButton>, ref) {
return (
<SecondaryButton {...props} ref={ref}>
<Icon {...iconProps} />
</SecondaryButton>
)
}
)

View File

@@ -1,28 +0,0 @@
import styled, { Color, css, Theme } from 'lib/theme'
const Column = styled.div<{
align?: string
color?: Color
justify?: string
gap?: number
padded?: true
flex?: true
grow?: true
theme: Theme
css?: ReturnType<typeof css>
}>`
align-items: ${({ align }) => align ?? 'center'};
color: ${({ color, theme }) => color && theme[color]};
display: ${({ flex }) => (flex ? 'flex' : 'grid')};
flex-direction: column;
flex-grow: ${({ grow }) => grow && 1};
gap: ${({ gap }) => gap && `${gap}em`};
grid-auto-flow: row;
grid-template-columns: 1fr;
justify-content: ${({ justify }) => justify ?? 'space-between'};
padding: ${({ padded }) => padded && '0.75em'};
${({ css }) => css}
`
export default Column

View File

@@ -1,118 +0,0 @@
import 'wicg-inert'
import useUnmount from 'lib/hooks/useUnmount'
import { X } from 'lib/icons'
import styled, { Color, Layer, ThemeProvider } from 'lib/theme'
import { createContext, ReactElement, ReactNode, useContext, useEffect, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
import { IconButton } from './Button'
import Column from './Column'
import { default as BaseHeader } from './Header'
import Rule from './Rule'
// Include inert from wicg-inert
declare global {
interface HTMLElement {
inert?: boolean
}
}
const Context = createContext({
element: null as HTMLElement | null,
active: false,
setActive: (active: boolean) => undefined as void,
})
interface ProviderProps {
value: HTMLElement | null
children: ReactNode
}
export function Provider({ value, children }: ProviderProps) {
// If a Dialog is active, mark the main content inert
const ref = useRef<HTMLDivElement>(null)
const [active, setActive] = useState(false)
const context = { element: value, active, setActive }
useEffect(() => {
if (ref.current) {
ref.current.inert = active
}
}, [active])
return (
<div
ref={ref}
style={{ isolation: 'isolate' }} // creates a new stacking context, preventing the dialog from intercepting non-dialog clicks
>
<Context.Provider value={context}>{children}</Context.Provider>
</div>
)
}
const OnCloseContext = createContext<() => void>(() => void 0)
interface HeaderProps {
title?: ReactElement
ruled?: boolean
children?: ReactNode
}
export function Header({ title, children, ruled }: HeaderProps) {
return (
<>
<Column>
<BaseHeader title={title}>
{children}
<IconButton color="primary" onClick={useContext(OnCloseContext)} icon={X} />
</BaseHeader>
{ruled && <Rule padded />}
</Column>
</>
)
}
export const Modal = styled.div<{ color: Color }>`
background-color: ${({ color, theme }) => theme[color]};
border-radius: ${({ theme }) => theme.borderRadius * 0.75}em;
display: flex;
flex-direction: column;
height: 100%;
left: 0;
overflow: hidden;
position: absolute;
top: 0;
width: 100%;
z-index: ${Layer.DIALOG};
`
interface DialogProps {
color: Color
children: ReactNode
onClose?: () => void
}
export default function Dialog({ color, children, onClose = () => void 0 }: DialogProps) {
const context = useContext(Context)
useEffect(() => {
context.setActive(true)
return () => context.setActive(false)
}, [context])
const dialog = useRef<HTMLDivElement>(null)
useUnmount(dialog)
useEffect(() => {
const close = (e: KeyboardEvent) => e.key === 'Escape' && onClose?.()
document.addEventListener('keydown', close, true)
return () => document.removeEventListener('keydown', close, true)
}, [onClose])
return (
context.element &&
createPortal(
<ThemeProvider>
<Modal color={color} ref={dialog}>
<OnCloseContext.Provider value={onClose}>{children}</OnCloseContext.Provider>
</Modal>
</ThemeProvider>,
context.element
)
)
}

View File

@@ -1,46 +0,0 @@
import { Trans } from '@lingui/macro'
import React, { ErrorInfo } from 'react'
import Dialog from '../Dialog'
import ErrorDialog from './ErrorDialog'
export type ErrorHandler = (error: Error, info: ErrorInfo) => void
interface ErrorBoundaryProps {
onError?: ErrorHandler
}
type ErrorBoundaryState = {
error: Error | null
}
export default class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props)
this.state = { error: null }
}
static getDerivedStateFromError(error: Error) {
return { error }
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
this.props.onError?.(error, errorInfo)
}
render() {
if (this.state.error) {
return (
<Dialog color="dialog">
<ErrorDialog
error={this.state.error}
header={<Trans>Something went wrong.</Trans>}
action={<Trans>Reload the page</Trans>}
onClick={() => window.location.reload()}
/>
</Dialog>
)
}
return this.props.children
}
}

View File

@@ -1,80 +0,0 @@
import { Trans } from '@lingui/macro'
import ActionButton from 'lib/components/ActionButton'
import Column from 'lib/components/Column'
import Expando from 'lib/components/Expando'
import { AlertTriangle, Icon, LargeIcon } from 'lib/icons'
import styled, { Color, ThemedText } from 'lib/theme'
import { ReactNode, useCallback, useState } from 'react'
const HeaderIcon = styled(LargeIcon)`
flex-grow: 1;
transition: height 0.25s, width 0.25s;
svg {
transition: height 0.25s, width 0.25s;
}
`
interface StatusHeaderProps {
icon: Icon
iconColor?: Color
iconSize?: number
children: ReactNode
}
export function StatusHeader({ icon: Icon, iconColor, iconSize = 4, children }: StatusHeaderProps) {
return (
<>
<Column flex style={{ flexGrow: 1 }}>
<HeaderIcon icon={Icon} color={iconColor} size={iconSize} />
<Column gap={0.75} flex style={{ textAlign: 'center' }}>
{children}
</Column>
</Column>
</>
)
}
const ErrorHeader = styled(Column)<{ open: boolean }>`
transition: gap 0.25s;
div:last-child {
max-height: ${({ open }) => (open ? 0 : 60 / 14)}em; // 3 * line-height
overflow-y: hidden;
transition: max-height 0.25s;
}
`
interface ErrorDialogProps {
header?: ReactNode
error: Error
action: ReactNode
onClick: () => void
}
export default function ErrorDialog({ header, error, action, onClick }: ErrorDialogProps) {
const [open, setOpen] = useState(false)
const onExpand = useCallback(() => setOpen((open) => !open), [])
return (
<Column flex padded gap={0.75} align="stretch" style={{ height: '100%' }}>
<StatusHeader icon={AlertTriangle} iconColor="error" iconSize={open ? 3 : 4}>
<ErrorHeader gap={open ? 0 : 0.75} open={open}>
<ThemedText.Subhead1>
<Trans>Something went wrong.</Trans>
</ThemedText.Subhead1>
<ThemedText.Body2>{header}</ThemedText.Body2>
</ErrorHeader>
</StatusHeader>
<Column gap={open ? 0 : 0.75} style={{ transition: 'gap 0.25s' }}>
<Expando title={<Trans>Error details</Trans>} open={open} onExpand={onExpand} height={7.5}>
<ThemedText.Code userSelect>
{error.name}
{error.message ? `: ${error.message}` : ''}
</ThemedText.Code>
</Expando>
<ActionButton onClick={onClick}>{action}</ActionButton>
</Column>
</Column>
)
}

View File

@@ -1,30 +0,0 @@
import { SUPPORTED_LOCALES } from 'constants/locales'
import { WidgetProps } from 'lib/components/Widget'
import { IntegrationError } from 'lib/errors'
import { PropsWithChildren, useEffect } from 'react'
export default function WidgetsPropsValidator(props: PropsWithChildren<WidgetProps>) {
const { jsonRpcEndpoint, provider } = props
useEffect(() => {
if (!provider && !jsonRpcEndpoint) {
throw new IntegrationError('This widget requires a provider or jsonRpcEndpoint.')
}
}, [provider, jsonRpcEndpoint])
const { width } = props
useEffect(() => {
if (width && width < 300) {
throw new IntegrationError(`Set widget width to at least 300px. (You set it to ${width}.)`)
}
}, [width])
const { locale } = props
useEffect(() => {
if (locale && locale !== 'pseudo' && !SUPPORTED_LOCALES.includes(locale)) {
console.warn('Unsupported locale: ', locale)
}
}, [locale])
return <>{props.children}</>
}

View File

@@ -1,36 +0,0 @@
import { SupportedChainId } from 'constants/chains'
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
import { Link } from 'lib/icons'
import styled, { Color } from 'lib/theme'
import { ReactNode, useMemo } from 'react'
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
import ExternalLink from './ExternalLink'
import Row from './Row'
const StyledExternalLink = styled(ExternalLink)<{ color: Color }>`
color: ${({ theme, color }) => theme[color]};
text-decoration: none;
`
interface EtherscanLinkProps {
type: ExplorerDataType
data?: string
color?: Color
children: ReactNode
}
export default function EtherscanLink({ data, type, color = 'currentColor', children }: EtherscanLinkProps) {
const { chainId } = useActiveWeb3React()
const url = useMemo(
() => data && getExplorerLink(chainId || SupportedChainId.MAINNET, data, type),
[chainId, data, type]
)
return (
<StyledExternalLink href={url} color={color} target="_blank">
<Row gap={0.25}>
{children} <Link />
</Row>
</StyledExternalLink>
)
}

View File

@@ -1,66 +0,0 @@
import { IconButton } from 'lib/components/Button'
import Column from 'lib/components/Column'
import Row from 'lib/components/Row'
import Rule from 'lib/components/Rule'
import useScrollbar from 'lib/hooks/useScrollbar'
import { Expando as ExpandoIcon } from 'lib/icons'
import styled from 'lib/theme'
import { PropsWithChildren, ReactNode, useState } from 'react'
const HeaderColumn = styled(Column)`
transition: gap 0.25s;
`
const ExpandoColumn = styled(Column)<{ height: number; open: boolean }>`
height: ${({ height, open }) => (open ? height : 0)}em;
overflow: hidden;
position: relative;
transition: height 0.25s, padding 0.25s;
:after {
background: linear-gradient(#ffffff00, ${({ theme }) => theme.dialog});
bottom: 0;
content: '';
height: 0.75em;
pointer-events: none;
position: absolute;
width: calc(100% - 1em);
}
`
const InnerColumn = styled(Column)<{ height: number }>`
height: ${({ height }) => height}em;
padding: 0.5em 0;
`
interface ExpandoProps {
title: ReactNode
open: boolean
onExpand: () => void
// The absolute height of the expanded container, in em.
height: number
}
/** A scrollable Expando with an absolute height. */
export default function Expando({ title, open, onExpand, height, children }: PropsWithChildren<ExpandoProps>) {
const [scrollingEl, setScrollingEl] = useState<HTMLDivElement | null>(null)
const scrollbar = useScrollbar(scrollingEl)
return (
<Column>
<HeaderColumn gap={open ? 0.5 : 0.75}>
<Rule />
<Row>
{title}
<IconButton color="secondary" onClick={onExpand} icon={ExpandoIcon} iconProps={{ open }} />
</Row>
<Rule />
</HeaderColumn>
<ExpandoColumn open={open} height={height}>
<InnerColumn flex align="stretch" height={height} ref={setScrollingEl} css={scrollbar}>
{children}
</InnerColumn>
</ExpandoColumn>
</Column>
)
return null
}

View File

@@ -1,17 +0,0 @@
import { HTMLProps } from 'react'
/**
* Outbound link
*/
export default function ExternalLink({
target = '_blank',
href,
rel = 'noopener noreferrer',
...rest
}: Omit<HTMLProps<HTMLAnchorElement>, 'as' | 'ref' | 'onClick'> & { href?: string }) {
return (
<a target={target} rel={rel} href={href} {...rest}>
{rest.children}
</a>
)
}

View File

@@ -1,26 +0,0 @@
import { largeIconCss } from 'lib/icons'
import styled, { ThemedText } from 'lib/theme'
import { ReactElement, ReactNode } from 'react'
import Row from './Row'
const HeaderRow = styled(Row)`
height: 1.75em;
margin: 0 0.75em 0.75em;
padding-top: 0.5em;
${largeIconCss}
`
export interface HeaderProps {
title?: ReactElement
children: ReactNode
}
export default function Header({ title, children }: HeaderProps) {
return (
<HeaderRow iconSize={1.2}>
<Row gap={0.5}>{title && <ThemedText.Subhead1>{title}</ThemedText.Subhead1>}</Row>
<Row gap={1}>{children}</Row>
</HeaderRow>
)
}

View File

@@ -1,179 +0,0 @@
import styled, { css } from 'lib/theme'
import { forwardRef, HTMLProps, useCallback, useEffect, useState } from 'react'
const Input = styled.input`
-webkit-appearance: textfield;
background-color: transparent;
border: none;
color: currentColor;
font-family: inherit;
font-size: inherit;
font-weight: inherit;
line-height: inherit;
margin: 0;
outline: none;
overflow: hidden;
padding: 0;
text-align: left;
text-overflow: ellipsis;
width: 100%;
::-webkit-search-decoration {
-webkit-appearance: none;
}
[type='number'] {
-moz-appearance: textfield;
}
::-webkit-outer-spin-button,
::-webkit-inner-spin-button {
-webkit-appearance: none;
}
::placeholder {
color: ${({ theme }) => theme.secondary};
}
`
export default Input
interface StringInputProps extends Omit<HTMLProps<HTMLInputElement>, 'onChange' | 'as' | 'value'> {
value: string
onChange: (input: string) => void
}
export const StringInput = forwardRef<HTMLInputElement, StringInputProps>(function StringInput(
{ value, onChange, ...props }: StringInputProps,
ref
) {
return (
<Input
value={value}
onChange={(e) => onChange(e.target.value)}
// universal input options
inputMode="text"
autoComplete="off"
autoCorrect="off"
// text-specific options
type="text"
placeholder={props.placeholder || '-'}
minLength={1}
spellCheck="false"
ref={ref as any}
{...props}
/>
)
})
interface NumericInputProps extends Omit<HTMLProps<HTMLInputElement>, 'onChange' | 'as' | 'value'> {
value: string
onChange: (input: string) => void
}
interface EnforcedNumericInputProps extends NumericInputProps {
// Validates nextUserInput; returns stringified value, or null if invalid
enforcer: (nextUserInput: string) => string | null
}
function isNumericallyEqual(a: string, b: string) {
const [aInteger, aDecimal] = toParts(a)
const [bInteger, bDecimal] = toParts(b)
return aInteger === bInteger && aDecimal === bDecimal
function toParts(num: string) {
let [integer, decimal] = num.split('.')
integer = integer?.match(/([1-9]\d*)/)?.[1] || ''
decimal = decimal?.match(/(\d*[1-9])/)?.[1] || ''
return [integer, decimal]
}
}
const NumericInput = forwardRef<HTMLInputElement, EnforcedNumericInputProps>(function NumericInput(
{ value, onChange, enforcer, pattern, ...props }: EnforcedNumericInputProps,
ref
) {
const [state, setState] = useState(value ?? '')
useEffect(() => {
if (!isNumericallyEqual(state, value)) {
setState(value ?? '')
}
}, [value, state, setState])
const validateChange = useCallback(
(event) => {
const nextInput = enforcer(event.target.value.replace(/,/g, '.'))
if (nextInput !== null) {
setState(nextInput ?? '')
if (!isNumericallyEqual(nextInput, value)) {
onChange(nextInput)
}
}
},
[value, onChange, enforcer]
)
return (
<Input
value={state}
onChange={validateChange}
// universal input options
inputMode="decimal"
autoComplete="off"
autoCorrect="off"
// text-specific options
type="text"
pattern={pattern}
placeholder={props.placeholder || '0'}
minLength={1}
maxLength={79}
spellCheck="false"
ref={ref as any}
{...props}
/>
)
})
const integerRegexp = /^\d*$/
const integerEnforcer = (nextUserInput: string) => {
if (nextUserInput === '' || integerRegexp.test(nextUserInput)) {
const nextInput = parseInt(nextUserInput)
return isNaN(nextInput) ? '' : nextInput.toString()
}
return null
}
export const IntegerInput = forwardRef(function IntegerInput(props: NumericInputProps, ref) {
return <NumericInput pattern="^[0-9]*$" enforcer={integerEnforcer} ref={ref as any} {...props} />
})
const decimalRegexp = /^\d*(?:[.])?\d*$/
const decimalEnforcer = (nextUserInput: string) => {
if (nextUserInput === '') {
return ''
} else if (nextUserInput === '.') {
return '0.'
} else if (decimalRegexp.test(nextUserInput)) {
return nextUserInput
}
return null
}
export const DecimalInput = forwardRef(function DecimalInput(props: NumericInputProps, ref) {
return <NumericInput pattern="^[0-9]*[.,]?[0-9]*$" enforcer={decimalEnforcer} ref={ref as any} {...props} />
})
export const inputCss = css`
background-color: ${({ theme }) => theme.container};
border: 1px solid ${({ theme }) => theme.container};
border-radius: ${({ theme }) => theme.borderRadius}em;
cursor: text;
padding: calc(0.5em - 1px);
:hover:not(:focus-within) {
background-color: ${({ theme }) => theme.onHover(theme.container)};
border-color: ${({ theme }) => theme.onHover(theme.container)};
}
:focus-within {
border-color: ${({ theme }) => theme.active};
}
`

View File

@@ -1,150 +0,0 @@
import { Options, Placement } from '@popperjs/core'
import styled, { Layer } from 'lib/theme'
import maxSize from 'popper-max-size-modifier'
import React, { createContext, useContext, useMemo, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
import { usePopper } from 'react-popper'
const BoundaryContext = createContext<HTMLDivElement | null>(null)
export const BoundaryProvider = BoundaryContext.Provider
const PopoverContainer = styled.div<{ show: boolean }>`
background-color: ${({ theme }) => theme.dialog};
border: 1px solid ${({ theme }) => theme.outline};
border-radius: 0.5em;
opacity: ${(props) => (props.show ? 1 : 0)};
padding: 10px 12px;
transition: visibility 0.25s linear, opacity 0.25s linear;
visibility: ${(props) => (props.show ? 'visible' : 'hidden')};
z-index: ${Layer.TOOLTIP};
`
const Reference = styled.div`
align-self: flex-start;
display: inline-block;
height: 1em;
`
const Arrow = styled.div`
height: 8px;
width: 8px;
z-index: ${Layer.TOOLTIP};
::before {
background: ${({ theme }) => theme.dialog};
border: 1px solid ${({ theme }) => theme.outline};
content: '';
height: 8px;
position: absolute;
transform: rotate(45deg);
width: 8px;
}
&.arrow-top {
bottom: -4px;
::before {
border-radius: 1px;
border-left: none;
border-top: none;
}
}
&.arrow-bottom {
top: -5px; // includes -1px from border
::before {
border-bottom: none;
border-right: none;
border-radius: 1px;
}
}
&.arrow-left {
right: -4px;
::before {
border-bottom: none;
border-left: none;
border-radius: 1px;
}
}
&.arrow-right {
left: -5px; // includes -1px from border
::before {
border-radius: 1px;
border-right: none;
border-top: none;
}
}
`
export interface PopoverProps {
content: React.ReactNode
show: boolean
children: React.ReactNode
placement: Placement
offset?: number
contained?: true
}
export default function Popover({ content, show, children, placement, offset, contained }: PopoverProps) {
const boundary = useContext(BoundaryContext)
const reference = useRef<HTMLDivElement>(null)
// Use callback refs to be notified when instantiated
const [popover, setPopover] = useState<HTMLDivElement | null>(null)
const [arrow, setArrow] = useState<HTMLDivElement | null>(null)
const options = useMemo((): Options => {
const modifiers: Options['modifiers'] = [
{ name: 'offset', options: { offset: [4, offset || 4] } },
{ name: 'arrow', options: { element: arrow, padding: 4 } },
]
if (contained) {
modifiers.push(
{ name: 'preventOverflow', options: { boundary, padding: 8 } },
{ name: 'flip', options: { boundary, padding: 8 } },
{ ...maxSize, options: { boundary, padding: 8 } },
{
name: 'applyMaxSize',
enabled: true,
phase: 'beforeWrite',
requires: ['maxSize'],
fn({ state }) {
const { width } = state.modifiersData.maxSize
state.styles.popper = {
...state.styles.popper,
maxWidth: `${width}px`,
}
},
}
)
}
return {
placement,
strategy: 'absolute',
modifiers,
}
}, [offset, arrow, contained, placement, boundary])
const { styles, attributes } = usePopper(reference.current, popover, options)
return (
<>
<Reference ref={reference}>{children}</Reference>
{boundary &&
createPortal(
<PopoverContainer show={show} ref={setPopover} style={styles.popper} {...attributes.popper}>
{content}
<Arrow
className={`arrow-${attributes.popper?.['data-popper-placement'] ?? ''}`}
ref={setArrow}
style={styles.arrow}
{...attributes.arrow}
/>
</PopoverContainer>,
boundary
)}
</>
)
}

View File

@@ -1,93 +0,0 @@
import { Trans } from '@lingui/macro'
import { Currency } from '@uniswap/sdk-core'
import { AlertTriangle, ArrowRight, CheckCircle, Spinner, Trash2 } from 'lib/icons'
import styled, { ThemedText } from 'lib/theme'
import { useMemo, useState } from 'react'
import Button from './Button'
import Column from './Column'
import { Header } from './Dialog'
import Row from './Row'
import TokenImg from './TokenImg'
interface ITokenAmount {
value: number
token: Currency
}
export enum TransactionStatus {
SUCCESS = 0,
ERROR,
PENDING,
}
interface ITransaction {
input: ITokenAmount
output: ITokenAmount
status: TransactionStatus
}
const TransactionRow = styled(Row)`
padding: 0.5em 1em;
:first-of-type {
padding-top: 1em;
}
`
function TokenAmount({ value: { value, token } }: { value: ITokenAmount }) {
return (
<Row gap={0.375}>
<TokenImg token={token} />
<ThemedText.Body2>
{value.toLocaleString('en')} {token.symbol}
</ThemedText.Body2>
</Row>
)
}
function Transaction({ tx }: { tx: ITransaction }) {
const statusIcon = useMemo(() => {
switch (tx.status) {
case TransactionStatus.SUCCESS:
return <CheckCircle color="success" />
case TransactionStatus.ERROR:
return <AlertTriangle color="error" />
case TransactionStatus.PENDING:
return <Spinner />
}
}, [tx.status])
return (
<TransactionRow grow>
<Row gap={0.75}>
<Row flex gap={0.5}>
<TokenAmount value={tx.input} />
<Row flex justify="flex-end" gap={0.5} grow>
<ArrowRight />
<TokenAmount value={tx.output} />
</Row>
</Row>
{statusIcon}
</Row>
</TransactionRow>
)
}
export default function RecentTransactionsDialog() {
const [txs, setTxs] = useState([])
return (
<>
<Header title={<Trans>Recent transactions</Trans>} ruled>
<Button>
<Trash2 onClick={() => setTxs([])} />
</Button>
</Header>
<Column>
{txs.map((tx, key) => (
<Transaction tx={tx} key={key} />
))}
</Column>
</>
)
}

View File

@@ -1,32 +0,0 @@
import styled, { Color, Theme } from 'lib/theme'
import { Children, ReactNode } from 'react'
const Row = styled.div<{
color?: Color
align?: string
justify?: string
pad?: number
gap?: number
flex?: true
grow?: true | 'first' | 'last'
children?: ReactNode
theme: Theme
}>`
align-items: ${({ align }) => align ?? 'center'};
color: ${({ color, theme }) => color && theme[color]};
display: ${({ flex }) => (flex ? 'flex' : 'grid')};
flex-flow: wrap;
flex-grow: ${({ grow }) => grow && 1};
gap: ${({ gap }) => gap && `${gap}em`};
grid-auto-flow: column;
grid-template-columns: ${({ grow, children }) => {
if (grow === 'first') return '1fr'
if (grow === 'last') return `repeat(${Children.count(children) - 1}, auto) 1fr`
if (grow) return `repeat(${Children.count(children)}, 1fr)`
return undefined
}};
justify-content: ${({ justify }) => justify ?? 'space-between'};
padding: ${({ pad }) => pad && `0 ${pad}em`};
`
export default Row

View File

@@ -1,15 +0,0 @@
import styled from 'lib/theme'
const Rule = styled.hr<{ padded?: true; scrollingEdge?: 'top' | 'bottom' }>`
border: none;
border-bottom: 1px solid ${({ theme }) => theme.outline};
margin: 0 ${({ padded }) => (padded ? '0.75em' : 0)};
margin-bottom: ${({ scrollingEdge }) => (scrollingEdge === 'bottom' ? -1 : 0)}px;
margin-top: ${({ scrollingEdge }) => (scrollingEdge !== 'bottom' ? -1 : 0)}px;
// Integrators will commonly modify hr width - this overrides any modifications within the widget.
max-width: auto;
width: auto;
`
export default Rule

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